From 10bb27b4da86adf6bc85dc504b53464dd0d50b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?= Date: Mon, 11 Sep 2023 15:41:36 +0200 Subject: [PATCH] fix: cli engine invocation order (#8327) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: cli engine invocation order Signed-off-by: Charles-Edouard Brétéché * mutate Signed-off-by: Charles-Edouard Brétéché * verify images Signed-off-by: Charles-Edouard Brétéché * validate Signed-off-by: Charles-Edouard Brétéché * fix Signed-off-by: Charles-Edouard Brétéché * generate Signed-off-by: Charles-Edouard Brétéché * fix Signed-off-by: Charles-Edouard Brétéché * fix and tests Signed-off-by: Charles-Edouard Brétéché * fix tests Signed-off-by: Charles-Edouard Brétéché --------- Signed-off-by: Charles-Edouard Brétéché --- .../kubectl-kyverno/commands/apply/command.go | 100 ++++--- .../commands/apply/command_test.go | 4 +- cmd/cli/kubectl-kyverno/commands/test/test.go | 66 ++--- .../processor/policy_processor.go | 261 +++++++++--------- .../processor/policy_processor_test.go | 123 +++++++++ cmd/cli/kubectl-kyverno/processor/utils.go | 31 --- .../utils/common/common_test.go | 74 +---- .../foreach/pod-updated-image.yaml | 4 + test/cli/test-mutate/patched-resource.yaml | 2 + test/cli/test-mutate/patchedResource8.yaml | 1 + 10 files changed, 347 insertions(+), 319 deletions(-) create mode 100644 cmd/cli/kubectl-kyverno/processor/policy_processor_test.go diff --git a/cmd/cli/kubectl-kyverno/commands/apply/command.go b/cmd/cli/kubectl-kyverno/commands/apply/command.go index 708ae6a98a..6d085bcd16 100644 --- a/cmd/cli/kubectl-kyverno/commands/apply/command.go +++ b/cmd/cli/kubectl-kyverno/commands/apply/command.go @@ -66,7 +66,6 @@ type ApplyCommandConfig struct { warnNoPassed bool } -// allow os.exit to be overwritten during unit tests func Command() *cobra.Command { var cmd *cobra.Command var removeColor, detailedResults, table bool @@ -248,61 +247,58 @@ func (c *ApplyCommandConfig) applyPolicytoResource( if vars != nil { vars.SetInStore() } - + // validate policies + var validPolicies []kyvernov1.PolicyInterface + for _, pol := range policies { + // TODO we should return this info to the caller + _, err := policyvalidation.Validate(pol, nil, nil, true, openApiManager, config.KyvernoUserName(config.KyvernoServiceAccountName())) + if err != nil { + log.Log.Error(err, "policy validation error") + if strings.HasPrefix(err.Error(), "variable 'element.name'") { + skipInvalidPolicies.invalid = append(skipInvalidPolicies.invalid, pol.GetName()) + } else { + skipInvalidPolicies.skipped = append(skipInvalidPolicies.skipped, pol.GetName()) + } + continue + } + matches, err := policy.ExtractVariables(pol) + if err != nil { + log.Log.Error(err, "skipping invalid policy", "name", pol.GetName()) + continue + } + if !vars.HasVariables() && variables.NeedsVariables(matches...) { + // check policy in variable file + if !vars.HasPolicyVariables(pol.GetName()) { + fmt.Printf("test skipped for policy %v (as required variables are not provided by the users) \n \n", pol.GetName()) + continue + } + } + validPolicies = append(validPolicies, pol) + } var rc processor.ResultCounts var responses []engineapi.EngineResponse for _, resource := range resources { - for _, pol := range policies { - _, err := policyvalidation.Validate(pol, nil, nil, true, openApiManager, config.KyvernoUserName(config.KyvernoServiceAccountName())) - if err != nil { - log.Log.Error(err, "policy validation error") - if strings.HasPrefix(err.Error(), "variable 'element.name'") { - skipInvalidPolicies.invalid = append(skipInvalidPolicies.invalid, pol.GetName()) - } else { - skipInvalidPolicies.skipped = append(skipInvalidPolicies.skipped, pol.GetName()) - } - - continue - } - matches, err := policy.ExtractVariables(pol) - if err != nil { - log.Log.Error(err, "skipping invalid policy", "name", pol.GetName()) - continue - } - if !vars.HasVariables() && variables.NeedsVariables(matches...) { - // check policy in variable file - if !vars.HasPolicyVariables(pol.GetName()) { - skipInvalidPolicies.skipped = append(skipInvalidPolicies.skipped, pol.GetName()) - continue - } - } - kindOnwhichPolicyIsApplied := common.GetKindsFromPolicy(pol, vars.Subresources(), dClient) - resourceValues, err := vars.ComputeVariables(pol.GetName(), resource.GetName(), resource.GetKind(), kindOnwhichPolicyIsApplied, matches...) - if err != nil { - return &rc, resources, responses, sanitizederror.NewWithError(fmt.Sprintf("policy `%s` have variables. pass the values for the variables for resource `%s` using set/values_file flag", pol.GetName(), resource.GetName()), err) - } - processor := processor.PolicyProcessor{ - Policy: pol, - Resource: resource, - MutateLogPath: c.MutateLogPath, - MutateLogPathIsDir: mutateLogPathIsDir, - Variables: resourceValues, - UserInfo: userInfo, - PolicyReport: c.PolicyReport, - NamespaceSelectorMap: vars.NamespaceSelectors(), - Stdin: c.Stdin, - Rc: &rc, - PrintPatchResource: true, - Client: dClient, - AuditWarn: c.AuditWarn, - Subresources: vars.Subresources(), - } - ers, err := processor.ApplyPolicyOnResource() - if err != nil { - return &rc, resources, responses, sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", pol.GetName(), resource.GetName()).Error(), err) - } - responses = append(responses, processSkipEngineResponses(ers)...) + processor := processor.PolicyProcessor{ + Policies: validPolicies, + Resource: *resource, + MutateLogPath: c.MutateLogPath, + MutateLogPathIsDir: mutateLogPathIsDir, + Variables: vars, + UserInfo: userInfo, + PolicyReport: c.PolicyReport, + NamespaceSelectorMap: vars.NamespaceSelectors(), + Stdin: c.Stdin, + Rc: &rc, + PrintPatchResource: true, + Client: dClient, + AuditWarn: c.AuditWarn, + Subresources: vars.Subresources(), } + ers, err := processor.ApplyPoliciesOnResource() + if err != nil { + return &rc, resources, responses, sanitizederror.NewWithError(fmt.Errorf("failed to apply policies on resource %v", resource.GetName()).Error(), err) + } + responses = append(responses, processSkipEngineResponses(ers)...) } return &rc, resources, responses, nil } diff --git a/cmd/cli/kubectl-kyverno/commands/apply/command_test.go b/cmd/cli/kubectl-kyverno/commands/apply/command_test.go index 86732ca6e8..d83c3b7b7b 100644 --- a/cmd/cli/kubectl-kyverno/commands/apply/command_test.go +++ b/cmd/cli/kubectl-kyverno/commands/apply/command_test.go @@ -329,7 +329,9 @@ func Test_Apply(t *testing.T) { } for _, tc := range testcases { - verifyTestcase(t, tc, compareSummary) + t.Run("", func(t *testing.T) { + verifyTestcase(t, tc, compareSummary) + }) } } diff --git a/cmd/cli/kubectl-kyverno/commands/test/test.go b/cmd/cli/kubectl-kyverno/commands/test/test.go index 20cb32565b..0f36d25f20 100644 --- a/cmd/cli/kubectl-kyverno/commands/test/test.go +++ b/cmd/cli/kubectl-kyverno/commands/test/test.go @@ -3,6 +3,7 @@ package test import ( "fmt" + kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" "github.com/kyverno/kyverno/api/kyverno/v1beta1" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/log" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/output/pluralize" @@ -112,11 +113,8 @@ func runTest(openApiManager openapi.Manager, testCase test.TestCase, auditWarn b } } } - // execute engine - fmt.Println(" Applying", len(policies), pluralize.Pluralize(len(policies), "policy", "policies"), "to", len(uniques), pluralize.Pluralize(len(uniques), "resource", "resources"), "...") - var engineResponses []engineapi.EngineResponse - var resultCounts processor.ResultCounts - // TODO loop through resources first, then through policies second + // validate policies + var validPolicies []kyvernov1.PolicyInterface for _, pol := range policies { // TODO we should return this info to the caller _, err := policyvalidation.Validate(pol, nil, nil, true, openApiManager, config.KyvernoUserName(config.KyvernoServiceAccountName())) @@ -133,42 +131,36 @@ func runTest(openApiManager openapi.Manager, testCase test.TestCase, auditWarn b // check policy in variable file if !vars.HasPolicyVariables(pol.GetName()) { fmt.Printf("test skipped for policy %v (as required variables are not provided by the users) \n \n", pol.GetName()) - // TODO continue ? return error ? + // continue } } + validPolicies = append(validPolicies, pol) + } + // execute engine + fmt.Println(" Applying", len(policies), pluralize.Pluralize(len(policies), "policy", "policies"), "to", len(uniques), pluralize.Pluralize(len(uniques), "resource", "resources"), "...") + var engineResponses []engineapi.EngineResponse + var resultCounts processor.ResultCounts - kindOnwhichPolicyIsApplied := common.GetKindsFromPolicy(pol, vars.Subresources(), dClient) - - for _, resource := range uniques { - resourceValues, err := vars.ComputeVariables(pol.GetName(), resource.GetName(), resource.GetKind(), kindOnwhichPolicyIsApplied, matches...) - if err != nil { - message := fmt.Sprintf( - "policy `%s` have variables. pass the values for the variables for resource `%s` using set/values_file flag", - pol.GetName(), - resource.GetName(), - ) - return nil, sanitizederror.NewWithError(message, err) - } - processor := processor.PolicyProcessor{ - Policy: pol, - Resource: resource, - MutateLogPath: "", - Variables: resourceValues, - UserInfo: userInfo, - PolicyReport: true, - NamespaceSelectorMap: vars.NamespaceSelectors(), - Rc: &resultCounts, - RuleToCloneSourceResource: ruleToCloneSourceResource, - Client: dClient, - Subresources: vars.Subresources(), - } - ers, err := processor.ApplyPolicyOnResource() - if err != nil { - message := fmt.Sprintf("failed to apply policy %v on resource %v", pol.GetName(), resource.GetName()) - return nil, sanitizederror.NewWithError(message, err) - } - engineResponses = append(engineResponses, ers...) + for _, resource := range uniques { + processor := processor.PolicyProcessor{ + Policies: validPolicies, + Resource: *resource, + MutateLogPath: "", + Variables: vars, + UserInfo: userInfo, + PolicyReport: true, + NamespaceSelectorMap: vars.NamespaceSelectors(), + Rc: &resultCounts, + RuleToCloneSourceResource: ruleToCloneSourceResource, + Client: dClient, + Subresources: vars.Subresources(), } + ers, err := processor.ApplyPoliciesOnResource() + if err != nil { + message := fmt.Sprintf("failed to apply policies on resource %v", resource.GetName()) + return nil, sanitizederror.NewWithError(message, err) + } + engineResponses = append(engineResponses, ers...) } for _, resource := range uniques { processor := processor.ValidatingAdmissionPolicyProcessor{ diff --git a/cmd/cli/kubectl-kyverno/processor/policy_processor.go b/cmd/cli/kubectl-kyverno/processor/policy_processor.go index 38cb2f8747..70c8d65652 100644 --- a/cmd/cli/kubectl-kyverno/processor/policy_processor.go +++ b/cmd/cli/kubectl-kyverno/processor/policy_processor.go @@ -12,7 +12,9 @@ import ( valuesapi "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/apis/values" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/log" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/store" + "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/common" sanitizederror "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/sanitizedError" + "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/variables" "github.com/kyverno/kyverno/pkg/autogen" "github.com/kyverno/kyverno/pkg/clients/dclient" "github.com/kyverno/kyverno/pkg/config" @@ -21,20 +23,20 @@ import ( engineapi "github.com/kyverno/kyverno/pkg/engine/api" "github.com/kyverno/kyverno/pkg/engine/factories" "github.com/kyverno/kyverno/pkg/engine/jmespath" + "github.com/kyverno/kyverno/pkg/engine/policycontext" "github.com/kyverno/kyverno/pkg/imageverifycache" "github.com/kyverno/kyverno/pkg/registryclient" - kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" yamlv2 "gopkg.in/yaml.v2" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" ) type PolicyProcessor struct { - Policy kyvernov1.PolicyInterface - Resource *unstructured.Unstructured + Policies []kyvernov1.PolicyInterface + Resource unstructured.Unstructured MutateLogPath string MutateLogPathIsDir bool - Variables map[string]interface{} + Variables *variables.Variables UserInfo *kyvernov1beta1.RequestInfo PolicyReport bool NamespaceSelectorMap map[string]map[string]string @@ -47,45 +49,28 @@ type PolicyProcessor struct { Subresources []valuesapi.Subresource } -func (p *PolicyProcessor) ApplyPolicyOnResource() ([]engineapi.EngineResponse, error) { - jp := jmespath.New(config.NewDefaultConfiguration(false)) - - var engineResponses []engineapi.EngineResponse - namespaceLabels := make(map[string]string) - operation := kyvernov1.Create - - if p.Variables["request.operation"] == "DELETE" { - operation = kyvernov1.Delete - } - rules := autogen.ComputeRules(p.Policy) - - if needsNamespaceLabels(rules...) { - resourceNamespace := p.Resource.GetNamespace() - namespaceLabels = p.NamespaceSelectorMap[p.Resource.GetNamespace()] - if resourceNamespace != "default" && len(namespaceLabels) < 1 { - return engineResponses, sanitizederror.NewWithError(fmt.Sprintf("failed to get namespace labels for resource %s. use --values-file flag to pass the namespace labels", p.Resource.GetName()), nil) - } - } - - resPath := fmt.Sprintf("%s/%s/%s", p.Resource.GetNamespace(), p.Resource.GetKind(), p.Resource.GetName()) - log.Log.V(3).Info("applying policy on resource", "policy", p.Policy.GetName(), "resource", resPath) - - resourceRaw, err := p.Resource.MarshalJSON() - if err != nil { - log.Log.Error(err, "failed to marshal resource") - } - - updatedResource, err := kubeutils.BytesToUnstructured(resourceRaw) - if err != nil { - log.Log.Error(err, "unable to convert raw resource to unstructured") - } - - if err != nil { - log.Log.Error(err, "failed to load resource in context") - } - +func (p *PolicyProcessor) ApplyPoliciesOnResource() ([]engineapi.EngineResponse, error) { cfg := config.NewDefaultConfiguration(false) - gvk, subresource := updatedResource.GroupVersionKind(), "" + jp := jmespath.New(cfg) + resource := p.Resource + namespaceLabels := p.NamespaceSelectorMap[p.Resource.GetNamespace()] + var client engineapi.Client + if p.Client != nil { + client = adapters.Client(p.Client) + } + rclient := registryclient.NewOrDie() + eng := engine.NewEngine( + cfg, + config.NewDefaultMetricsConfiguration(), + jmespath.New(cfg), + client, + factories.DefaultRegistryClientFactory(adapters.RegistryClient(rclient), nil), + imageverifycache.DisabledImageVerifyCache(), + store.ContextLoaderFactory(nil), + nil, + "", + ) + gvk, subresource := resource.GroupVersionKind(), "" // If --cluster flag is not set, then we need to find the top level resource GVK and subresource if p.Client == nil { for _, s := range p.Subresources { @@ -105,25 +90,114 @@ func (p *PolicyProcessor) ApplyPolicyOnResource() ([]engineapi.EngineResponse, e } } } - var client engineapi.Client - if p.Client != nil { - client = adapters.Client(p.Client) + resPath := fmt.Sprintf("%s/%s/%s", resource.GetNamespace(), resource.GetKind(), resource.GetName()) + var responses []engineapi.EngineResponse + // mutate + for _, policy := range p.Policies { + policyContext, err := p.makePolicyContext(jp, cfg, resource, policy, namespaceLabels, gvk, subresource) + if err != nil { + return responses, err + } + mutateResponse := eng.Mutate(context.Background(), policyContext) + combineRuleResponses(mutateResponse) + err = p.processMutateEngineResponse(mutateResponse, resPath) + if err != nil { + if !sanitizederror.IsErrorSanitized(err) { + return responses, sanitizederror.NewWithError("failed to print mutated result", err) + } + } + responses = append(responses, mutateResponse) + resource = mutateResponse.PatchedResource + } + // verify images + for _, policy := range p.Policies { + policyContext, err := p.makePolicyContext(jp, cfg, resource, policy, namespaceLabels, gvk, subresource) + if err != nil { + return responses, err + } + // TODO annotation + verifyImageResponse, _ := eng.VerifyAndPatchImages(context.TODO(), policyContext) + if !verifyImageResponse.IsEmpty() { + verifyImageResponse = combineRuleResponses(verifyImageResponse) + responses = append(responses, verifyImageResponse) + resource = verifyImageResponse.PatchedResource + } + } + // validate + for _, policy := range p.Policies { + policyContext, err := p.makePolicyContext(jp, cfg, resource, policy, namespaceLabels, gvk, subresource) + if err != nil { + return responses, err + } + validateResponse := eng.Validate(context.TODO(), policyContext) + if !validateResponse.IsEmpty() { + validateResponse = combineRuleResponses(validateResponse) + responses = append(responses, validateResponse) + resource = validateResponse.PatchedResource + } + } + // generate + for _, policy := range p.Policies { + var policyHasGenerate bool + for _, rule := range autogen.ComputeRules(policy) { + if rule.HasGenerate() { + policyHasGenerate = true + } + } + if policyHasGenerate { + policyContext, err := p.makePolicyContext(jp, cfg, resource, policy, namespaceLabels, gvk, subresource) + if err != nil { + return responses, err + } + + generateResponse := eng.ApplyBackgroundChecks(context.TODO(), policyContext) + if !generateResponse.IsEmpty() { + newRuleResponse, err := handleGeneratePolicy(&generateResponse, *policyContext, p.RuleToCloneSourceResource) + if err != nil { + log.Log.Error(err, "failed to apply generate policy") + } else { + generateResponse.PolicyResponse.Rules = newRuleResponse + } + combineRuleResponses(generateResponse) + responses = append(responses, generateResponse) + } + p.Rc.addGenerateResponse(p.AuditWarn, resPath, generateResponse) + } + } + p.Rc.addEngineResponses(p.AuditWarn, responses...) + return responses, nil +} + +func (p *PolicyProcessor) makePolicyContext( + jp jmespath.Interface, + cfg config.Configuration, + resource unstructured.Unstructured, + policy kyvernov1.PolicyInterface, + namespaceLabels map[string]string, + gvk schema.GroupVersionKind, + subresource string, +) (*policycontext.PolicyContext, error) { + operation := kyvernov1.Create + var resourceValues map[string]interface{} + if p.Variables != nil { + kindOnwhichPolicyIsApplied := common.GetKindsFromPolicy(policy, p.Variables.Subresources(), p.Client) + vals, err := p.Variables.ComputeVariables(policy.GetName(), resource.GetName(), resource.GetKind(), kindOnwhichPolicyIsApplied /*matches...*/) + if err != nil { + message := fmt.Sprintf( + "policy `%s` have variables. pass the values for the variables for resource `%s` using set/values_file flag", + policy.GetName(), + resource.GetName(), + ) + return nil, sanitizederror.NewWithError(message, err) + } + resourceValues = vals + } + if resourceValues["request.operation"] == "DELETE" { + operation = kyvernov1.Delete } - rclient := registryclient.NewOrDie() - eng := engine.NewEngine( - cfg, - config.NewDefaultMetricsConfiguration(), - jmespath.New(cfg), - client, - factories.DefaultRegistryClientFactory(adapters.RegistryClient(rclient), nil), - imageverifycache.DisabledImageVerifyCache(), - store.ContextLoaderFactory(nil), - nil, - "", - ) policyContext, err := engine.NewPolicyContext( jp, - *updatedResource, + resource, operation, p.UserInfo, cfg, @@ -131,80 +205,17 @@ func (p *PolicyProcessor) ApplyPolicyOnResource() ([]engineapi.EngineResponse, e if err != nil { log.Log.Error(err, "failed to create policy context") } - policyContext = policyContext. - WithPolicy(p.Policy). + WithPolicy(policy). WithNamespaceLabels(namespaceLabels). WithResourceKind(gvk, subresource) - - for key, value := range p.Variables { + for key, value := range resourceValues { err = policyContext.JSONContext().AddVariable(key, value) if err != nil { log.Log.Error(err, "failed to add variable to context") } } - - mutateResponse := eng.Mutate(context.Background(), policyContext) - combineRuleResponses(mutateResponse) - engineResponses = append(engineResponses, mutateResponse) - - err = p.processMutateEngineResponse(mutateResponse, resPath) - if err != nil { - if !sanitizederror.IsErrorSanitized(err) { - return engineResponses, sanitizederror.NewWithError("failed to print mutated result", err) - } - } - - verifyImageResponse, _ := eng.VerifyAndPatchImages(context.TODO(), policyContext) - if !verifyImageResponse.IsEmpty() { - verifyImageResponse = combineRuleResponses(verifyImageResponse) - engineResponses = append(engineResponses, verifyImageResponse) - } - - var policyHasValidate bool - for _, rule := range rules { - if rule.HasValidate() || rule.HasVerifyImageChecks() { - policyHasValidate = true - } - } - - policyContext = policyContext.WithNewResource(mutateResponse.PatchedResource) - - var validateResponse engineapi.EngineResponse - if policyHasValidate { - validateResponse = eng.Validate(context.Background(), policyContext) - validateResponse = combineRuleResponses(validateResponse) - } - - if !validateResponse.IsEmpty() { - engineResponses = append(engineResponses, validateResponse) - } - - var policyHasGenerate bool - for _, rule := range rules { - if rule.HasGenerate() { - policyHasGenerate = true - } - } - - if policyHasGenerate { - generateResponse := eng.ApplyBackgroundChecks(context.TODO(), policyContext) - if !generateResponse.IsEmpty() { - newRuleResponse, err := handleGeneratePolicy(&generateResponse, *policyContext, p.RuleToCloneSourceResource) - if err != nil { - log.Log.Error(err, "failed to apply generate policy") - } else { - generateResponse.PolicyResponse.Rules = newRuleResponse - } - combineRuleResponses(generateResponse) - engineResponses = append(engineResponses, generateResponse) - } - p.Rc.addGenerateResponse(p.AuditWarn, resPath, generateResponse) - } - - p.Rc.addEngineResponses(p.AuditWarn, engineResponses...) - - return engineResponses, nil + return policyContext, nil } func (p *PolicyProcessor) processMutateEngineResponse(response engineapi.EngineResponse, resourcePath string) error { @@ -219,7 +230,7 @@ func (p *PolicyProcessor) processMutateEngineResponse(response engineapi.EngineR mutatedResource := string(yamlEncodedResource) + string("\n---") if len(strings.TrimSpace(mutatedResource)) > 0 { if !p.Stdin { - fmt.Printf("\nmutate policy %s applied to %s:", p.Policy.GetName(), resourcePath) + fmt.Printf("\nmutate policy %s applied to %s:", response.Policy().GetName(), resourcePath) } fmt.Printf("\n" + mutatedResource + "\n") } diff --git a/cmd/cli/kubectl-kyverno/processor/policy_processor_test.go b/cmd/cli/kubectl-kyverno/processor/policy_processor_test.go new file mode 100644 index 0000000000..747db814ec --- /dev/null +++ b/cmd/cli/kubectl-kyverno/processor/policy_processor_test.go @@ -0,0 +1,123 @@ +package processor + +import ( + "testing" + + "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/resource" + yamlutils "github.com/kyverno/kyverno/pkg/utils/yaml" + "gotest.tools/assert" +) + +var policyNamespaceSelector = []byte(`{ + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "enforce-pod-name" + }, + "spec": { + "validationFailureAction": "audit", + "background": true, + "rules": [ + { + "name": "validate-name", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaceSelector": { + "matchExpressions": [ + { + "key": "foo.com/managed-state", + "operator": "In", + "values": [ + "managed" + ] + } + ] + } + } + }, + "validate": { + "message": "The Pod must end with -nginx", + "pattern": { + "metadata": { + "name": "*-nginx" + } + } + } + } + ] + } + } +`) + +func Test_NamespaceSelector(t *testing.T) { + type resultCounts struct { + pass int + fail int + warn int + err int + skip int + } + type TestCase struct { + policy []byte + resource []byte + namespaceSelectorMap map[string]map[string]string + result ResultCounts + } + + testcases := []TestCase{ + { + policy: policyNamespaceSelector, + resource: []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"name":"nginx","namespace":"test1"},"spec":{"containers":[{"image":"nginx:latest","name":"test-fail"}]}}`), + namespaceSelectorMap: map[string]map[string]string{ + "test1": { + "foo.com/managed-state": "managed", + }, + }, + result: ResultCounts{ + pass: 0, + fail: 1, + warn: 0, + err: 0, + skip: 2, + }, + }, + { + policy: policyNamespaceSelector, + resource: []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"name":"test-nginx","namespace":"test1"},"spec":{"containers":[{"image":"nginx:latest","name":"test-pass"}]}}`), + namespaceSelectorMap: map[string]map[string]string{ + "test1": { + "foo.com/managed-state": "managed", + }, + }, + result: ResultCounts{ + pass: 1, + fail: 1, + warn: 0, + err: 0, + skip: 4, + }, + }, + } + rc := &ResultCounts{} + for _, tc := range testcases { + policyArray, _, _ := yamlutils.GetPolicy(tc.policy) + resourceArray, _ := resource.GetUnstructuredResources(tc.resource) + processor := PolicyProcessor{ + Policies: policyArray, + Resource: *resourceArray[0], + MutateLogPath: "", + UserInfo: nil, + NamespaceSelectorMap: tc.namespaceSelectorMap, + Rc: rc, + } + processor.ApplyPoliciesOnResource() + assert.Equal(t, int64(rc.Pass()), int64(tc.result.pass)) + assert.Equal(t, int64(rc.Fail()), int64(tc.result.fail)) + assert.Equal(t, int64(rc.Skip()), int64(tc.result.skip)) + assert.Equal(t, int64(rc.Warn()), int64(tc.result.warn)) + assert.Equal(t, int64(rc.Error()), int64(tc.result.err)) + } +} diff --git a/cmd/cli/kubectl-kyverno/processor/utils.go b/cmd/cli/kubectl-kyverno/processor/utils.go index ead22ffe72..f834b489a5 100644 --- a/cmd/cli/kubectl-kyverno/processor/utils.go +++ b/cmd/cli/kubectl-kyverno/processor/utils.go @@ -3,7 +3,6 @@ package processor import ( "strings" - kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" engineapi "github.com/kyverno/kyverno/pkg/engine/api" ) @@ -78,33 +77,3 @@ func combineRuleResponses(imageResponse engineapi.EngineResponse) engineapi.Engi imageResponse.PolicyResponse.Rules = combineRuleResponses return imageResponse } - -func needsNamespaceLabels(rules ...kyvernov1.Rule) bool { - for _, p := range rules { - if p.MatchResources.ResourceDescription.NamespaceSelector != nil || - p.ExcludeResources.ResourceDescription.NamespaceSelector != nil { - return true - } - for _, m := range p.MatchResources.Any { - if m.ResourceDescription.NamespaceSelector != nil { - return true - } - } - for _, m := range p.MatchResources.All { - if m.ResourceDescription.NamespaceSelector != nil { - return true - } - } - for _, e := range p.ExcludeResources.Any { - if e.ResourceDescription.NamespaceSelector != nil { - return true - } - } - for _, e := range p.ExcludeResources.All { - if e.ResourceDescription.NamespaceSelector != nil { - return true - } - } - } - return false -} diff --git a/cmd/cli/kubectl-kyverno/utils/common/common_test.go b/cmd/cli/kubectl-kyverno/utils/common/common_test.go index 37f61d0eab..2a9e17b934 100644 --- a/cmd/cli/kubectl-kyverno/utils/common/common_test.go +++ b/cmd/cli/kubectl-kyverno/utils/common/common_test.go @@ -4,9 +4,7 @@ import ( "testing" valuesapi "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/apis/values" - "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/processor" - "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/resource" - yamlutils "github.com/kyverno/kyverno/pkg/utils/yaml" + "gotest.tools/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -55,76 +53,6 @@ var policyNamespaceSelector = []byte(`{ } `) -func Test_NamespaceSelector(t *testing.T) { - type ResultCounts struct { - pass int - fail int - warn int - err int - skip int - } - type TestCase struct { - policy []byte - resource []byte - namespaceSelectorMap map[string]map[string]string - result ResultCounts - } - - testcases := []TestCase{ - { - policy: policyNamespaceSelector, - resource: []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"name":"nginx","namespace":"test1"},"spec":{"containers":[{"image":"nginx:latest","name":"test-fail"}]}}`), - namespaceSelectorMap: map[string]map[string]string{ - "test1": { - "foo.com/managed-state": "managed", - }, - }, - result: ResultCounts{ - pass: 0, - fail: 1, - warn: 0, - err: 0, - skip: 2, - }, - }, - { - policy: policyNamespaceSelector, - resource: []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"name":"test-nginx","namespace":"test1"},"spec":{"containers":[{"image":"nginx:latest","name":"test-pass"}]}}`), - namespaceSelectorMap: map[string]map[string]string{ - "test1": { - "foo.com/managed-state": "managed", - }, - }, - result: ResultCounts{ - pass: 1, - fail: 1, - warn: 0, - err: 0, - skip: 4, - }, - }, - } - rc := &processor.ResultCounts{} - for _, tc := range testcases { - policyArray, _, _ := yamlutils.GetPolicy(tc.policy) - resourceArray, _ := resource.GetUnstructuredResources(tc.resource) - processor := processor.PolicyProcessor{ - Policy: policyArray[0], - Resource: resourceArray[0], - MutateLogPath: "", - UserInfo: nil, - NamespaceSelectorMap: tc.namespaceSelectorMap, - Rc: rc, - } - processor.ApplyPolicyOnResource() - assert.Equal(t, int64(rc.Pass()), int64(tc.result.pass)) - assert.Equal(t, int64(rc.Fail()), int64(tc.result.fail)) - assert.Equal(t, int64(rc.Skip()), int64(tc.result.skip)) - assert.Equal(t, int64(rc.Warn()), int64(tc.result.warn)) - assert.Equal(t, int64(rc.Error()), int64(tc.result.err)) - } -} - func Test_GetGitBranchOrPolicyPaths(t *testing.T) { type TestCase struct { gitBranch string diff --git a/test/cli/test-mutate/foreach/pod-updated-image.yaml b/test/cli/test-mutate/foreach/pod-updated-image.yaml index cddfef2452..5f8de00e87 100644 --- a/test/cli/test-mutate/foreach/pod-updated-image.yaml +++ b/test/cli/test-mutate/foreach/pod-updated-image.yaml @@ -10,11 +10,15 @@ spec: containers: - image: registry.digitalocean.com/runlevl4/nginxasdfasdf:latest name: busybox01 + securityContext: + runAsNonRoot: true args: - sleep - "9999" - image: registry.digitalocean.com/runlevl4/bbbbbbbbbb-ccccc:1.28 name: busybox02 + securityContext: + runAsNonRoot: true args: - sleep - "9999" diff --git a/test/cli/test-mutate/patched-resource.yaml b/test/cli/test-mutate/patched-resource.yaml index 149bccc81a..766bd304df 100644 --- a/test/cli/test-mutate/patched-resource.yaml +++ b/test/cli/test-mutate/patched-resource.yaml @@ -5,3 +5,5 @@ metadata: annotations: key1: "1" key2: "1" + labels: + color: orange \ No newline at end of file diff --git a/test/cli/test-mutate/patchedResource8.yaml b/test/cli/test-mutate/patchedResource8.yaml index 4754a1f6b2..f13851989f 100644 --- a/test/cli/test-mutate/patchedResource8.yaml +++ b/test/cli/test-mutate/patchedResource8.yaml @@ -3,6 +3,7 @@ kind: Pod metadata: labels: foo: bar + color: orange name: same-name-but-diff-namespace namespace: testing spec: