diff --git a/pkg/autogen/autogen.go b/pkg/autogen/autogen.go new file mode 100644 index 0000000000..dc5bbeb45f --- /dev/null +++ b/pkg/autogen/autogen.go @@ -0,0 +1,185 @@ +package autogen + +import ( + "encoding/json" + "fmt" + "reflect" + "strconv" + + jsonpatch "github.com/evanphx/json-patch" + "github.com/go-logr/logr" + kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + "github.com/kyverno/kyverno/pkg/engine" +) + +// CanAutoGen checks whether the rule(s) (in policy) can be applied to Pod controllers +// returns controllers as: +// - "" if: +// - name or selector is defined +// - mixed kinds (Pod + pod controller) is defined +// - Pod and PodControllers are not defined +// - 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 + for _, rule := range spec.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 { + 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 { + 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 { + 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 { + 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 { + 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 { + return false, "" + } + + return true, engine.PodControllers +} + +// podControllersKey annotation could be: +// scenario A: not exist, set default to "all", which generates on all pod controllers +// - if name / selector exist in resource description -> skip +// as these fields may not be applicable to pod controllers +// scenario B: "none", user explicitly disable this feature -> skip +// scenario C: some certain controllers that user set -> generate on defined controllers +// copy entire match / exclude block, it's users' responsibility to +// make sure all fields are applicable to pod controllers + +// 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) { + insertIdx := len(spec.Rules) + + ruleMap := createRuleMap(spec.Rules) + var ruleIndex = make(map[string]int) + for index, rule := range spec.Rules { + ruleIndex[rule.Name] = index + } + + for _, rule := range spec.Rules { + 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 + } + operation = "replace" + patchPostion = ruleIndex[genRule.Name] + } + + // generate patch bytes + jsonPatch := struct { + Path string `json:"path"` + Op string `json:"op"` + Value interface{} `json:"value"` + }{ + fmt.Sprintf("/spec/rules/%s", strconv.Itoa(patchPostion)), + operation, + genRule, + } + pbytes, err := json.Marshal(jsonPatch) + if err != nil { + errs = append(errs, err) + return nil + } + + // check the patch + if _, err := jsonpatch.DecodePatch([]byte("[" + string(pbytes) + "]")); err != nil { + errs = append(errs, err) + return nil + } + + return pbytes + } + + // handle all other controllers other than CronJob + genRule := generateRuleForControllers(rule, stripCronJob(controllers), log) + if !reflect.DeepEqual(genRule, kyvernoRule{}) { + 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 !reflect.DeepEqual(genRule, kyvernoRule{}) { + pbytes := convertToPatches(genRule, patchPostion) + pbytes = updateGenRuleByte(pbytes, "Cronjob", genRule) + if pbytes != nil { + rulePatches = append(rulePatches, pbytes) + } + insertIdx++ + } + } + return +} diff --git a/pkg/autogen/autogen_test.go b/pkg/autogen/autogen_test.go new file mode 100644 index 0000000000..a3856f2327 --- /dev/null +++ b/pkg/autogen/autogen_test.go @@ -0,0 +1,416 @@ +package autogen + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + + kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + "github.com/kyverno/kyverno/pkg/engine" + "github.com/kyverno/kyverno/pkg/utils" + "gotest.tools/assert" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +func Test_getControllers(t *testing.T) { + testCases := []struct { + name string + policy []byte + expectedControllers string + }{ + { + name: "rule-with-match-name", + policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"test","match":{"resources":{"kinds":["Namespace"],"name":"*"}}}]}}`), + expectedControllers: "none", + }, + { + name: "rule-with-match-selector", + policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test-getcontrollers"},"spec":{"background":false,"rules":[{"name":"test-getcontrollers","match":{"resources":{"kinds":["Pod"],"selector":{"matchLabels":{"foo":"bar"}}}}}]}}`), + expectedControllers: "none", + }, + { + name: "rule-with-exclude-name", + policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test-getcontrollers"},"spec":{"background":false,"rules":[{"name":"test-getcontrollers","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"name":"test"}}}]}}`), + expectedControllers: "none", + }, + { + name: "rule-with-exclude-selector", + policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test-getcontrollers"},"spec":{"background":false,"rules":[{"name":"test-getcontrollers","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"selector":{"matchLabels":{"foo":"bar"}}}}}]}}`), + expectedControllers: "none", + }, + { + name: "rule-with-deny", + policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"require-network-policy","match":{"resources":{"kinds":["Pod"]}},"validate":{"message":"testpolicy","deny":{"conditions":[{"key":"{{request.object.metadata.labels.foo}}","operator":"Equals","value":"bar"}]}}}]}}`), + expectedControllers: engine.PodControllers, + }, + + { + name: "rule-with-match-mixed-kinds-pod-podcontrollers", + policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Pod","Deployment"]}},"preconditions":{"any":[{"key":"{{request.operation}}","operator":"Equals","value":"CREATE"}]},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`), + expectedControllers: "none", + }, + { + name: "rule-with-exclude-mixed-kinds-pod-podcontrollers", + policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"kinds":["Pod","Deployment"]}},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`), + expectedControllers: "none", + }, + { + name: "rule-with-match-kinds-pod-only", + policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"require-network-policy","match":{"resources":{"kinds":["Pod"]}},"validate":{"message":"testpolicy","pattern":{"metadata":{"labels":{"foo":"bar"}}}}}]}}`), + expectedControllers: engine.PodControllers, + }, + { + name: "rule-with-exclude-kinds-pod-only", + policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"require-network-policy","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"kinds":["Pod"],"namespaces":["test"]}},"validate":{"message":"testpolicy","pattern":{"metadata":{"labels":{"foo":"bar"}}}}}]}}`), + expectedControllers: engine.PodControllers, + }, + { + name: "rule-with-mutate-patches", + policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"test","match":{"resources":{"kinds":["Pod"]}},"mutate":{"patchesJson6902":"-op:add\npath:/spec/containers/0/env/-1\nvalue:{\"name\":\"SERVICE\",\"value\":{{request.object.spec.template.metadata.labels.app}}}"}}]}}`), + expectedControllers: "none", + }, + { + name: "rule-with-generate", + policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"add-networkpolicy"},"spec":{"rules":[{"name":"default-deny-ingress","match":{"resources":{"kinds":["Namespace"],"name":"*"}},"exclude":{"resources":{"namespaces":["kube-system","default","kube-public","kyverno"]}},"generate":{"kind":"NetworkPolicy","name":"default-deny-ingress","namespace":"{{request.object.metadata.name}}","synchronize":true,"data":{"spec":{"podSelector":{},"policyTypes":["Ingress"]}}}}]}}`), + expectedControllers: "none", + }, + { + name: "rule-with-predefined-invalid-controllers", + policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"annotations":null,"pod-policies.kyverno.io/autogen-controllers":"DaemonSet,Deployment,StatefulSet","spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Pod","Deployment"]}},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`), + expectedControllers: "none", + }, + { + name: "rule-with-predefined-valid-controllers", + policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"annotations":null,"pod-policies.kyverno.io/autogen-controllers":"none","spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Pod","Deployment"]}},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`), + expectedControllers: "none", + }, + { + name: "rule-with-only-predefined-valid-controllers", + policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"annotations":null,"pod-policies.kyverno.io/autogen-controllers":"none","spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Namespace"]}},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`), + expectedControllers: "none", + }, + } + + for _, test := range testCases { + var policy kyverno.ClusterPolicy + err := json.Unmarshal(test.policy, &policy) + assert.NilError(t, err) + + applyAutoGen, controllers := CanAutoGen(&policy.Spec, log.Log) + if !applyAutoGen { + controllers = "none" + } + assert.Equal(t, test.expectedControllers, controllers, fmt.Sprintf("test %s failed", test.name)) + } +} + +func Test_Any(t *testing.T) { + dir, err := os.Getwd() + baseDir := filepath.Dir(filepath.Dir(dir)) + assert.NilError(t, err) + file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml") + if err != nil { + t.Log(err) + } + policies, err := utils.GetPolicy(file) + if err != nil { + t.Log(err) + } + + policy := policies[0] + policy.Spec.Rules[0].MatchResources.Any = kyverno.ResourceFilters{ + { + ResourceDescription: kyverno.ResourceDescription{ + Kinds: []string{"Pod"}, + }, + }, + } + + rulePatches, errs := GenerateRulePatches(&policy.Spec, engine.PodControllers, log.Log) + fmt.Println("utils.JoinPatches(patches)erterter", string(utils.JoinPatches(rulePatches))) + if len(errs) != 0 { + t.Log(errs) + } + expectedPatches := [][]byte{ + []byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-validate-hostPath","match":{"any":[{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}}],"resources":{"kinds":["Pod"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}`), + []byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"any":[{"resources":{"kinds":["CronJob"]}}],"resources":{"kinds":["Pod"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`), + } + + 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_All(t *testing.T) { + dir, err := os.Getwd() + baseDir := filepath.Dir(filepath.Dir(dir)) + assert.NilError(t, err) + file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml") + if err != nil { + t.Log(err) + } + policies, err := utils.GetPolicy(file) + if err != nil { + t.Log(err) + } + + policy := policies[0] + policy.Spec.Rules[0].MatchResources.All = kyverno.ResourceFilters{ + { + ResourceDescription: kyverno.ResourceDescription{ + Kinds: []string{"Pod"}, + }, + }, + } + + rulePatches, errs := GenerateRulePatches(&policy.Spec, engine.PodControllers, log.Log) + if len(errs) != 0 { + t.Log(errs) + } + + expectedPatches := [][]byte{ + []byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-validate-hostPath","match":{"all":[{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}}],"resources":{"kinds":["Pod"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}`), + []byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"all":[{"resources":{"kinds":["CronJob"]}}],"resources":{"kinds":["Pod"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`), + } + + 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_Exclude(t *testing.T) { + dir, err := os.Getwd() + baseDir := filepath.Dir(filepath.Dir(dir)) + assert.NilError(t, err) + file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml") + if err != nil { + t.Log(err) + } + policies, err := utils.GetPolicy(file) + if err != nil { + t.Log(err) + } + + policy := policies[0] + policy.Spec.Rules[0].ExcludeResources.Namespaces = []string{"fake-namespce"} + + rulePatches, errs := GenerateRulePatches(&policy.Spec, engine.PodControllers, log.Log) + if len(errs) != 0 { + t.Log(errs) + } + + expectedPatches := [][]byte{ + []byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-validate-hostPath","match":{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}},"exclude":{"resources":{"namespaces":["fake-namespce"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}`), + []byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"resources":{"kinds":["CronJob"]}},"exclude":{"resources":{"namespaces":["fake-namespce"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`), + } + + 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_CronJobOnly(t *testing.T) { + + controllers := engine.PodControllerCronJob + dir, err := os.Getwd() + baseDir := filepath.Dir(filepath.Dir(dir)) + assert.NilError(t, err) + file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml") + if err != nil { + t.Log(err) + } + policies, err := utils.GetPolicy(file) + if err != nil { + t.Log(err) + } + + policy := policies[0] + policy.SetAnnotations(map[string]string{ + engine.PodControllersAnnotation: controllers, + }) + + rulePatches, errs := GenerateRulePatches(&policy.Spec, controllers, log.Log) + if len(errs) != 0 { + t.Log(errs) + } + + expectedPatches := [][]byte{ + []byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"resources":{"kinds":["CronJob"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`), + } + + assert.DeepEqual(t, rulePatches, expectedPatches) +} + +func Test_ForEachPod(t *testing.T) { + dir, err := os.Getwd() + baseDir := filepath.Dir(filepath.Dir(dir)) + assert.NilError(t, err) + file, err := ioutil.ReadFile(baseDir + "/test/policy/mutate/policy_mutate_pod_foreach_with_context.yaml") + if err != nil { + t.Log(err) + } + policies, err := utils.GetPolicy(file) + if err != nil { + t.Log(err) + } + + policy := policies[0] + policy.Spec.Rules[0].ExcludeResources.Namespaces = []string{"fake-namespce"} + + rulePatches, errs := GenerateRulePatches(&policy.Spec, engine.PodControllers, log.Log) + if len(errs) != 0 { + t.Log(errs) + } + + expectedPatches := [][]byte{ + []byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-resolve-image-containers","match":{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}},"exclude":{"resources":{"namespaces":["fake-namespce"]}},"preconditions":{"all":[{"key":"{{request.operation}}","operator":"In","value":["CREATE","UPDATE"]}]},"mutate":{"foreach":[{"list":"request.object.spec.template.spec.containers","context":[{"name":"dictionary","configMap":{"name":"some-config-map","namespace":"some-namespace"}}],"patchStrategicMerge":{"spec":{"template":{"spec":{"containers":[{"image":"{{ dictionary.data.image }}","name":"{{ element.name }}"}]}}}}}]}}}`), + []byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-resolve-image-containers","match":{"resources":{"kinds":["CronJob"]}},"exclude":{"resources":{"namespaces":["fake-namespce"]}},"preconditions":{"all":[{"key":"{{request.operation}}","operator":"In","value":["CREATE","UPDATE"]}]},"mutate":{"foreach":[{"list":"request.object.spec.jobTemplate.spec.template.spec.containers","context":[{"name":"dictionary","configMap":{"name":"some-config-map","namespace":"some-namespace"}}],"patchStrategicMerge":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"containers":[{"image":"{{ dictionary.data.image }}","name":"{{ element.name }}"}]}}}}}}}]}}}`), + } + + 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_CronJob_hasExclude(t *testing.T) { + + controllers := engine.PodControllerCronJob + dir, err := os.Getwd() + baseDir := filepath.Dir(filepath.Dir(dir)) + assert.NilError(t, err) + + file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml") + if err != nil { + t.Log(err) + } + policies, err := utils.GetPolicy(file) + if err != nil { + t.Log(err) + } + + policy := policies[0] + policy.SetAnnotations(map[string]string{ + engine.PodControllersAnnotation: controllers, + }) + + rule := policy.Spec.Rules[0].DeepCopy() + rule.ExcludeResources.Kinds = []string{"Pod"} + rule.ExcludeResources.Namespaces = []string{"test"} + policy.Spec.Rules[0] = *rule + + rulePatches, errs := GenerateRulePatches(&policy.Spec, controllers, log.Log) + if len(errs) != 0 { + t.Log(errs) + } + + expectedPatches := [][]byte{ + []byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"resources":{"kinds":["CronJob"]}},"exclude":{"resources":{"kinds":["CronJob"],"namespaces":["test"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`), + } + + assert.DeepEqual(t, rulePatches, expectedPatches) +} + +func Test_CronJobAndDeployment(t *testing.T) { + controllers := strings.Join([]string{engine.PodControllerCronJob, "Deployment"}, ",") + dir, err := os.Getwd() + baseDir := filepath.Dir(filepath.Dir(dir)) + assert.NilError(t, err) + file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml") + if err != nil { + t.Log(err) + } + policies, err := utils.GetPolicy(file) + if err != nil { + t.Log(err) + } + + policy := policies[0] + policy.SetAnnotations(map[string]string{ + engine.PodControllersAnnotation: controllers, + }) + + rulePatches, errs := GenerateRulePatches(&policy.Spec, controllers, log.Log) + if len(errs) != 0 { + t.Log(errs) + } + + expectedPatches := [][]byte{ + []byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-validate-hostPath","match":{"resources":{"kinds":["Deployment"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}`), + []byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"resources":{"kinds":["CronJob"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`), + } + + assert.DeepEqual(t, rulePatches, expectedPatches) +} + +func Test_UpdateVariablePath(t *testing.T) { + dir, err := os.Getwd() + baseDir := filepath.Dir(filepath.Dir(dir)) + assert.NilError(t, err) + file, err := ioutil.ReadFile(baseDir + "/test/best_practices/select-secrets.yaml") + if err != nil { + t.Log(err) + } + policies, err := utils.GetPolicy(file) + if err != nil { + t.Log(err) + } + + policy := policies[0] + + rulePatches, errs := GenerateRulePatches(&policy.Spec, engine.PodControllers, log.Log) + if len(errs) != 0 { + t.Log(errs) + } + expectedPatches := [][]byte{ + []byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-select-secrets-from-volumes","match":{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}},"context":[{"name":"volsecret","apiCall":{"urlPath":"/api/v1/namespaces/{{request.object.spec.template.metadata.namespace}}/secrets/{{request.object.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.template.spec.volumes[0].secret.secretName}} is restricted and may not be used.","pattern":{"spec":{"template":{"spec":{"containers":[{"image":"registry.domain.com/*"}]}}}}}}}`), + []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) +} + +func Test_Deny(t *testing.T) { + dir, err := os.Getwd() + baseDir := filepath.Dir(filepath.Dir(dir)) + assert.NilError(t, err) + file, err := ioutil.ReadFile(baseDir + "/test/policy/deny/policy.yaml") + if err != nil { + t.Log(err) + } + policies, err := utils.GetPolicy(file) + if err != nil { + t.Log(err) + } + + policy := policies[0] + policy.Spec.Rules[0].MatchResources.Any = kyverno.ResourceFilters{ + { + ResourceDescription: kyverno.ResourceDescription{ + Kinds: []string{"Pod"}, + }, + }, + } + + rulePatches, errs := GenerateRulePatches(&policy.Spec, engine.PodControllers, log.Log) + fmt.Println("utils.JoinPatches(patches)erterter", string(utils.JoinPatches(rulePatches))) + if len(errs) != 0 { + t.Log(errs) + } + expectedPatches := [][]byte{ + []byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-disallow-mount-containerd-sock","match":{"any":[{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}}],"resources":{"kinds":["Pod"]}},"validate":{"foreach":[{"list":"request.object.spec.template.spec.volumes[]","deny":{"conditions":{"any":[{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"/var/run/containerd/containerd.sock"},{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"/run/containerd/containerd.sock"},{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"\\var\\run\\containerd\\containerd.sock"}]}}}]}}}`), + []byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-disallow-mount-containerd-sock","match":{"any":[{"resources":{"kinds":["CronJob"]}}],"resources":{"kinds":["Pod"]}},"validate":{"foreach":[{"list":"request.object.spec.jobTemplate.spec.template.spec.volumes[]","deny":{"conditions":{"any":[{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"/var/run/containerd/containerd.sock"},{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"/run/containerd/containerd.sock"},{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"\\var\\run\\containerd\\containerd.sock"}]}}}]}}}`), + } + + for i, ep := range expectedPatches { + assert.Equal(t, string(rulePatches[i]), string(ep), + fmt.Sprintf("unexpected patch: %s\nexpected: %s", rulePatches[i], ep)) + } +} diff --git a/pkg/autogen/rule.go b/pkg/autogen/rule.go new file mode 100644 index 0000000000..b3eee0548f --- /dev/null +++ b/pkg/autogen/rule.go @@ -0,0 +1,421 @@ +package autogen + +import ( + "encoding/json" + "fmt" + "reflect" + "strings" + + "github.com/go-logr/logr" + kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + "github.com/kyverno/kyverno/pkg/engine" + "github.com/kyverno/kyverno/pkg/engine/variables" + "github.com/kyverno/kyverno/pkg/utils" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" +) + +// 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 *kyverno.MatchResources `json:"match"` + ExcludeResources *kyverno.ExcludeResources `json:"exclude,omitempty"` + Context *[]kyverno.ContextEntry `json:"context,omitempty"` + AnyAllConditions *apiextensions.JSON `json:"preconditions,omitempty"` + Mutation *kyverno.Mutation `json:"mutate,omitempty"` + Validation *kyverno.Validation `json:"validate,omitempty"` + 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.ExcludeResources{}) { + 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 + } + return ruleMap +} + +func generateRuleForControllers(rule kyverno.Rule, controllers string, log logr.Logger) kyvernoRule { + logger := log.WithName("generateRuleForControllers") + + if strings.HasPrefix(rule.Name, "autogen-") || controllers == "" { + logger.V(5).Info("skip generateRuleForControllers") + return kyvernoRule{} + } + + logger.V(3).Info("processing rule", "rulename", rule.Name) + + match := rule.MatchResources + exclude := rule.ExcludeResources + + matchResourceDescriptionsKinds := rule.MatchKinds() + excludeResourceDescriptionsKinds := rule.ExcludeKinds() + + if !utils.ContainsPod(matchResourceDescriptionsKinds, "Pod") || + (len(excludeResourceDescriptionsKinds) != 0 && !utils.ContainsPod(excludeResourceDescriptionsKinds, "Pod")) { + return kyvernoRule{} + } + + // 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} + 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" + } else { + 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.AnyAllConditions) + switch typedAnyAllConditions := kyvernoAnyAllConditions.(type) { + case kyverno.AnyAllConditions: + if !reflect.DeepEqual(typedAnyAllConditions, kyverno.AnyAllConditions{}) { + controllerRule.AnyAllConditions = &rule.DeepCopy().AnyAllConditions + } + case []kyverno.Condition: + if len(typedAnyAllConditions) > 0 { + controllerRule.AnyAllConditions = &rule.DeepCopy().AnyAllConditions + } + } + + if !reflect.DeepEqual(exclude, kyverno.ExcludeResources{}) { + 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 rule.Mutation.PatchStrategicMerge != nil { + newMutation := &kyverno.Mutation{ + PatchStrategicMerge: map[string]interface{}{ + "spec": map[string]interface{}{ + "template": rule.Mutation.PatchStrategicMerge, + }, + }, + } + + 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 { + newForeachMutation = append(newForeachMutation, &kyverno.ForEachMutation{ + List: foreach.List, + Context: foreach.Context, + AnyAllConditions: foreach.AnyAllConditions, + PatchStrategicMerge: map[string]interface{}{ + "spec": map[string]interface{}{ + "template": foreach.PatchStrategicMerge, + }, + }, + }) + } + controllerRule.Mutation = &kyverno.Mutation{ + ForEachMutation: newForeachMutation, + } + return *controllerRule + } + + if rule.Validation.Pattern != nil { + newValidate := &kyverno.Validation{ + Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/template", "pattern"), + Pattern: map[string]interface{}{ + "spec": map[string]interface{}{ + "template": rule.Validation.Pattern, + }, + }, + } + 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.AnyPattern != 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"), + AnyPattern: 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 kyvernoRule{} +} + +func generateCronJobRule(rule kyverno.Rule, controllers string, log logr.Logger) kyvernoRule { + logger := log.WithName("handleCronJob") + + hasCronJob := strings.Contains(controllers, engine.PodControllerCronJob) || strings.Contains(controllers, "all") + if !hasCronJob { + return kyvernoRule{} + } + + logger.V(3).Info("generating rule for cronJob") + jobRule := generateRuleForControllers(rule, "Job", logger) + + if reflect.DeepEqual(jobRule, kyvernoRule{}) { + return kyvernoRule{} + } + + 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{engine.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{engine.PodControllerCronJob} + } + } + + if (jobRule.Mutation != nil) && (jobRule.Mutation.PatchStrategicMerge != nil) { + newMutation := &kyverno.Mutation{ + PatchStrategicMerge: map[string]interface{}{ + "spec": map[string]interface{}{ + "jobTemplate": jobRule.Mutation.PatchStrategicMerge, + }, + }, + } + cronJobRule.Mutation = newMutation.DeepCopy() + return *cronJobRule + } + + if (jobRule.Validation != nil) && (jobRule.Validation.Pattern != nil) { + newValidate := &kyverno.Validation{ + Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/jobTemplate/spec/template", "pattern"), + Pattern: map[string]interface{}{ + "spec": map[string]interface{}{ + "jobTemplate": jobRule.Validation.Pattern, + }, + }, + } + cronJobRule.Validation = newValidate.DeepCopy() + return *cronJobRule + } + + if (jobRule.Validation != nil) && (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 (jobRule.Validation != nil) && (jobRule.Validation.AnyPattern != 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"), + AnyPattern: patterns, + } + return *cronJobRule + } + + if (jobRule.Validation != nil) && 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 { + newForeachMutation = append(newForeachMutation, &kyverno.ForEachMutation{ + List: foreach.List, + Context: foreach.Context, + AnyAllConditions: foreach.AnyAllConditions, + PatchStrategicMerge: map[string]interface{}{ + "spec": map[string]interface{}{ + "jobTemplate": foreach.PatchStrategicMerge, + }, + }, + }) + } + 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 kyvernoRule{} +} + +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 + } + if kind == "Pod" { + obj = []byte(strings.Replace(string(pbyte), "request.object.spec", "request.object.spec.template.spec", -1)) + } + if kind == "Cronjob" { + obj = []byte(strings.Replace(string(pbyte), "request.object.spec", "request.object.spec.jobTemplate.spec.template.spec", -1)) + } + obj = []byte(strings.Replace(string(obj), "request.object.metadata", "request.object.spec.template.metadata", -1)) + return obj +} diff --git a/pkg/autogen/utils.go b/pkg/autogen/utils.go new file mode 100644 index 0000000000..bd55694215 --- /dev/null +++ b/pkg/autogen/utils.go @@ -0,0 +1,74 @@ +package autogen + +import ( + "strings" + + kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + "github.com/kyverno/kyverno/pkg/engine" + "github.com/kyverno/kyverno/pkg/utils" +) + +func isKindOtherthanPod(kinds []string) bool { + if len(kinds) > 1 && utils.ContainsPod(kinds, "Pod") { + return true + } + return false +} + +func hasAutogenKinds(kind []string) bool { + for _, v := range kind { + if v == "Pod" || strings.Contains(engine.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 utils.ContainsPod(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 == engine.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 utils.ContainsPod(value.Kinds, "Job") { + anyKind[i].Kinds = []string{engine.PodControllerCronJob} + } + } + return anyKind +} diff --git a/pkg/policy/policy_controller.go b/pkg/policy/policy_controller.go index 94848283b0..6fbd4f98a8 100644 --- a/pkg/policy/policy_controller.go +++ b/pkg/policy/policy_controller.go @@ -11,6 +11,7 @@ import ( "github.com/go-logr/logr" kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + "github.com/kyverno/kyverno/pkg/autogen" kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned" "github.com/kyverno/kyverno/pkg/client/clientset/versioned/scheme" kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1" @@ -21,7 +22,6 @@ import ( "github.com/kyverno/kyverno/pkg/event" "github.com/kyverno/kyverno/pkg/kyverno/common" "github.com/kyverno/kyverno/pkg/metrics" - pm "github.com/kyverno/kyverno/pkg/policymutation" "github.com/kyverno/kyverno/pkg/policyreport" "github.com/kyverno/kyverno/pkg/utils" v1 "k8s.io/api/core/v1" @@ -563,7 +563,7 @@ func updateGR(kyvernoClient *kyvernoclient.Clientset, policyKey string, grList [ func missingAutoGenRules(policy *kyverno.ClusterPolicy, log logr.Logger) bool { var podRuleName []string ruleCount := 1 - if canApplyAutoGen, _ := pm.CanAutoGen(&policy.Spec, log); canApplyAutoGen { + if canApplyAutoGen, _ := autogen.CanAutoGen(&policy.Spec, log); canApplyAutoGen { for _, rule := range policy.Spec.Rules { podRuleName = append(podRuleName, rule.Name) } diff --git a/pkg/policymutation/cronjob.go b/pkg/policymutation/cronjob.go deleted file mode 100644 index 9518a8bf0b..0000000000 --- a/pkg/policymutation/cronjob.go +++ /dev/null @@ -1,192 +0,0 @@ -package policymutation - -import ( - "fmt" - "reflect" - "strings" - - "github.com/go-logr/logr" - kyverno "github.com/kyverno/kyverno/api/kyverno/v1" - "github.com/kyverno/kyverno/pkg/engine" - "github.com/kyverno/kyverno/pkg/engine/variables" - "github.com/kyverno/kyverno/pkg/utils" -) - -func generateCronJobRule(rule kyverno.Rule, controllers string, log logr.Logger) kyvernoRule { - logger := log.WithName("handleCronJob") - - hasCronJob := strings.Contains(controllers, engine.PodControllerCronJob) || strings.Contains(controllers, "all") - if !hasCronJob { - return kyvernoRule{} - } - - logger.V(3).Info("generating rule for cronJob") - jobRule := generateRuleForControllers(rule, "Job", logger) - - if reflect.DeepEqual(jobRule, kyvernoRule{}) { - return kyvernoRule{} - } - - 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{engine.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{engine.PodControllerCronJob} - } - } - - if (jobRule.Mutation != nil) && (jobRule.Mutation.PatchStrategicMerge != nil) { - newMutation := &kyverno.Mutation{ - PatchStrategicMerge: map[string]interface{}{ - "spec": map[string]interface{}{ - "jobTemplate": jobRule.Mutation.PatchStrategicMerge, - }, - }, - } - cronJobRule.Mutation = newMutation.DeepCopy() - return *cronJobRule - } - - if (jobRule.Validation != nil) && (jobRule.Validation.Pattern != nil) { - newValidate := &kyverno.Validation{ - Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/jobTemplate/spec/template", "pattern"), - Pattern: map[string]interface{}{ - "spec": map[string]interface{}{ - "jobTemplate": jobRule.Validation.Pattern, - }, - }, - } - cronJobRule.Validation = newValidate.DeepCopy() - return *cronJobRule - } - - if (jobRule.Validation != nil) && (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 (jobRule.Validation != nil) && (jobRule.Validation.AnyPattern != 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"), - AnyPattern: patterns, - } - return *cronJobRule - } - - if (jobRule.Validation != nil) && 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 { - newForeachMutation = append(newForeachMutation, &kyverno.ForEachMutation{ - List: foreach.List, - Context: foreach.Context, - AnyAllConditions: foreach.AnyAllConditions, - PatchStrategicMerge: map[string]interface{}{ - "spec": map[string]interface{}{ - "jobTemplate": foreach.PatchStrategicMerge, - }, - }, - }) - } - 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 kyvernoRule{} -} - -// stripCronJob removes CronJob from controllers -func stripCronJob(controllers string) string { - var newControllers []string - - controllerArr := strings.Split(controllers, ",") - for _, c := range controllerArr { - if c == engine.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 utils.ContainsPod(value.Kinds, "Job") { - anyKind[i].Kinds = []string{engine.PodControllerCronJob} - } - } - return anyKind -} diff --git a/pkg/policymutation/policymutation.go b/pkg/policymutation/policymutation.go index 1a50d6e0c5..b3b4a8fee8 100644 --- a/pkg/policymutation/policymutation.go +++ b/pkg/policymutation/policymutation.go @@ -3,18 +3,15 @@ package policymutation import ( "encoding/json" "fmt" - "reflect" "strconv" "strings" - jsonpatch "github.com/evanphx/json-patch/v5" "github.com/go-logr/logr" kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + "github.com/kyverno/kyverno/pkg/autogen" "github.com/kyverno/kyverno/pkg/common" "github.com/kyverno/kyverno/pkg/engine" - "github.com/kyverno/kyverno/pkg/engine/variables" "github.com/kyverno/kyverno/pkg/utils" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" ) // GenerateJSONPatchesForDefaults generates default JSON patches for @@ -253,7 +250,7 @@ func defaultFailurePolicy(spec *kyverno.Spec, log logr.Logger) ([]byte, string) // GeneratePodControllerRule returns two patches: rulePatches and annotation patch(if necessary) func GeneratePodControllerRule(policy kyverno.ClusterPolicy, log logr.Logger) (patches [][]byte, errs []error) { - applyAutoGen, desiredControllers := CanAutoGen(&policy.Spec, log) + applyAutoGen, desiredControllers := autogen.CanAutoGen(&policy.Spec, log) if !applyAutoGen { desiredControllers = "none" @@ -285,473 +282,12 @@ func GeneratePodControllerRule(policy kyverno.ClusterPolicy, log logr.Logger) (p log.V(3).Info("auto generating rule for pod controllers", "controllers", actualControllers) - p, err := generateRulePatches(&policy.Spec, actualControllers, log) + p, err := autogen.GenerateRulePatches(&policy.Spec, actualControllers, log) patches = append(patches, p...) errs = append(errs, err...) return } -// CanAutoGen checks whether the rule(s) (in policy) can be applied to Pod controllers -// returns controllers as: -// - "" if: -// - name or selector is defined -// - mixed kinds (Pod + pod controller) is defined -// - Pod and PodControllers are not defined -// - 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 - for _, rule := range spec.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 { - 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 { - 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 { - 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 { - 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 { - 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 { - return false, "" - } - - return true, engine.PodControllers -} - -func isKindOtherthanPod(kinds []string) bool { - if len(kinds) > 1 && utils.ContainsPod(kinds, "Pod") { - return true - } - return false -} - -func hasAutogenKinds(kind []string) bool { - for _, v := range kind { - if v == "Pod" || strings.Contains(engine.PodControllers, v) { - return true - } - } - - return false -} - -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.ExcludeResources{}) { - 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 - } - return ruleMap -} -func updateGenRuleByte(pbyte []byte, kind string, genRule kyvernoRule) (obj []byte) { - if err := json.Unmarshal(pbyte, &genRule); err != nil { - return obj - } - if kind == "Pod" { - obj = []byte(strings.Replace(string(pbyte), "request.object.spec", "request.object.spec.template.spec", -1)) - } - if kind == "Cronjob" { - obj = []byte(strings.Replace(string(pbyte), "request.object.spec", "request.object.spec.jobTemplate.spec.template.spec", -1)) - } - obj = []byte(strings.Replace(string(obj), "request.object.metadata", "request.object.spec.template.metadata", -1)) - return obj -} - -// 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) { - insertIdx := len(spec.Rules) - - ruleMap := createRuleMap(spec.Rules) - var ruleIndex = make(map[string]int) - for index, rule := range spec.Rules { - ruleIndex[rule.Name] = index - } - - for _, rule := range spec.Rules { - 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 - } - operation = "replace" - patchPostion = ruleIndex[genRule.Name] - } - - // generate patch bytes - jsonPatch := struct { - Path string `json:"path"` - Op string `json:"op"` - Value interface{} `json:"value"` - }{ - fmt.Sprintf("/spec/rules/%s", strconv.Itoa(patchPostion)), - operation, - genRule, - } - pbytes, err := json.Marshal(jsonPatch) - if err != nil { - errs = append(errs, err) - return nil - } - - // check the patch - if _, err := jsonpatch.DecodePatch([]byte("[" + string(pbytes) + "]")); err != nil { - errs = append(errs, err) - return nil - } - - return pbytes - } - - // handle all other controllers other than CronJob - genRule := generateRuleForControllers(rule, stripCronJob(controllers), log) - if !reflect.DeepEqual(genRule, kyvernoRule{}) { - 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 !reflect.DeepEqual(genRule, kyvernoRule{}) { - pbytes := convertToPatches(genRule, patchPostion) - pbytes = updateGenRuleByte(pbytes, "Cronjob", genRule) - if pbytes != nil { - rulePatches = append(rulePatches, pbytes) - } - insertIdx++ - } - } - return -} - -// 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 *kyverno.MatchResources `json:"match"` - ExcludeResources *kyverno.ExcludeResources `json:"exclude,omitempty"` - Context *[]kyverno.ContextEntry `json:"context,omitempty"` - AnyAllConditions *apiextensions.JSON `json:"preconditions,omitempty"` - Mutation *kyverno.Mutation `json:"mutate,omitempty"` - Validation *kyverno.Validation `json:"validate,omitempty"` - VerifyImages []*kyverno.ImageVerification `json:"verifyImages,omitempty" yaml:"verifyImages,omitempty"` -} - -func generateRuleForControllers(rule kyverno.Rule, controllers string, log logr.Logger) kyvernoRule { - logger := log.WithName("generateRuleForControllers") - - if strings.HasPrefix(rule.Name, "autogen-") || controllers == "" { - logger.V(5).Info("skip generateRuleForControllers") - return kyvernoRule{} - } - - logger.V(3).Info("processing rule", "rulename", rule.Name) - - match := rule.MatchResources - exclude := rule.ExcludeResources - - matchResourceDescriptionsKinds := rule.MatchKinds() - excludeResourceDescriptionsKinds := rule.ExcludeKinds() - - if !utils.ContainsPod(matchResourceDescriptionsKinds, "Pod") || - (len(excludeResourceDescriptionsKinds) != 0 && !utils.ContainsPod(excludeResourceDescriptionsKinds, "Pod")) { - return kyvernoRule{} - } - - // 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} - 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" - } else { - 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.AnyAllConditions) - switch typedAnyAllConditions := kyvernoAnyAllConditions.(type) { - case kyverno.AnyAllConditions: - if !reflect.DeepEqual(typedAnyAllConditions, kyverno.AnyAllConditions{}) { - controllerRule.AnyAllConditions = &rule.DeepCopy().AnyAllConditions - } - case []kyverno.Condition: - if len(typedAnyAllConditions) > 0 { - controllerRule.AnyAllConditions = &rule.DeepCopy().AnyAllConditions - } - } - - if !reflect.DeepEqual(exclude, kyverno.ExcludeResources{}) { - 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 rule.Mutation.PatchStrategicMerge != nil { - newMutation := &kyverno.Mutation{ - PatchStrategicMerge: map[string]interface{}{ - "spec": map[string]interface{}{ - "template": rule.Mutation.PatchStrategicMerge, - }, - }, - } - - 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 { - newForeachMutation = append(newForeachMutation, &kyverno.ForEachMutation{ - List: foreach.List, - Context: foreach.Context, - AnyAllConditions: foreach.AnyAllConditions, - PatchStrategicMerge: map[string]interface{}{ - "spec": map[string]interface{}{ - "template": foreach.PatchStrategicMerge, - }, - }, - }) - } - controllerRule.Mutation = &kyverno.Mutation{ - ForEachMutation: newForeachMutation, - } - return *controllerRule - } - - if rule.Validation.Pattern != nil { - newValidate := &kyverno.Validation{ - Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/template", "pattern"), - Pattern: map[string]interface{}{ - "spec": map[string]interface{}{ - "template": rule.Validation.Pattern, - }, - }, - } - 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.AnyPattern != 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"), - AnyPattern: 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 kyvernoRule{} -} - -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 utils.ContainsPod(value.Kinds, "Pod") { - anyKind[i].Kinds = strings.Split(controllers, ",") - } - } - return anyKind -} - // defaultPodControllerAnnotation inserts an annotation // "pod-policies.kyverno.io/autogen-controllers=" to policy func defaultPodControllerAnnotation(ann map[string]string, controllers string) ([]byte, error) { diff --git a/pkg/policymutation/policymutation_test.go b/pkg/policymutation/policymutation_test.go index c2b024389e..a0da43fa61 100644 --- a/pkg/policymutation/policymutation_test.go +++ b/pkg/policymutation/policymutation_test.go @@ -3,15 +3,11 @@ package policymutation import ( "encoding/json" "fmt" - "io/ioutil" "os" "path/filepath" - "strings" "testing" kyverno "github.com/kyverno/kyverno/api/kyverno/v1" - "github.com/kyverno/kyverno/pkg/engine" - "github.com/kyverno/kyverno/pkg/utils" "gotest.tools/assert" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -25,367 +21,6 @@ func currentDir() (string, error) { return filepath.Join(homedir, "github.com/kyverno/kyverno"), nil } -func Test_Any(t *testing.T) { - dir, err := os.Getwd() - baseDir := filepath.Dir(filepath.Dir(dir)) - assert.NilError(t, err) - file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml") - if err != nil { - t.Log(err) - } - policies, err := utils.GetPolicy(file) - if err != nil { - t.Log(err) - } - - policy := policies[0] - policy.Spec.Rules[0].MatchResources.Any = kyverno.ResourceFilters{ - { - ResourceDescription: kyverno.ResourceDescription{ - Kinds: []string{"Pod"}, - }, - }, - } - - rulePatches, errs := generateRulePatches(&policy.Spec, engine.PodControllers, log.Log) - fmt.Println("utils.JoinPatches(patches)erterter", string(utils.JoinPatches(rulePatches))) - if len(errs) != 0 { - t.Log(errs) - } - expectedPatches := [][]byte{ - []byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-validate-hostPath","match":{"any":[{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}}],"resources":{"kinds":["Pod"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}`), - []byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"any":[{"resources":{"kinds":["CronJob"]}}],"resources":{"kinds":["Pod"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`), - } - - 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_All(t *testing.T) { - dir, err := os.Getwd() - baseDir := filepath.Dir(filepath.Dir(dir)) - assert.NilError(t, err) - file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml") - if err != nil { - t.Log(err) - } - policies, err := utils.GetPolicy(file) - if err != nil { - t.Log(err) - } - - policy := policies[0] - policy.Spec.Rules[0].MatchResources.All = kyverno.ResourceFilters{ - { - ResourceDescription: kyverno.ResourceDescription{ - Kinds: []string{"Pod"}, - }, - }, - } - - rulePatches, errs := generateRulePatches(&policy.Spec, engine.PodControllers, log.Log) - if len(errs) != 0 { - t.Log(errs) - } - - expectedPatches := [][]byte{ - []byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-validate-hostPath","match":{"all":[{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}}],"resources":{"kinds":["Pod"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}`), - []byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"all":[{"resources":{"kinds":["CronJob"]}}],"resources":{"kinds":["Pod"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`), - } - - 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_Exclude(t *testing.T) { - dir, err := os.Getwd() - baseDir := filepath.Dir(filepath.Dir(dir)) - assert.NilError(t, err) - file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml") - if err != nil { - t.Log(err) - } - policies, err := utils.GetPolicy(file) - if err != nil { - t.Log(err) - } - - policy := policies[0] - policy.Spec.Rules[0].ExcludeResources.Namespaces = []string{"fake-namespce"} - - rulePatches, errs := generateRulePatches(&policy.Spec, engine.PodControllers, log.Log) - if len(errs) != 0 { - t.Log(errs) - } - - expectedPatches := [][]byte{ - []byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-validate-hostPath","match":{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}},"exclude":{"resources":{"namespaces":["fake-namespce"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}`), - []byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"resources":{"kinds":["CronJob"]}},"exclude":{"resources":{"namespaces":["fake-namespce"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`), - } - - 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_CronJobOnly(t *testing.T) { - - controllers := engine.PodControllerCronJob - dir, err := os.Getwd() - baseDir := filepath.Dir(filepath.Dir(dir)) - assert.NilError(t, err) - file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml") - if err != nil { - t.Log(err) - } - policies, err := utils.GetPolicy(file) - if err != nil { - t.Log(err) - } - - policy := policies[0] - policy.SetAnnotations(map[string]string{ - engine.PodControllersAnnotation: controllers, - }) - - rulePatches, errs := generateRulePatches(&policy.Spec, controllers, log.Log) - if len(errs) != 0 { - t.Log(errs) - } - - expectedPatches := [][]byte{ - []byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"resources":{"kinds":["CronJob"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`), - } - - assert.DeepEqual(t, rulePatches, expectedPatches) -} - -func Test_ForEachPod(t *testing.T) { - dir, err := os.Getwd() - baseDir := filepath.Dir(filepath.Dir(dir)) - assert.NilError(t, err) - file, err := ioutil.ReadFile(baseDir + "/test/policy/mutate/policy_mutate_pod_foreach_with_context.yaml") - if err != nil { - t.Log(err) - } - policies, err := utils.GetPolicy(file) - if err != nil { - t.Log(err) - } - - policy := policies[0] - policy.Spec.Rules[0].ExcludeResources.Namespaces = []string{"fake-namespce"} - - rulePatches, errs := generateRulePatches(&policy.Spec, engine.PodControllers, log.Log) - if len(errs) != 0 { - t.Log(errs) - } - - expectedPatches := [][]byte{ - []byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-resolve-image-containers","match":{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}},"exclude":{"resources":{"namespaces":["fake-namespce"]}},"preconditions":{"all":[{"key":"{{request.operation}}","operator":"In","value":["CREATE","UPDATE"]}]},"mutate":{"foreach":[{"list":"request.object.spec.template.spec.containers","context":[{"name":"dictionary","configMap":{"name":"some-config-map","namespace":"some-namespace"}}],"patchStrategicMerge":{"spec":{"template":{"spec":{"containers":[{"image":"{{ dictionary.data.image }}","name":"{{ element.name }}"}]}}}}}]}}}`), - []byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-resolve-image-containers","match":{"resources":{"kinds":["CronJob"]}},"exclude":{"resources":{"namespaces":["fake-namespce"]}},"preconditions":{"all":[{"key":"{{request.operation}}","operator":"In","value":["CREATE","UPDATE"]}]},"mutate":{"foreach":[{"list":"request.object.spec.jobTemplate.spec.template.spec.containers","context":[{"name":"dictionary","configMap":{"name":"some-config-map","namespace":"some-namespace"}}],"patchStrategicMerge":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"containers":[{"image":"{{ dictionary.data.image }}","name":"{{ element.name }}"}]}}}}}}}]}}}`), - } - - 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_CronJob_hasExclude(t *testing.T) { - - controllers := engine.PodControllerCronJob - dir, err := os.Getwd() - baseDir := filepath.Dir(filepath.Dir(dir)) - assert.NilError(t, err) - - file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml") - if err != nil { - t.Log(err) - } - policies, err := utils.GetPolicy(file) - if err != nil { - t.Log(err) - } - - policy := policies[0] - policy.SetAnnotations(map[string]string{ - engine.PodControllersAnnotation: controllers, - }) - - rule := policy.Spec.Rules[0].DeepCopy() - rule.ExcludeResources.Kinds = []string{"Pod"} - rule.ExcludeResources.Namespaces = []string{"test"} - policy.Spec.Rules[0] = *rule - - rulePatches, errs := generateRulePatches(&policy.Spec, controllers, log.Log) - if len(errs) != 0 { - t.Log(errs) - } - - expectedPatches := [][]byte{ - []byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"resources":{"kinds":["CronJob"]}},"exclude":{"resources":{"kinds":["CronJob"],"namespaces":["test"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`), - } - - assert.DeepEqual(t, rulePatches, expectedPatches) -} - -func Test_CronJobAndDeployment(t *testing.T) { - controllers := strings.Join([]string{engine.PodControllerCronJob, "Deployment"}, ",") - dir, err := os.Getwd() - baseDir := filepath.Dir(filepath.Dir(dir)) - assert.NilError(t, err) - file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml") - if err != nil { - t.Log(err) - } - policies, err := utils.GetPolicy(file) - if err != nil { - t.Log(err) - } - - policy := policies[0] - policy.SetAnnotations(map[string]string{ - engine.PodControllersAnnotation: controllers, - }) - - rulePatches, errs := generateRulePatches(&policy.Spec, controllers, log.Log) - if len(errs) != 0 { - t.Log(errs) - } - - expectedPatches := [][]byte{ - []byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-validate-hostPath","match":{"resources":{"kinds":["Deployment"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}`), - []byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"resources":{"kinds":["CronJob"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`), - } - - assert.DeepEqual(t, rulePatches, expectedPatches) -} - -func Test_getControllers(t *testing.T) { - testCases := []struct { - name string - policy []byte - expectedControllers string - }{ - { - name: "rule-with-match-name", - policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"test","match":{"resources":{"kinds":["Namespace"],"name":"*"}}}]}}`), - expectedControllers: "none", - }, - { - name: "rule-with-match-selector", - policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test-getcontrollers"},"spec":{"background":false,"rules":[{"name":"test-getcontrollers","match":{"resources":{"kinds":["Pod"],"selector":{"matchLabels":{"foo":"bar"}}}}}]}}`), - expectedControllers: "none", - }, - { - name: "rule-with-exclude-name", - policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test-getcontrollers"},"spec":{"background":false,"rules":[{"name":"test-getcontrollers","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"name":"test"}}}]}}`), - expectedControllers: "none", - }, - { - name: "rule-with-exclude-selector", - policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test-getcontrollers"},"spec":{"background":false,"rules":[{"name":"test-getcontrollers","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"selector":{"matchLabels":{"foo":"bar"}}}}}]}}`), - expectedControllers: "none", - }, - { - name: "rule-with-deny", - policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"require-network-policy","match":{"resources":{"kinds":["Pod"]}},"validate":{"message":"testpolicy","deny":{"conditions":[{"key":"{{request.object.metadata.labels.foo}}","operator":"Equals","value":"bar"}]}}}]}}`), - expectedControllers: engine.PodControllers, - }, - - { - name: "rule-with-match-mixed-kinds-pod-podcontrollers", - policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Pod","Deployment"]}},"preconditions":{"any":[{"key":"{{request.operation}}","operator":"Equals","value":"CREATE"}]},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`), - expectedControllers: "none", - }, - { - name: "rule-with-exclude-mixed-kinds-pod-podcontrollers", - policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"kinds":["Pod","Deployment"]}},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`), - expectedControllers: "none", - }, - { - name: "rule-with-match-kinds-pod-only", - policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"require-network-policy","match":{"resources":{"kinds":["Pod"]}},"validate":{"message":"testpolicy","pattern":{"metadata":{"labels":{"foo":"bar"}}}}}]}}`), - expectedControllers: engine.PodControllers, - }, - { - name: "rule-with-exclude-kinds-pod-only", - policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"require-network-policy","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"kinds":["Pod"],"namespaces":["test"]}},"validate":{"message":"testpolicy","pattern":{"metadata":{"labels":{"foo":"bar"}}}}}]}}`), - expectedControllers: engine.PodControllers, - }, - { - name: "rule-with-mutate-patches", - policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"test","match":{"resources":{"kinds":["Pod"]}},"mutate":{"patchesJson6902":"-op:add\npath:/spec/containers/0/env/-1\nvalue:{\"name\":\"SERVICE\",\"value\":{{request.object.spec.template.metadata.labels.app}}}"}}]}}`), - expectedControllers: "none", - }, - { - name: "rule-with-generate", - policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"add-networkpolicy"},"spec":{"rules":[{"name":"default-deny-ingress","match":{"resources":{"kinds":["Namespace"],"name":"*"}},"exclude":{"resources":{"namespaces":["kube-system","default","kube-public","kyverno"]}},"generate":{"kind":"NetworkPolicy","name":"default-deny-ingress","namespace":"{{request.object.metadata.name}}","synchronize":true,"data":{"spec":{"podSelector":{},"policyTypes":["Ingress"]}}}}]}}`), - expectedControllers: "none", - }, - { - name: "rule-with-predefined-invalid-controllers", - policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"annotations":null,"pod-policies.kyverno.io/autogen-controllers":"DaemonSet,Deployment,StatefulSet","spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Pod","Deployment"]}},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`), - expectedControllers: "none", - }, - { - name: "rule-with-predefined-valid-controllers", - policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"annotations":null,"pod-policies.kyverno.io/autogen-controllers":"none","spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Pod","Deployment"]}},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`), - expectedControllers: "none", - }, - { - name: "rule-with-only-predefined-valid-controllers", - policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"annotations":null,"pod-policies.kyverno.io/autogen-controllers":"none","spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Namespace"]}},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`), - expectedControllers: "none", - }, - } - - for _, test := range testCases { - var policy kyverno.ClusterPolicy - err := json.Unmarshal(test.policy, &policy) - assert.NilError(t, err) - - applyAutoGen, controllers := CanAutoGen(&policy.Spec, log.Log) - if !applyAutoGen { - controllers = "none" - } - assert.Equal(t, test.expectedControllers, controllers, fmt.Sprintf("test %s failed", test.name)) - } -} - -func Test_UpdateVariablePath(t *testing.T) { - dir, err := os.Getwd() - baseDir := filepath.Dir(filepath.Dir(dir)) - assert.NilError(t, err) - file, err := ioutil.ReadFile(baseDir + "/test/best_practices/select-secrets.yaml") - if err != nil { - t.Log(err) - } - policies, err := utils.GetPolicy(file) - if err != nil { - t.Log(err) - } - - policy := policies[0] - - rulePatches, errs := generateRulePatches(&policy.Spec, engine.PodControllers, log.Log) - if len(errs) != 0 { - t.Log(errs) - } - expectedPatches := [][]byte{ - []byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-select-secrets-from-volumes","match":{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}},"context":[{"name":"volsecret","apiCall":{"urlPath":"/api/v1/namespaces/{{request.object.spec.template.metadata.namespace}}/secrets/{{request.object.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.template.spec.volumes[0].secret.secretName}} is restricted and may not be used.","pattern":{"spec":{"template":{"spec":{"containers":[{"image":"registry.domain.com/*"}]}}}}}}}`), - []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) -} - func Test_checkForGVKFormatPatch(t *testing.T) { testCases := []struct { name string @@ -466,41 +101,3 @@ func Test_checkForGVKFormatPatch(t *testing.T) { } } } - -func Test_Deny(t *testing.T) { - dir, err := os.Getwd() - baseDir := filepath.Dir(filepath.Dir(dir)) - assert.NilError(t, err) - file, err := ioutil.ReadFile(baseDir + "/test/policy/deny/policy.yaml") - if err != nil { - t.Log(err) - } - policies, err := utils.GetPolicy(file) - if err != nil { - t.Log(err) - } - - policy := policies[0] - policy.Spec.Rules[0].MatchResources.Any = kyverno.ResourceFilters{ - { - ResourceDescription: kyverno.ResourceDescription{ - Kinds: []string{"Pod"}, - }, - }, - } - - rulePatches, errs := generateRulePatches(&policy.Spec, engine.PodControllers, log.Log) - fmt.Println("utils.JoinPatches(patches)erterter", string(utils.JoinPatches(rulePatches))) - if len(errs) != 0 { - t.Log(errs) - } - expectedPatches := [][]byte{ - []byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-disallow-mount-containerd-sock","match":{"any":[{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}}],"resources":{"kinds":["Pod"]}},"validate":{"foreach":[{"list":"request.object.spec.template.spec.volumes[]","deny":{"conditions":{"any":[{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"/var/run/containerd/containerd.sock"},{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"/run/containerd/containerd.sock"},{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"\\var\\run\\containerd\\containerd.sock"}]}}}]}}}`), - []byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-disallow-mount-containerd-sock","match":{"any":[{"resources":{"kinds":["CronJob"]}}],"resources":{"kinds":["Pod"]}},"validate":{"foreach":[{"list":"request.object.spec.jobTemplate.spec.template.spec.volumes[]","deny":{"conditions":{"any":[{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"/var/run/containerd/containerd.sock"},{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"/run/containerd/containerd.sock"},{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"\\var\\run\\containerd\\containerd.sock"}]}}}]}}}`), - } - - for i, ep := range expectedPatches { - assert.Equal(t, string(rulePatches[i]), string(ep), - fmt.Sprintf("unexpected patch: %s\nexpected: %s", rulePatches[i], ep)) - } -}