diff --git a/api/kyverno/v1/violated_rule_types.go b/api/kyverno/v1/violated_rule_types.go deleted file mode 100755 index 945cf419cb..0000000000 --- a/api/kyverno/v1/violated_rule_types.go +++ /dev/null @@ -1,17 +0,0 @@ -package v1 - -// ViolatedRule stores the information regarding the rule. -type ViolatedRule struct { - // Name specifies violated rule name. - Name string `json:"name" yaml:"name"` - - // Type specifies violated rule type. - Type string `json:"type" yaml:"type"` - - // Message specifies violation message. - // +optional - Message string `json:"message" yaml:"message"` - - // Status shows the rule response status - Status string `json:"status" yaml:"status"` -} diff --git a/api/kyverno/v1/zz_generated.deepcopy.go b/api/kyverno/v1/zz_generated.deepcopy.go index 96142f8650..4c5c990fc5 100755 --- a/api/kyverno/v1/zz_generated.deepcopy.go +++ b/api/kyverno/v1/zz_generated.deepcopy.go @@ -1423,18 +1423,3 @@ func (in *Variable) DeepCopy() *Variable { in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ViolatedRule) DeepCopyInto(out *ViolatedRule) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ViolatedRule. -func (in *ViolatedRule) DeepCopy() *ViolatedRule { - if in == nil { - return nil - } - out := new(ViolatedRule) - in.DeepCopyInto(out) - return out -} diff --git a/cmd/cli/kubectl-kyverno/apply/apply_command.go b/cmd/cli/kubectl-kyverno/apply/apply_command.go index b23fc74a55..3fddb5d931 100644 --- a/cmd/cli/kubectl-kyverno/apply/apply_command.go +++ b/cmd/cli/kubectl-kyverno/apply/apply_command.go @@ -16,8 +16,10 @@ import ( "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/utils/store" + "github.com/kyverno/kyverno/pkg/autogen" "github.com/kyverno/kyverno/pkg/clients/dclient" "github.com/kyverno/kyverno/pkg/config" + engineapi "github.com/kyverno/kyverno/pkg/engine/api" "github.com/kyverno/kyverno/pkg/openapi" policy2 "github.com/kyverno/kyverno/pkg/policy" gitutils "github.com/kyverno/kyverno/pkg/utils/git" @@ -175,7 +177,7 @@ func Command() *cobra.Command { return err } - PrintReportOrViolation(applyCommandConfig.PolicyReport, rc, applyCommandConfig.ResourcePaths, len(resources), skipInvalidPolicies, applyCommandConfig.Stdin, pvInfos, applyCommandConfig.warnExitCode, applyCommandConfig.warnNoPassed) + PrintReportOrViolation(applyCommandConfig.PolicyReport, rc, applyCommandConfig.ResourcePaths, len(resources), skipInvalidPolicies, applyCommandConfig.Stdin, pvInfos, applyCommandConfig.warnExitCode, applyCommandConfig.warnNoPassed, applyCommandConfig.AuditWarn) return nil }, } @@ -199,7 +201,7 @@ func Command() *cobra.Command { return cmd } -func (c *ApplyCommandConfig) applyCommandHelper() (rc *common.ResultCounts, resources []*unstructured.Unstructured, skipInvalidPolicies SkippedInvalidPolicies, pvInfos []common.Info, err error) { +func (c *ApplyCommandConfig) applyCommandHelper() (rc *common.ResultCounts, resources []*unstructured.Unstructured, skipInvalidPolicies SkippedInvalidPolicies, responses []engineapi.EngineResponse, err error) { store.SetMock(true) store.SetRegistryAccess(c.RegistryAccess) if c.Cluster { @@ -208,48 +210,48 @@ func (c *ApplyCommandConfig) applyCommandHelper() (rc *common.ResultCounts, reso fs := memfs.New() if c.ValuesFile != "" && c.VariablesString != "" { - return rc, resources, skipInvalidPolicies, pvInfos, sanitizederror.NewWithError("pass the values either using set flag or values_file flag", err) + return rc, resources, skipInvalidPolicies, responses, sanitizederror.NewWithError("pass the values either using set flag or values_file flag", err) } variables, globalValMap, valuesMap, namespaceSelectorMap, subresources, err := common.GetVariable(c.VariablesString, c.ValuesFile, fs, false, "") if err != nil { if !sanitizederror.IsErrorSanitized(err) { - return rc, resources, skipInvalidPolicies, pvInfos, sanitizederror.NewWithError("failed to decode yaml", err) + return rc, resources, skipInvalidPolicies, responses, sanitizederror.NewWithError("failed to decode yaml", err) } - return rc, resources, skipInvalidPolicies, pvInfos, err + return rc, resources, skipInvalidPolicies, responses, err } openApiManager, err := openapi.NewManager(log.Log) if err != nil { - return rc, resources, skipInvalidPolicies, pvInfos, sanitizederror.NewWithError("failed to initialize openAPIController", err) + return rc, resources, skipInvalidPolicies, responses, sanitizederror.NewWithError("failed to initialize openAPIController", err) } var dClient dclient.Interface if c.Cluster { restConfig, err := config.CreateClientConfigWithContext(c.KubeConfig, c.Context) if err != nil { - return rc, resources, skipInvalidPolicies, pvInfos, err + return rc, resources, skipInvalidPolicies, responses, err } kubeClient, err := kubernetes.NewForConfig(restConfig) if err != nil { - return rc, resources, skipInvalidPolicies, pvInfos, err + return rc, resources, skipInvalidPolicies, responses, err } dynamicClient, err := dynamic.NewForConfig(restConfig) if err != nil { - return rc, resources, skipInvalidPolicies, pvInfos, err + return rc, resources, skipInvalidPolicies, responses, err } dClient, err = dclient.NewClient(context.Background(), dynamicClient, kubeClient, 15*time.Minute) if err != nil { - return rc, resources, skipInvalidPolicies, pvInfos, err + return rc, resources, skipInvalidPolicies, responses, err } } if len(c.PolicyPaths) == 0 { - return rc, resources, skipInvalidPolicies, pvInfos, sanitizederror.NewWithError("require policy", err) + return rc, resources, skipInvalidPolicies, responses, sanitizederror.NewWithError("require policy", err) } if (len(c.PolicyPaths) > 0 && c.PolicyPaths[0] == "-") && len(c.ResourcePaths) > 0 && c.ResourcePaths[0] == "-" { - return rc, resources, skipInvalidPolicies, pvInfos, sanitizederror.NewWithError("a stdin pipe can be used for either policies or resources, not both", err) + return rc, resources, skipInvalidPolicies, responses, sanitizederror.NewWithError("a stdin pipe can be used for either policies or resources, not both", err) } var policies []kyvernov1.PolicyInterface @@ -282,7 +284,7 @@ func (c *ApplyCommandConfig) applyCommandHelper() (rc *common.ResultCounts, reso } policyYamls, err := gitutils.ListYamls(fs, gitPathToYamls) if err != nil { - return rc, resources, skipInvalidPolicies, pvInfos, sanitizederror.NewWithError("failed to list YAMLs in repository", err) + return rc, resources, skipInvalidPolicies, responses, sanitizederror.NewWithError("failed to list YAMLs in repository", err) } sort.Strings(policyYamls) c.PolicyPaths = policyYamls @@ -294,15 +296,15 @@ func (c *ApplyCommandConfig) applyCommandHelper() (rc *common.ResultCounts, reso } if len(c.ResourcePaths) == 0 && !c.Cluster { - return rc, resources, skipInvalidPolicies, pvInfos, sanitizederror.NewWithError("resource file(s) or cluster required", err) + return rc, resources, skipInvalidPolicies, responses, sanitizederror.NewWithError("resource file(s) or cluster required", err) } mutateLogPathIsDir, err := checkMutateLogPath(c.MutateLogPath) if err != nil { if !sanitizederror.IsErrorSanitized(err) { - return rc, resources, skipInvalidPolicies, pvInfos, sanitizederror.NewWithError("failed to create file/folder", err) + return rc, resources, skipInvalidPolicies, responses, sanitizederror.NewWithError("failed to create file/folder", err) } - return rc, resources, skipInvalidPolicies, pvInfos, err + return rc, resources, skipInvalidPolicies, responses, err } // empty the previous contents of the file just in case if the file already existed before with some content(so as to perform overwrites) @@ -313,15 +315,15 @@ func (c *ApplyCommandConfig) applyCommandHelper() (rc *common.ResultCounts, reso _, err := os.OpenFile(c.MutateLogPath, os.O_TRUNC|os.O_WRONLY, 0o600) // #nosec G304 if err != nil { if !sanitizederror.IsErrorSanitized(err) { - return rc, resources, skipInvalidPolicies, pvInfos, sanitizederror.NewWithError("failed to truncate the existing file at "+c.MutateLogPath, err) + return rc, resources, skipInvalidPolicies, responses, sanitizederror.NewWithError("failed to truncate the existing file at "+c.MutateLogPath, err) } - return rc, resources, skipInvalidPolicies, pvInfos, err + return rc, resources, skipInvalidPolicies, responses, err } } err = common.PrintMutatedPolicy(policies) if err != nil { - return rc, resources, skipInvalidPolicies, pvInfos, sanitizederror.NewWithError("failed to marshal mutated policy", err) + return rc, resources, skipInvalidPolicies, responses, sanitizederror.NewWithError("failed to marshal mutated policy", err) } resources, err = common.GetResourceAccordingToResourcePath(fs, c.ResourcePaths, c.Cluster, policies, dClient, c.Namespace, c.PolicyReport, false, "") @@ -331,7 +333,7 @@ func (c *ApplyCommandConfig) applyCommandHelper() (rc *common.ResultCounts, reso } if (len(resources) > 1 || len(policies) > 1) && c.VariablesString != "" { - return rc, resources, skipInvalidPolicies, pvInfos, sanitizederror.NewWithError("currently `set` flag supports variable for single policy applied on single resource ", nil) + return rc, resources, skipInvalidPolicies, responses, sanitizederror.NewWithError("currently `set` flag supports variable for single policy applied on single resource ", nil) } // get the user info as request info from a different file @@ -415,7 +417,7 @@ func (c *ApplyCommandConfig) applyCommandHelper() (rc *common.ResultCounts, reso for _, resource := range resources { thisPolicyResourceValues, err := common.CheckVariableForPolicy(valuesMap, globalValMap, policy.GetName(), resource.GetName(), resource.GetKind(), variables, kindOnwhichPolicyIsApplied, variable) if err != nil { - return rc, resources, skipInvalidPolicies, pvInfos, 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) + 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{ Policy: policy, @@ -433,15 +435,60 @@ func (c *ApplyCommandConfig) applyCommandHelper() (rc *common.ResultCounts, reso AuditWarn: c.AuditWarn, Subresources: subresources, } - _, info, err := common.ApplyPolicyOnResource(applyPolicyConfig) + ers, err := common.ApplyPolicyOnResource(applyPolicyConfig) if err != nil { - return rc, resources, skipInvalidPolicies, pvInfos, sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.GetName(), resource.GetName()).Error(), err) + return rc, resources, skipInvalidPolicies, responses, sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.GetName(), resource.GetName()).Error(), err) + } + for _, response := range ers { + if !response.IsEmpty() { + for _, rule := range autogen.ComputeRules(response.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: + ann := policy.GetAnnotations() + if scored, ok := ann[kyvernov1.AnnotationPolicyScored]; ok && scored == "false" { + rc.Warn++ + break + } else if applyPolicyConfig.AuditWarn && response.GetValidationFailureAction().Audit() { + rc.Warn++ + } else { + rc.Fail++ + } + case engineapi.RuleStatusError: + rc.Error++ + case engineapi.RuleStatusWarn: + rc.Warn++ + case engineapi.RuleStatusSkip: + rc.Skip++ + } + continue + } + } + if !ruleFoundInEngineResponse { + rc.Skip++ + response.PolicyResponse.Rules = append(response.PolicyResponse.Rules, + *engineapi.RuleSkip( + rule.Name, + engineapi.Validation, + rule.Validation.Message, + ), + ) + } + } + } + } + responses = append(responses, response) } - pvInfos = append(pvInfos, info) } } - return rc, resources, skipInvalidPolicies, pvInfos, nil + return rc, resources, skipInvalidPolicies, responses, nil } // checkMutateLogPath - checking path for printing mutated resource (-o flag) @@ -467,7 +514,7 @@ func checkMutateLogPath(mutateLogPath string) (mutateLogPathIsDir bool, err erro } // PrintReportOrViolation - printing policy report/violations -func PrintReportOrViolation(policyReport bool, rc *common.ResultCounts, resourcePaths []string, resourcesLen int, skipInvalidPolicies SkippedInvalidPolicies, stdin bool, pvInfos []common.Info, warnExitCode int, warnNoPassed bool) { +func PrintReportOrViolation(policyReport bool, rc *common.ResultCounts, resourcePaths []string, resourcesLen int, skipInvalidPolicies SkippedInvalidPolicies, stdin bool, engineResponses []engineapi.EngineResponse, warnExitCode int, warnNoPassed bool, auditWarn bool) { divider := "----------------------------------------------------------------------" if len(skipInvalidPolicies.skipped) > 0 { @@ -488,7 +535,7 @@ func PrintReportOrViolation(policyReport bool, rc *common.ResultCounts, resource } if policyReport { - resps := buildPolicyReports(pvInfos) + resps := buildPolicyReports(auditWarn, engineResponses...) if len(resps) > 0 || resourcesLen == 0 { fmt.Println(divider) fmt.Println("POLICY REPORT:") @@ -502,8 +549,7 @@ func PrintReportOrViolation(policyReport bool, rc *common.ResultCounts, resource } } else { if !stdin { - fmt.Printf("\npass: %d, fail: %d, warn: %d, error: %d, skip: %d \n", - rc.Pass, rc.Fail, rc.Warn, rc.Error, rc.Skip) + fmt.Printf("\npass: %d, fail: %d, warn: %d, error: %d, skip: %d \n", rc.Pass, rc.Fail, rc.Warn, rc.Error, rc.Skip) } } diff --git a/cmd/cli/kubectl-kyverno/apply/apply_command_test.go b/cmd/cli/kubectl-kyverno/apply/apply_command_test.go index 48e3e5e2af..76345ae78e 100644 --- a/cmd/cli/kubectl-kyverno/apply/apply_command_test.go +++ b/cmd/cli/kubectl-kyverno/apply/apply_command_test.go @@ -206,7 +206,7 @@ func Test_Apply(t *testing.T) { _, _, _, info, err := tc.config.applyCommandHelper() assert.NilError(t, err, desc) - resps := buildPolicyReports(info) + resps := buildPolicyReports(tc.config.AuditWarn, info...) assert.Assert(t, len(resps) > 0, "policy reports should not be empty: %s", desc) for i, resp := range resps { compareSummary(tc.expectedPolicyReports[i].Summary, resp.UnstructuredContent()["summary"].(map[string]interface{}), desc) diff --git a/cmd/cli/kubectl-kyverno/apply/report.go b/cmd/cli/kubectl-kyverno/apply/report.go index 1d31a1456f..cfd4b3bbc1 100644 --- a/cmd/cli/kubectl-kyverno/apply/report.go +++ b/cmd/cli/kubectl-kyverno/apply/report.go @@ -8,24 +8,22 @@ import ( kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" policyreportv1alpha2 "github.com/kyverno/kyverno/api/policyreport/v1alpha2" - "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/common" engineapi "github.com/kyverno/kyverno/pkg/engine/api" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/log" ) const clusterpolicyreport = "clusterpolicyreport" // resps is the engine responses generated for a single policy -func buildPolicyReports(pvInfos []common.Info) (res []*unstructured.Unstructured) { +func buildPolicyReports(auditWarn bool, engineResponses ...engineapi.EngineResponse) (res []*unstructured.Unstructured) { var raw []byte var err error - resultsMap := buildPolicyResults(pvInfos) + resultsMap := buildPolicyResults(auditWarn, engineResponses...) for scope, result := range resultsMap { if scope == clusterpolicyreport { report := &policyreportv1alpha2.ClusterPolicyReport{ @@ -74,46 +72,63 @@ func buildPolicyReports(pvInfos []common.Info) (res []*unstructured.Unstructured // buildPolicyResults returns a string-PolicyReportResult map // the key of the map is one of "clusterpolicyreport", "policyreport-ns-" -func buildPolicyResults(infos []common.Info) map[string][]policyreportv1alpha2.PolicyReportResult { +func buildPolicyResults(auditWarn bool, engineResponses ...engineapi.EngineResponse) map[string][]policyreportv1alpha2.PolicyReportResult { results := make(map[string][]policyreportv1alpha2.PolicyReportResult) now := metav1.Timestamp{Seconds: time.Now().Unix()} - for _, info := range infos { + for _, engineResponse := range engineResponses { + policy := engineResponse.Policy var appname string - ns := info.Namespace + ns := policy.GetNamespace() if ns != "" { appname = fmt.Sprintf("policyreport-ns-%s", ns) } else { appname = clusterpolicyreport } - for _, infoResult := range info.Results { - for _, rule := range infoResult.Rules { - if rule.Type != string(engineapi.Validation) { - continue - } - - result := policyreportv1alpha2.PolicyReportResult{ - Policy: info.PolicyName, - Resources: []corev1.ObjectReference{ - { - Kind: infoResult.Resource.Kind, - Namespace: infoResult.Resource.Namespace, - APIVersion: infoResult.Resource.APIVersion, - Name: infoResult.Resource.Name, - UID: types.UID(infoResult.Resource.UID), - }, - }, - Scored: true, - } - - result.Rule = rule.Name - result.Message = rule.Message - result.Result = policyreportv1alpha2.PolicyResult(rule.Status) - result.Source = kyvernov1.ValueKyvernoApp - result.Timestamp = now - results[appname] = append(results[appname], result) + for _, ruleResponse := range engineResponse.PolicyResponse.Rules { + if ruleResponse.RuleType() != engineapi.Validation { + continue } + + result := policyreportv1alpha2.PolicyReportResult{ + Policy: policy.GetName(), + Resources: []corev1.ObjectReference{ + { + Kind: engineResponse.Resource.GetKind(), + Namespace: engineResponse.Resource.GetNamespace(), + APIVersion: engineResponse.Resource.GetAPIVersion(), + Name: engineResponse.Resource.GetName(), + UID: engineResponse.Resource.GetUID(), + }, + }, + Scored: true, + } + + ann := engineResponse.Policy.GetAnnotations() + if ruleResponse.Status() == engineapi.RuleStatusSkip { + result.Result = policyreportv1alpha2.StatusSkip + } else if ruleResponse.Status() == engineapi.RuleStatusError { + result.Result = policyreportv1alpha2.StatusError + } else if ruleResponse.Status() == engineapi.RuleStatusPass { + result.Result = policyreportv1alpha2.StatusPass + } else if ruleResponse.Status() == engineapi.RuleStatusFail { + if scored, ok := ann[kyvernov1.AnnotationPolicyScored]; ok && scored == "false" { + result.Result = policyreportv1alpha2.StatusWarn + } else if auditWarn && engineResponse.GetValidationFailureAction().Audit() { + result.Result = policyreportv1alpha2.StatusWarn + } else { + result.Result = policyreportv1alpha2.StatusFail + } + } else { + fmt.Println(ruleResponse) + } + + result.Rule = ruleResponse.Name() + result.Message = ruleResponse.Message() + result.Source = kyvernov1.ValueKyvernoApp + result.Timestamp = now + results[appname] = append(results[appname], result) } } diff --git a/cmd/cli/kubectl-kyverno/apply/report_test.go b/cmd/cli/kubectl-kyverno/apply/report_test.go index 34ea473f93..4f5c175603 100644 --- a/cmd/cli/kubectl-kyverno/apply/report_test.go +++ b/cmd/cli/kubectl-kyverno/apply/report_test.go @@ -6,8 +6,6 @@ import ( kyverno "github.com/kyverno/kyverno/api/kyverno/v1" preport "github.com/kyverno/kyverno/api/policyreport/v1alpha2" - "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/common" - kyvCommon "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/common" engineapi "github.com/kyverno/kyverno/pkg/engine/api" "gotest.tools/assert" v1 "k8s.io/api/core/v1" @@ -84,8 +82,6 @@ var rawPolicy = []byte(` `) func Test_buildPolicyReports(t *testing.T) { - rc := &kyvCommon.ResultCounts{} - var pvInfos []common.Info var policy kyverno.ClusterPolicy err := json.Unmarshal(rawPolicy, &policy) assert.NilError(t, err) @@ -106,10 +102,7 @@ func Test_buildPolicyReports(t *testing.T) { ), ) - info := kyvCommon.ProcessValidateEngineResponse(&policy, &er, "", rc, true, false) - pvInfos = append(pvInfos, info) - - reports := buildPolicyReports(pvInfos) + reports := buildPolicyReports(false, er) assert.Assert(t, len(reports) == 1, len(reports)) for _, report := range reports { @@ -132,8 +125,6 @@ func Test_buildPolicyReports(t *testing.T) { } func Test_buildPolicyResults(t *testing.T) { - rc := &kyvCommon.ResultCounts{} - var pvInfos []common.Info var policy kyverno.ClusterPolicy err := json.Unmarshal(rawPolicy, &policy) assert.NilError(t, err) @@ -153,10 +144,7 @@ func Test_buildPolicyResults(t *testing.T) { ), ) - info := kyvCommon.ProcessValidateEngineResponse(&policy, &er, "", rc, true, false) - pvInfos = append(pvInfos, info) - - results := buildPolicyResults(pvInfos) + results := buildPolicyResults(false, er) for _, result := range results { assert.Assert(t, len(result) == 2, len(result)) diff --git a/cmd/cli/kubectl-kyverno/test/test_command.go b/cmd/cli/kubectl-kyverno/test/test_command.go index c4d019c8bc..29621de323 100644 --- a/cmd/cli/kubectl-kyverno/test/test_command.go +++ b/cmd/cli/kubectl-kyverno/test/test_command.go @@ -11,7 +11,6 @@ import ( "regexp" "sort" "strings" - "time" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/memfs" @@ -33,7 +32,6 @@ import ( "github.com/spf13/cobra" "golang.org/x/exp/slices" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/yaml" "sigs.k8s.io/controller-runtime/pkg/log" @@ -189,7 +187,7 @@ func Command() *cobra.Command { manifest.PrintValidate() } else { store.SetRegistryAccess(registryAccess) - _, err = testCommandExecute(dirPath, fileName, gitBranch, testCase, failOnly, removeColor) + _, err = testCommandExecute(dirPath, fileName, gitBranch, testCase, failOnly, removeColor, false) if err != nil { log.Log.V(3).Info("a directory is required") return err @@ -232,7 +230,15 @@ type testFilter struct { var ftable []Table -func testCommandExecute(dirPath []string, fileName string, gitBranch string, testCase string, failOnly bool, removeColor bool) (rc *resultCounts, err error) { +func testCommandExecute( + dirPath []string, + fileName string, + gitBranch string, + testCase string, + failOnly bool, + removeColor bool, + auditWarn bool, +) (rc *resultCounts, err error) { var errors []error fs := memfs.New() rc = &resultCounts{} @@ -352,7 +358,7 @@ func testCommandExecute(dirPath []string, fileName string, gitBranch string, tes errors = append(errors, sanitizederror.NewWithError("failed to convert to JSON", err)) continue } - if err := applyPoliciesFromPath(fs, policyBytes, true, policyresoucePath, rc, openApiManager, tf, failOnly, removeColor); err != nil { + if err := applyPoliciesFromPath(fs, policyBytes, true, policyresoucePath, rc, openApiManager, tf, failOnly, removeColor, auditWarn); err != nil { return rc, sanitizederror.NewWithError("failed to apply test command", err) } } @@ -364,7 +370,7 @@ func testCommandExecute(dirPath []string, fileName string, gitBranch string, tes } else { var testFiles int path := filepath.Clean(dirPath[0]) - errors = getLocalDirTestFiles(fs, path, fileName, rc, &testFiles, openApiManager, tf, failOnly, removeColor) + errors = getLocalDirTestFiles(fs, path, fileName, rc, &testFiles, openApiManager, tf, failOnly, removeColor, auditWarn) if testFiles == 0 { fmt.Printf("\n No test files found. Please provide test YAML files named kyverno-test.yaml \n") @@ -393,7 +399,18 @@ func testCommandExecute(dirPath []string, fileName string, gitBranch string, tes return rc, nil } -func getLocalDirTestFiles(fs billy.Filesystem, path, fileName string, rc *resultCounts, testFiles *int, openApiManager openapi.Manager, tf *testFilter, failOnly, removeColor bool) []error { +func getLocalDirTestFiles( + fs billy.Filesystem, + path string, + fileName string, + rc *resultCounts, + testFiles *int, + openApiManager openapi.Manager, + tf *testFilter, + failOnly bool, + removeColor bool, + auditWarn bool, +) []error { var errors []error files, err := os.ReadDir(path) @@ -402,7 +419,7 @@ func getLocalDirTestFiles(fs billy.Filesystem, path, fileName string, rc *result } for _, file := range files { if file.IsDir() { - getLocalDirTestFiles(fs, filepath.Join(path, file.Name()), fileName, rc, testFiles, openApiManager, tf, failOnly, removeColor) + getLocalDirTestFiles(fs, filepath.Join(path, file.Name()), fileName, rc, testFiles, openApiManager, tf, failOnly, removeColor, auditWarn) continue } if file.Name() == fileName { @@ -418,7 +435,7 @@ func getLocalDirTestFiles(fs billy.Filesystem, path, fileName string, rc *result errors = append(errors, sanitizederror.NewWithError("failed to convert json", err)) continue } - if err := applyPoliciesFromPath(fs, valuesBytes, false, path, rc, openApiManager, tf, failOnly, removeColor); err != nil { + if err := applyPoliciesFromPath(fs, valuesBytes, false, path, rc, openApiManager, tf, failOnly, removeColor, auditWarn); err != nil { errors = append(errors, sanitizederror.NewWithError(fmt.Sprintf("failed to apply test command from file %s", file.Name()), err)) continue } @@ -427,9 +444,15 @@ func getLocalDirTestFiles(fs billy.Filesystem, path, fileName string, rc *result return errors } -func buildPolicyResults(engineResponses []*engineapi.EngineResponse, testResults []api.TestResults, infos []common.Info, policyResourcePath string, fs billy.Filesystem, isGit bool) (map[string]policyreportv1alpha2.PolicyReportResult, []api.TestResults) { - results := make(map[string]policyreportv1alpha2.PolicyReportResult) - now := metav1.Timestamp{Seconds: time.Now().Unix()} +func buildPolicyResults( + engineResponses []engineapi.EngineResponse, + testResults []api.TestResults, + policyResourcePath string, + fs billy.Filesystem, + isGit bool, + auditWarn bool, +) (map[string]policyreportv1alpha2.PolicyReportResult, []api.TestResults) { + results := map[string]policyreportv1alpha2.PolicyReportResult{} for _, resp := range engineResponses { policyName := resp.Policy.GetName() @@ -573,75 +596,85 @@ func buildPolicyResults(engineResponses []*engineapi.EngineResponse, testResults results[resultKey] = result } } - } - for _, rule := range resp.PolicyResponse.Rules { - if rule.RuleType() != engineapi.Mutation { - continue - } - - var resultsKey []string - var resultKey string - var result policyreportv1alpha2.PolicyReportResult - resultsKey = GetAllPossibleResultsKey(policyNamespace, policyName, rule.Name(), resourceNamespace, resourceKind, resourceName) - for _, key := range resultsKey { - if val, ok := results[key]; ok { - result = val - resultKey = key - } else { + for _, rule := range resp.PolicyResponse.Rules { + if rule.RuleType() != engineapi.Mutation || test.Rule != rule.Name() { continue } - if rule.Status() == engineapi.RuleStatusSkip { - result.Result = policyreportv1alpha2.StatusSkip - } else if rule.Status() == engineapi.RuleStatusError { - result.Result = policyreportv1alpha2.StatusError - } else { - var x string - for _, path := range patchedResourcePath { - result.Result = policyreportv1alpha2.StatusFail - x = getAndCompareResource(path, resp.PatchedResource, isGit, policyResourcePath, fs, false) - if x == "pass" { - result.Result = policyreportv1alpha2.StatusPass - break - } - } - } - - results[resultKey] = result - } - } - } - - for _, info := range infos { - for _, infoResult := range info.Results { - for _, rule := range infoResult.Rules { - if rule.Type != string(engineapi.Validation) && rule.Type != string(engineapi.ImageVerify) { - continue - } - - var result policyreportv1alpha2.PolicyReportResult - var resultsKeys []string + var resultsKey []string var resultKey string - resultsKeys = GetAllPossibleResultsKey("", info.PolicyName, rule.Name, infoResult.Resource.Namespace, infoResult.Resource.Kind, infoResult.Resource.Name) - for _, key := range resultsKeys { + var result policyreportv1alpha2.PolicyReportResult + resultsKey = GetAllPossibleResultsKey(policyNamespace, policyName, rule.Name(), resourceNamespace, resourceKind, resourceName) + for _, key := range resultsKey { if val, ok := results[key]; ok { result = val resultKey = key } else { continue } + + if rule.Status() == engineapi.RuleStatusSkip { + result.Result = policyreportv1alpha2.StatusSkip + } else if rule.Status() == engineapi.RuleStatusError { + result.Result = policyreportv1alpha2.StatusError + } else { + var x string + for _, path := range patchedResourcePath { + result.Result = policyreportv1alpha2.StatusFail + x = getAndCompareResource(path, resp.PatchedResource, isGit, policyResourcePath, fs, false) + if x == "pass" { + result.Result = policyreportv1alpha2.StatusPass + break + } + } + } + + results[resultKey] = result + } + } + + for _, rule := range resp.PolicyResponse.Rules { + if rule.RuleType() != engineapi.Validation && rule.RuleType() != engineapi.ImageVerify || test.Rule != rule.Name() { + continue } - result.Rule = rule.Name - result.Result = policyreportv1alpha2.PolicyResult(rule.Status) - result.Source = kyvernov1.ValueKyvernoApp - result.Timestamp = now - results[resultKey] = result + var resultsKey []string + var resultKey string + var result policyreportv1alpha2.PolicyReportResult + resultsKey = GetAllPossibleResultsKey(policyNamespace, policyName, rule.Name(), resourceNamespace, resourceKind, resourceName) + for _, key := range resultsKey { + if val, ok := results[key]; ok { + result = val + resultKey = key + } else { + continue + } + + ann := resp.Policy.GetAnnotations() + if rule.Status() == engineapi.RuleStatusSkip { + result.Result = policyreportv1alpha2.StatusSkip + } else if rule.Status() == engineapi.RuleStatusError { + result.Result = policyreportv1alpha2.StatusError + } else if rule.Status() == engineapi.RuleStatusPass { + result.Result = policyreportv1alpha2.StatusPass + } else if rule.Status() == engineapi.RuleStatusFail { + if scored, ok := ann[kyvernov1.AnnotationPolicyScored]; ok && scored == "false" { + result.Result = policyreportv1alpha2.StatusWarn + } else if auditWarn && resp.GetValidationFailureAction().Audit() { + result.Result = policyreportv1alpha2.StatusWarn + } else { + result.Result = policyreportv1alpha2.StatusFail + } + } else { + fmt.Println(rule) + } + + results[resultKey] = result + } } } } - return results, testResults } @@ -707,7 +740,7 @@ func getAndCompareResource(path string, engineResource unstructured.Unstructured return status } -func buildMessage(resp *engineapi.EngineResponse) string { +func buildMessage(resp engineapi.EngineResponse) string { var bldr strings.Builder for _, ruleResp := range resp.PolicyResponse.Rules { fmt.Fprintf(&bldr, " %s: %s \n", ruleResp.Name(), ruleResp.Status()) @@ -730,12 +763,22 @@ func getFullPath(paths []string, policyResourcePath string, isGit bool) []string return paths } -func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, isGit bool, policyResourcePath string, rc *resultCounts, openApiManager openapi.Manager, tf *testFilter, failOnly, removeColor bool) (err error) { - engineResponses := make([]*engineapi.EngineResponse, 0) +func applyPoliciesFromPath( + fs billy.Filesystem, + policyBytes []byte, + isGit bool, + policyResourcePath string, + rc *resultCounts, + openApiManager openapi.Manager, + tf *testFilter, + failOnly bool, + removeColor bool, + auditWarn bool, +) (err error) { + engineResponses := make([]engineapi.EngineResponse, 0) var dClient dclient.Interface values := &api.Test{} var variablesString string - var pvInfos []common.Info var resultCounts common.ResultCounts store.SetMock(true) @@ -911,15 +954,14 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, isGit bool, Client: dClient, Subresources: subresources, } - ers, info, err := common.ApplyPolicyOnResource(applyPolicyConfig) + ers, err := common.ApplyPolicyOnResource(applyPolicyConfig) if err != nil { return sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.GetName(), resource.GetName()).Error(), err) } engineResponses = append(engineResponses, ers...) - pvInfos = append(pvInfos, info) } } - resultsMap, testResults := buildPolicyResults(engineResponses, values.Results, pvInfos, policyResourcePath, fs, isGit) + resultsMap, testResults := buildPolicyResults(engineResponses, values.Results, policyResourcePath, fs, isGit, auditWarn) resultErr := printTestResult(resultsMap, testResults, rc, failOnly, removeColor) if resultErr != nil { return sanitizederror.NewWithError("failed to print test result:", resultErr) diff --git a/cmd/cli/kubectl-kyverno/utils/common/common.go b/cmd/cli/kubectl-kyverno/utils/common/common.go index 8f90f21f2a..644023e06d 100644 --- a/cmd/cli/kubectl-kyverno/utils/common/common.go +++ b/cmd/cli/kubectl-kyverno/utils/common/common.go @@ -14,7 +14,6 @@ import ( "github.com/go-git/go-billy/v5" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1" - policyreportv1alpha2 "github.com/kyverno/kyverno/api/policyreport/v1alpha2" 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" @@ -369,8 +368,8 @@ func GetVariable(variablesString, valuesFile string, fs billy.Filesystem, isGit } // ApplyPolicyOnResource - function to apply policy on resource -func ApplyPolicyOnResource(c ApplyPolicyConfig) ([]*engineapi.EngineResponse, Info, error) { - var engineResponses []*engineapi.EngineResponse +func ApplyPolicyOnResource(c ApplyPolicyConfig) ([]engineapi.EngineResponse, error) { + var engineResponses []engineapi.EngineResponse namespaceLabels := make(map[string]string) operationIsDelete := false @@ -416,7 +415,7 @@ OuterLoop: resourceNamespace := c.Resource.GetNamespace() namespaceLabels = c.NamespaceSelectorMap[c.Resource.GetNamespace()] if resourceNamespace != "default" && len(namespaceLabels) < 1 { - return engineResponses, Info{}, 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", c.Resource.GetName()), nil) } } @@ -492,12 +491,12 @@ OuterLoop: WithResourceKind(gvk, subresource) mutateResponse := eng.Mutate(context.Background(), policyContext) - engineResponses = append(engineResponses, &mutateResponse) + engineResponses = append(engineResponses, mutateResponse) err = processMutateEngineResponse(c, &mutateResponse, resPath) if err != nil { if !sanitizederror.IsErrorSanitized(err) { - return engineResponses, Info{}, sanitizederror.NewWithError("failed to print mutated result", err) + return engineResponses, sanitizederror.NewWithError("failed to print mutated result", err) } } @@ -510,21 +509,20 @@ OuterLoop: policyContext = policyContext.WithNewResource(mutateResponse.PatchedResource) - var info Info var validateResponse engineapi.EngineResponse if policyHasValidate { validateResponse = eng.Validate(context.Background(), policyContext) - info = ProcessValidateEngineResponse(c.Policy, &validateResponse, resPath, c.Rc, c.PolicyReport, c.AuditWarn) + ProcessValidateEngineResponse(c.Policy, validateResponse, resPath, c.Rc, c.PolicyReport, c.AuditWarn) } if !validateResponse.IsEmpty() { - engineResponses = append(engineResponses, &validateResponse) + engineResponses = append(engineResponses, validateResponse) } verifyImageResponse, _ := eng.VerifyAndPatchImages(context.TODO(), policyContext) if !verifyImageResponse.IsEmpty() { - engineResponses = append(engineResponses, &verifyImageResponse) - info = ProcessValidateEngineResponse(c.Policy, &verifyImageResponse, resPath, c.Rc, c.PolicyReport, c.AuditWarn) + engineResponses = append(engineResponses, verifyImageResponse) + ProcessValidateEngineResponse(c.Policy, verifyImageResponse, resPath, c.Rc, c.PolicyReport, c.AuditWarn) } var policyHasGenerate bool @@ -543,12 +541,66 @@ OuterLoop: } else { generateResponse.PolicyResponse.Rules = newRuleResponse } - engineResponses = append(engineResponses, &generateResponse) + engineResponses = append(engineResponses, generateResponse) } updateResultCounts(c.Policy, &generateResponse, resPath, c.Rc, c.AuditWarn) } - return engineResponses, info, nil + return engineResponses, nil +} + +func ProcessValidateEngineResponse(policy kyvernov1.PolicyInterface, validateResponse engineapi.EngineResponse, resPath string, rc *ResultCounts, policyReport bool, auditWarn bool) { + printCount := 0 + for _, policyRule := range autogen.ComputeRules(policy) { + ruleFoundInEngineResponse := false + if !policyRule.HasValidate() && !policyRule.HasVerifyImageChecks() && !policyRule.HasVerifyImages() { + continue + } + + for i, valResponseRule := range validateResponse.PolicyResponse.Rules { + if policyRule.Name == valResponseRule.Name() { + ruleFoundInEngineResponse = true + switch valResponseRule.Status() { + case engineapi.RuleStatusPass: + rc.Pass++ + case engineapi.RuleStatusFail: + auditWarning := false + ann := policy.GetAnnotations() + if scored, ok := ann[kyvernov1.AnnotationPolicyScored]; ok && scored == "false" { + rc.Warn++ + break + } else if auditWarn && validateResponse.GetValidationFailureAction().Audit() { + rc.Warn++ + auditWarning = true + } else { + rc.Fail++ + } + if !policyReport { + if printCount < 1 { + if auditWarning { + fmt.Printf("\npolicy %s -> resource %s failed as audit warning: \n", policy.GetName(), resPath) + } else { + fmt.Printf("\npolicy %s -> resource %s failed: \n", policy.GetName(), resPath) + } + printCount++ + } + + fmt.Printf("%d. %s: %s \n", i+1, valResponseRule.Name(), valResponseRule.Message()) + } + case engineapi.RuleStatusError: + rc.Error++ + case engineapi.RuleStatusWarn: + rc.Warn++ + case engineapi.RuleStatusSkip: + rc.Skip++ + } + continue + } + } + if !ruleFoundInEngineResponse { + rc.Skip++ + } + } } // PrintMutatedOutput - function to print output in provided file or directory @@ -697,105 +749,6 @@ func GetResourceAccordingToResourcePath(fs billy.Filesystem, resourcePaths []str return resources, err } -func ProcessValidateEngineResponse(policy kyvernov1.PolicyInterface, validateResponse *engineapi.EngineResponse, resPath string, rc *ResultCounts, policyReport bool, auditWarn bool) Info { - var violatedRules []kyvernov1.ViolatedRule - - printCount := 0 - for _, policyRule := range autogen.ComputeRules(policy) { - ruleFoundInEngineResponse := false - if !policyRule.HasValidate() && !policyRule.HasVerifyImageChecks() && !policyRule.HasVerifyImages() { - continue - } - - for i, valResponseRule := range validateResponse.PolicyResponse.Rules { - if policyRule.Name == valResponseRule.Name() { - ruleFoundInEngineResponse = true - vrule := kyvernov1.ViolatedRule{ - Name: valResponseRule.Name(), - Type: string(valResponseRule.RuleType()), - Message: valResponseRule.Message(), - } - - switch valResponseRule.Status() { - case engineapi.RuleStatusPass: - rc.Pass++ - vrule.Status = policyreportv1alpha2.StatusPass - - case engineapi.RuleStatusFail: - auditWarning := false - ann := policy.GetAnnotations() - if scored, ok := ann[kyvernov1.AnnotationPolicyScored]; ok && scored == "false" { - rc.Warn++ - vrule.Status = policyreportv1alpha2.StatusWarn - break - } else if auditWarn && validateResponse.GetValidationFailureAction().Audit() { - rc.Warn++ - auditWarning = true - vrule.Status = policyreportv1alpha2.StatusWarn - } else { - rc.Fail++ - vrule.Status = policyreportv1alpha2.StatusFail - } - - if !policyReport { - if printCount < 1 { - if auditWarning { - fmt.Printf("\npolicy %s -> resource %s failed as audit warning: \n", policy.GetName(), resPath) - } else { - fmt.Printf("\npolicy %s -> resource %s failed: \n", policy.GetName(), resPath) - } - printCount++ - } - - fmt.Printf("%d. %s: %s \n", i+1, valResponseRule.Name(), valResponseRule.Message()) - } - - case engineapi.RuleStatusError: - rc.Error++ - vrule.Status = policyreportv1alpha2.StatusError - - case engineapi.RuleStatusWarn: - rc.Warn++ - vrule.Status = policyreportv1alpha2.StatusWarn - - case engineapi.RuleStatusSkip: - rc.Skip++ - vrule.Status = policyreportv1alpha2.StatusSkip - } - - violatedRules = append(violatedRules, vrule) - continue - } - } - - if !ruleFoundInEngineResponse { - rc.Skip++ - vruleSkip := kyvernov1.ViolatedRule{ - Name: policyRule.Name, - Type: "Validation", - Message: policyRule.Validation.Message, - Status: policyreportv1alpha2.StatusSkip, - } - violatedRules = append(violatedRules, vruleSkip) - } - } - return buildPVInfo(validateResponse, violatedRules) -} - -func buildPVInfo(er *engineapi.EngineResponse, violatedRules []kyvernov1.ViolatedRule) Info { - info := Info{ - PolicyName: er.Policy.GetName(), - Namespace: er.PatchedResource.GetNamespace(), - Results: []EngineResponseResult{ - { - Resource: er.GetResourceSpec(), - Rules: violatedRules, - }, - }, - } - return info -} - func updateResultCounts(policy kyvernov1.PolicyInterface, engineResponse *engineapi.EngineResponse, resPath string, rc *ResultCounts, auditWarn bool) { printCount := 0 for _, policyRule := range autogen.ComputeRules(policy) { diff --git a/cmd/cli/kubectl-kyverno/utils/common/info.go b/cmd/cli/kubectl-kyverno/utils/common/info.go deleted file mode 100644 index 83f83a0bdb..0000000000 --- a/cmd/cli/kubectl-kyverno/utils/common/info.go +++ /dev/null @@ -1,19 +0,0 @@ -package common - -import ( - kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" - engineapi "github.com/kyverno/kyverno/pkg/engine/api" -) - -// Info stores the policy application results for all matched resources -// Namespace is set to empty "" if resource is cluster wide resource -type Info struct { - PolicyName string - Namespace string - Results []EngineResponseResult -} - -type EngineResponseResult struct { - Resource engineapi.ResourceSpec - Rules []kyvernov1.ViolatedRule -} diff --git a/docs/user/crd/index.html b/docs/user/crd/index.html index 5efdcf8c02..48b5010140 100644 --- a/docs/user/crd/index.html +++ b/docs/user/crd/index.html @@ -3866,67 +3866,6 @@ expression evaluates to nil


-

ViolatedRule -

-

-

ViolatedRule stores the information regarding the rule.

-

- - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescription
-name
- -string - -
-

Name specifies violated rule name.

-
-type
- -string - -
-

Type specifies violated rule type.

-
-message
- -string - -
-(Optional) -

Message specifies violation message.

-
-status
- -string - -
-

Status shows the rule response status

-
-

kyverno.io/v1alpha2

Package v1alpha2 contains API Schema definitions for the policy v1alpha2 API group