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:
parent
bc1a504462
commit
2289720ba0
5 changed files with 75 additions and 20 deletions
|
@ -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]'
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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(`\{{(.*?)\}}`)
|
||||
)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(`
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue