1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00

refactor: remove obsolete structs from CLI (#6802)

* feat: add policy reporter to the dev lab

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* refactor: remove obsolete structs from CLI

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* codegen

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2023-04-12 14:51:03 +02:00 committed by GitHub
parent d08a50a641
commit 4c740e6999
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 305 additions and 373 deletions

View file

@ -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"`
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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)

View file

@ -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-<namespace>"
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)
}
}

View file

@ -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))

View file

@ -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)

View file

@ -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) {

View file

@ -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
}

View file

@ -3866,67 +3866,6 @@ expression evaluates to nil</p>
</tbody>
</table>
<hr />
<h3 id="kyverno.io/v1.ViolatedRule">ViolatedRule
</h3>
<p>
<p>ViolatedRule stores the information regarding the rule.</p>
</p>
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>name</code><br/>
<em>
string
</em>
</td>
<td>
<p>Name specifies violated rule name.</p>
</td>
</tr>
<tr>
<td>
<code>type</code><br/>
<em>
string
</em>
</td>
<td>
<p>Type specifies violated rule type.</p>
</td>
</tr>
<tr>
<td>
<code>message</code><br/>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Message specifies violation message.</p>
</td>
</tr>
<tr>
<td>
<code>status</code><br/>
<em>
string
</em>
</td>
<td>
<p>Status shows the rule response status</p>
</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="kyverno.io/v1alpha2">kyverno.io/v1alpha2</h2>
<p>
<p>Package v1alpha2 contains API Schema definitions for the policy v1alpha2 API group</p>