1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00

add support for shallow substitution (#11058)

* add support for shallow substitution

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* linter issue

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* exclude EphemeralReport and ClusterEphemeralReport

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* update codegen

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

---------

Signed-off-by: Jim Bugwadia <jim@nirmata.com>
Co-authored-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
Jim Bugwadia 2024-10-08 12:43:04 -07:00 committed by GitHub
parent bc1a504462
commit 2289720ba0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 75 additions and 20 deletions

View file

@ -224,6 +224,8 @@ config:
- '[Pod/binding,*,*]'
- '[ReplicaSet,*,*]'
- '[ReplicaSet/*,*,*]'
- '[EphemeralReport,*,*]'
- '[ClusterEphemeralReport,*,*]'
# exclude resources from the chart
- '[ClusterRole,*,{{ template "kyverno.admission-controller.roleName" . }}]'
- '[ClusterRole,*,{{ template "kyverno.admission-controller.roleName" . }}:core]'

View file

@ -86,6 +86,8 @@ data:
[Pod/binding,*,*]
[ReplicaSet,*,*]
[ReplicaSet/*,*,*]
[EphemeralReport,*,*]
[ClusterEphemeralReport,*,*]
[ClusterRole,*,kyverno:admission-controller]
[ClusterRole,*,kyverno:admission-controller:core]
[ClusterRole,*,kyverno:admission-controller:additional]

View file

@ -3,10 +3,9 @@ package regex
import "regexp"
var (
// RegexVariables is the Regex for '{{...}}' at the beginning of the string, and 'x{{...}}' where 'x' is not '\'
RegexVariables = regexp.MustCompile(`(^|[^\\])(\{\{(?:\{[^{}]*\}|[^{}])*\}\})`)
RegexEscpVariables = regexp.MustCompile(`\\\{\{(\{[^{}]*\}|[^{}])*\}\}`)
// RegexReferences is the Regex for '$(...)' at the beginning of the string, and 'x$(...)' where 'x' is not '\'
RegexReferences = regexp.MustCompile(`^\$\(.[^\ ]*\)|[^\\]\$\(.[^\ ]*\)`)
@ -16,6 +15,4 @@ var (
RegexVariableInit = regexp.MustCompile(`^\{\{(\{[^{}]*\}|[^{}])*\}\}`)
RegexElementIndex = regexp.MustCompile(`{{\s*elementIndex\d*\s*}}`)
RegexVariableKey = regexp.MustCompile(`\{{(.*?)\}}`)
)

View file

@ -222,7 +222,7 @@ func validateElementInForEach() jsonUtils.Action {
v = v[1:]
}
variable := replaceBracesAndTrimSpaces(v)
variable, _ := replaceBracesAndTrimSpaces(v)
isElementVar := strings.HasPrefix(variable, "element") || variable == "elementIndex"
if isElementVar && !strings.Contains(data.Path, "/foreach/") {
return nil, fmt.Errorf("variable '%v' present outside of foreach at path %s", variable, data.Path)
@ -308,7 +308,7 @@ func DefaultVariableResolver(ctx context.EvalInterface, variable string) (interf
return ctx.Query(variable)
}
func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface, vr VariableResolver) jsonUtils.Action {
func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface, lookupVar VariableResolver) jsonUtils.Action {
isDeleteRequest := isDeleteRequest(ctx)
return jsonUtils.OnlyForLeafsAndKeys(func(data *jsonUtils.ActionData) (interface{}, error) {
value, ok := data.Element.(string)
@ -319,16 +319,16 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface, vr Var
vars := regex.RegexVariables.FindAllString(value, -1)
for len(vars) > 0 {
originalPattern := value
shallowSubstitution := false
var variable string
for _, v := range vars {
initial := len(regex.RegexVariableInit.FindAllString(v, -1)) > 0
old := v
if !initial {
v = v[1:]
}
variable := replaceBracesAndTrimSpaces(v)
variable, shallowSubstitution = replaceBracesAndTrimSpaces(v)
if variable == "@" {
pathPrefix := "target"
if _, err := ctx.Query("target"); err != nil {
@ -339,7 +339,6 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface, vr Var
// Skip 2 elements (e.g. mutate.overlay | validate.pattern) plus "foreach" if it is part of the pointer.
// Prefix the pointer with pathPrefix.
val := jsonpointer.ParsePath(data.Path).SkipPast("foreach").SkipN(2).Prepend(strings.Split(pathPrefix, ".")...).JMESPath()
variable = strings.Replace(variable, "@", val, -1)
}
@ -347,7 +346,7 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface, vr Var
variable = strings.ReplaceAll(variable, "request.object", "request.oldObject")
}
substitutedVar, err := vr(ctx, variable)
substitutedVar, err := lookupVar(ctx, variable)
if err != nil {
switch err.(type) {
case context.InvalidVariableError, gojmespath.NotFoundError:
@ -368,6 +367,10 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface, vr Var
prefix = string(old[0])
}
if shallowSubstitution {
substitutedVar = strings.ReplaceAll(substitutedVar.(string), "{{", "\\{{")
}
if value, err = substituteVarInPattern(prefix, value, v, substitutedVar); err != nil {
return nil, fmt.Errorf("failed to resolve %v at path %s: %s", variable, data.Path, err.Error())
}
@ -375,14 +378,16 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface, vr Var
continue
}
// check for nested variables in strings
vars = regex.RegexVariables.FindAllString(value, -1)
}
for _, v := range regex.RegexEscpVariables.FindAllString(value, -1) {
value = strings.Replace(value, v, v[1:], -1)
if shallowSubstitution {
vars = []string{}
} else {
// check for nested variables in strings
vars = regex.RegexVariables.FindAllString(value, -1)
}
}
// Unescape escaped braces
value = strings.ReplaceAll(value, "\\{{", "{{")
return value, nil
})
}
@ -418,11 +423,16 @@ func substituteVarInPattern(prefix, pattern, variable string, value interface{})
return strings.Replace(pattern, variable, stringToSubstitute, 1), nil
}
func replaceBracesAndTrimSpaces(v string) string {
variable := strings.ReplaceAll(v, "{{", "")
func replaceBracesAndTrimSpaces(v string) (variable string, isShallow bool) {
variable = strings.ReplaceAll(v, "{{", "")
variable = strings.ReplaceAll(variable, "}}", "")
variable = strings.TrimSpace(variable)
return variable
if strings.HasPrefix(variable, "-") {
variable = strings.TrimSpace(variable[1:])
return variable, true
}
return variable, false
}
func resolveReference(fullDocument interface{}, reference, absolutePath string) (interface{}, error) {

View file

@ -544,6 +544,50 @@ func Test_SubstituteRecursive(t *testing.T) {
}
}
func Test_SubstituteShallow(t *testing.T) {
ctx := context.NewContext(jp)
data := map[string]interface{}{
"variableWithVariables": "{{ DO_NOT_SUBSTITUTE_ME {{OR_ME}} }}",
"foo": "bar",
"foo2": "bar2",
"variablesNested": "{{foo2}}",
}
assert.NilError(t, context.AddJSONObject(ctx, data))
patternRaw := []byte(`"{{- variableWithVariables }} {{foo}} {{variablesNested}}"`)
action := substituteVariablesIfAny(logr.Discard(), ctx, DefaultVariableResolver)
results, err := action(&ju.ActionData{
Document: nil,
Element: string(patternRaw),
Path: "/",
})
assert.NilError(t, err)
assert.Equal(t, results.(string), "\"{{ DO_NOT_SUBSTITUTE_ME {{OR_ME}} }} bar bar2\"")
patternRaw = []byte(`"{{foo}} {{- variableWithVariables }} {{variablesNested}}"`)
action = substituteVariablesIfAny(logr.Discard(), ctx, DefaultVariableResolver)
results, err = action(&ju.ActionData{
Document: nil,
Element: string(patternRaw),
Path: "/",
})
assert.NilError(t, err)
assert.Equal(t, results.(string), "\"bar {{ DO_NOT_SUBSTITUTE_ME {{OR_ME}} }} bar2\"")
patternRaw = []byte(`"{{- variableWithVariables {{foo}} {{variablesNested}} }}"`)
action = substituteVariablesIfAny(logr.Discard(), ctx, DefaultVariableResolver)
_, err = action(&ju.ActionData{
Document: nil,
Element: string(patternRaw),
Path: "/",
})
assert.ErrorContains(t, err, "failed to resolve variableWithVariables bar bar2")
}
func Test_policyContextValidation(t *testing.T) {
policyContext := []byte(`
{