From 88d161739004dac605d9ae16489ae74b4be51683 Mon Sep 17 00:00:00 2001 From: Pooja Singh Date: Sat, 24 Jul 2021 00:02:48 +0530 Subject: [PATCH] Resolving variables from the resource passed | CLI (#2180) * added logic for adding resources in context Signed-off-by: NoSkillGirl * small fix Signed-off-by: NoSkillGirl * returning error if request.object is passed from cmd Signed-off-by: NoSkillGirl * reterning error if request.object is passed from value file Signed-off-by: NoSkillGirl * removing comments Signed-off-by: NoSkillGirl * small fix Signed-off-by: NoSkillGirl * handling context for delete operation Signed-off-by: NoSkillGirl * small fix for test command Signed-off-by: NoSkillGirl --- pkg/engine/context/context.go | 28 +++++++++++++++++ pkg/kyverno/apply/apply_command.go | 10 +++--- pkg/kyverno/common/common.go | 50 ++++++++++++++++++++++++------ pkg/kyverno/common/common_test.go | 2 +- pkg/kyverno/test/test_command.go | 10 +++--- 5 files changed, 80 insertions(+), 20 deletions(-) diff --git a/pkg/engine/context/context.go b/pkg/engine/context/context.go index 8159a5a945..b5abce1289 100644 --- a/pkg/engine/context/context.go +++ b/pkg/engine/context/context.go @@ -143,6 +143,34 @@ func (ctx *Context) AddResource(dataRaw []byte) error { return ctx.AddJSON(objRaw) } +//AddResourceInOldObject data at path: request.oldObject +func (ctx *Context) AddResourceInOldObject(dataRaw []byte) error { + + // unmarshal the resource struct + var data interface{} + if err := json.Unmarshal(dataRaw, &data); err != nil { + ctx.log.Error(err, "failed to unmarshal the resource") + return err + } + + modifiedResource := struct { + Request interface{} `json:"request"` + }{ + Request: struct { + OldObject interface{} `json:"oldObject"` + }{ + OldObject: data, + }, + } + + objRaw, err := json.Marshal(modifiedResource) + if err != nil { + ctx.log.Error(err, "failed to marshal the resource") + return err + } + return ctx.AddJSON(objRaw) +} + func (ctx *Context) AddResourceAsObject(data interface{}) error { modifiedResource := struct { Request interface{} `json:"request"` diff --git a/pkg/kyverno/apply/apply_command.go b/pkg/kyverno/apply/apply_command.go index e817ae2c01..380f194f2b 100644 --- a/pkg/kyverno/apply/apply_command.go +++ b/pkg/kyverno/apply/apply_command.go @@ -157,7 +157,7 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool, return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("pass the values either using set flag or values_file flag", err) } - variables, valuesMap, namespaceSelectorMap, err := common.GetVariable(variablesString, valuesFile, fs, false, "") + variables, valuesMap, namespaceSelectorMap, operationIsDelete, 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) @@ -263,9 +263,9 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool, } matches := common.PolicyHasVariables(*policy) - variable := common.RemoveDuplicateVariables(matches) + variable := common.RemoveDuplicateAndObjectVariables(matches) - if len(matches) > 0 && variablesString == "" && valuesFile == "" { + if len(variable) > 0 && variablesString == "" && valuesFile == "" { rc.skip++ skipPolicy := SkippedPolicy{ Name: policy.GetName(), @@ -288,11 +288,11 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool, thisPolicyResourceValues[k] = v } - if len(common.PolicyHasVariables(*policy)) > 0 && len(thisPolicyResourceValues) == 0 && len(store.GetContext().Policies) == 0 { + 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 using set/values_file flag", policy.Name), err) } - ers, validateErs, responseError, rcErs, err := common.ApplyPolicyOnResource(policy, resource, mutateLogPath, mutateLogPathIsDir, thisPolicyResourceValues, policyReport, namespaceSelectorMap, stdin) + ers, validateErs, responseError, rcErs, err := common.ApplyPolicyOnResource(policy, resource, mutateLogPath, mutateLogPathIsDir, thisPolicyResourceValues, policyReport, namespaceSelectorMap, stdin, operationIsDelete) 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) } diff --git a/pkg/kyverno/common/common.go b/pkg/kyverno/common/common.go index a779c88a46..7e270997a7 100644 --- a/pkg/kyverno/common/common.go +++ b/pkg/kyverno/common/common.go @@ -366,34 +366,44 @@ func IsInputFromPipe() bool { return fileInfo.Mode()&os.ModeCharDevice == 0 } -// RemoveDuplicateVariables - remove duplicate variables -func RemoveDuplicateVariables(matches [][]string) string { +// RemoveDuplicateAndObjectVariables - remove duplicate variables +func RemoveDuplicateAndObjectVariables(matches [][]string) string { var variableStr string for _, m := range matches { for _, v := range m { foundVariable := strings.Contains(variableStr, v) if !foundVariable { - variableStr = variableStr + " " + v + if !strings.Contains(v, "request.object") { + variableStr = variableStr + " " + v + } } } } return variableStr } -func GetVariable(variablesString, valuesFile string, fs billy.Filesystem, isGit bool, policyResourcePath string) (map[string]string, map[string]map[string]Resource, map[string]map[string]string, error) { +func GetVariable(variablesString, valuesFile string, fs billy.Filesystem, isGit bool, policyResourcePath string) (map[string]string, map[string]map[string]Resource, map[string]map[string]string, bool, error) { valuesMapResource := make(map[string]map[string]Resource) valuesMapRule := make(map[string]map[string]Rule) namespaceSelectorMap := make(map[string]map[string]string) variables := make(map[string]string) + operationIsDelete := false var yamlFile []byte var err error if variablesString != "" { kvpairs := strings.Split(strings.Trim(variablesString, " "), ",") for _, kvpair := range kvpairs { kvs := strings.Split(strings.Trim(kvpair, " "), "=") + if strings.Contains(kvs[0], "request.object") { + return variables, valuesMapResource, namespaceSelectorMap, operationIsDelete, sanitizederror.NewWithError("variable request.object.* is handled by kyverno. please do not pass value for request.object variables ", err) + } + if strings.Contains(kvs[0], "request.operation") && strings.Contains(kvs[1], "DELETE") { + operationIsDelete = true + } variables[strings.Trim(kvs[0], " ")] = strings.Trim(kvs[1], " ") } } + if valuesFile != "" { if isGit { filep, err := fs.Open(filepath.Join(policyResourcePath, valuesFile)) @@ -406,22 +416,30 @@ func GetVariable(variablesString, valuesFile string, fs billy.Filesystem, isGit } if err != nil { - return variables, valuesMapResource, namespaceSelectorMap, sanitizederror.NewWithError("unable to read yaml", err) + return variables, valuesMapResource, namespaceSelectorMap, operationIsDelete, sanitizederror.NewWithError("unable to read yaml", err) } valuesBytes, err := yaml.ToJSON(yamlFile) if err != nil { - return variables, valuesMapResource, namespaceSelectorMap, sanitizederror.NewWithError("failed to convert json", err) + return variables, valuesMapResource, namespaceSelectorMap, operationIsDelete, sanitizederror.NewWithError("failed to convert json", err) } values := &Values{} if err := json.Unmarshal(valuesBytes, values); err != nil { - return variables, valuesMapResource, namespaceSelectorMap, sanitizederror.NewWithError("failed to decode yaml", err) + return variables, valuesMapResource, namespaceSelectorMap, operationIsDelete, sanitizederror.NewWithError("failed to decode yaml", err) } for _, p := range values.Policies { resourceMap := make(map[string]Resource) for _, r := range p.Resources { + for variableInFile, valueInFile := range r.Values { + if strings.Contains(variableInFile, "request.object") { + return variables, valuesMapResource, namespaceSelectorMap, operationIsDelete, sanitizederror.NewWithError("variable request.object.* is handled by kyverno. please do not pass value for request.object variables ", err) + } + if strings.Contains(variableInFile, "request.operation") && strings.Contains(valueInFile, "DELETE") { + operationIsDelete = true + } + } resourceMap[r.Name] = r } valuesMapResource[p.Name] = resourceMap @@ -459,7 +477,7 @@ func GetVariable(variablesString, valuesFile string, fs billy.Filesystem, isGit Policies: storePolices, }) - return variables, valuesMapResource, namespaceSelectorMap, nil + return variables, valuesMapResource, namespaceSelectorMap, operationIsDelete, nil } // MutatePolices - function to apply mutation on policies @@ -482,7 +500,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) ([]*response.EngineResponse, *response.EngineResponse, bool, bool, error) { + mutateLogPath string, mutateLogPathIsDir bool, variables map[string]string, policyReport bool, namespaceSelectorMap map[string]map[string]string, stdin bool, operationIsDelete bool) ([]*response.EngineResponse, *response.EngineResponse, bool, bool, error) { responseError := false rcError := false @@ -510,6 +528,20 @@ func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unst log.Log.V(3).Info("applying policy on resource", "policy", policy.Name, "resource", resPath) ctx := context.NewContext() + resourceRaw, err := resource.MarshalJSON() + if err != nil { + log.Log.Error(err, "failed to marshal resource") + } + + if operationIsDelete { + err = ctx.AddResourceInOldObject(resourceRaw) + } else { + err = ctx.AddResource(resourceRaw) + } + if err != nil { + log.Log.Error(err, "failed to load resource in context") + } + for key, value := range variables { jsonData := pkgcommon.VariableToJSON(key, value) ctx.AddJSON(jsonData) diff --git a/pkg/kyverno/common/common_test.go b/pkg/kyverno/common/common_test.go index 1176969b08..bf09fe1010 100644 --- a/pkg/kyverno/common/common_test.go +++ b/pkg/kyverno/common/common_test.go @@ -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) + _, validateErs, _, _, _ := ApplyPolicyOnResource(policyArray[0], resourceArray[0], "", false, nil, false, tc.namespaceSelectorMap, false, false) assert.Assert(t, tc.success == validateErs.IsSuccessful()) } } diff --git a/pkg/kyverno/test/test_command.go b/pkg/kyverno/test/test_command.go index 5ea04625b5..a2e1e3d7e6 100644 --- a/pkg/kyverno/test/test_command.go +++ b/pkg/kyverno/test/test_command.go @@ -305,7 +305,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s fmt.Printf("\nExecuting %s...", values.Name) - _, valuesMap, namespaceSelectorMap, err := common.GetVariable(variablesString, values.Variables, fs, isGit, policyResourcePath) + _, valuesMap, namespaceSelectorMap, operationIsDelete, err := common.GetVariable(variablesString, values.Variables, fs, isGit, policyResourcePath) if err != nil { if !sanitizederror.IsErrorSanitized(err) { return sanitizederror.NewWithError("failed to decode yaml", err) @@ -357,8 +357,8 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s } matches := common.PolicyHasVariables(*policy) - variable := common.RemoveDuplicateVariables(matches) - if len(matches) > 0 && variablesString == "" && values.Variables == "" { + variable := common.RemoveDuplicateAndObjectVariables(matches) + if len(variable) > 0 && variablesString == "" && values.Variables == "" { skipPolicy := SkippedPolicy{ Name: policy.GetName(), Rules: policy.Spec.Rules, @@ -385,11 +385,11 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s if len(valuesMap[policy.GetName()]) != 0 && !reflect.DeepEqual(valuesMap[policy.GetName()][resource.GetName()], Resource{}) { thisPolicyResourceValues = valuesMap[policy.GetName()][resource.GetName()].Values } - if len(common.PolicyHasVariables(*policy)) > 0 && len(thisPolicyResourceValues) == 0 { + if len(variable) > 0 && len(thisPolicyResourceValues) == 0 { return sanitizederror.NewWithError(fmt.Sprintf("policy %s have variables. pass the values for the variables using set/values_file flag", policy.Name), err) } - ers, validateErs, _, _, err := common.ApplyPolicyOnResource(policy, resource, "", false, thisPolicyResourceValues, true, namespaceSelectorMap, false) + ers, validateErs, _, _, err := common.ApplyPolicyOnResource(policy, resource, "", false, thisPolicyResourceValues, true, namespaceSelectorMap, false, operationIsDelete) if err != nil { return sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err) }