1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-15 12:17:56 +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,*,*]' - '[Pod/binding,*,*]'
- '[ReplicaSet,*,*]' - '[ReplicaSet,*,*]'
- '[ReplicaSet/*,*,*]' - '[ReplicaSet/*,*,*]'
- '[EphemeralReport,*,*]'
- '[ClusterEphemeralReport,*,*]'
# exclude resources from the chart # exclude resources from the chart
- '[ClusterRole,*,{{ template "kyverno.admission-controller.roleName" . }}]' - '[ClusterRole,*,{{ template "kyverno.admission-controller.roleName" . }}]'
- '[ClusterRole,*,{{ template "kyverno.admission-controller.roleName" . }}:core]' - '[ClusterRole,*,{{ template "kyverno.admission-controller.roleName" . }}:core]'

View file

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

View file

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

View file

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