diff --git a/go.mod b/go.mod index 80fa63b38b..e6bfca11e5 100644 --- a/go.mod +++ b/go.mod @@ -195,6 +195,7 @@ require ( github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jedisct1/go-minisign v0.0.0-20210703085342-c1f07ee84431 // indirect github.com/jhump/protoreflect v1.9.0 // indirect + github.com/jmoiron/jsonq v0.0.0-20150511023944-e874b168d07e // indirect github.com/jonboulle/clockwork v0.2.2 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/go.sum b/go.sum index 5ab32227b1..aa14c23879 100644 --- a/go.sum +++ b/go.sum @@ -1310,6 +1310,8 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548 h1:dYTbLf4m0a5u0KLmPfB6mgxbcV7588bOCx79hxa5Sr4= github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVzF6no3QaDSMLGLEHtHSBSefs+MgcDWnmhmo= +github.com/jmoiron/jsonq v0.0.0-20150511023944-e874b168d07e h1:ZZCvgaRDZg1gC9/1xrsgaJzQUCQgniKtw0xjWywWAOE= +github.com/jmoiron/jsonq v0.0.0-20150511023944-e874b168d07e/go.mod h1:+rHyWac2R9oAZwFe1wGY2HBzFJJy++RHBg1cU23NkD8= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= diff --git a/pkg/policy/validate.go b/pkg/policy/validate.go index 4d78ec14c7..318dac9a3c 100644 --- a/pkg/policy/validate.go +++ b/pkg/policy/validate.go @@ -11,6 +11,7 @@ import ( "github.com/distribution/distribution/reference" jsonpatch "github.com/evanphx/json-patch/v5" "github.com/jmespath/go-jmespath" + "github.com/jmoiron/jsonq" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/common" "github.com/kyverno/kyverno/pkg/autogen" @@ -562,34 +563,26 @@ func validateMatchKindHelper(rule kyvernov1.Rule) error { // isLabelAndAnnotationsString :- Validate if labels and annotations contains only string values func isLabelAndAnnotationsString(rule kyvernov1.Rule) bool { - // checkMetadata - Verify if the labels and annotations contains string value inside metadata - checkMetadata := func(patternMap map[string]interface{}) bool { - for k := range patternMap { - if k == "metadata" { - metaKey, ok := patternMap[k].(map[string]interface{}) + + checkLabelAnnotation := func(metaKey map[string]interface{}) bool { + for mk := range metaKey { + if mk == "labels" { + labelKey, ok := metaKey[mk].(map[string]interface{}) if ok { - // range over metadata - for mk := range metaKey { - if mk == "labels" { - labelKey, ok := metaKey[mk].(map[string]interface{}) - if ok { - // range over labels - for _, val := range labelKey { - if reflect.TypeOf(val).String() != "string" { - return false - } - } - } - } else if mk == "annotations" { - annotationKey, ok := metaKey[mk].(map[string]interface{}) - if ok { - // range over annotations - for _, val := range annotationKey { - if reflect.TypeOf(val).String() != "string" { - return false - } - } - } + // range over labels + for _, val := range labelKey { + if reflect.TypeOf(val).String() != "string" { + return false + } + } + } + } else if mk == "annotations" { + annotationKey, ok := metaKey[mk].(map[string]interface{}) + if ok { + // range over annotations + for _, val := range annotationKey { + if reflect.TypeOf(val).String() != "string" { + return false } } } @@ -598,26 +591,72 @@ func isLabelAndAnnotationsString(rule kyvernov1.Rule) bool { return true } - patternMap, ok := rule.Validation.GetPattern().(map[string]interface{}) - if ok { - return checkMetadata(patternMap) - } else if rule.Validation.GetAnyPattern() != nil { - anyPatterns, err := rule.Validation.DeserializeAnyPattern() - if err != nil { - log.Log.Error(err, "failed to deserialize anyPattern, expect type array") - return false + // checkMetadata - Verify if the labels and annotations contains string value inside metadata + checkMetadata := func(patternMap map[string]interface{}) bool { + for k := range patternMap { + if k == "metadata" { + metaKey, ok := patternMap[k].(map[string]interface{}) + if ok { + // range over metadata + return checkLabelAnnotation(metaKey) + } + } + if k == "spec" { + metadata, _ := jsonq.NewQuery(patternMap).Object("spec", "template", "metadata") + return checkLabelAnnotation(metadata) + } } + return true + } - for _, pattern := range anyPatterns { - patternMap, ok := pattern.(map[string]interface{}) + if rule.HasValidate() { + if rule.Validation.ForEachValidation != nil { + for _, foreach := range rule.Validation.ForEachValidation { + patternMap, ok := foreach.GetPattern().(map[string]interface{}) + if ok { + return checkMetadata(patternMap) + } + } + } else { + patternMap, ok := rule.Validation.GetPattern().(map[string]interface{}) if ok { - ret := checkMetadata(patternMap) - if !ret { - return ret + return checkMetadata(patternMap) + } else if rule.Validation.GetAnyPattern() != nil { + anyPatterns, err := rule.Validation.DeserializeAnyPattern() + if err != nil { + log.Log.Error(err, "failed to deserialize anyPattern, expect type array") + return false + } + + for _, pattern := range anyPatterns { + patternMap, ok := pattern.(map[string]interface{}) + if ok { + ret := checkMetadata(patternMap) + if !ret { + return ret + } + } } } } } + + if rule.HasMutate() { + if rule.Mutation.ForEachMutation != nil { + for _, foreach := range rule.Mutation.ForEachMutation { + forEachStrategicMergeMap, ok := foreach.GetPatchStrategicMerge().(map[string]interface{}) + if ok { + return checkMetadata(forEachStrategicMergeMap) + } + } + } else { + strategicMergeMap, ok := rule.Mutation.GetPatchStrategicMerge().(map[string]interface{}) + if ok { + return checkMetadata(strategicMergeMap) + } + } + } + return true }