1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-29 10:55:05 +00:00

added different logic for policy report in CLI

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>
This commit is contained in:
NoSkillGirl 2021-09-02 00:02:55 +05:30
parent 1e6b4bdcee
commit 868537f04d
7 changed files with 106 additions and 68 deletions

View file

@ -19,6 +19,7 @@ import (
"github.com/kyverno/kyverno/pkg/kyverno/store"
"github.com/kyverno/kyverno/pkg/openapi"
policy2 "github.com/kyverno/kyverno/pkg/policy"
"github.com/kyverno/kyverno/pkg/policyreport"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/cli-runtime/pkg/genericclioptions"
@ -121,12 +122,12 @@ func Command() *cobra.Command {
}
}()
validateEngineResponses, rc, resources, skippedPolicies, err := applyCommandHelper(resourcePaths, cluster, policyReport, mutateLogPath, variablesString, valuesFile, namespace, policyPaths, stdin)
validateEngineResponses, rc, resources, skippedPolicies, pvInfos, err := applyCommandHelper(resourcePaths, cluster, policyReport, mutateLogPath, variablesString, valuesFile, namespace, policyPaths, stdin)
if err != nil {
return err
}
printReportOrViolation(policyReport, validateEngineResponses, rc, resourcePaths, len(resources), skippedPolicies, stdin)
printReportOrViolation(policyReport, validateEngineResponses, rc, resourcePaths, len(resources), skippedPolicies, stdin, pvInfos)
return nil
},
}
@ -144,48 +145,48 @@ func Command() *cobra.Command {
}
func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool, mutateLogPath string,
variablesString string, valuesFile string, namespace string, policyPaths []string, stdin bool) (validateEngineResponses []*response.EngineResponse, rc *common.ResultCounts, resources []*unstructured.Unstructured, skippedPolicies []string, err error) {
variablesString string, valuesFile string, namespace string, policyPaths []string, stdin bool) (validateEngineResponses []*response.EngineResponse, rc *common.ResultCounts, resources []*unstructured.Unstructured, skippedPolicies []string, pvInfos []policyreport.Info, err error) {
store.SetMock(true)
kubernetesConfig := genericclioptions.NewConfigFlags(true)
fs := memfs.New()
if valuesFile != "" && variablesString != "" {
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("pass the values either using set flag or values_file flag", err)
return validateEngineResponses, rc, resources, skippedPolicies, pvInfos, sanitizederror.NewWithError("pass the values either using set flag or values_file flag", err)
}
variables, valuesMap, namespaceSelectorMap, err := common.GetVariable(variablesString, valuesFile, fs, false, "")
if err != nil {
if !sanitizederror.IsErrorSanitized(err) {
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("failed to decode yaml", err)
return validateEngineResponses, rc, resources, skippedPolicies, pvInfos, sanitizederror.NewWithError("failed to decode yaml", err)
}
return validateEngineResponses, rc, resources, skippedPolicies, err
return validateEngineResponses, rc, resources, skippedPolicies, pvInfos, err
}
openAPIController, err := openapi.NewOpenAPIController()
if err != nil {
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("failed to initialize openAPIController", err)
return validateEngineResponses, rc, resources, skippedPolicies, pvInfos, sanitizederror.NewWithError("failed to initialize openAPIController", err)
}
var dClient *client.Client
if cluster {
restConfig, err := kubernetesConfig.ToRESTConfig()
if err != nil {
return validateEngineResponses, rc, resources, skippedPolicies, err
return validateEngineResponses, rc, resources, skippedPolicies, pvInfos, err
}
dClient, err = client.NewClient(restConfig, 15*time.Minute, make(chan struct{}), log.Log)
if err != nil {
return validateEngineResponses, rc, resources, skippedPolicies, err
return validateEngineResponses, rc, resources, skippedPolicies, pvInfos, err
}
}
if len(policyPaths) == 0 {
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError(fmt.Sprintf("require policy"), err)
return validateEngineResponses, rc, resources, skippedPolicies, pvInfos, sanitizederror.NewWithError(fmt.Sprintf("require policy"), err)
}
if (len(policyPaths) > 0 && policyPaths[0] == "-") && len(resourcePaths) > 0 && resourcePaths[0] == "-" {
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("a stdin pipe can be used for either policies or resources, not both", err)
return validateEngineResponses, rc, resources, skippedPolicies, pvInfos, sanitizederror.NewWithError("a stdin pipe can be used for either policies or resources, not both", err)
}
policies, err := common.GetPoliciesFromPaths(fs, policyPaths, false, "")
@ -195,15 +196,15 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
}
if len(resourcePaths) == 0 && !cluster {
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError(fmt.Sprintf("resource file(s) or cluster required"), err)
return validateEngineResponses, rc, resources, skippedPolicies, pvInfos, sanitizederror.NewWithError(fmt.Sprintf("resource file(s) or cluster required"), err)
}
mutateLogPathIsDir, err := checkMutateLogPath(mutateLogPath)
if err != nil {
if !sanitizederror.IsErrorSanitized(err) {
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("failed to create file/folder", err)
return validateEngineResponses, rc, resources, skippedPolicies, pvInfos, sanitizederror.NewWithError("failed to create file/folder", err)
}
return validateEngineResponses, rc, resources, skippedPolicies, err
return validateEngineResponses, rc, resources, skippedPolicies, pvInfos, 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)
@ -212,23 +213,23 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
_, err := os.OpenFile(mutateLogPath, os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
if !sanitizederror.IsErrorSanitized(err) {
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("failed to truncate the existing file at "+mutateLogPath, err)
return validateEngineResponses, rc, resources, skippedPolicies, pvInfos, sanitizederror.NewWithError("failed to truncate the existing file at "+mutateLogPath, err)
}
return validateEngineResponses, rc, resources, skippedPolicies, err
return validateEngineResponses, rc, resources, skippedPolicies, pvInfos, err
}
}
mutatedPolicies, err := common.MutatePolices(policies)
if err != nil {
if !sanitizederror.IsErrorSanitized(err) {
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("failed to mutate policy", err)
return validateEngineResponses, rc, resources, skippedPolicies, pvInfos, sanitizederror.NewWithError("failed to mutate policy", err)
}
}
for _, policy := range mutatedPolicies {
p, err := json.Marshal(policy)
if err != nil {
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("failed to marsal mutated policy", err)
return validateEngineResponses, rc, resources, skippedPolicies, pvInfos, sanitizederror.NewWithError("failed to marsal mutated policy", err)
}
log.Log.V(5).Info("mutated Policy:", string(p))
@ -241,7 +242,7 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
}
if (len(resources) > 1 || len(mutatedPolicies) > 1) && variablesString != "" {
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("currently `set` flag supports variable for single policy applied on single resource ", nil)
return validateEngineResponses, rc, resources, skippedPolicies, pvInfos, sanitizederror.NewWithError("currently `set` flag supports variable for single policy applied on single resource ", nil)
}
if variablesString != "" {
@ -311,20 +312,21 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
// skipping the variable check for non matching kind
if _, ok := kindOnwhichPolicyIsApplied[resource.GetKind()]; ok {
if len(variable) > 0 && len(thisPolicyResourceValues) == 0 && len(store.GetContext().Policies) == 0 {
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError(fmt.Sprintf("policy `%s` have variables. pass the values for the variables for resource `%s` using set/values_file flag", policy.Name, resource.GetName()), err)
return validateEngineResponses, rc, resources, skippedPolicies, pvInfos, sanitizederror.NewWithError(fmt.Sprintf("policy `%s` have variables. pass the values for the variables for resource `%s` using set/values_file flag", policy.Name, resource.GetName()), err)
}
}
validateErs, err := common.ApplyPolicyOnResource(policy, resource, mutateLogPath, mutateLogPathIsDir, thisPolicyResourceValues, policyReport, namespaceSelectorMap, stdin, rc)
validateErs, info, err := common.ApplyPolicyOnResource(policy, resource, mutateLogPath, mutateLogPathIsDir, thisPolicyResourceValues, policyReport, namespaceSelectorMap, stdin, rc)
if err != nil {
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err)
return validateEngineResponses, rc, resources, skippedPolicies, pvInfos, sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err)
}
pvInfos = append(pvInfos, info)
validateEngineResponses = append(validateEngineResponses, validateErs)
}
}
return validateEngineResponses, rc, resources, skippedPolicies, nil
return validateEngineResponses, rc, resources, skippedPolicies, pvInfos, nil
}
// checkMutateLogPath - checking path for printing mutated resource (-o flag)
@ -350,7 +352,7 @@ func checkMutateLogPath(mutateLogPath string) (mutateLogPathIsDir bool, err erro
}
// printReportOrViolation - printing policy report/violations
func printReportOrViolation(policyReport bool, validateEngineResponses []*response.EngineResponse, rc *common.ResultCounts, resourcePaths []string, resourcesLen int, skippedPolicies []string, stdin bool) {
func printReportOrViolation(policyReport bool, validateEngineResponses []*response.EngineResponse, rc *common.ResultCounts, resourcePaths []string, resourcesLen int, skippedPolicies []string, stdin bool, pvInfos []policyreport.Info) {
if len(skippedPolicies) > 0 {
fmt.Println("----------------------------------------------------------------------\nPolicies Skipped(as required variables are not provided by the users):")
for i, policyName := range skippedPolicies {
@ -361,7 +363,7 @@ func printReportOrViolation(policyReport bool, validateEngineResponses []*respon
if policyReport {
os.Setenv("POLICY-TYPE", pkgCommon.PolicyReport)
resps := buildPolicyReports(validateEngineResponses)
resps := buildPolicyReports(validateEngineResponses, pvInfos)
if len(resps) > 0 || resourcesLen == 0 {
fmt.Println("\n----------------------------------------------------------------------\nPOLICY REPORT:\n----------------------------------------------------------------------")
report, _ := generateCLIRaw(resps)

View file

@ -56,8 +56,8 @@ func Test_Apply(t *testing.T) {
}
for _, tc := range testcases {
validateEngineResponses, _, _, _, _ := applyCommandHelper(tc.ResourcePaths, false, true, "", "", "", "", tc.PolicyPaths, false)
resps := buildPolicyReports(validateEngineResponses)
validateEngineResponses, _, _, _, info, _ := applyCommandHelper(tc.ResourcePaths, false, true, "", "", "", "", tc.PolicyPaths, false)
resps := buildPolicyReports(validateEngineResponses, info)
for i, resp := range resps {
compareSummary(tc.expectedPolicyReports[i].Summary, resp.UnstructuredContent()["summary"].(map[string]interface{}))
}

View file

@ -21,11 +21,11 @@ import (
const clusterpolicyreport = "clusterpolicyreport"
// resps is the engine responses generated for a single policy
func buildPolicyReports(resps []*response.EngineResponse) (res []*unstructured.Unstructured) {
func buildPolicyReports(resps []*response.EngineResponse, pvInfos []policyreport.Info) (res []*unstructured.Unstructured) {
var raw []byte
var err error
resultsMap := buildPolicyResults(resps)
resultsMap := buildPolicyResults(resps, pvInfos)
for scope, result := range resultsMap {
if scope == clusterpolicyreport {
report := &report.ClusterPolicyReport{
@ -74,9 +74,8 @@ func buildPolicyReports(resps []*response.EngineResponse) (res []*unstructured.U
// buildPolicyResults returns a string-PolicyReportResult map
// the key of the map is one of "clusterpolicyreport", "policyreport-ns-<namespace>"
func buildPolicyResults(resps []*response.EngineResponse) map[string][]*report.PolicyReportResult {
func buildPolicyResults(resps []*response.EngineResponse, infos []policyreport.Info) map[string][]*report.PolicyReportResult {
results := make(map[string][]*report.PolicyReportResult)
infos := policyreport.GeneratePRsFromEngineResponse(resps, log.Log)
now := metav1.Timestamp{Seconds: time.Now().Unix()}
for _, info := range infos {

View file

@ -72,7 +72,7 @@ var engineResponses = []*response.EngineResponse{
func Test_buildPolicyReports(t *testing.T) {
os.Setenv("POLICY-TYPE", common.PolicyReport)
reports := buildPolicyReports(engineResponses)
reports := buildPolicyReports(engineResponses, nil)
assert.Assert(t, len(reports) == 2, len(reports))
for _, report := range reports {
@ -97,7 +97,7 @@ func Test_buildPolicyReports(t *testing.T) {
func Test_buildPolicyResults(t *testing.T) {
os.Setenv("POLICY-TYPE", common.PolicyReport)
results := buildPolicyResults(engineResponses)
results := buildPolicyResults(engineResponses, nil)
assert.Assert(t, len(results[clusterpolicyreport]) == 2, len(results[clusterpolicyreport]))
assert.Assert(t, len(results["policyreport-ns-policy1-namespace"]) == 2, len(results["policyreport-ns-policy1-namespace"]))

View file

@ -16,6 +16,7 @@ import (
"github.com/go-git/go-billy/v5"
"github.com/go-logr/logr"
v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
report "github.com/kyverno/kyverno/pkg/api/policyreport/v1alpha2"
pkgcommon "github.com/kyverno/kyverno/pkg/common"
client "github.com/kyverno/kyverno/pkg/dclient"
"github.com/kyverno/kyverno/pkg/engine"
@ -25,6 +26,7 @@ import (
sanitizederror "github.com/kyverno/kyverno/pkg/kyverno/sanitizedError"
"github.com/kyverno/kyverno/pkg/kyverno/store"
"github.com/kyverno/kyverno/pkg/policymutation"
"github.com/kyverno/kyverno/pkg/policyreport"
"github.com/kyverno/kyverno/pkg/utils"
ut "github.com/kyverno/kyverno/pkg/utils"
yamlv2 "gopkg.in/yaml.v2"
@ -514,7 +516,7 @@ func MutatePolices(policies []*v1.ClusterPolicy) ([]*v1.ClusterPolicy, error) {
// ApplyPolicyOnResource - function to apply policy on resource
func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unstructured,
mutateLogPath string, mutateLogPathIsDir bool, variables map[string]string, policyReport bool, namespaceSelectorMap map[string]map[string]string, stdin bool, rc *ResultCounts) (*response.EngineResponse, error) {
mutateLogPath string, mutateLogPathIsDir bool, variables map[string]string, policyReport bool, namespaceSelectorMap map[string]map[string]string, stdin bool, rc *ResultCounts) (*response.EngineResponse, policyreport.Info, error) {
operationIsDelete := false
@ -539,7 +541,7 @@ func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unst
resourceNamespace := resource.GetNamespace()
namespaceLabels = namespaceSelectorMap[resource.GetNamespace()]
if resourceNamespace != "default" && len(namespaceLabels) < 1 {
return &response.EngineResponse{}, sanitizederror.NewWithError(fmt.Sprintf("failed to get namesapce labels for resource %s. use --values-file flag to pass the namespace labels", resource.GetName()), nil)
return &response.EngineResponse{}, policyreport.Info{}, sanitizederror.NewWithError(fmt.Sprintf("failed to get namesapce labels for resource %s. use --values-file flag to pass the namespace labels", resource.GetName()), nil)
}
}
@ -579,7 +581,7 @@ func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unst
if len(mutateResponse.PolicyResponse.Rules) > 0 {
yamlEncodedResource, err := yamlv2.Marshal(mutateResponse.PatchedResource.Object)
if err != nil {
return &response.EngineResponse{}, sanitizederror.NewWithError("failed to marshal", err)
return &response.EngineResponse{}, policyreport.Info{}, sanitizederror.NewWithError("failed to marshal", err)
}
if mutateLogPath == "" {
@ -594,7 +596,7 @@ func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unst
} else {
err := PrintMutatedOutput(mutateLogPath, mutateLogPathIsDir, string(yamlEncodedResource), resource.GetName()+"-mutated")
if err != nil {
return &response.EngineResponse{}, sanitizederror.NewWithError("failed to print mutated result", err)
return &response.EngineResponse{}, policyreport.Info{}, sanitizederror.NewWithError("failed to print mutated result", err)
}
fmt.Printf("\n\nMutation:\nMutation has been applied successfully. Check the files.")
}
@ -612,34 +614,7 @@ func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unst
policyCtx := &engine.PolicyContext{Policy: *policy, NewResource: mutateResponse.PatchedResource, JSONContext: ctx, NamespaceLabels: namespaceLabels}
validateResponse := engine.Validate(policyCtx)
printCount := 0
if !policyReport {
for _, policyRule := range policy.Spec.Rules {
ruleFoundInEngineResponse := false
for i, valResponseRule := range validateResponse.PolicyResponse.Rules {
if policyRule.Name == valResponseRule.Name {
ruleFoundInEngineResponse = true
if valResponseRule.Success {
rc.Pass++
} else {
if printCount < 1 {
fmt.Printf("\npolicy %s -> resource %s failed: \n", policy.Name, resPath)
printCount++
}
fmt.Printf("%d. %s: %s \n", i+1, valResponseRule.Name, valResponseRule.Message)
rc.Fail++
}
continue
}
}
if !ruleFoundInEngineResponse {
rc.Skip++
}
}
}
info := checkValidateEngineResponse(policy, validateResponse, resPath, rc)
var policyHasGenerate bool
for _, rule := range policy.Spec.Rules {
@ -673,7 +648,7 @@ func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unst
}
}
return validateResponse, nil
return validateResponse, info, nil
}
// PrintMutatedOutput - function to print output in provided file or directory
@ -798,3 +773,65 @@ func GetResourceAccordingToResourcePath(fs billy.Filesystem, resourcePaths []str
}
return resources, err
}
func checkValidateEngineResponse(policy *v1.ClusterPolicy, validateResponse *response.EngineResponse, resPath string, rc *ResultCounts) policyreport.Info {
var violatedRules []v1.ViolatedRule
printCount := 0
for _, policyRule := range policy.Spec.Rules {
ruleFoundInEngineResponse := false
for i, valResponseRule := range validateResponse.PolicyResponse.Rules {
if policyRule.Name == valResponseRule.Name {
ruleFoundInEngineResponse = true
vrule := v1.ViolatedRule{
Name: valResponseRule.Name,
Type: valResponseRule.Type,
Message: valResponseRule.Message,
}
if valResponseRule.Success {
rc.Pass++
vrule.Check = report.StatusPass
} else {
if printCount < 1 {
fmt.Printf("\npolicy %s -> resource %s failed: \n", policy.Name, resPath)
printCount++
}
fmt.Printf("%d. %s: %s \n", i+1, valResponseRule.Name, valResponseRule.Message)
rc.Fail++
vrule.Check = report.StatusFail
}
violatedRules = append(violatedRules, vrule)
continue
}
}
if !ruleFoundInEngineResponse {
rc.Skip++
vruleSkip := v1.ViolatedRule{
Name: policyRule.Name,
Type: "Validation",
Message: policyRule.Validation.Message,
Check: report.StatusSkip,
}
violatedRules = append(violatedRules, vruleSkip)
}
}
return buildPVInfo(validateResponse, violatedRules)
}
func buildPVInfo(er *response.EngineResponse, violatedRules []v1.ViolatedRule) policyreport.Info {
info := policyreport.Info{
PolicyName: er.PolicyResponse.Policy.Name,
Namespace: er.PatchedResource.GetNamespace(),
Results: []policyreport.EngineResponseResult{
{
Resource: er.GetResourceSpec(),
Rules: violatedRules,
},
},
}
return info
}

View file

@ -85,7 +85,7 @@ func Test_NamespaceSelector(t *testing.T) {
for _, tc := range testcases {
policyArray, _ := ut.GetPolicy(tc.policy)
resourceArray, _ := GetResource(tc.resource)
validateErs, _ := ApplyPolicyOnResource(policyArray[0], resourceArray[0], "", false, nil, false, tc.namespaceSelectorMap, false, nil)
validateErs, _, _ := ApplyPolicyOnResource(policyArray[0], resourceArray[0], "", false, nil, false, tc.namespaceSelectorMap, false, nil)
assert.Assert(t, tc.success == validateErs.IsSuccessful())
}
}

View file

@ -394,7 +394,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
return sanitizederror.NewWithError(fmt.Sprintf("policy %s have variables. pass the values for the variables using set/values_file flag", policy.Name), err)
}
validateErs, err := common.ApplyPolicyOnResource(policy, resource, "", false, thisPolicyResourceValues, true, namespaceSelectorMap, false, nil)
validateErs, _, err := common.ApplyPolicyOnResource(policy, resource, "", false, thisPolicyResourceValues, true, namespaceSelectorMap, false, nil)
if err != nil {
return sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err)
}