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