package autogen import ( "reflect" "strings" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" "github.com/kyverno/kyverno/pkg/engine/variables" "github.com/kyverno/kyverno/pkg/utils" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" ) // the kyvernoRule holds the temporary kyverno rule struct // each field is a pointer to the actual object // when serializing data, we would expect to drop the omitempty key // otherwise (without the pointer), it will be set to empty value // - an empty struct in this case, some may fail the schema validation // may related to: // https://github.com/kyverno/kyverno/pull/549#discussion_r360088556 // https://github.com/kyverno/kyverno/issues/568 type kyvernoRule struct { Name string `json:"name"` MatchResources *kyvernov1.MatchResources `json:"match"` ExcludeResources *kyvernov1.MatchResources `json:"exclude,omitempty"` Context *[]kyvernov1.ContextEntry `json:"context,omitempty"` AnyAllConditions *apiextensions.JSON `json:"preconditions,omitempty"` Mutation *kyvernov1.Mutation `json:"mutate,omitempty"` Validation *kyvernov1.Validation `json:"validate,omitempty"` VerifyImages []kyvernov1.ImageVerification `json:"verifyImages,omitempty" yaml:"verifyImages,omitempty"` } func createRule(rule *kyvernov1.Rule) *kyvernoRule { if rule == nil { return nil } jsonFriendlyStruct := kyvernoRule{ Name: rule.Name, VerifyImages: rule.VerifyImages, } if !reflect.DeepEqual(rule.MatchResources, kyvernov1.MatchResources{}) { jsonFriendlyStruct.MatchResources = rule.MatchResources.DeepCopy() } if !reflect.DeepEqual(rule.ExcludeResources, kyvernov1.MatchResources{}) { jsonFriendlyStruct.ExcludeResources = rule.ExcludeResources.DeepCopy() } if !reflect.DeepEqual(rule.Mutation, kyvernov1.Mutation{}) { jsonFriendlyStruct.Mutation = rule.Mutation.DeepCopy() } if !reflect.DeepEqual(rule.Validation, kyvernov1.Validation{}) { jsonFriendlyStruct.Validation = rule.Validation.DeepCopy() } kyvernoAnyAllConditions, _ := utils.ApiextensionsJsonToKyvernoConditions(rule.GetAnyAllConditions()) switch typedAnyAllConditions := kyvernoAnyAllConditions.(type) { case kyvernov1.AnyAllConditions: if !reflect.DeepEqual(typedAnyAllConditions, kyvernov1.AnyAllConditions{}) { jsonFriendlyStruct.AnyAllConditions = rule.DeepCopy().RawAnyAllConditions } case []kyvernov1.Condition: if len(typedAnyAllConditions) > 0 { jsonFriendlyStruct.AnyAllConditions = rule.DeepCopy().RawAnyAllConditions } } if len(rule.Context) > 0 { jsonFriendlyStruct.Context = &rule.DeepCopy().Context } return &jsonFriendlyStruct } type generateResourceFilters func(kyvernov1.ResourceFilters, []string) kyvernov1.ResourceFilters func generateRule(name string, rule *kyvernov1.Rule, tplKey, shift string, kinds []string, grf generateResourceFilters) *kyvernov1.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 := kyvernov1.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 []kyvernov1.ForEachMutation for _, foreach := range rule.Mutation.ForEachMutation { temp := kyvernov1.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 = kyvernov1.Mutation{ ForEachMutation: newForeachMutation, } return rule } if target := rule.Validation.GetPattern(); target != nil { newValidate := kyvernov1.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 := kyvernov1.Validation{ Message: variables.FindAndShiftReferences(logger, rule.Validation.Message, shift, "deny"), Deny: rule.Validation.Deny, } rule.Validation = deny return rule } if rule.Validation.PodSecurity != nil { newExclude := make([]kyvernov1.PodSecurityStandard, len(rule.Validation.PodSecurity.Exclude)) copy(newExclude, rule.Validation.PodSecurity.Exclude) podSecurity := kyvernov1.Validation{ Message: variables.FindAndShiftReferences(logger, rule.Validation.Message, shift, "podSecurity"), PodSecurity: &kyvernov1.PodSecurity{ Level: rule.Validation.PodSecurity.Level, Version: rule.Validation.PodSecurity.Version, Exclude: newExclude, }, } rule.Validation = podSecurity 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 = kyvernov1.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([]kyvernov1.ForEachValidation, len(rule.Validation.ForEachValidation)) copy(newForeachValidate, rule.Validation.ForEachValidation) rule.Validation = kyvernov1.Validation{ Message: variables.FindAndShiftReferences(logger, rule.Validation.Message, shift, "pattern"), ForEachValidation: newForeachValidate, } return rule } if rule.VerifyImages != nil { newVerifyImages := make([]kyvernov1.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 kyvernov1.ResourceFilters, match string, kinds []string) kyvernov1.ResourceFilters { anyKind := v.DeepCopy() for i, value := range v { if kubeutils.ContainsKind(value.Kinds, match) { anyKind[i].Kinds = kinds } } return anyKind } func generateRuleForControllers(rule *kyvernov1.Rule, controllers string) *kyvernov1.Rule { if isAutogenRuleName(rule.Name) || controllers == "" { logger.V(5).Info("skip generateRuleForControllers") return nil } logger.V(3).Info("processing rule", "rulename", rule.Name) 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 if controllers == "all" { skipAutoGeneration = true } else if controllers != "none" && controllers != "all" { controllersList := map[string]int{ "DaemonSet": 1, "Deployment": 1, "Job": 1, "StatefulSet": 1, "ReplicaSet": 1, "ReplicationController": 1, } for _, value := range strings.Split(controllers, ",") { if _, ok := controllersList[value]; ok { controllersValidated = append(controllersValidated, value) } } if len(controllersValidated) > 0 { skipAutoGeneration = true } } if skipAutoGeneration { if controllers == "all" { controllers = "DaemonSet,Deployment,Job,StatefulSet,ReplicaSet,ReplicationController" } else { controllers = strings.Join(controllersValidated, ",") } } return generateRule( getAutogenRuleName("autogen", rule.Name), rule, "template", "spec/template", strings.Split(controllers, ","), func(r kyvernov1.ResourceFilters, kinds []string) kyvernov1.ResourceFilters { return getAnyAllAutogenRule(r, "Pod", kinds) }, ) } func generateCronJobRule(rule *kyvernov1.Rule, controllers string) *kyvernov1.Rule { hasCronJob := strings.Contains(controllers, PodControllerCronJob) || strings.Contains(controllers, "all") if !hasCronJob { return nil } logger.V(3).Info("generating rule for cronJob") return generateRule( getAutogenRuleName("autogen-cronjob", rule.Name), generateRuleForControllers(rule, controllers), "jobTemplate", "spec/jobTemplate/spec/template", []string{PodControllerCronJob}, func(r kyvernov1.ResourceFilters, kinds []string) kyvernov1.ResourceFilters { return getAnyAllAutogenRule(r, "Job", kinds) }, ) } 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")) } if kind == "Cronjob" { obj = []byte(strings.ReplaceAll(string(pbyte), "request.object.spec", "request.object.spec.jobTemplate.spec.template.spec")) } obj = []byte(strings.ReplaceAll(string(obj), "request.object.metadata", "request.object.spec.template.metadata")) return obj } func updateRestrictedFields(pbyte []byte, kind string) (obj []byte) { if kind == "Pod" { obj = []byte(strings.ReplaceAll(string(pbyte), `"restrictedField":"spec`, `"restrictedField":"spec.template.spec`)) } if kind == "Cronjob" { obj = []byte(strings.ReplaceAll(string(pbyte), `"restrictedField":"spec`, `"restrictedField":"spec.jobTemplate.spec.template.spec`)) } obj = []byte(strings.ReplaceAll(string(obj), "metadata", "spec.template.metadata")) return obj }