diff --git a/api/kyverno/v1/match_resources_types.go b/api/kyverno/v1/match_resources_types.go index 915dbc2146..de93c4a886 100644 --- a/api/kyverno/v1/match_resources_types.go +++ b/api/kyverno/v1/match_resources_types.go @@ -30,6 +30,18 @@ type MatchResources struct { ResourceDescription `json:"resources,omitempty" yaml:"resources,omitempty"` } +// GetKinds returns all kinds +func (m *MatchResources) GetKinds() []string { + kinds := m.ResourceDescription.Kinds + for _, value := range m.All { + kinds = append(kinds, value.ResourceDescription.Kinds...) + } + for _, value := range m.Any { + kinds = append(kinds, value.ResourceDescription.Kinds...) + } + return kinds +} + // Validate implements programmatic validation func (m *MatchResources) Validate(path *field.Path, namespaced bool, clusterResources sets.String) (errs field.ErrorList) { if len(m.Any) > 0 && len(m.All) > 0 { diff --git a/api/kyverno/v1/rule_types.go b/api/kyverno/v1/rule_types.go index 7225a1f83a..1199f0c432 100644 --- a/api/kyverno/v1/rule_types.go +++ b/api/kyverno/v1/rule_types.go @@ -81,31 +81,6 @@ func (r *Rule) HasGenerate() bool { return !reflect.DeepEqual(r.Generation, Generation{}) } -// MatchKinds returns a slice of all kinds to match -func (r *Rule) MatchKinds() []string { - matchKinds := r.MatchResources.ResourceDescription.Kinds - for _, value := range r.MatchResources.All { - matchKinds = append(matchKinds, value.ResourceDescription.Kinds...) - } - for _, value := range r.MatchResources.Any { - matchKinds = append(matchKinds, value.ResourceDescription.Kinds...) - } - - return matchKinds -} - -// ExcludeKinds returns a slice of all kinds to exclude -func (r *Rule) ExcludeKinds() []string { - excludeKinds := r.ExcludeResources.ResourceDescription.Kinds - for _, value := range r.ExcludeResources.All { - excludeKinds = append(excludeKinds, value.ResourceDescription.Kinds...) - } - for _, value := range r.ExcludeResources.Any { - excludeKinds = append(excludeKinds, value.ResourceDescription.Kinds...) - } - return excludeKinds -} - func (r *Rule) GetAnyAllConditions() apiextensions.JSON { return FromJSON(r.RawAnyAllConditions) } diff --git a/pkg/autogen/autogen.go b/pkg/autogen/autogen.go index c0b4cc1598..0f0f96b82c 100644 --- a/pkg/autogen/autogen.go +++ b/pkg/autogen/autogen.go @@ -11,7 +11,9 @@ import ( "github.com/kyverno/kyverno/pkg/toggle" "github.com/kyverno/kyverno/pkg/utils" jsonutils "github.com/kyverno/kyverno/pkg/utils/json" + kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" log "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -22,6 +24,46 @@ const ( PodControllers = "DaemonSet,Deployment,Job,StatefulSet,CronJob" ) +var ( + podControllersKindsSet = sets.NewString(append(strings.Split(PodControllers, ","), "Pod")...) + podSet = sets.NewString("Pod") +) + +func isKindOtherthanPod(kinds []string) bool { + if len(kinds) > 1 && kubeutils.ContainsKind(kinds, "Pod") { + return true + } + return false +} + +func checkAutogenSupport(needed *bool, subjects ...kyverno.ResourceDescription) bool { + for _, subject := range subjects { + if subject.Name != "" || subject.Selector != nil || subject.Annotations != nil || isKindOtherthanPod(subject.Kinds) { + return false + } + if needed != nil { + *needed = *needed || podControllersKindsSet.HasAny(subject.Kinds...) + } + } + return true +} + +// stripCronJob removes CronJob from controllers +func stripCronJob(controllers string) string { + var newControllers []string + controllerArr := strings.Split(controllers, ",") + for _, c := range controllerArr { + if c == PodControllerCronJob { + continue + } + newControllers = append(newControllers, c) + } + if len(newControllers) == 0 { + return "" + } + return strings.Join(newControllers, ",") +} + // CanAutoGen checks whether the rule(s) (in policy) can be applied to Pod controllers // returns controllers as: // - "" if: @@ -31,82 +73,44 @@ const ( // - mutate.Patches/mutate.PatchesJSON6902/validate.deny/generate rule is defined // - otherwise it returns all pod controllers func CanAutoGen(spec *kyverno.Spec, log logr.Logger) (applyAutoGen bool, controllers string) { - var needAutogen bool - rules := spec.Rules - for _, rule := range rules { - match := rule.MatchResources - exclude := rule.ExcludeResources - - if match.ResourceDescription.Name != "" || match.ResourceDescription.Selector != nil || match.ResourceDescription.Annotations != nil || - exclude.ResourceDescription.Name != "" || exclude.ResourceDescription.Selector != nil || exclude.ResourceDescription.Annotations != nil { + needed := false + for _, rule := range spec.Rules { + if rule.Mutation.PatchesJSON6902 != "" || rule.HasGenerate() { + return false, "none" + } + match, exclude := rule.MatchResources, rule.ExcludeResources + if !checkAutogenSupport(&needed, match.ResourceDescription, exclude.ResourceDescription) { log.V(3).Info("skip generating rule on pod controllers: Name / Selector in resource description may not be applicable.", "rule", rule.Name) return false, "" } - - if isKindOtherthanPod(match.Kinds) || isKindOtherthanPod(exclude.Kinds) { - return false, "" - } - - needAutogen = hasAutogenKinds(match.Kinds) || hasAutogenKinds(exclude.Kinds) - for _, value := range match.Any { - if isKindOtherthanPod(value.Kinds) { - return false, "" - } - if !needAutogen { - needAutogen = hasAutogenKinds(value.Kinds) - } - if value.Name != "" || value.Selector != nil || value.Annotations != nil { + if !checkAutogenSupport(&needed, value.ResourceDescription) { log.V(3).Info("skip generating rule on pod controllers: Name / Selector in match any block is not be applicable.", "rule", rule.Name) return false, "" } } for _, value := range match.All { - if isKindOtherthanPod(value.Kinds) { - return false, "" - } - if !needAutogen { - needAutogen = hasAutogenKinds(value.Kinds) - } - if value.Name != "" || value.Selector != nil || value.Annotations != nil { + if !checkAutogenSupport(&needed, value.ResourceDescription) { log.V(3).Info("skip generating rule on pod controllers: Name / Selector in match all block is not be applicable.", "rule", rule.Name) return false, "" } } for _, value := range exclude.Any { - if isKindOtherthanPod(value.Kinds) { - return false, "" - } - if !needAutogen { - needAutogen = hasAutogenKinds(value.Kinds) - } - if value.Name != "" || value.Selector != nil || value.Annotations != nil { + if !checkAutogenSupport(&needed, value.ResourceDescription) { log.V(3).Info("skip generating rule on pod controllers: Name / Selector in exclude any block is not be applicable.", "rule", rule.Name) return false, "" } } for _, value := range exclude.All { - if isKindOtherthanPod(value.Kinds) { - return false, "" - } - if !needAutogen { - needAutogen = hasAutogenKinds(value.Kinds) - } - if value.Name != "" || value.Selector != nil || value.Annotations != nil { + if !checkAutogenSupport(&needed, value.ResourceDescription) { log.V(3).Info("skip generating rule on pod controllers: Name / Selector in exclud all block is not be applicable.", "rule", rule.Name) return false, "" } } - - if rule.Mutation.PatchesJSON6902 != "" || rule.HasGenerate() { - return false, "none" - } } - - if !needAutogen { + if !needed { return false, "" } - return true, PodControllers } @@ -139,14 +143,11 @@ func GetRequestedControllers(meta *metav1.ObjectMeta) []string { // It returns the requested, supported and effective controllers (intersection of requested and supported ones). func GetControllers(meta *metav1.ObjectMeta, spec *kyverno.Spec, log logr.Logger) ([]string, []string, []string) { // compute supported and requested controllers - supported := GetSupportedControllers(spec, log) - requested := GetRequestedControllers(meta) - + supported, requested := GetSupportedControllers(spec, log), GetRequestedControllers(meta) // no specific request, we can return supported controllers without further filtering if requested == nil { return requested, supported, supported } - // filter supported controllers, keeping only those that have been requested var activated []string for _, controller := range supported { @@ -168,24 +169,19 @@ func GetControllers(meta *metav1.ObjectMeta, spec *kyverno.Spec, log logr.Logger // GenerateRulePatches generates rule for podControllers based on scenario A and C func GenerateRulePatches(spec *kyverno.Spec, controllers string, log logr.Logger) (rulePatches [][]byte, errs []error) { - ruleMap := createRuleMap(spec.Rules) var ruleIndex = make(map[string]int) for index, rule := range spec.Rules { ruleIndex[rule.Name] = index } insertIdx := len(spec.Rules) - for _, rule := range spec.Rules { + genRules := generateRules(spec, controllers, log) + for i := range genRules { patchPostion := insertIdx convertToPatches := func(genRule kyvernoRule, patchPostion int) []byte { operation := "add" - if existingAutoGenRule, alreadyExists := ruleMap[genRule.Name]; alreadyExists { - existingAutoGenRuleRaw, _ := json.Marshal(existingAutoGenRule) - genRuleRaw, _ := json.Marshal(genRule) - if string(existingAutoGenRuleRaw) == string(genRuleRaw) { - return nil - } + if existingIndex, alreadyExists := ruleIndex[genRule.Name]; alreadyExists { operation = "replace" - patchPostion = ruleIndex[genRule.Name] + patchPostion = existingIndex } patch := jsonutils.NewPatch(fmt.Sprintf("/spec/rules/%s", strconv.Itoa(patchPostion)), operation, genRule) pbytes, err := patch.Marshal() @@ -199,27 +195,15 @@ func GenerateRulePatches(spec *kyverno.Spec, controllers string, log logr.Logger } return pbytes } - // handle all other controllers other than CronJob - genRule := generateRuleForControllers(rule, stripCronJob(controllers), log) + genRule := createRule(&genRules[i]) if genRule != nil { pbytes := convertToPatches(*genRule, patchPostion) - pbytes = updateGenRuleByte(pbytes, "Pod", *genRule) if pbytes != nil { rulePatches = append(rulePatches, pbytes) } insertIdx++ patchPostion = insertIdx } - // handle CronJob, it appends an additional rule - genRule = generateCronJobRule(rule, controllers, log) - if genRule != nil { - pbytes := convertToPatches(*genRule, patchPostion) - pbytes = updateGenRuleByte(pbytes, "Cronjob", *genRule) - if pbytes != nil { - rulePatches = append(rulePatches, pbytes) - } - insertIdx++ - } } return } @@ -233,18 +217,18 @@ func GenerateRulePatches(spec *kyverno.Spec, controllers string, log logr.Logger // copy entire match / exclude block, it's users' responsibility to // make sure all fields are applicable to pod controllers -// GenerateRules generates rule for podControllers based on scenario A and C -func GenerateRules(spec *kyverno.Spec, controllers string, log logr.Logger) []kyverno.Rule { +// generateRules generates rule for podControllers based on scenario A and C +func generateRules(spec *kyverno.Spec, controllers string, log logr.Logger) []kyverno.Rule { var rules []kyverno.Rule - for _, rule := range spec.Rules { + for i := range spec.Rules { // handle all other controllers other than CronJob - if genRule := generateRuleForControllers(*rule.DeepCopy(), stripCronJob(controllers), log); genRule != nil { + if genRule := createRule(generateRuleForControllers(&spec.Rules[i], stripCronJob(controllers), log)); genRule != nil { if convRule, err := convertRule(*genRule, "Pod"); err == nil { rules = append(rules, *convRule) } } // handle CronJob, it appends an additional rule - if genRule := generateCronJobRule(*rule.DeepCopy(), controllers, log); genRule != nil { + if genRule := createRule(generateCronJobRule(&spec.Rules[i], controllers, log)); genRule != nil { if convRule, err := convertRule(*genRule, "Cronjob"); err == nil { rules = append(rules, *convRule) } @@ -257,7 +241,7 @@ func convertRule(rule kyvernoRule, kind string) (*kyverno.Rule, error) { if bytes, err := json.Marshal(rule); err != nil { return nil, err } else { - bytes = updateGenRuleByte(bytes, kind, rule) + bytes = updateGenRuleByte(bytes, kind) if err := json.Unmarshal(bytes, &rule); err != nil { return nil, err } @@ -308,7 +292,7 @@ func ComputeRules(p kyverno.PolicyInterface) []kyverno.Rule { if actualControllers == "none" { return spec.Rules } - genRules := GenerateRules(spec.DeepCopy(), actualControllers, log.Log) + genRules := generateRules(spec.DeepCopy(), actualControllers, log.Log) if len(genRules) == 0 { return spec.Rules } diff --git a/pkg/autogen/autogen_test.go b/pkg/autogen/autogen_test.go index 879d4800bb..7e6d2378c5 100644 --- a/pkg/autogen/autogen_test.go +++ b/pkg/autogen/autogen_test.go @@ -11,12 +11,47 @@ import ( kyverno "github.com/kyverno/kyverno/api/kyverno/v1" "github.com/kyverno/kyverno/pkg/utils" - jsonutils "github.com/kyverno/kyverno/pkg/utils/json" "gotest.tools/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/log" ) +func Test_getAutogenRuleName(t *testing.T) { + testCases := []struct { + name string + ruleName string + prefix string + expected string + }{ + {"valid", "valid-rule-name", "autogen", "autogen-valid-rule-name"}, + {"truncated", "too-long-this-rule-name-will-be-truncated-to-63-characters", "autogen", "autogen-too-long-this-rule-name-will-be-truncated-to-63-charact"}, + {"valid-cronjob", "valid-rule-name", "autogen-cronjob", "autogen-cronjob-valid-rule-name"}, + {"truncated-cronjob", "too-long-this-rule-name-will-be-truncated-to-63-characters", "autogen-cronjob", "autogen-cronjob-too-long-this-rule-name-will-be-truncated-to-63"}, + } + for _, test := range testCases { + res := getAutogenRuleName(test.prefix, test.ruleName) + assert.Equal(t, test.expected, res) + } +} + +func Test_isAutogenRule(t *testing.T) { + testCases := []struct { + name string + ruleName string + expected bool + }{ + {"normal", "valid-rule-name", false}, + {"simple", "autogen-simple", true}, + {"simple-cronjob", "autogen-cronjob-simple", true}, + {"truncated", "autogen-too-long-this-rule-name-will-be-truncated-to-63-charact", true}, + {"truncated-cronjob", "autogen-cronjob-too-long-this-rule-name-will-be-truncated-to-63", true}, + } + for _, test := range testCases { + res := isAutogenRuleName(test.ruleName) + assert.Equal(t, test.expected, res) + } +} + func Test_CanAutoGen(t *testing.T) { testCases := []struct { name string @@ -271,7 +306,6 @@ func Test_Any(t *testing.T) { } rulePatches, errs := GenerateRulePatches(spec, PodControllers, log.Log) - fmt.Println("utils.JoinPatches(patches)erterter", string(jsonutils.JoinPatches(rulePatches...))) if len(errs) != 0 { t.Log(errs) } @@ -359,7 +393,6 @@ func Test_Exclude(t *testing.T) { } func Test_CronJobOnly(t *testing.T) { - controllers := PodControllerCronJob dir, err := os.Getwd() baseDir := filepath.Dir(filepath.Dir(dir)) @@ -424,7 +457,6 @@ func Test_ForEachPod(t *testing.T) { } func Test_CronJob_hasExclude(t *testing.T) { - controllers := PodControllerCronJob dir, err := os.Getwd() baseDir := filepath.Dir(filepath.Dir(dir)) @@ -518,7 +550,10 @@ func Test_UpdateVariablePath(t *testing.T) { []byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-select-secrets-from-volumes","match":{"resources":{"kinds":["CronJob"]}},"context":[{"name":"volsecret","apiCall":{"urlPath":"/api/v1/namespaces/{{request.object.spec.template.metadata.namespace}}/secrets/{{request.object.spec.jobTemplate.spec.template.spec.volumes[0].secret.secretName}}","jmesPath":"metadata.labels.foo"}}],"preconditions":[{"key":"{{ request.operation }}","operator":"Equals","value":"CREATE"}],"validate":{"message":"The Secret named {{request.object.spec.jobTemplate.spec.template.spec.volumes[0].secret.secretName}} is restricted and may not be used.","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"containers":[{"image":"registry.domain.com/*"}]}}}}}}}}}`), } - assert.DeepEqual(t, rulePatches, expectedPatches) + for i, ep := range expectedPatches { + assert.Equal(t, string(rulePatches[i]), string(ep), + fmt.Sprintf("unexpected patch: %s\nexpected: %s", rulePatches[i], ep)) + } } func Test_Deny(t *testing.T) { @@ -545,7 +580,6 @@ func Test_Deny(t *testing.T) { } rulePatches, errs := GenerateRulePatches(spec, PodControllers, log.Log) - fmt.Println("utils.JoinPatches(patches)erterter", string(jsonutils.JoinPatches(rulePatches...))) if len(errs) != 0 { t.Log(errs) } diff --git a/pkg/autogen/rule.go b/pkg/autogen/rule.go index a9f852f09b..5294ea6d94 100644 --- a/pkg/autogen/rule.go +++ b/pkg/autogen/rule.go @@ -1,8 +1,6 @@ package autogen import ( - "encoding/json" - "fmt" "reflect" "strings" @@ -34,55 +32,199 @@ type kyvernoRule struct { VerifyImages []*kyverno.ImageVerification `json:"verifyImages,omitempty" yaml:"verifyImages,omitempty"` } -func createRuleMap(rules []kyverno.Rule) map[string]kyvernoRule { - var ruleMap = make(map[string]kyvernoRule) - for _, rule := range rules { - var jsonFriendlyStruct kyvernoRule - - jsonFriendlyStruct.Name = rule.Name - - if !reflect.DeepEqual(rule.MatchResources, kyverno.MatchResources{}) { - jsonFriendlyStruct.MatchResources = rule.MatchResources.DeepCopy() - } - - if !reflect.DeepEqual(rule.ExcludeResources, kyverno.MatchResources{}) { - jsonFriendlyStruct.ExcludeResources = rule.ExcludeResources.DeepCopy() - } - - if !reflect.DeepEqual(rule.Mutation, kyverno.Mutation{}) { - jsonFriendlyStruct.Mutation = rule.Mutation.DeepCopy() - } - - if !reflect.DeepEqual(rule.Validation, kyverno.Validation{}) { - jsonFriendlyStruct.Validation = rule.Validation.DeepCopy() - } - - ruleMap[rule.Name] = jsonFriendlyStruct +func createRule(rule *kyverno.Rule) *kyvernoRule { + if rule == nil { + return nil } - return ruleMap + jsonFriendlyStruct := kyvernoRule{ + Name: rule.Name, + } + if !reflect.DeepEqual(rule.MatchResources, kyverno.MatchResources{}) { + jsonFriendlyStruct.MatchResources = rule.MatchResources.DeepCopy() + } + if !reflect.DeepEqual(rule.ExcludeResources, kyverno.MatchResources{}) { + jsonFriendlyStruct.ExcludeResources = rule.ExcludeResources.DeepCopy() + } + if !reflect.DeepEqual(rule.Mutation, kyverno.Mutation{}) { + jsonFriendlyStruct.Mutation = rule.Mutation.DeepCopy() + } + if !reflect.DeepEqual(rule.Validation, kyverno.Validation{}) { + jsonFriendlyStruct.Validation = rule.Validation.DeepCopy() + } + kyvernoAnyAllConditions, _ := utils.ApiextensionsJsonToKyvernoConditions(rule.GetAnyAllConditions()) + switch typedAnyAllConditions := kyvernoAnyAllConditions.(type) { + case kyverno.AnyAllConditions: + if !reflect.DeepEqual(typedAnyAllConditions, kyverno.AnyAllConditions{}) { + jsonFriendlyStruct.AnyAllConditions = rule.DeepCopy().RawAnyAllConditions + } + case []kyverno.Condition: + if len(typedAnyAllConditions) > 0 { + jsonFriendlyStruct.AnyAllConditions = rule.DeepCopy().RawAnyAllConditions + } + } + if len(rule.Context) > 0 { + jsonFriendlyStruct.Context = &rule.DeepCopy().Context + } + return &jsonFriendlyStruct } -func generateRuleForControllers(rule kyverno.Rule, controllers string, log logr.Logger) *kyvernoRule { - logger := log.WithName("generateRuleForControllers") +type generateResourceFilters func(kyverno.ResourceFilters, []string) kyverno.ResourceFilters - if strings.HasPrefix(rule.Name, "autogen-") || controllers == "" { +func generateRule(logger logr.Logger, name string, rule *kyverno.Rule, tplKey, shift string, kinds []string, grf generateResourceFilters) *kyverno.Rule { + if rule == nil { + return nil + } + rule = rule.DeepCopy() + rule.Name = name + // overwrite Kinds by pod controllers defined in the annotation + if len(rule.MatchResources.Any) > 0 { + rule.MatchResources.Any = grf(rule.MatchResources.Any, kinds) + } else if len(rule.MatchResources.All) > 0 { + rule.MatchResources.All = grf(rule.MatchResources.All, kinds) + } else { + rule.MatchResources.Kinds = kinds + } + if len(rule.ExcludeResources.Any) > 0 { + rule.ExcludeResources.Any = grf(rule.ExcludeResources.Any, kinds) + } else if len(rule.ExcludeResources.All) > 0 { + rule.ExcludeResources.All = grf(rule.ExcludeResources.All, kinds) + } else { + if len(rule.ExcludeResources.Kinds) != 0 { + rule.ExcludeResources.Kinds = kinds + } + } + if target := rule.Mutation.GetPatchStrategicMerge(); target != nil { + newMutation := kyverno.Mutation{} + newMutation.SetPatchStrategicMerge( + map[string]interface{}{ + "spec": map[string]interface{}{ + tplKey: target, + }, + }, + ) + rule.Mutation = newMutation + return rule + } + if len(rule.Mutation.ForEachMutation) > 0 && rule.Mutation.ForEachMutation != nil { + var newForeachMutation []*kyverno.ForEachMutation + for _, foreach := range rule.Mutation.ForEachMutation { + temp := kyverno.ForEachMutation{ + List: foreach.List, + Context: foreach.Context, + AnyAllConditions: foreach.AnyAllConditions, + } + temp.SetPatchStrategicMerge( + map[string]interface{}{ + "spec": map[string]interface{}{ + tplKey: foreach.GetPatchStrategicMerge(), + }, + }, + ) + newForeachMutation = append(newForeachMutation, &temp) + } + rule.Mutation = kyverno.Mutation{ + ForEachMutation: newForeachMutation, + } + return rule + } + if target := rule.Validation.GetPattern(); target != nil { + newValidate := kyverno.Validation{ + Message: variables.FindAndShiftReferences(logger, rule.Validation.Message, shift, "pattern"), + } + newValidate.SetPattern( + map[string]interface{}{ + "spec": map[string]interface{}{ + tplKey: target, + }, + }, + ) + rule.Validation = newValidate + return rule + } + if rule.Validation.Deny != nil { + deny := kyverno.Validation{ + Message: variables.FindAndShiftReferences(logger, rule.Validation.Message, shift, "deny"), + Deny: rule.Validation.Deny, + } + rule.Validation = deny + return rule + } + if rule.Validation.GetAnyPattern() != nil { + anyPatterns, err := rule.Validation.DeserializeAnyPattern() + if err != nil { + logger.Error(err, "failed to deserialize anyPattern, expect type array") + } + var patterns []interface{} + for _, pattern := range anyPatterns { + newPattern := map[string]interface{}{ + "spec": map[string]interface{}{ + tplKey: pattern, + }, + } + patterns = append(patterns, newPattern) + } + rule.Validation = kyverno.Validation{ + Message: variables.FindAndShiftReferences(logger, rule.Validation.Message, shift, "anyPattern"), + } + rule.Validation.SetAnyPattern(patterns) + return rule + } + if len(rule.Validation.ForEachValidation) > 0 && rule.Validation.ForEachValidation != nil { + newForeachValidate := make([]*kyverno.ForEachValidation, len(rule.Validation.ForEachValidation)) + for i, foreach := range rule.Validation.ForEachValidation { + newForeachValidate[i] = foreach + } + rule.Validation = kyverno.Validation{ + Message: variables.FindAndShiftReferences(logger, rule.Validation.Message, shift, "pattern"), + ForEachValidation: newForeachValidate, + } + return rule + } + if rule.VerifyImages != nil { + newVerifyImages := make([]*kyverno.ImageVerification, len(rule.VerifyImages)) + for i, vi := range rule.VerifyImages { + newVerifyImages[i] = vi.DeepCopy() + } + rule.VerifyImages = newVerifyImages + return rule + } + return nil +} + +func getAutogenRuleName(prefix string, name string) string { + name = prefix + "-" + name + if len(name) > 63 { + name = name[:63] + } + return name +} + +func isAutogenRuleName(name string) bool { + return strings.HasPrefix(name, "autogen-") +} + +func getAnyAllAutogenRule(v kyverno.ResourceFilters, match string, kinds []string) kyverno.ResourceFilters { + anyKind := v.DeepCopy() + for i, value := range v { + if kubeutils.ContainsKind(value.Kinds, match) { + anyKind[i].Kinds = kinds + } + } + return anyKind +} + +func generateRuleForControllers(rule *kyverno.Rule, controllers string, log logr.Logger) *kyverno.Rule { + logger := log.WithName("generateRuleForControllers") + if isAutogenRuleName(rule.Name) || controllers == "" { logger.V(5).Info("skip generateRuleForControllers") return nil } - logger.V(3).Info("processing rule", "rulename", rule.Name) - - match := rule.MatchResources - exclude := rule.ExcludeResources - - matchResourceDescriptionsKinds := rule.MatchKinds() - excludeResourceDescriptionsKinds := rule.ExcludeKinds() - - if !kubeutils.ContainsKind(matchResourceDescriptionsKinds, "Pod") || - (len(excludeResourceDescriptionsKinds) != 0 && !kubeutils.ContainsKind(excludeResourceDescriptionsKinds, "Pod")) { + match, exclude := rule.MatchResources, rule.ExcludeResources + matchKinds, excludeKinds := match.GetKinds(), exclude.GetKinds() + if !kubeutils.ContainsKind(matchKinds, "Pod") || (len(excludeKinds) != 0 && !kubeutils.ContainsKind(excludeKinds, "Pod")) { return nil } - // Support backwards compatibility skipAutoGeneration := false var controllersValidated []string @@ -99,7 +241,6 @@ func generateRuleForControllers(rule kyverno.Rule, controllers string, log logr. skipAutoGeneration = true } } - if skipAutoGeneration { if controllers == "all" { controllers = "DaemonSet,Deployment,Job,StatefulSet" @@ -107,317 +248,40 @@ func generateRuleForControllers(rule kyverno.Rule, controllers string, log logr. controllers = strings.Join(controllersValidated, ",") } } - - name := fmt.Sprintf("autogen-%s", rule.Name) - if len(name) > 63 { - name = name[:63] - } - - controllerRule := &kyvernoRule{ - Name: name, - MatchResources: match.DeepCopy(), - } - - if len(rule.Context) > 0 { - controllerRule.Context = &rule.DeepCopy().Context - } - - kyvernoAnyAllConditions, _ := utils.ApiextensionsJsonToKyvernoConditions(rule.GetAnyAllConditions()) - switch typedAnyAllConditions := kyvernoAnyAllConditions.(type) { - case kyverno.AnyAllConditions: - if !reflect.DeepEqual(typedAnyAllConditions, kyverno.AnyAllConditions{}) { - controllerRule.AnyAllConditions = rule.DeepCopy().RawAnyAllConditions - } - case []kyverno.Condition: - if len(typedAnyAllConditions) > 0 { - controllerRule.AnyAllConditions = rule.DeepCopy().RawAnyAllConditions - } - } - - if !reflect.DeepEqual(exclude, kyverno.MatchResources{}) { - controllerRule.ExcludeResources = exclude.DeepCopy() - } - - // overwrite Kinds by pod controllers defined in the annotation - if len(rule.MatchResources.Any) > 0 { - rule := getAnyAllAutogenRule(controllerRule.MatchResources.Any, controllers) - controllerRule.MatchResources.Any = rule - } else if len(rule.MatchResources.All) > 0 { - rule := getAnyAllAutogenRule(controllerRule.MatchResources.All, controllers) - controllerRule.MatchResources.All = rule - } else { - controllerRule.MatchResources.Kinds = strings.Split(controllers, ",") - } - - if len(rule.ExcludeResources.Any) > 0 { - rule := getAnyAllAutogenRule(controllerRule.ExcludeResources.Any, controllers) - controllerRule.ExcludeResources.Any = rule - } else if len(rule.ExcludeResources.All) > 0 { - rule := getAnyAllAutogenRule(controllerRule.ExcludeResources.All, controllers) - controllerRule.ExcludeResources.All = rule - } else { - if len(exclude.Kinds) != 0 { - controllerRule.ExcludeResources.Kinds = strings.Split(controllers, ",") - } - } - - if target := rule.Mutation.GetPatchStrategicMerge(); target != nil { - newMutation := &kyverno.Mutation{} - newMutation.SetPatchStrategicMerge( - map[string]interface{}{ - "spec": map[string]interface{}{ - "template": target, - }, - }, - ) - controllerRule.Mutation = newMutation.DeepCopy() - return controllerRule - } - - if len(rule.Mutation.ForEachMutation) > 0 && rule.Mutation.ForEachMutation != nil { - var newForeachMutation []*kyverno.ForEachMutation - for _, foreach := range rule.Mutation.ForEachMutation { - temp := &kyverno.ForEachMutation{ - List: foreach.List, - Context: foreach.Context, - AnyAllConditions: foreach.AnyAllConditions, - } - temp.SetPatchStrategicMerge( - map[string]interface{}{ - "spec": map[string]interface{}{ - "template": foreach.GetPatchStrategicMerge(), - }, - }, - ) - newForeachMutation = append(newForeachMutation, temp) - } - controllerRule.Mutation = &kyverno.Mutation{ - ForEachMutation: newForeachMutation, - } - return controllerRule - } - - if target := rule.Validation.GetPattern(); target != nil { - newValidate := &kyverno.Validation{ - Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/template", "pattern"), - } - newValidate.SetPattern( - map[string]interface{}{ - "spec": map[string]interface{}{ - "template": target, - }, - }, - ) - controllerRule.Validation = newValidate.DeepCopy() - return controllerRule - } - - if rule.Validation.Deny != nil { - deny := &kyverno.Validation{ - Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/template", "deny"), - Deny: rule.Validation.Deny, - } - controllerRule.Validation = deny.DeepCopy() - return controllerRule - } - - if rule.Validation.GetAnyPattern() != nil { - anyPatterns, err := rule.Validation.DeserializeAnyPattern() - if err != nil { - logger.Error(err, "failed to deserialize anyPattern, expect type array") - } - patterns := validateAnyPattern(anyPatterns) - controllerRule.Validation = &kyverno.Validation{ - Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/template", "anyPattern"), - } - controllerRule.Validation.SetAnyPattern(patterns) - return controllerRule - } - - if len(rule.Validation.ForEachValidation) > 0 && rule.Validation.ForEachValidation != nil { - newForeachValidate := make([]*kyverno.ForEachValidation, len(rule.Validation.ForEachValidation)) - for i, foreach := range rule.Validation.ForEachValidation { - newForeachValidate[i] = foreach - } - controllerRule.Validation = &kyverno.Validation{ - Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/template", "pattern"), - ForEachValidation: newForeachValidate, - } - return controllerRule - } - - if rule.VerifyImages != nil { - newVerifyImages := make([]*kyverno.ImageVerification, len(rule.VerifyImages)) - for i, vi := range rule.VerifyImages { - newVerifyImages[i] = vi.DeepCopy() - } - - controllerRule.VerifyImages = newVerifyImages - return controllerRule - } - - return nil + return generateRule( + logger, + getAutogenRuleName("autogen", rule.Name), + rule, + "template", + "spec/template", + strings.Split(controllers, ","), + func(r kyverno.ResourceFilters, kinds []string) kyverno.ResourceFilters { + return getAnyAllAutogenRule(r, "Pod", kinds) + }, + ) } -func generateCronJobRule(rule kyverno.Rule, controllers string, log logr.Logger) *kyvernoRule { - logger := log.WithName("handleCronJob") - +func generateCronJobRule(rule *kyverno.Rule, controllers string, log logr.Logger) *kyverno.Rule { + logger := log.WithName("generateCronJobRule") hasCronJob := strings.Contains(controllers, PodControllerCronJob) || strings.Contains(controllers, "all") if !hasCronJob { return nil } - logger.V(3).Info("generating rule for cronJob") - jobRule := generateRuleForControllers(rule, "Job", logger) - - if jobRule == nil { - return nil - } - - cronJobRule := jobRule - - name := fmt.Sprintf("autogen-cronjob-%s", rule.Name) - if len(name) > 63 { - name = name[:63] - } - cronJobRule.Name = name - - if len(jobRule.MatchResources.Any) > 0 { - rule := cronJobAnyAllAutogenRule(cronJobRule.MatchResources.Any) - cronJobRule.MatchResources.Any = rule - } else if len(jobRule.MatchResources.All) > 0 { - rule := cronJobAnyAllAutogenRule(cronJobRule.MatchResources.All) - cronJobRule.MatchResources.All = rule - } else { - cronJobRule.MatchResources.Kinds = []string{PodControllerCronJob} - } - - if (jobRule.ExcludeResources) != nil && len(jobRule.ExcludeResources.Any) > 0 { - rule := cronJobAnyAllAutogenRule(cronJobRule.ExcludeResources.Any) - cronJobRule.ExcludeResources.Any = rule - } else if (jobRule.ExcludeResources) != nil && len(jobRule.ExcludeResources.All) > 0 { - rule := cronJobAnyAllAutogenRule(cronJobRule.ExcludeResources.All) - cronJobRule.ExcludeResources.All = rule - } else { - if (jobRule.ExcludeResources) != nil && (len(jobRule.ExcludeResources.Kinds) > 0) { - cronJobRule.ExcludeResources.Kinds = []string{PodControllerCronJob} - } - } - - if jobRule.Mutation != nil { - if target := jobRule.Mutation.GetPatchStrategicMerge(); target != nil { - newMutation := &kyverno.Mutation{} - newMutation.SetPatchStrategicMerge( - map[string]interface{}{ - "spec": map[string]interface{}{ - "jobTemplate": target, - }, - }, - ) - cronJobRule.Mutation = newMutation.DeepCopy() - return cronJobRule - } - } - - if jobRule.Validation != nil { - if target := jobRule.Validation.GetPattern(); target != nil { - newValidate := &kyverno.Validation{ - Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/jobTemplate/spec/template", "pattern"), - } - newValidate.SetPattern( - map[string]interface{}{ - "spec": map[string]interface{}{ - "jobTemplate": target, - }, - }, - ) - cronJobRule.Validation = newValidate.DeepCopy() - return cronJobRule - } - - if jobRule.Validation.Deny != nil { - newValidate := &kyverno.Validation{ - Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/jobTemplate/spec/template", "pattern"), - Deny: jobRule.Validation.Deny, - } - cronJobRule.Validation = newValidate.DeepCopy() - return cronJobRule - } - - if target := jobRule.Validation.GetAnyPattern(); target != nil { - var patterns []interface{} - anyPatterns, err := jobRule.Validation.DeserializeAnyPattern() - if err != nil { - logger.Error(err, "failed to deserialize anyPattern, expect type array") - } - for _, pattern := range anyPatterns { - newPattern := map[string]interface{}{ - "spec": map[string]interface{}{ - "jobTemplate": pattern, - }, - } - patterns = append(patterns, newPattern) - } - cronJobRule.Validation = &kyverno.Validation{ - Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/jobTemplate/spec/template", "anyPattern"), - } - cronJobRule.Validation.SetAnyPattern(patterns) - return cronJobRule - } - - if len(jobRule.Validation.ForEachValidation) > 0 && jobRule.Validation.ForEachValidation != nil { - newForeachValidate := make([]*kyverno.ForEachValidation, len(jobRule.Validation.ForEachValidation)) - for i, foreach := range rule.Validation.ForEachValidation { - newForeachValidate[i] = foreach - } - cronJobRule.Validation = &kyverno.Validation{ - Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/template", "pattern"), - ForEachValidation: newForeachValidate, - } - return cronJobRule - } - } - - if jobRule.Mutation != nil && len(jobRule.Mutation.ForEachMutation) > 0 && jobRule.Mutation.ForEachMutation != nil { - var newForeachMutation []*kyverno.ForEachMutation - for _, foreach := range jobRule.Mutation.ForEachMutation { - temp := &kyverno.ForEachMutation{ - List: foreach.List, - Context: foreach.Context, - AnyAllConditions: foreach.AnyAllConditions, - } - temp.SetPatchStrategicMerge( - map[string]interface{}{ - "spec": map[string]interface{}{ - "jobTemplate": foreach.GetPatchStrategicMerge(), - }, - }, - ) - newForeachMutation = append(newForeachMutation, temp) - } - cronJobRule.Mutation = &kyverno.Mutation{ - ForEachMutation: newForeachMutation, - } - return cronJobRule - } - - if jobRule.VerifyImages != nil { - newVerifyImages := make([]*kyverno.ImageVerification, len(jobRule.VerifyImages)) - for i, vi := range rule.VerifyImages { - newVerifyImages[i] = vi.DeepCopy() - } - cronJobRule.VerifyImages = newVerifyImages - return cronJobRule - } - - return nil + return generateRule( + logger, + getAutogenRuleName("autogen-cronjob", rule.Name), + generateRuleForControllers(rule, controllers, log), + "jobTemplate", + "spec/jobTemplate/spec/template", + []string{PodControllerCronJob}, + func(r kyverno.ResourceFilters, kinds []string) kyverno.ResourceFilters { + return getAnyAllAutogenRule(r, "Job", kinds) + }, + ) } -func updateGenRuleByte(pbyte []byte, kind string, genRule kyvernoRule) (obj []byte) { - // TODO: do we need to unmarshall here ? - if err := json.Unmarshal(pbyte, &genRule); err != nil { - return obj - } +func updateGenRuleByte(pbyte []byte, kind string) (obj []byte) { if kind == "Pod" { obj = []byte(strings.ReplaceAll(string(pbyte), "request.object.spec", "request.object.spec.template.spec")) } diff --git a/pkg/autogen/utils.go b/pkg/autogen/utils.go deleted file mode 100644 index 5b4981cc9a..0000000000 --- a/pkg/autogen/utils.go +++ /dev/null @@ -1,73 +0,0 @@ -package autogen - -import ( - "strings" - - kyverno "github.com/kyverno/kyverno/api/kyverno/v1" - kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" -) - -func isKindOtherthanPod(kinds []string) bool { - if len(kinds) > 1 && kubeutils.ContainsKind(kinds, "Pod") { - return true - } - return false -} - -func hasAutogenKinds(kind []string) bool { - for _, v := range kind { - if v == "Pod" || strings.Contains(PodControllers, v) { - return true - } - } - return false -} - -func validateAnyPattern(anyPatterns []interface{}) []interface{} { - var patterns []interface{} - for _, pattern := range anyPatterns { - newPattern := map[string]interface{}{ - "spec": map[string]interface{}{ - "template": pattern, - }, - } - patterns = append(patterns, newPattern) - } - return patterns -} - -func getAnyAllAutogenRule(v kyverno.ResourceFilters, controllers string) kyverno.ResourceFilters { - anyKind := v.DeepCopy() - for i, value := range v { - if kubeutils.ContainsKind(value.Kinds, "Pod") { - anyKind[i].Kinds = strings.Split(controllers, ",") - } - } - return anyKind -} - -// stripCronJob removes CronJob from controllers -func stripCronJob(controllers string) string { - var newControllers []string - controllerArr := strings.Split(controllers, ",") - for _, c := range controllerArr { - if c == PodControllerCronJob { - continue - } - newControllers = append(newControllers, c) - } - if len(newControllers) == 0 { - return "" - } - return strings.Join(newControllers, ",") -} - -func cronJobAnyAllAutogenRule(v kyverno.ResourceFilters) kyverno.ResourceFilters { - anyKind := v.DeepCopy() - for i, value := range v { - if kubeutils.ContainsKind(value.Kinds, "Job") { - anyKind[i].Kinds = []string{PodControllerCronJob} - } - } - return anyKind -} diff --git a/pkg/policy/existing.go b/pkg/policy/existing.go index b00af3e221..12f8a4ec62 100644 --- a/pkg/policy/existing.go +++ b/pkg/policy/existing.go @@ -30,8 +30,7 @@ func (pc *PolicyController) processExistingResources(policy kyverno.PolicyInterf if !rule.HasValidate() && !rule.HasVerifyImages() { continue } - - matchKinds := rule.MatchKinds() + matchKinds := rule.MatchResources.GetKinds() pc.processExistingKinds(matchKinds, policy, rule, logger) } } diff --git a/pkg/webhookconfig/configmanager.go b/pkg/webhookconfig/configmanager.go index 505fd9f7b7..91f3043326 100644 --- a/pkg/webhookconfig/configmanager.go +++ b/pkg/webhookconfig/configmanager.go @@ -734,7 +734,7 @@ func (m *webhookConfigManager) mergeWebhook(dst *webhook, policy kyverno.PolicyI for _, rule := range autogen.ComputeRules(policy) { // matching kinds in generate policies need to be added to both webhook if rule.HasGenerate() { - matchedGVK = append(matchedGVK, rule.MatchKinds()...) + matchedGVK = append(matchedGVK, rule.MatchResources.GetKinds()...) matchedGVK = append(matchedGVK, rule.Generation.ResourceSpec.Kind) continue } @@ -742,7 +742,7 @@ func (m *webhookConfigManager) mergeWebhook(dst *webhook, policy kyverno.PolicyI if (updateValidate && rule.HasValidate()) || (!updateValidate && rule.HasMutate()) || (!updateValidate && rule.HasVerifyImages()) { - matchedGVK = append(matchedGVK, rule.MatchKinds()...) + matchedGVK = append(matchedGVK, rule.MatchResources.GetKinds()...) } } @@ -847,7 +847,7 @@ func webhookKey(webhookKind, failurePolicy string) string { func hasWildcard(spec *kyverno.Spec) bool { for _, rule := range spec.Rules { - if kinds := rule.MatchKinds(); utils.ContainsString(kinds, "*") { + if kinds := rule.MatchResources.GetKinds(); utils.ContainsString(kinds, "*") { return true } }