From b1c40e172d860107f714e99dcbce35e2d9b2f138 Mon Sep 17 00:00:00 2001 From: Kumar Mallikarjuna Date: Thu, 21 Oct 2021 05:40:24 +0530 Subject: [PATCH] Escape variables (#2563) * Escape variables Signed-off-by: Kumar Mallikarjuna * Escape variables test - nested Signed-off-by: Kumar Mallikarjuna * Fixed missing changes Signed-off-by: Kumar Mallikarjuna --- pkg/engine/variables/vars.go | 58 ++++++++++++++++++++++++++++--- pkg/engine/variables/vars_test.go | 36 +++++++++++++++++++ 2 files changed, 89 insertions(+), 5 deletions(-) diff --git a/pkg/engine/variables/vars.go b/pkg/engine/variables/vars.go index ba2dd18840..bbcc585b49 100644 --- a/pkg/engine/variables/vars.go +++ b/pkg/engine/variables/vars.go @@ -17,7 +17,9 @@ import ( "github.com/kyverno/kyverno/pkg/engine/operator" ) -var RegexVariables = regexp.MustCompile(`\{\{[^{}]*\}\}`) +var RegexVariables = regexp.MustCompile(`^\{\{[^{}]*\}\}|[^\\]\{\{[^{}]*\}\}`) + +var RegexEscpVariables = regexp.MustCompile(`\\\{\{[^{}]*\}\}`) // Regex for '$(...)' at the beginning of the string, and 'x$(...)' where 'x' is not '\' var RegexReferences = regexp.MustCompile(`^\$\(.[^\ ]*\)|[^\\]\$\(.[^\ ]*\)`) @@ -25,6 +27,8 @@ var RegexReferences = regexp.MustCompile(`^\$\(.[^\ ]*\)|[^\\]\$\(.[^\ ]*\)`) // Regex for '\$(...)' var RegexEscpReferences = regexp.MustCompile(`\\\$\(.[^\ ]*\)`) +var regexVariableInit = regexp.MustCompile(`^\{\{[^{}]*\}\}`) + // IsVariable returns true if the element contains a 'valid' variable {{}} func IsVariable(value string) bool { groups := RegexVariables.FindAllStringSubmatch(value, -1) @@ -40,7 +44,19 @@ func IsReference(value string) bool { // ReplaceAllVars replaces all variables with the value defined in the replacement function // This is used to avoid validation errors func ReplaceAllVars(src string, repl func(string) string) string { - return RegexVariables.ReplaceAllStringFunc(src, repl) + wrapper := func(s string) string { + initial := len(regexVariableInit.FindAllString(s, -1)) > 0 + prefix := "" + + if !initial { + prefix = string(s[0]) + s = s[1:] + } + + return prefix + repl(s) + } + + return RegexVariables.ReplaceAllStringFunc(src, wrapper) } func newPreconditionsVariableResolver(log logr.Logger) VariableResolver { @@ -215,6 +231,12 @@ func validateBackgroundModeVars(log logr.Logger, ctx context.EvalInterface) json } vars := RegexVariables.FindAllString(value, -1) for _, v := range vars { + initial := len(regexVariableInit.FindAllString(v, -1)) > 0 + + if !initial { + v = v[1:] + } + variable := replaceBracesAndTrimSpaces(v) _, err := ctx.Query(variable) @@ -241,6 +263,12 @@ func validateElementInForEach(log logr.Logger) jsonUtils.Action { } vars := RegexVariables.FindAllString(value, -1) for _, v := range vars { + initial := len(regexVariableInit.FindAllString(v, -1)) > 0 + + if !initial { + v = v[1:] + } + variable := replaceBracesAndTrimSpaces(v) if strings.HasPrefix(variable, "element") && !strings.Contains(data.Path, "/foreach/") { @@ -341,6 +369,13 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface, vr Var originalPattern := value for _, v := range vars { + initial := len(regexVariableInit.FindAllString(v, -1)) > 0 + v_old := v + + if !initial { + v = v[1:] + } + variable := replaceBracesAndTrimSpaces(v) if variable == "@" { @@ -368,7 +403,13 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface, vr Var return substitutedVar, nil } - if value, err = substituteVarInPattern(originalPattern, v, substitutedVar); err != nil { + prefix := "" + + if !initial { + prefix = string(v_old[0]) + } + + if value, err = substituteVarInPattern(prefix, originalPattern, v, substitutedVar); err != nil { return nil, fmt.Errorf("failed to resolve %v at path %s: %s", variable, data.Path, err.Error()) } @@ -379,6 +420,10 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface, vr Var vars = RegexVariables.FindAllString(value, -1) } + for _, v := range RegexEscpVariables.FindAllString(value, -1) { + value = strings.Replace(value, v, v[1:], -1) + } + return value, nil }) } @@ -404,7 +449,7 @@ func getJMESPath(rawPath string) string { return string(regex.ReplaceAll([]byte(path), []byte("[$1]."))) } -func substituteVarInPattern(pattern, variable string, value interface{}) (string, error) { +func substituteVarInPattern(prefix, pattern, variable string, value interface{}) (string, error) { var stringToSubstitute string if s, ok := value.(string); ok { @@ -417,7 +462,10 @@ func substituteVarInPattern(pattern, variable string, value interface{}) (string stringToSubstitute = string(buffer) } - return strings.Replace(pattern, variable, stringToSubstitute, -1), nil + stringToSubstitute = prefix + stringToSubstitute + variable = prefix + variable + + return strings.Replace(pattern, variable, stringToSubstitute, 1), nil } func replaceBracesAndTrimSpaces(v string) string { diff --git a/pkg/engine/variables/vars_test.go b/pkg/engine/variables/vars_test.go index e6da8e4b4d..c9d29474c3 100644 --- a/pkg/engine/variables/vars_test.go +++ b/pkg/engine/variables/vars_test.go @@ -1115,3 +1115,39 @@ func Test_EscpReferenceSubstitution(t *testing.T) { assert.DeepEqual(t, expectedDocument, actualDocument) } + +func Test_ReplacingEscpNestedVariableWhenDeleting(t *testing.T) { + patternRaw := []byte(`"\\{{request.object.metadata.annotations.{{request.object.metadata.annotations.targetnew}}}}"`) + + var resourceRaw = []byte(` + { + "request":{ + "operation":"DELETE", + "oldObject":{ + "metadata":{ + "name":"current", + "namespace":"ns", + "annotations":{ + "target":"nested_target", + "targetnew":"target" + } + } + } + } + }`) + + var pattern interface{} + var err error + err = json.Unmarshal(patternRaw, &pattern) + if err != nil { + t.Error(err) + } + ctx := context.NewContext() + err = ctx.AddJSON(resourceRaw) + assert.NilError(t, err) + + pattern, err = SubstituteAll(log.Log, ctx, pattern) + assert.NilError(t, err) + + assert.Equal(t, fmt.Sprintf("%v", pattern), "{{request.object.metadata.annotations.target}}") +}