From e98bfd1cd9a0562bf5c32704a0375d78eb612505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?= Date: Wed, 6 Sep 2023 06:48:55 +0200 Subject: [PATCH] refactor: introduce cli processor package (#8281) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: introduce cli processor package Signed-off-by: Charles-Edouard Brétéché * tests Signed-off-by: Charles-Edouard Brétéché * counts Signed-off-by: Charles-Edouard Brétéché --------- Signed-off-by: Charles-Edouard Brétéché --- .../kubectl-kyverno/commands/apply/command.go | 47 ++- cmd/cli/kubectl-kyverno/commands/test/test.go | 14 +- cmd/cli/kubectl-kyverno/processor/generate.go | 104 ++++++ .../policy_processor.go} | 195 +++++------ cmd/cli/kubectl-kyverno/processor/result.go | 171 ++++++++++ cmd/cli/kubectl-kyverno/processor/utils.go | 79 +++++ .../processor/vap_processor.go | 21 ++ .../kubectl-kyverno/utils/common/common.go | 303 ------------------ .../utils/common/common_test.go | 44 +-- .../utils/common/policy_interface.go | 9 - .../utils/common/resource_interface.go | 11 - .../validating_admission_policies_types.go | 23 -- ...s.go => validating_admission_resources.go} | 0 13 files changed, 531 insertions(+), 490 deletions(-) create mode 100644 cmd/cli/kubectl-kyverno/processor/generate.go rename cmd/cli/kubectl-kyverno/{utils/common/kyverno_policies_types.go => processor/policy_processor.go} (59%) create mode 100644 cmd/cli/kubectl-kyverno/processor/result.go create mode 100644 cmd/cli/kubectl-kyverno/processor/utils.go create mode 100644 cmd/cli/kubectl-kyverno/processor/vap_processor.go delete mode 100644 cmd/cli/kubectl-kyverno/utils/common/policy_interface.go delete mode 100644 cmd/cli/kubectl-kyverno/utils/common/resource_interface.go delete mode 100644 cmd/cli/kubectl-kyverno/utils/common/validating_admission_policies_types.go rename cmd/cli/kubectl-kyverno/utils/common/{validatingadmission_resources_types.go => validating_admission_resources.go} (100%) diff --git a/cmd/cli/kubectl-kyverno/commands/apply/command.go b/cmd/cli/kubectl-kyverno/commands/apply/command.go index d16cb5a593..d790994264 100644 --- a/cmd/cli/kubectl-kyverno/commands/apply/command.go +++ b/cmd/cli/kubectl-kyverno/commands/apply/command.go @@ -16,6 +16,7 @@ import ( "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/log" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/output/color" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/policy" + "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/processor" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/source" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/userinfo" cobrautils "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/cobra" @@ -127,7 +128,7 @@ func Command() *cobra.Command { return cmd } -func (c *ApplyCommandConfig) applyCommandHelper() (*common.ResultCounts, []*unstructured.Unstructured, SkippedInvalidPolicies, []engineapi.EngineResponse, error) { +func (c *ApplyCommandConfig) applyCommandHelper() (*processor.ResultCounts, []*unstructured.Unstructured, SkippedInvalidPolicies, []engineapi.EngineResponse, error) { rc, uu, skipInvalidPolicies, er, err := c.checkArguments() if err != nil { return rc, uu, skipInvalidPolicies, er, err @@ -178,7 +179,7 @@ func (c *ApplyCommandConfig) applyCommandHelper() (*common.ResultCounts, []*unst return rc, resources, skipInvalidPolicies, er, nil } -func (c *ApplyCommandConfig) getMutateLogPathIsDir(skipInvalidPolicies SkippedInvalidPolicies) (*common.ResultCounts, []*unstructured.Unstructured, SkippedInvalidPolicies, []engineapi.EngineResponse, error, bool) { +func (c *ApplyCommandConfig) getMutateLogPathIsDir(skipInvalidPolicies SkippedInvalidPolicies) (*processor.ResultCounts, []*unstructured.Unstructured, SkippedInvalidPolicies, []engineapi.EngineResponse, error, bool) { mutateLogPathIsDir, err := checkMutateLogPath(c.MutateLogPath) if err != nil { if !sanitizederror.IsErrorSanitized(err) { @@ -189,20 +190,16 @@ func (c *ApplyCommandConfig) getMutateLogPathIsDir(skipInvalidPolicies SkippedIn return nil, nil, skipInvalidPolicies, nil, err, mutateLogPathIsDir } -func (c *ApplyCommandConfig) applyValidatingAdmissionPolicytoResource(validatingAdmissionPolicies []v1alpha1.ValidatingAdmissionPolicy, resources []*unstructured.Unstructured, rc *common.ResultCounts, dClient dclient.Interface, subresources []valuesapi.Subresource, skipInvalidPolicies SkippedInvalidPolicies, responses []engineapi.EngineResponse) (*common.ResultCounts, []*unstructured.Unstructured, SkippedInvalidPolicies, []engineapi.EngineResponse, error) { - validatingAdmissionPolicy := common.ValidatingAdmissionPolicies{} +func (c *ApplyCommandConfig) applyValidatingAdmissionPolicytoResource(validatingAdmissionPolicies []v1alpha1.ValidatingAdmissionPolicy, resources []*unstructured.Unstructured, rc *processor.ResultCounts, dClient dclient.Interface, subresources []valuesapi.Subresource, skipInvalidPolicies SkippedInvalidPolicies, responses []engineapi.EngineResponse) (*processor.ResultCounts, []*unstructured.Unstructured, SkippedInvalidPolicies, []engineapi.EngineResponse, error) { for _, resource := range resources { for _, policy := range validatingAdmissionPolicies { - applyPolicyConfig := common.ApplyPolicyConfig{ + processor := processor.ValidatingAdmissionPolicyProcessor{ ValidatingAdmissionPolicy: policy, Resource: resource, PolicyReport: c.PolicyReport, Rc: rc, - Client: dClient, - AuditWarn: c.AuditWarn, - Subresources: subresources, } - ers, err := validatingAdmissionPolicy.ApplyPolicyOnResource(applyPolicyConfig) + ers, err := processor.ApplyPolicyOnResource() if err != nil { return rc, resources, skipInvalidPolicies, responses, sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.GetName(), resource.GetName()).Error(), err) } @@ -212,7 +209,7 @@ func (c *ApplyCommandConfig) applyValidatingAdmissionPolicytoResource(validating return rc, resources, skipInvalidPolicies, responses, nil } -func (c *ApplyCommandConfig) applyPolicytoResource(variables map[string]string, policies []kyvernov1.PolicyInterface, validatingAdmissionPolicies []v1alpha1.ValidatingAdmissionPolicy, resources []*unstructured.Unstructured, openApiManager openapi.Manager, skipInvalidPolicies SkippedInvalidPolicies, valuesMap map[string]map[string]valuesapi.Resource, dClient dclient.Interface, subresources []valuesapi.Subresource, globalValMap map[string]string, userInfo *v1beta1.RequestInfo, mutateLogPathIsDir bool, namespaceSelectorMap map[string]map[string]string) (*common.ResultCounts, []*unstructured.Unstructured, SkippedInvalidPolicies, []engineapi.EngineResponse, error) { +func (c *ApplyCommandConfig) applyPolicytoResource(variables map[string]string, policies []kyvernov1.PolicyInterface, validatingAdmissionPolicies []v1alpha1.ValidatingAdmissionPolicy, resources []*unstructured.Unstructured, openApiManager openapi.Manager, skipInvalidPolicies SkippedInvalidPolicies, valuesMap map[string]map[string]valuesapi.Resource, dClient dclient.Interface, subresources []valuesapi.Subresource, globalValMap map[string]string, userInfo *v1beta1.RequestInfo, mutateLogPathIsDir bool, namespaceSelectorMap map[string]map[string]string) (*processor.ResultCounts, []*unstructured.Unstructured, SkippedInvalidPolicies, []engineapi.EngineResponse, error) { if len(variables) != 0 { variables = common.SetInStoreContext(policies, variables) } @@ -226,7 +223,7 @@ func (c *ApplyCommandConfig) applyPolicytoResource(variables map[string]string, fmt.Printf("\nApplying %d policy rule(s) to %d resource(s)...\n", policyRulesCount, len(resources)) } - var rc common.ResultCounts + var rc processor.ResultCounts var responses []engineapi.EngineResponse for _, resource := range resources { for _, policy := range policies { @@ -257,7 +254,7 @@ func (c *ApplyCommandConfig) applyPolicytoResource(variables map[string]string, if err != nil { return &rc, resources, skipInvalidPolicies, responses, sanitizederror.NewWithError(fmt.Sprintf("policy `%s` have variables. pass the values for the variables for resource `%s` using set/values_file flag", policy.GetName(), resource.GetName()), err) } - applyPolicyConfig := common.ApplyPolicyConfig{ + processor := processor.PolicyProcessor{ Policy: policy, Resource: resource, MutateLogPath: c.MutateLogPath, @@ -273,11 +270,11 @@ func (c *ApplyCommandConfig) applyPolicytoResource(variables map[string]string, AuditWarn: c.AuditWarn, Subresources: subresources, } - ers, err := common.ApplyPolicyOnResource(applyPolicyConfig) + ers, err := processor.ApplyPolicyOnResource() if err != nil { return &rc, resources, skipInvalidPolicies, responses, sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.GetName(), resource.GetName()).Error(), err) } - responses = append(responses, processSkipEngineResponses(ers, applyPolicyConfig)...) + responses = append(responses, processSkipEngineResponses(ers)...) } } return &rc, resources, skipInvalidPolicies, responses, nil @@ -292,7 +289,7 @@ func (c *ApplyCommandConfig) loadResources(policies []kyvernov1.PolicyInterface, return resources } -func (c *ApplyCommandConfig) loadPolicies(skipInvalidPolicies SkippedInvalidPolicies) (*common.ResultCounts, []*unstructured.Unstructured, SkippedInvalidPolicies, []engineapi.EngineResponse, error, []kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy) { +func (c *ApplyCommandConfig) loadPolicies(skipInvalidPolicies SkippedInvalidPolicies) (*processor.ResultCounts, []*unstructured.Unstructured, SkippedInvalidPolicies, []engineapi.EngineResponse, error, []kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy) { // load policies var policies []kyvernov1.PolicyInterface var validatingAdmissionPolicies []v1alpha1.ValidatingAdmissionPolicy @@ -345,7 +342,7 @@ func (c *ApplyCommandConfig) loadPolicies(skipInvalidPolicies SkippedInvalidPoli return nil, nil, skipInvalidPolicies, nil, nil, policies, validatingAdmissionPolicies } -func (c *ApplyCommandConfig) initStoreAndClusterClient(skipInvalidPolicies SkippedInvalidPolicies) (*common.ResultCounts, []*unstructured.Unstructured, SkippedInvalidPolicies, []engineapi.EngineResponse, error, dclient.Interface) { +func (c *ApplyCommandConfig) initStoreAndClusterClient(skipInvalidPolicies SkippedInvalidPolicies) (*processor.ResultCounts, []*unstructured.Unstructured, SkippedInvalidPolicies, []engineapi.EngineResponse, error, dclient.Interface) { store.SetLocal(true) store.SetRegistryAccess(c.RegistryAccess) if c.Cluster { @@ -374,7 +371,7 @@ func (c *ApplyCommandConfig) initStoreAndClusterClient(skipInvalidPolicies Skipp return nil, nil, skipInvalidPolicies, nil, err, dClient } -func (c *ApplyCommandConfig) cleanPreviousContent(mutateLogPathIsDir bool, skipInvalidPolicies SkippedInvalidPolicies) (*common.ResultCounts, []*unstructured.Unstructured, SkippedInvalidPolicies, []engineapi.EngineResponse, error) { +func (c *ApplyCommandConfig) cleanPreviousContent(mutateLogPathIsDir bool, skipInvalidPolicies SkippedInvalidPolicies) (*processor.ResultCounts, []*unstructured.Unstructured, SkippedInvalidPolicies, []engineapi.EngineResponse, error) { // empty the previous contents of the file just in case if the file already existed before with some content(so as to perform overwrites) // the truncation of files for the case when mutateLogPath is dir, is handled under pkg/kyverno/apply/common.go if !mutateLogPathIsDir && c.MutateLogPath != "" { @@ -391,7 +388,7 @@ func (c *ApplyCommandConfig) cleanPreviousContent(mutateLogPathIsDir bool, skipI return nil, nil, skipInvalidPolicies, nil, nil } -func (c *ApplyCommandConfig) checkArguments() (*common.ResultCounts, []*unstructured.Unstructured, SkippedInvalidPolicies, []engineapi.EngineResponse, error) { +func (c *ApplyCommandConfig) checkArguments() (*processor.ResultCounts, []*unstructured.Unstructured, SkippedInvalidPolicies, []engineapi.EngineResponse, error) { var skipInvalidPolicies SkippedInvalidPolicies if c.ValuesFile != "" && c.Variables != nil { return nil, nil, skipInvalidPolicies, nil, sanitizederror.New("pass the values either using set flag or values_file flag") @@ -442,21 +439,21 @@ func printReport(engineResponses []engineapi.EngineResponse, auditWarn bool) { } } -func printViolations(rc *common.ResultCounts) { - fmt.Printf("\npass: %d, fail: %d, warn: %d, error: %d, skip: %d \n", rc.Pass, rc.Fail, rc.Warn, rc.Error, rc.Skip) +func printViolations(rc *processor.ResultCounts) { + fmt.Printf("\npass: %d, fail: %d, warn: %d, error: %d, skip: %d \n", rc.Pass(), rc.Fail(), rc.Warn(), rc.Error(), rc.Skip()) } -func exit(rc *common.ResultCounts, warnExitCode int, warnNoPassed bool) { - if rc.Fail > 0 || rc.Error > 0 { +func exit(rc *processor.ResultCounts, warnExitCode int, warnNoPassed bool) { + if rc.Fail() > 0 || rc.Error() > 0 { osExit(1) - } else if rc.Warn > 0 && warnExitCode != 0 { + } else if rc.Warn() > 0 && warnExitCode != 0 { osExit(warnExitCode) - } else if rc.Pass == 0 && warnNoPassed { + } else if rc.Pass() == 0 && warnNoPassed { osExit(warnExitCode) } } -func processSkipEngineResponses(responses []engineapi.EngineResponse, c common.ApplyPolicyConfig) []engineapi.EngineResponse { +func processSkipEngineResponses(responses []engineapi.EngineResponse) []engineapi.EngineResponse { var processedEngineResponses []engineapi.EngineResponse for _, response := range responses { if !response.IsEmpty() { diff --git a/cmd/cli/kubectl-kyverno/commands/test/test.go b/cmd/cli/kubectl-kyverno/commands/test/test.go index 6c05ab2539..2fe3266f2c 100644 --- a/cmd/cli/kubectl-kyverno/commands/test/test.go +++ b/cmd/cli/kubectl-kyverno/commands/test/test.go @@ -7,6 +7,7 @@ import ( "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/log" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/output/pluralize" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/policy" + "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/processor" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/resource" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/test" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/userinfo" @@ -114,7 +115,7 @@ 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 common.ResultCounts + var resultCounts processor.ResultCounts // TODO loop through resources first, then through policies second for _, policy := range policies { // TODO we should return this info to the caller @@ -149,7 +150,7 @@ func runTest(openApiManager openapi.Manager, testCase test.TestCase, auditWarn b ) return nil, sanitizederror.NewWithError(message, err) } - applyPolicyConfig := common.ApplyPolicyConfig{ + processor := processor.PolicyProcessor{ Policy: policy, Resource: resource, MutateLogPath: "", @@ -162,7 +163,7 @@ func runTest(openApiManager openapi.Manager, testCase test.TestCase, auditWarn b Client: dClient, Subresources: subresources, } - ers, err := common.ApplyPolicyOnResource(applyPolicyConfig) + ers, err := processor.ApplyPolicyOnResource() if err != nil { message := fmt.Sprintf("failed to apply policy %v on resource %v", policy.GetName(), resource.GetName()) return nil, sanitizederror.NewWithError(message, err) @@ -170,18 +171,15 @@ func runTest(openApiManager openapi.Manager, testCase test.TestCase, auditWarn b engineResponses = append(engineResponses, ers...) } } - validatingAdmissionPolicy := common.ValidatingAdmissionPolicies{} for _, policy := range validatingAdmissionPolicies { for _, resource := range uniques { - applyPolicyConfig := common.ApplyPolicyConfig{ + processor := processor.ValidatingAdmissionPolicyProcessor{ ValidatingAdmissionPolicy: policy, Resource: resource, PolicyReport: true, Rc: &resultCounts, - Client: dClient, - Subresources: subresources, } - ers, err := validatingAdmissionPolicy.ApplyPolicyOnResource(applyPolicyConfig) + ers, err := processor.ApplyPolicyOnResource() if err != nil { message := fmt.Sprintf("failed to apply policy %v on resource %v", policy.GetName(), resource.GetName()) return nil, sanitizederror.NewWithError(message, err) diff --git a/cmd/cli/kubectl-kyverno/processor/generate.go b/cmd/cli/kubectl-kyverno/processor/generate.go new file mode 100644 index 0000000000..9442ba842f --- /dev/null +++ b/cmd/cli/kubectl-kyverno/processor/generate.go @@ -0,0 +1,104 @@ +package processor + +import ( + "fmt" + + kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" + kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1" + "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/log" + "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/resource" + "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/store" + "github.com/kyverno/kyverno/pkg/background/generate" + "github.com/kyverno/kyverno/pkg/clients/dclient" + "github.com/kyverno/kyverno/pkg/config" + "github.com/kyverno/kyverno/pkg/engine" + "github.com/kyverno/kyverno/pkg/engine/adapters" + engineapi "github.com/kyverno/kyverno/pkg/engine/api" + "github.com/kyverno/kyverno/pkg/engine/jmespath" + "github.com/kyverno/kyverno/pkg/imageverifycache" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" +) + +func handleGeneratePolicy(generateResponse *engineapi.EngineResponse, policyContext engine.PolicyContext, ruleToCloneSourceResource map[string]string) ([]engineapi.RuleResponse, error) { + newResource := policyContext.NewResource() + objects := []runtime.Object{&newResource} + resources := []*unstructured.Unstructured{} + for _, rule := range generateResponse.PolicyResponse.Rules { + if path, ok := ruleToCloneSourceResource[rule.Name()]; ok { + resourceBytes, err := resource.GetFileBytes(path) + if err != nil { + fmt.Printf("failed to get resource bytes\n") + } else { + resources, err = resource.GetUnstructuredResources(resourceBytes) + if err != nil { + fmt.Printf("failed to convert resource bytes to unstructured format\n") + } + } + } + } + + for _, res := range resources { + objects = append(objects, res) + } + + c, err := initializeMockController(objects) + if err != nil { + fmt.Println("error at controller") + return nil, err + } + + gr := kyvernov1beta1.UpdateRequest{ + Spec: kyvernov1beta1.UpdateRequestSpec{ + Type: kyvernov1beta1.Generate, + Policy: generateResponse.Policy().GetName(), + Resource: kyvernov1.ResourceSpec{ + Kind: generateResponse.Resource.GetKind(), + Namespace: generateResponse.Resource.GetNamespace(), + Name: generateResponse.Resource.GetName(), + APIVersion: generateResponse.Resource.GetAPIVersion(), + }, + }, + } + + var newRuleResponse []engineapi.RuleResponse + + for _, rule := range generateResponse.PolicyResponse.Rules { + genResource, err := c.ApplyGeneratePolicy(log.Log.V(2), &policyContext, gr, []string{rule.Name()}) + if err != nil { + return nil, err + } + + if genResource != nil { + unstrGenResource, err := c.GetUnstrResource(genResource[0]) + if err != nil { + return nil, err + } + newRuleResponse = append(newRuleResponse, *rule.WithGeneratedResource(*unstrGenResource)) + } + } + + return newRuleResponse, nil +} + +func initializeMockController(objects []runtime.Object) (*generate.GenerateController, error) { + client, err := dclient.NewFakeClient(runtime.NewScheme(), nil, objects...) + if err != nil { + fmt.Printf("Failed to mock dynamic client") + return nil, err + } + client.SetDiscovery(dclient.NewFakeDiscoveryClient(nil)) + cfg := config.NewDefaultConfiguration(false) + c := generate.NewGenerateControllerWithOnlyClient(client, engine.NewEngine( + cfg, + config.NewDefaultMetricsConfiguration(), + jmespath.New(cfg), + adapters.Client(client), + nil, + imageverifycache.DisabledImageVerifyCache(), + store.ContextLoaderFactory(nil), + nil, + "", + )) + return c, nil +} diff --git a/cmd/cli/kubectl-kyverno/utils/common/kyverno_policies_types.go b/cmd/cli/kubectl-kyverno/processor/policy_processor.go similarity index 59% rename from cmd/cli/kubectl-kyverno/utils/common/kyverno_policies_types.go rename to cmd/cli/kubectl-kyverno/processor/policy_processor.go index 66b69e28b0..6633c379b1 100644 --- a/cmd/cli/kubectl-kyverno/utils/common/kyverno_policies_types.go +++ b/cmd/cli/kubectl-kyverno/processor/policy_processor.go @@ -1,15 +1,20 @@ -package common +package processor import ( "context" "fmt" + "os" + "path/filepath" "strings" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" + kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1" + valuesapi "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/apis/values" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/log" sanitizederror "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/sanitizedError" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/store" "github.com/kyverno/kyverno/pkg/autogen" + "github.com/kyverno/kyverno/pkg/clients/dclient" "github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/engine" "github.com/kyverno/kyverno/pkg/engine/adapters" @@ -19,24 +24,43 @@ import ( "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" ) -// ApplyPolicyOnResource - function to apply policy on resource -func ApplyPolicyOnResource(c ApplyPolicyConfig) ([]engineapi.EngineResponse, error) { +type PolicyProcessor struct { + Policy kyvernov1.PolicyInterface + Resource *unstructured.Unstructured + MutateLogPath string + MutateLogPathIsDir bool + Variables map[string]interface{} + UserInfo *kyvernov1beta1.RequestInfo + PolicyReport bool + NamespaceSelectorMap map[string]map[string]string + Stdin bool + Rc *ResultCounts + PrintPatchResource bool + RuleToCloneSourceResource map[string]string + Client dclient.Interface + AuditWarn bool + 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 c.Variables["request.operation"] == "DELETE" { + if p.Variables["request.operation"] == "DELETE" { operation = kyvernov1.Delete } policyWithNamespaceSelector := false OuterLoop: - for _, p := range autogen.ComputeRules(c.Policy) { + for _, p := range autogen.ComputeRules(p.Policy) { if p.MatchResources.ResourceDescription.NamespaceSelector != nil || p.ExcludeResources.ResourceDescription.NamespaceSelector != nil { policyWithNamespaceSelector = true @@ -69,17 +93,17 @@ OuterLoop: } if policyWithNamespaceSelector { - resourceNamespace := c.Resource.GetNamespace() - namespaceLabels = c.NamespaceSelectorMap[c.Resource.GetNamespace()] + 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", c.Resource.GetName()), nil) + 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", c.Resource.GetNamespace(), c.Resource.GetKind(), c.Resource.GetName()) - log.Log.V(3).Info("applying policy on resource", "policy", c.Policy.GetName(), "resource", resPath) + 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 := c.Resource.MarshalJSON() + resourceRaw, err := p.Resource.MarshalJSON() if err != nil { log.Log.Error(err, "failed to marshal resource") } @@ -96,8 +120,8 @@ OuterLoop: cfg := config.NewDefaultConfiguration(false) gvk, subresource := updatedResource.GroupVersionKind(), "" // If --cluster flag is not set, then we need to find the top level resource GVK and subresource - if c.Client == nil { - for _, s := range c.Subresources { + if p.Client == nil { + for _, s := range p.Subresources { subgvk := schema.GroupVersionKind{ Group: s.APIResource.Group, Version: s.APIResource.Version, @@ -115,8 +139,8 @@ OuterLoop: } } var client engineapi.Client - if c.Client != nil { - client = adapters.Client(c.Client) + if p.Client != nil { + client = adapters.Client(p.Client) } rclient := registryclient.NewOrDie() eng := engine.NewEngine( @@ -134,7 +158,7 @@ OuterLoop: jp, *updatedResource, operation, - c.UserInfo, + p.UserInfo, cfg, ) if err != nil { @@ -142,11 +166,11 @@ OuterLoop: } policyContext = policyContext. - WithPolicy(c.Policy). + WithPolicy(p.Policy). WithNamespaceLabels(namespaceLabels). WithResourceKind(gvk, subresource) - for key, value := range c.Variables { + for key, value := range p.Variables { err = policyContext.JSONContext().AddVariable(key, value) if err != nil { log.Log.Error(err, "failed to add variable to context") @@ -157,7 +181,7 @@ OuterLoop: combineRuleResponses(mutateResponse) engineResponses = append(engineResponses, mutateResponse) - err = processMutateEngineResponse(c, &mutateResponse, resPath) + err = p.processMutateEngineResponse(mutateResponse, resPath) if err != nil { if !sanitizederror.IsErrorSanitized(err) { return engineResponses, sanitizederror.NewWithError("failed to print mutated result", err) @@ -171,7 +195,7 @@ OuterLoop: } var policyHasValidate bool - for _, rule := range autogen.ComputeRules(c.Policy) { + for _, rule := range autogen.ComputeRules(p.Policy) { if rule.HasValidate() || rule.HasVerifyImageChecks() { policyHasValidate = true } @@ -190,7 +214,7 @@ OuterLoop: } var policyHasGenerate bool - for _, rule := range autogen.ComputeRules(c.Policy) { + for _, rule := range autogen.ComputeRules(p.Policy) { if rule.HasGenerate() { policyHasGenerate = true } @@ -199,7 +223,7 @@ OuterLoop: if policyHasGenerate { generateResponse := eng.ApplyBackgroundChecks(context.TODO(), policyContext) if !generateResponse.IsEmpty() { - newRuleResponse, err := handleGeneratePolicy(&generateResponse, *policyContext, c.RuleToCloneSourceResource) + newRuleResponse, err := handleGeneratePolicy(&generateResponse, *policyContext, p.RuleToCloneSourceResource) if err != nil { log.Log.Error(err, "failed to apply generate policy") } else { @@ -208,82 +232,67 @@ OuterLoop: combineRuleResponses(generateResponse) engineResponses = append(engineResponses, generateResponse) } - updateResultCounts(c.Policy, &generateResponse, resPath, c.Rc, c.AuditWarn) + p.Rc.addGenerateResponse(p.AuditWarn, resPath, generateResponse) } - processEngineResponses(engineResponses, c) + p.Rc.addEngineResponses(p.AuditWarn, engineResponses...) return engineResponses, nil } -func combineRuleResponses(imageResponse engineapi.EngineResponse) engineapi.EngineResponse { - if imageResponse.PolicyResponse.RulesAppliedCount() == 0 { - return imageResponse +func (p *PolicyProcessor) processMutateEngineResponse(response engineapi.EngineResponse, resourcePath string) error { + printMutatedRes := p.Rc.addMutateResponse(resourcePath, response) + if printMutatedRes && p.PrintPatchResource { + yamlEncodedResource, err := yamlv2.Marshal(response.PatchedResource.Object) + if err != nil { + return sanitizederror.NewWithError("failed to marshal", err) + } + + if p.MutateLogPath == "" { + 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("\n" + mutatedResource + "\n") + } + } else { + err := p.printMutatedOutput(string(yamlEncodedResource)) + if err != nil { + return sanitizederror.NewWithError("failed to print mutated result", err) + } + fmt.Printf("\n\nMutation:\nMutation has been applied successfully. Check the files.") + } } - - completeRuleResponses := imageResponse.PolicyResponse.Rules - var combineRuleResponses []engineapi.RuleResponse - - ruleNameType := make(map[string][]engineapi.RuleResponse) - for _, rsp := range completeRuleResponses { - key := rsp.Name() + ";" + string(rsp.RuleType()) - ruleNameType[key] = append(ruleNameType[key], rsp) - } - - for key, ruleResponses := range ruleNameType { - tokens := strings.Split(key, ";") - ruleName := tokens[0] - ruleType := tokens[1] - var failRuleResponses []engineapi.RuleResponse - var errorRuleResponses []engineapi.RuleResponse - var passRuleResponses []engineapi.RuleResponse - var skipRuleResponses []engineapi.RuleResponse - - ruleMesssage := "" - for _, rsp := range ruleResponses { - if rsp.Status() == engineapi.RuleStatusFail { - failRuleResponses = append(failRuleResponses, rsp) - } else if rsp.Status() == engineapi.RuleStatusError { - errorRuleResponses = append(errorRuleResponses, rsp) - } else if rsp.Status() == engineapi.RuleStatusPass { - passRuleResponses = append(passRuleResponses, rsp) - } else if rsp.Status() == engineapi.RuleStatusSkip { - skipRuleResponses = append(skipRuleResponses, rsp) - } - } - if len(errorRuleResponses) > 0 { - for _, errRsp := range errorRuleResponses { - ruleMesssage += errRsp.Message() + ";" - } - errorResponse := engineapi.NewRuleResponse(ruleName, engineapi.RuleType(ruleType), ruleMesssage, engineapi.RuleStatusError) - combineRuleResponses = append(combineRuleResponses, *errorResponse) - continue - } - - if len(failRuleResponses) > 0 { - for _, failRsp := range failRuleResponses { - ruleMesssage += failRsp.Message() + ";" - } - failResponse := engineapi.NewRuleResponse(ruleName, engineapi.RuleType(ruleType), ruleMesssage, engineapi.RuleStatusFail) - combineRuleResponses = append(combineRuleResponses, *failResponse) - continue - } - - if len(passRuleResponses) > 0 { - for _, passRsp := range passRuleResponses { - ruleMesssage += passRsp.Message() + ";" - } - passResponse := engineapi.NewRuleResponse(ruleName, engineapi.RuleType(ruleType), ruleMesssage, engineapi.RuleStatusPass) - combineRuleResponses = append(combineRuleResponses, *passResponse) - continue - } - - for _, skipRsp := range skipRuleResponses { - ruleMesssage += skipRsp.Message() + ";" - } - skipResponse := engineapi.NewRuleResponse(ruleName, engineapi.RuleType(ruleType), ruleMesssage, engineapi.RuleStatusSkip) - combineRuleResponses = append(combineRuleResponses, *skipResponse) - } - imageResponse.PolicyResponse.Rules = combineRuleResponses - return imageResponse + return nil +} + +func (p *PolicyProcessor) printMutatedOutput(yaml string) error { + var file *os.File + mutateLogPath := filepath.Clean(p.MutateLogPath) + filename := p.Resource.GetName() + "-mutated" + if !p.MutateLogPathIsDir { + // truncation for the case when mutateLogPath is a file (not a directory) is handled under pkg/kyverno/apply/test_command.go + f, err := os.OpenFile(mutateLogPath, os.O_APPEND|os.O_WRONLY, 0o600) // #nosec G304 + if err != nil { + return err + } + file = f + } else { + f, err := os.OpenFile(filepath.Join(mutateLogPath, filename+".yaml"), os.O_CREATE|os.O_WRONLY, 0o600) // #nosec G304 + if err != nil { + return err + } + file = f + } + if _, err := file.Write([]byte(yaml + "\n---\n\n")); err != nil { + if err := file.Close(); err != nil { + log.Log.Error(err, "failed to close file") + } + return err + } + if err := file.Close(); err != nil { + return err + } + return nil } diff --git a/cmd/cli/kubectl-kyverno/processor/result.go b/cmd/cli/kubectl-kyverno/processor/result.go new file mode 100644 index 0000000000..8cc16c6234 --- /dev/null +++ b/cmd/cli/kubectl-kyverno/processor/result.go @@ -0,0 +1,171 @@ +package processor + +import ( + "fmt" + + kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" + "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/policy/annotations" + "github.com/kyverno/kyverno/pkg/autogen" + engineapi "github.com/kyverno/kyverno/pkg/engine/api" + "k8s.io/api/admissionregistration/v1alpha1" +) + +type ResultCounts struct { + pass int + fail int + warn int + err int + skip int +} + +func (rc ResultCounts) Pass() int { return rc.pass } +func (rc ResultCounts) Fail() int { return rc.fail } +func (rc ResultCounts) Warn() int { return rc.warn } +func (rc ResultCounts) Error() int { return rc.err } +func (rc ResultCounts) Skip() int { return rc.skip } + +func (rc *ResultCounts) addEngineResponses(auditWarn bool, responses ...engineapi.EngineResponse) { + for _, response := range responses { + rc.addEngineResponse(auditWarn, response) + } +} + +func (rc *ResultCounts) addEngineResponse(auditWarn bool, response engineapi.EngineResponse) { + if !response.IsEmpty() { + genericPolicy := response.Policy() + if polType := genericPolicy.GetType(); polType == engineapi.ValidatingAdmissionPolicyType { + return + } + policy := genericPolicy.GetPolicy().(kyvernov1.PolicyInterface) + scored := annotations.Scored(policy.GetAnnotations()) + for _, rule := range autogen.ComputeRules(policy) { + if rule.HasValidate() || rule.HasVerifyImageChecks() || rule.HasVerifyImages() { + ruleFoundInEngineResponse := false + for _, valResponseRule := range response.PolicyResponse.Rules { + if rule.Name == valResponseRule.Name() { + ruleFoundInEngineResponse = true + switch valResponseRule.Status() { + case engineapi.RuleStatusPass: + rc.pass++ + case engineapi.RuleStatusFail: + if !scored { + rc.warn++ + break + } else if auditWarn && response.GetValidationFailureAction().Audit() { + rc.warn++ + } else { + rc.fail++ + } + case engineapi.RuleStatusError: + rc.err++ + case engineapi.RuleStatusWarn: + rc.warn++ + case engineapi.RuleStatusSkip: + rc.skip++ + } + continue + } + } + if !ruleFoundInEngineResponse { + rc.skip++ + } + } + } + } +} + +func (rc *ResultCounts) addGenerateResponse(auditWarn bool, resPath string, response engineapi.EngineResponse) { + genericPolicy := response.Policy() + if polType := genericPolicy.GetType(); polType == engineapi.ValidatingAdmissionPolicyType { + return + } + policy := genericPolicy.GetPolicy().(kyvernov1.PolicyInterface) + printCount := 0 + for _, policyRule := range autogen.ComputeRules(policy) { + ruleFoundInEngineResponse := false + for i, ruleResponse := range response.PolicyResponse.Rules { + if policyRule.Name == ruleResponse.Name() { + ruleFoundInEngineResponse = true + if ruleResponse.Status() == engineapi.RuleStatusPass { + rc.pass++ + } else { + if printCount < 1 { + fmt.Println("\ninvalid resource", "policy", policy.GetName(), "resource", resPath) + printCount++ + } + fmt.Printf("%d. %s - %s\n", i+1, ruleResponse.Name(), ruleResponse.Message()) + if auditWarn && response.GetValidationFailureAction().Audit() { + rc.warn++ + } else { + rc.fail++ + } + } + continue + } + } + if !ruleFoundInEngineResponse { + rc.skip++ + } + } +} + +func (rc *ResultCounts) addMutateResponse(resourcePath string, response engineapi.EngineResponse) bool { + genericPolicy := response.Policy() + if polType := genericPolicy.GetType(); polType == engineapi.ValidatingAdmissionPolicyType { + return false + } + policy := genericPolicy.GetPolicy().(kyvernov1.PolicyInterface) + var policyHasMutate bool + for _, rule := range autogen.ComputeRules(policy) { + if rule.HasMutate() { + policyHasMutate = true + } + } + if !policyHasMutate { + return false + } + printCount := 0 + printMutatedRes := false + for _, policyRule := range autogen.ComputeRules(policy) { + ruleFoundInEngineResponse := false + for i, mutateResponseRule := range response.PolicyResponse.Rules { + if policyRule.Name == mutateResponseRule.Name() { + ruleFoundInEngineResponse = true + if mutateResponseRule.Status() == engineapi.RuleStatusPass { + rc.pass++ + printMutatedRes = true + } else if mutateResponseRule.Status() == engineapi.RuleStatusSkip { + fmt.Printf("\nskipped mutate policy %s -> resource %s", policy.GetName(), resourcePath) + rc.skip++ + } else if mutateResponseRule.Status() == engineapi.RuleStatusError { + fmt.Printf("\nerror while applying mutate policy %s -> resource %s\nerror: %s", policy.GetName(), resourcePath, mutateResponseRule.Message()) + rc.err++ + } else { + if printCount < 1 { + fmt.Printf("\nfailed to apply mutate policy %s -> resource %s", policy.GetName(), resourcePath) + printCount++ + } + fmt.Printf("%d. %s - %s \n", i+1, mutateResponseRule.Name(), mutateResponseRule.Message()) + rc.fail++ + } + continue + } + } + if !ruleFoundInEngineResponse { + rc.skip++ + } + } + return printMutatedRes +} + +func (rc *ResultCounts) addValidatingAdmissionResponse(vap v1alpha1.ValidatingAdmissionPolicy, engineResponse engineapi.EngineResponse) { + for _, ruleResp := range engineResponse.PolicyResponse.Rules { + if ruleResp.Status() == engineapi.RuleStatusPass { + rc.pass++ + } else if ruleResp.Status() == engineapi.RuleStatusFail { + rc.fail++ + } else if ruleResp.Status() == engineapi.RuleStatusError { + rc.err++ + } + } +} diff --git a/cmd/cli/kubectl-kyverno/processor/utils.go b/cmd/cli/kubectl-kyverno/processor/utils.go new file mode 100644 index 0000000000..f834b489a5 --- /dev/null +++ b/cmd/cli/kubectl-kyverno/processor/utils.go @@ -0,0 +1,79 @@ +package processor + +import ( + "strings" + + engineapi "github.com/kyverno/kyverno/pkg/engine/api" +) + +func combineRuleResponses(imageResponse engineapi.EngineResponse) engineapi.EngineResponse { + if imageResponse.PolicyResponse.RulesAppliedCount() == 0 { + return imageResponse + } + + completeRuleResponses := imageResponse.PolicyResponse.Rules + var combineRuleResponses []engineapi.RuleResponse + + ruleNameType := make(map[string][]engineapi.RuleResponse) + for _, rsp := range completeRuleResponses { + key := rsp.Name() + ";" + string(rsp.RuleType()) + ruleNameType[key] = append(ruleNameType[key], rsp) + } + + for key, ruleResponses := range ruleNameType { + tokens := strings.Split(key, ";") + ruleName := tokens[0] + ruleType := tokens[1] + var failRuleResponses []engineapi.RuleResponse + var errorRuleResponses []engineapi.RuleResponse + var passRuleResponses []engineapi.RuleResponse + var skipRuleResponses []engineapi.RuleResponse + + ruleMesssage := "" + for _, rsp := range ruleResponses { + if rsp.Status() == engineapi.RuleStatusFail { + failRuleResponses = append(failRuleResponses, rsp) + } else if rsp.Status() == engineapi.RuleStatusError { + errorRuleResponses = append(errorRuleResponses, rsp) + } else if rsp.Status() == engineapi.RuleStatusPass { + passRuleResponses = append(passRuleResponses, rsp) + } else if rsp.Status() == engineapi.RuleStatusSkip { + skipRuleResponses = append(skipRuleResponses, rsp) + } + } + if len(errorRuleResponses) > 0 { + for _, errRsp := range errorRuleResponses { + ruleMesssage += errRsp.Message() + ";" + } + errorResponse := engineapi.NewRuleResponse(ruleName, engineapi.RuleType(ruleType), ruleMesssage, engineapi.RuleStatusError) + combineRuleResponses = append(combineRuleResponses, *errorResponse) + continue + } + + if len(failRuleResponses) > 0 { + for _, failRsp := range failRuleResponses { + ruleMesssage += failRsp.Message() + ";" + } + failResponse := engineapi.NewRuleResponse(ruleName, engineapi.RuleType(ruleType), ruleMesssage, engineapi.RuleStatusFail) + combineRuleResponses = append(combineRuleResponses, *failResponse) + continue + } + + if len(passRuleResponses) > 0 { + for _, passRsp := range passRuleResponses { + ruleMesssage += passRsp.Message() + ";" + } + passResponse := engineapi.NewRuleResponse(ruleName, engineapi.RuleType(ruleType), ruleMesssage, engineapi.RuleStatusPass) + combineRuleResponses = append(combineRuleResponses, *passResponse) + continue + } + + for _, skipRsp := range skipRuleResponses { + ruleMesssage += skipRsp.Message() + ";" + } + skipResponse := engineapi.NewRuleResponse(ruleName, engineapi.RuleType(ruleType), ruleMesssage, engineapi.RuleStatusSkip) + combineRuleResponses = append(combineRuleResponses, *skipResponse) + } + imageResponse.PolicyResponse.Rules = combineRuleResponses + return imageResponse +} diff --git a/cmd/cli/kubectl-kyverno/processor/vap_processor.go b/cmd/cli/kubectl-kyverno/processor/vap_processor.go new file mode 100644 index 0000000000..128cc39802 --- /dev/null +++ b/cmd/cli/kubectl-kyverno/processor/vap_processor.go @@ -0,0 +1,21 @@ +package processor + +import ( + engineapi "github.com/kyverno/kyverno/pkg/engine/api" + "github.com/kyverno/kyverno/pkg/validatingadmissionpolicy" + "k8s.io/api/admissionregistration/v1alpha1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +type ValidatingAdmissionPolicyProcessor struct { + ValidatingAdmissionPolicy v1alpha1.ValidatingAdmissionPolicy + Resource *unstructured.Unstructured + PolicyReport bool + Rc *ResultCounts +} + +func (p *ValidatingAdmissionPolicyProcessor) ApplyPolicyOnResource() ([]engineapi.EngineResponse, error) { + engineResp := validatingadmissionpolicy.Validate(p.ValidatingAdmissionPolicy, *p.Resource) + p.Rc.addValidatingAdmissionResponse(p.ValidatingAdmissionPolicy, engineResp) + return []engineapi.EngineResponse{engineResp}, nil +} diff --git a/cmd/cli/kubectl-kyverno/utils/common/common.go b/cmd/cli/kubectl-kyverno/utils/common/common.go index d2b0cde186..909ddef5d0 100644 --- a/cmd/cli/kubectl-kyverno/utils/common/common.go +++ b/cmd/cli/kubectl-kyverno/utils/common/common.go @@ -9,59 +9,19 @@ import ( "github.com/go-git/go-billy/v5" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" - kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1" 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/policy/annotations" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/resource" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/source" sanitizederror "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/sanitizedError" - "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/store" "github.com/kyverno/kyverno/pkg/autogen" - "github.com/kyverno/kyverno/pkg/background/generate" "github.com/kyverno/kyverno/pkg/clients/dclient" - "github.com/kyverno/kyverno/pkg/config" - "github.com/kyverno/kyverno/pkg/engine" - "github.com/kyverno/kyverno/pkg/engine/adapters" - engineapi "github.com/kyverno/kyverno/pkg/engine/api" - "github.com/kyverno/kyverno/pkg/engine/jmespath" - "github.com/kyverno/kyverno/pkg/imageverifycache" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" - yamlv2 "gopkg.in/yaml.v2" "k8s.io/api/admissionregistration/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) -type ResultCounts struct { - Pass int - Fail int - Warn int - Error int - Skip int -} - -type ApplyPolicyConfig struct { - Policy kyvernov1.PolicyInterface - ValidatingAdmissionPolicy v1alpha1.ValidatingAdmissionPolicy - Resource *unstructured.Unstructured - MutateLogPath string - MutateLogPathIsDir bool - Variables map[string]interface{} - UserInfo *kyvernov1beta1.RequestInfo - PolicyReport bool - NamespaceSelectorMap map[string]map[string]string - Stdin bool - Rc *ResultCounts - PrintPatchResource bool - RuleToCloneSourceResource map[string]string - Client dclient.Interface - AuditWarn bool - Subresources []valuesapi.Subresource -} - // RemoveDuplicateAndObjectVariables - remove duplicate variables func RemoveDuplicateAndObjectVariables(matches [][]string) string { var variableStr string @@ -78,37 +38,6 @@ func RemoveDuplicateAndObjectVariables(matches [][]string) string { return variableStr } -// PrintMutatedOutput - function to print output in provided file or directory -func PrintMutatedOutput(mutateLogPath string, mutateLogPathIsDir bool, yaml string, fileName string) error { - var f *os.File - var err error - yaml = yaml + ("\n---\n\n") - - mutateLogPath = filepath.Clean(mutateLogPath) - if !mutateLogPathIsDir { - // truncation for the case when mutateLogPath is a file (not a directory) is handled under pkg/kyverno/apply/test_command.go - f, err = os.OpenFile(mutateLogPath, os.O_APPEND|os.O_WRONLY, 0o600) // #nosec G304 - } else { - f, err = os.OpenFile(mutateLogPath+"/"+fileName+".yaml", os.O_CREATE|os.O_WRONLY, 0o600) // #nosec G304 - } - - if err != nil { - return err - } - if _, err := f.Write([]byte(yaml)); err != nil { - closeErr := f.Close() - if closeErr != nil { - log.Log.Error(closeErr, "failed to close file") - } - return err - } - if err := f.Close(); err != nil { - return err - } - - return nil -} - // GetResourceAccordingToResourcePath - get resources according to the resource path func GetResourceAccordingToResourcePath(fs billy.Filesystem, resourcePaths []string, cluster bool, policies []kyvernov1.PolicyInterface, validatingAdmissionPolicies []v1alpha1.ValidatingAdmissionPolicy, dClient dclient.Interface, namespace string, policyReport bool, isGit bool, policyResourcePath string, @@ -164,108 +93,6 @@ func GetResourceAccordingToResourcePath(fs billy.Filesystem, resourcePaths []str return resources, err } -func updateResultCounts(policy kyvernov1.PolicyInterface, engineResponse *engineapi.EngineResponse, resPath string, rc *ResultCounts, auditWarn bool) { - printCount := 0 - for _, policyRule := range autogen.ComputeRules(policy) { - ruleFoundInEngineResponse := false - for i, ruleResponse := range engineResponse.PolicyResponse.Rules { - if policyRule.Name == ruleResponse.Name() { - ruleFoundInEngineResponse = true - - if ruleResponse.Status() == engineapi.RuleStatusPass { - rc.Pass++ - } else { - if printCount < 1 { - fmt.Println("\ninvalid resource", "policy", policy.GetName(), "resource", resPath) - printCount++ - } - fmt.Printf("%d. %s - %s\n", i+1, ruleResponse.Name(), ruleResponse.Message()) - - if auditWarn && engineResponse.GetValidationFailureAction().Audit() { - rc.Warn++ - } else { - rc.Fail++ - } - } - continue - } - } - - if !ruleFoundInEngineResponse { - rc.Skip++ - } - } -} - -func processMutateEngineResponse(c ApplyPolicyConfig, mutateResponse *engineapi.EngineResponse, resPath string) error { - var policyHasMutate bool - for _, rule := range autogen.ComputeRules(c.Policy) { - if rule.HasMutate() { - policyHasMutate = true - } - } - if !policyHasMutate { - return nil - } - - printCount := 0 - printMutatedRes := false - for _, policyRule := range autogen.ComputeRules(c.Policy) { - ruleFoundInEngineResponse := false - for i, mutateResponseRule := range mutateResponse.PolicyResponse.Rules { - if policyRule.Name == mutateResponseRule.Name() { - ruleFoundInEngineResponse = true - if mutateResponseRule.Status() == engineapi.RuleStatusPass { - c.Rc.Pass++ - printMutatedRes = true - } else if mutateResponseRule.Status() == engineapi.RuleStatusSkip { - fmt.Printf("\nskipped mutate policy %s -> resource %s", c.Policy.GetName(), resPath) - c.Rc.Skip++ - } else if mutateResponseRule.Status() == engineapi.RuleStatusError { - fmt.Printf("\nerror while applying mutate policy %s -> resource %s\nerror: %s", c.Policy.GetName(), resPath, mutateResponseRule.Message()) - c.Rc.Error++ - } else { - if printCount < 1 { - fmt.Printf("\nfailed to apply mutate policy %s -> resource %s", c.Policy.GetName(), resPath) - printCount++ - } - fmt.Printf("%d. %s - %s \n", i+1, mutateResponseRule.Name(), mutateResponseRule.Message()) - c.Rc.Fail++ - } - continue - } - } - if !ruleFoundInEngineResponse { - c.Rc.Skip++ - } - } - - if printMutatedRes && c.PrintPatchResource { - yamlEncodedResource, err := yamlv2.Marshal(mutateResponse.PatchedResource.Object) - if err != nil { - return sanitizederror.NewWithError("failed to marshal", err) - } - - if c.MutateLogPath == "" { - mutatedResource := string(yamlEncodedResource) + string("\n---") - if len(strings.TrimSpace(mutatedResource)) > 0 { - if !c.Stdin { - fmt.Printf("\nmutate policy %s applied to %s:", c.Policy.GetName(), resPath) - } - fmt.Printf("\n" + mutatedResource + "\n") - } - } else { - err := PrintMutatedOutput(c.MutateLogPath, c.MutateLogPathIsDir, string(yamlEncodedResource), c.Resource.GetName()+"-mutated") - if err != nil { - return sanitizederror.NewWithError("failed to print mutated result", err) - } - fmt.Printf("\n\nMutation:\nMutation has been applied successfully. Check the files.") - } - } - - return nil -} - func GetKindsFromPolicy(policy kyvernov1.PolicyInterface, subresources []valuesapi.Subresource, dClient dclient.Interface) map[string]struct{} { kindOnwhichPolicyIsApplied := make(map[string]struct{}) for _, rule := range autogen.ComputeRules(policy) { @@ -328,91 +155,6 @@ func getSubresourceKind(groupVersion, parentKind, subresourceName string, subres return "", sanitizederror.NewWithError(fmt.Sprintf("subresource %s not found for parent resource %s", subresourceName, parentKind), nil) } -// initializeMockController initializes a basic Generate Controller with a fake dynamic client. -func initializeMockController(objects []runtime.Object) (*generate.GenerateController, error) { - client, err := dclient.NewFakeClient(runtime.NewScheme(), nil, objects...) - if err != nil { - fmt.Printf("Failed to mock dynamic client") - return nil, err - } - client.SetDiscovery(dclient.NewFakeDiscoveryClient(nil)) - cfg := config.NewDefaultConfiguration(false) - c := generate.NewGenerateControllerWithOnlyClient(client, engine.NewEngine( - cfg, - config.NewDefaultMetricsConfiguration(), - jmespath.New(cfg), - adapters.Client(client), - nil, - imageverifycache.DisabledImageVerifyCache(), - store.ContextLoaderFactory(nil), - nil, - "", - )) - return c, nil -} - -// handleGeneratePolicy returns a new RuleResponse with the Kyverno generated resource configuration by applying the generate rule. -func handleGeneratePolicy(generateResponse *engineapi.EngineResponse, policyContext engine.PolicyContext, ruleToCloneSourceResource map[string]string) ([]engineapi.RuleResponse, error) { - newResource := policyContext.NewResource() - objects := []runtime.Object{&newResource} - resources := []*unstructured.Unstructured{} - for _, rule := range generateResponse.PolicyResponse.Rules { - if path, ok := ruleToCloneSourceResource[rule.Name()]; ok { - resourceBytes, err := resource.GetFileBytes(path) - if err != nil { - fmt.Printf("failed to get resource bytes\n") - } else { - resources, err = resource.GetUnstructuredResources(resourceBytes) - if err != nil { - fmt.Printf("failed to convert resource bytes to unstructured format\n") - } - } - } - } - - for _, res := range resources { - objects = append(objects, res) - } - - c, err := initializeMockController(objects) - if err != nil { - fmt.Println("error at controller") - return nil, err - } - - gr := kyvernov1beta1.UpdateRequest{ - Spec: kyvernov1beta1.UpdateRequestSpec{ - Type: kyvernov1beta1.Generate, - Policy: generateResponse.Policy().GetName(), - Resource: kyvernov1.ResourceSpec{ - Kind: generateResponse.Resource.GetKind(), - Namespace: generateResponse.Resource.GetNamespace(), - Name: generateResponse.Resource.GetName(), - APIVersion: generateResponse.Resource.GetAPIVersion(), - }, - }, - } - - var newRuleResponse []engineapi.RuleResponse - - for _, rule := range generateResponse.PolicyResponse.Rules { - genResource, err := c.ApplyGeneratePolicy(log.Log.V(2), &policyContext, gr, []string{rule.Name()}) - if err != nil { - return nil, err - } - - if genResource != nil { - unstrGenResource, err := c.GetUnstrResource(genResource[0]) - if err != nil { - return nil, err - } - newRuleResponse = append(newRuleResponse, *rule.WithGeneratedResource(*unstrGenResource)) - } - } - - return newRuleResponse, nil -} - func GetGitBranchOrPolicyPaths(gitBranch, repoURL string, policyPaths ...string) (string, string) { var gitPathToYamls string if gitBranch == "" { @@ -436,48 +178,3 @@ func GetGitBranchOrPolicyPaths(gitBranch, repoURL string, policyPaths ...string) } return gitBranch, gitPathToYamls } - -func processEngineResponses(responses []engineapi.EngineResponse, c ApplyPolicyConfig) { - for _, response := range responses { - if !response.IsEmpty() { - pol := response.Policy() - if polType := pol.GetType(); polType == engineapi.ValidatingAdmissionPolicyType { - return - } - scored := annotations.Scored(c.Policy.GetAnnotations()) - for _, rule := range autogen.ComputeRules(pol.GetPolicy().(kyvernov1.PolicyInterface)) { - if rule.HasValidate() || rule.HasVerifyImageChecks() || rule.HasVerifyImages() { - ruleFoundInEngineResponse := false - for _, valResponseRule := range response.PolicyResponse.Rules { - if rule.Name == valResponseRule.Name() { - ruleFoundInEngineResponse = true - switch valResponseRule.Status() { - case engineapi.RuleStatusPass: - c.Rc.Pass++ - case engineapi.RuleStatusFail: - if !scored { - c.Rc.Warn++ - break - } else if c.AuditWarn && response.GetValidationFailureAction().Audit() { - c.Rc.Warn++ - } else { - c.Rc.Fail++ - } - case engineapi.RuleStatusError: - c.Rc.Error++ - case engineapi.RuleStatusWarn: - c.Rc.Warn++ - case engineapi.RuleStatusSkip: - c.Rc.Skip++ - } - continue - } - } - if !ruleFoundInEngineResponse { - c.Rc.Skip++ - } - } - } - } - } -} diff --git a/cmd/cli/kubectl-kyverno/utils/common/common_test.go b/cmd/cli/kubectl-kyverno/utils/common/common_test.go index fc51a58dd7..fa7d862fa6 100644 --- a/cmd/cli/kubectl-kyverno/utils/common/common_test.go +++ b/cmd/cli/kubectl-kyverno/utils/common/common_test.go @@ -4,6 +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" @@ -55,6 +56,13 @@ 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 @@ -72,11 +80,11 @@ func Test_NamespaceSelector(t *testing.T) { }, }, result: ResultCounts{ - Pass: 0, - Fail: 1, - Warn: 0, - Error: 0, - Skip: 2, + pass: 0, + fail: 1, + warn: 0, + err: 0, + skip: 2, }, }, { @@ -88,19 +96,19 @@ func Test_NamespaceSelector(t *testing.T) { }, }, result: ResultCounts{ - Pass: 1, - Fail: 1, - Warn: 0, - Error: 0, - Skip: 4, + pass: 1, + fail: 1, + warn: 0, + err: 0, + skip: 4, }, }, } - rc := &ResultCounts{} + rc := &processor.ResultCounts{} for _, tc := range testcases { policyArray, _, _ := yamlutils.GetPolicy(tc.policy) resourceArray, _ := resource.GetUnstructuredResources(tc.resource) - applyPolicyConfig := ApplyPolicyConfig{ + processor := processor.PolicyProcessor{ Policy: policyArray[0], Resource: resourceArray[0], MutateLogPath: "", @@ -108,13 +116,13 @@ func Test_NamespaceSelector(t *testing.T) { NamespaceSelectorMap: tc.namespaceSelectorMap, Rc: rc, } - ApplyPolicyOnResource(applyPolicyConfig) - assert.Equal(t, int64(rc.Pass), int64(tc.result.Pass)) - assert.Equal(t, int64(rc.Fail), int64(tc.result.Fail)) + processor.ApplyPolicyOnResource() + assert.Equal(t, int64(rc.Pass()), int64(tc.result.pass)) + assert.Equal(t, int64(rc.Fail()), int64(tc.result.fail)) // TODO: autogen rules seem to not be present when autogen internals is disabled - 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.Error)) + 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/utils/common/policy_interface.go b/cmd/cli/kubectl-kyverno/utils/common/policy_interface.go deleted file mode 100644 index 9ac9852182..0000000000 --- a/cmd/cli/kubectl-kyverno/utils/common/policy_interface.go +++ /dev/null @@ -1,9 +0,0 @@ -package common - -import ( - engineapi "github.com/kyverno/kyverno/pkg/engine/api" -) - -type PolicyInterface interface { - ApplyPolicyOnResource(c ApplyPolicyConfig) ([]engineapi.EngineResponse, error) -} diff --git a/cmd/cli/kubectl-kyverno/utils/common/resource_interface.go b/cmd/cli/kubectl-kyverno/utils/common/resource_interface.go deleted file mode 100644 index 566abb7a9b..0000000000 --- a/cmd/cli/kubectl-kyverno/utils/common/resource_interface.go +++ /dev/null @@ -1,11 +0,0 @@ -package common - -import ( - "github.com/kyverno/kyverno/pkg/clients/dclient" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" -) - -// ResourceInterface abstracts the matched resources by either Kyverno Policies or Validating Admission Policies -type ResourceInterface interface { - FetchResourcesFromPolicy(resourcePaths []string, dClient dclient.Interface, namespace string, policyReport bool) ([]*unstructured.Unstructured, error) -} diff --git a/cmd/cli/kubectl-kyverno/utils/common/validating_admission_policies_types.go b/cmd/cli/kubectl-kyverno/utils/common/validating_admission_policies_types.go deleted file mode 100644 index cada29a4fc..0000000000 --- a/cmd/cli/kubectl-kyverno/utils/common/validating_admission_policies_types.go +++ /dev/null @@ -1,23 +0,0 @@ -package common - -import ( - engineapi "github.com/kyverno/kyverno/pkg/engine/api" - "github.com/kyverno/kyverno/pkg/validatingadmissionpolicy" -) - -type ValidatingAdmissionPolicies struct{} - -func (p *ValidatingAdmissionPolicies) ApplyPolicyOnResource(c ApplyPolicyConfig) ([]engineapi.EngineResponse, error) { - engineResp := validatingadmissionpolicy.Validate(c.ValidatingAdmissionPolicy, *c.Resource) - ruleResp := engineResp.PolicyResponse.Rules[0] - - if ruleResp.Status() == engineapi.RuleStatusPass { - c.Rc.Pass++ - } else if ruleResp.Status() == engineapi.RuleStatusFail { - c.Rc.Fail++ - } else if ruleResp.Status() == engineapi.RuleStatusError { - c.Rc.Error++ - } - - return []engineapi.EngineResponse{engineResp}, nil -} diff --git a/cmd/cli/kubectl-kyverno/utils/common/validatingadmission_resources_types.go b/cmd/cli/kubectl-kyverno/utils/common/validating_admission_resources.go similarity index 100% rename from cmd/cli/kubectl-kyverno/utils/common/validatingadmission_resources_types.go rename to cmd/cli/kubectl-kyverno/utils/common/validating_admission_resources.go