From fa178ebd82fa37ed1037853e171ca205b1e9d66e Mon Sep 17 00:00:00 2001 From: Sandesh More <34198712+sandeshlmore@users.noreply.github.com> Date: Wed, 19 Oct 2022 22:09:15 +0530 Subject: [PATCH] added apiCalls support in kyverno-apply command (#4938) Signed-off-by: Sandesh More Signed-off-by: Sandesh More --- .../kubectl-kyverno/apply/apply_command.go | 21 +++- cmd/cli/kubectl-kyverno/test/test_command.go | 15 ++- .../kubectl-kyverno/utils/common/common.go | 105 ++++++++++-------- .../utils/common/common_test.go | 11 +- cmd/cli/kubectl-kyverno/utils/store/store.go | 18 ++- pkg/engine/jsonContext.go | 4 + 6 files changed, 117 insertions(+), 57 deletions(-) diff --git a/cmd/cli/kubectl-kyverno/apply/apply_command.go b/cmd/cli/kubectl-kyverno/apply/apply_command.go index c4f7522bfc..d47299c903 100644 --- a/cmd/cli/kubectl-kyverno/apply/apply_command.go +++ b/cmd/cli/kubectl-kyverno/apply/apply_command.go @@ -170,7 +170,9 @@ func Command() *cobra.Command { func (c *ApplyCommandConfig) applyCommandHelper() (rc *common.ResultCounts, resources []*unstructured.Unstructured, skipInvalidPolicies SkippedInvalidPolicies, pvInfos []policyreport.Info, err error) { store.SetMock(true) store.SetRegistryAccess(c.RegistryAccess) - + if c.Cluster { + store.AllowApiCall(true) + } fs := memfs.New() if c.ValuesFile != "" && c.VariablesString != "" { @@ -353,8 +355,21 @@ func (c *ApplyCommandConfig) applyCommandHelper() (rc *common.ResultCounts, reso 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) } - - _, info, err := common.ApplyPolicyOnResource(policy, resource, c.MutateLogPath, mutateLogPathIsDir, thisPolicyResourceValues, userInfo, c.PolicyReport, namespaceSelectorMap, c.Stdin, rc, true, nil) + applyPolicyConfig := common.ApplyPolicyConfig{ + Policy: policy, + Resource: resource, + MutateLogPath: c.MutateLogPath, + MutateLogPathIsDir: mutateLogPathIsDir, + Variables: thisPolicyResourceValues, + UserInfo: userInfo, + PolicyReport: c.PolicyReport, + NamespaceSelectorMap: namespaceSelectorMap, + Stdin: c.Stdin, + Rc: rc, + PrintPatchResource: true, + Client: dClient, + } + _, info, 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) } diff --git a/cmd/cli/kubectl-kyverno/test/test_command.go b/cmd/cli/kubectl-kyverno/test/test_command.go index 4d87a092e7..f465da05ff 100644 --- a/cmd/cli/kubectl-kyverno/test/test_command.go +++ b/cmd/cli/kubectl-kyverno/test/test_command.go @@ -1010,8 +1010,19 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, isGit bool, if err != nil { return 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) } - - ers, info, err := common.ApplyPolicyOnResource(policy, resource, "", false, thisPolicyResourceValues, userInfo, true, namespaceSelectorMap, false, &resultCounts, false, ruleToCloneSourceResource) + applyPolicyConfig := common.ApplyPolicyConfig{ + Policy: policy, + Resource: resource, + MutateLogPath: "", + Variables: thisPolicyResourceValues, + UserInfo: userInfo, + PolicyReport: true, + NamespaceSelectorMap: namespaceSelectorMap, + Rc: &resultCounts, + RuleToCloneSourceResource: ruleToCloneSourceResource, + Client: dClient, + } + ers, info, 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) } diff --git a/cmd/cli/kubectl-kyverno/utils/common/common.go b/cmd/cli/kubectl-kyverno/utils/common/common.go index c70531c50b..9448d4319e 100644 --- a/cmd/cli/kubectl-kyverno/utils/common/common.go +++ b/cmd/cli/kubectl-kyverno/utils/common/common.go @@ -73,6 +73,22 @@ type NamespaceSelector struct { Labels map[string]string `json:"labels"` } +type ApplyPolicyConfig struct { + Policy kyvernov1.PolicyInterface + Resource *unstructured.Unstructured + MutateLogPath string + MutateLogPathIsDir bool + Variables map[string]interface{} + UserInfo kyvernov1beta1.RequestInfo + PolicyReport bool + NamespaceSelectorMap map[string]map[string]string + Stdin bool + Rc *ResultCounts + PrintPatchResource bool + RuleToCloneSourceResource map[string]string + Client dclient.Interface +} + // HasVariables - check for variables in the policy func HasVariables(policy kyvernov1.PolicyInterface) [][]string { policyRaw, _ := json.Marshal(policy) @@ -395,22 +411,18 @@ func MutatePolicies(policies []kyvernov1.PolicyInterface) ([]kyvernov1.PolicyInt } // ApplyPolicyOnResource - function to apply policy on resource -func ApplyPolicyOnResource(policy kyvernov1.PolicyInterface, resource *unstructured.Unstructured, - mutateLogPath string, mutateLogPathIsDir bool, variables map[string]interface{}, userInfo kyvernov1beta1.RequestInfo, policyReport bool, - namespaceSelectorMap map[string]map[string]string, stdin bool, rc *ResultCounts, - printPatchResource bool, ruleToCloneSourceResource map[string]string, -) ([]*response.EngineResponse, policyreport.Info, error) { +func ApplyPolicyOnResource(c ApplyPolicyConfig) ([]*response.EngineResponse, policyreport.Info, error) { var engineResponses []*response.EngineResponse namespaceLabels := make(map[string]string) operationIsDelete := false - if variables["request.operation"] == "DELETE" { + if c.Variables["request.operation"] == "DELETE" { operationIsDelete = true } policyWithNamespaceSelector := false OuterLoop: - for _, p := range autogen.ComputeRules(policy) { + for _, p := range autogen.ComputeRules(c.Policy) { if p.MatchResources.ResourceDescription.NamespaceSelector != nil || p.ExcludeResources.ResourceDescription.NamespaceSelector != nil { policyWithNamespaceSelector = true @@ -443,17 +455,17 @@ OuterLoop: } if policyWithNamespaceSelector { - resourceNamespace := resource.GetNamespace() - namespaceLabels = namespaceSelectorMap[resource.GetNamespace()] + resourceNamespace := c.Resource.GetNamespace() + namespaceLabels = c.NamespaceSelectorMap[c.Resource.GetNamespace()] if resourceNamespace != "default" && len(namespaceLabels) < 1 { - return engineResponses, policyreport.Info{}, sanitizederror.NewWithError(fmt.Sprintf("failed to get namespace labels for resource %s. use --values-file flag to pass the namespace labels", resource.GetName()), nil) + return engineResponses, policyreport.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) } } - resPath := fmt.Sprintf("%s/%s/%s", resource.GetNamespace(), resource.GetKind(), resource.GetName()) - log.Log.V(3).Info("applying policy on resource", "policy", policy.GetName(), "resource", resPath) + resPath := fmt.Sprintf("%s/%s/%s", c.Resource.GetNamespace(), c.Resource.GetKind(), c.Resource.GetName()) + log.Log.V(3).Info("applying policy on resource", "policy", c.Policy.GetName(), "resource", resPath) - resourceRaw, err := resource.MarshalJSON() + resourceRaw, err := c.Resource.MarshalJSON() if err != nil { log.Log.Error(err, "failed to marshal resource") } @@ -474,14 +486,14 @@ OuterLoop: log.Log.Error(err, "failed to load resource in context") } - for key, value := range variables { + for key, value := range c.Variables { err = ctx.AddVariable(key, value) if err != nil { log.Log.Error(err, "failed to add variable to context") } } - if err := ctx.AddImageInfos(resource); err != nil { + if err := ctx.AddImageInfos(c.Resource); err != nil { if err != nil { log.Log.Error(err, "failed to add image variables to context") } @@ -492,11 +504,12 @@ OuterLoop: } policyContext := &engine.PolicyContext{ - Policy: policy, + Policy: c.Policy, NewResource: *updatedResource, JSONContext: ctx, NamespaceLabels: namespaceLabels, - AdmissionInfo: userInfo, + AdmissionInfo: c.UserInfo, + Client: c.Client, } mutateResponse := engine.Mutate(policyContext) @@ -504,25 +517,25 @@ OuterLoop: engineResponses = append(engineResponses, mutateResponse) } - err = processMutateEngineResponse(policy, mutateResponse, resPath, rc, mutateLogPath, stdin, mutateLogPathIsDir, resource.GetName(), printPatchResource) + err = processMutateEngineResponse(c, mutateResponse, resPath) if err != nil { if !sanitizederror.IsErrorSanitized(err) { return engineResponses, policyreport.Info{}, sanitizederror.NewWithError("failed to print mutated result", err) } } - if resource.GetKind() == "Pod" && len(resource.GetOwnerReferences()) > 0 { - if policy.HasAutoGenAnnotation() { - annotations := policy.GetAnnotations() + if c.Resource.GetKind() == "Pod" && len(c.Resource.GetOwnerReferences()) > 0 { + if c.Policy.HasAutoGenAnnotation() { + annotations := c.Policy.GetAnnotations() if _, ok := annotations[kyvernov1.PodControllersAnnotation]; ok { delete(annotations, kyvernov1.PodControllersAnnotation) - policy.SetAnnotations(annotations) + c.Policy.SetAnnotations(annotations) } } } var policyHasValidate bool - for _, rule := range autogen.ComputeRules(policy) { + for _, rule := range autogen.ComputeRules(c.Policy) { if rule.HasValidate() || rule.HasImagesValidationChecks() { policyHasValidate = true } @@ -534,7 +547,7 @@ OuterLoop: var validateResponse *response.EngineResponse if policyHasValidate { validateResponse = engine.Validate(policyContext) - info = ProcessValidateEngineResponse(policy, validateResponse, resPath, rc, policyReport) + info = ProcessValidateEngineResponse(c.Policy, validateResponse, resPath, c.Rc, c.PolicyReport) } if validateResponse != nil && !validateResponse.IsEmpty() { @@ -544,11 +557,11 @@ OuterLoop: verifyImageResponse, _ := engine.VerifyAndPatchImages(policyContext) if verifyImageResponse != nil && !verifyImageResponse.IsEmpty() { engineResponses = append(engineResponses, verifyImageResponse) - info = ProcessValidateEngineResponse(policy, verifyImageResponse, resPath, rc, policyReport) + info = ProcessValidateEngineResponse(c.Policy, verifyImageResponse, resPath, c.Rc, c.PolicyReport) } var policyHasGenerate bool - for _, rule := range autogen.ComputeRules(policy) { + for _, rule := range autogen.ComputeRules(c.Policy) { if rule.HasGenerate() { policyHasGenerate = true } @@ -556,8 +569,8 @@ OuterLoop: if policyHasGenerate { policyContext := &engine.PolicyContext{ - NewResource: *resource, - Policy: policy, + NewResource: *c.Resource, + Policy: c.Policy, ExcludeGroupRole: []string{}, ExcludeResourceFunc: func(s1, s2, s3 string) bool { return false @@ -567,7 +580,7 @@ OuterLoop: } generateResponse := engine.ApplyBackgroundChecks(policyContext) if generateResponse != nil && !generateResponse.IsEmpty() { - newRuleResponse, err := handleGeneratePolicy(generateResponse, *policyContext, ruleToCloneSourceResource) + newRuleResponse, err := handleGeneratePolicy(generateResponse, *policyContext, c.RuleToCloneSourceResource) if err != nil { log.Log.Error(err, "failed to apply generate policy") } else { @@ -575,7 +588,7 @@ OuterLoop: } engineResponses = append(engineResponses, generateResponse) } - updateResultCounts(policy, generateResponse, resPath, rc) + updateResultCounts(c.Policy, generateResponse, resPath, c.Rc) } return engineResponses, info, nil @@ -879,9 +892,9 @@ func SetInStoreContext(mutatedPolicies []kyvernov1.PolicyInterface, variables ma return variables } -func processMutateEngineResponse(policy kyvernov1.PolicyInterface, mutateResponse *response.EngineResponse, resPath string, rc *ResultCounts, mutateLogPath string, stdin bool, mutateLogPathIsDir bool, resourceName string, printPatchResource bool) error { +func processMutateEngineResponse(c ApplyPolicyConfig, mutateResponse *response.EngineResponse, resPath string) error { var policyHasMutate bool - for _, rule := range autogen.ComputeRules(policy) { + for _, rule := range autogen.ComputeRules(c.Policy) { if rule.HasMutate() { policyHasMutate = true } @@ -892,52 +905,52 @@ func processMutateEngineResponse(policy kyvernov1.PolicyInterface, mutateRespons printCount := 0 printMutatedRes := false - for _, policyRule := range autogen.ComputeRules(policy) { + for _, policyRule := range autogen.ComputeRules(c.Policy) { ruleFoundInEngineResponse := false for i, mutateResponseRule := range mutateResponse.PolicyResponse.Rules { if policyRule.Name == mutateResponseRule.Name { ruleFoundInEngineResponse = true if mutateResponseRule.Status == response.RuleStatusPass { - rc.Pass++ + c.Rc.Pass++ printMutatedRes = true } else if mutateResponseRule.Status == response.RuleStatusSkip { - fmt.Printf("\nskipped mutate policy %s -> resource %s", policy.GetName(), resPath) - rc.Skip++ + fmt.Printf("\nskipped mutate policy %s -> resource %s", c.Policy.GetName(), resPath) + c.Rc.Skip++ } else if mutateResponseRule.Status == response.RuleStatusError { - fmt.Printf("\nerror while applying mutate policy %s -> resource %s\nerror: %s", policy.GetName(), resPath, mutateResponseRule.Message) - rc.Error++ + fmt.Printf("\nerror while applying mutate policy %s -> resource %s\nerror: %s", c.Policy.GetName(), resPath, mutateResponseRule.Message) + c.Rc.Error++ } else { if printCount < 1 { - fmt.Printf("\nfailed to apply mutate policy %s -> resource %s", policy.GetName(), resPath) + fmt.Printf("\nfailed to apply mutate policy %s -> resource %s", c.Policy.GetName(), resPath) printCount++ } fmt.Printf("%d. %s - %s \n", i+1, mutateResponseRule.Name, mutateResponseRule.Message) - rc.Fail++ + c.Rc.Fail++ } continue } } if !ruleFoundInEngineResponse { - rc.Skip++ + c.Rc.Skip++ } } - if printMutatedRes && printPatchResource { + if printMutatedRes && c.PrintPatchResource { yamlEncodedResource, err := yamlv2.Marshal(mutateResponse.PatchedResource.Object) if err != nil { return sanitizederror.NewWithError("failed to marshal", err) } - if mutateLogPath == "" { + if c.MutateLogPath == "" { mutatedResource := string(yamlEncodedResource) + string("\n---") if len(strings.TrimSpace(mutatedResource)) > 0 { - if !stdin { - fmt.Printf("\nmutate policy %s applied to %s:", policy.GetName(), resPath) + if !c.Stdin { + fmt.Printf("\nmutate policy %s applied to %s:", c.Policy.GetName(), resPath) } fmt.Printf("\n" + mutatedResource + "\n") } } else { - err := PrintMutatedOutput(mutateLogPath, mutateLogPathIsDir, string(yamlEncodedResource), resourceName+"-mutated") + err := PrintMutatedOutput(c.MutateLogPath, c.MutateLogPathIsDir, string(yamlEncodedResource), c.Resource.GetName()+"-mutated") if err != nil { return sanitizederror.NewWithError("failed to print mutated result", err) } diff --git a/cmd/cli/kubectl-kyverno/utils/common/common_test.go b/cmd/cli/kubectl-kyverno/utils/common/common_test.go index d18e3c7f4b..6e10264c72 100644 --- a/cmd/cli/kubectl-kyverno/utils/common/common_test.go +++ b/cmd/cli/kubectl-kyverno/utils/common/common_test.go @@ -95,12 +95,19 @@ func Test_NamespaceSelector(t *testing.T) { }, }, } - rc := &ResultCounts{} for _, tc := range testcases { policyArray, _ := yamlutils.GetPolicy(tc.policy) resourceArray, _ := GetResource(tc.resource) - ApplyPolicyOnResource(policyArray[0], resourceArray[0], "", false, nil, v1beta1.RequestInfo{}, false, tc.namespaceSelectorMap, false, rc, false, nil) + applyPolicyConfig := ApplyPolicyConfig{ + Policy: policyArray[0], + Resource: resourceArray[0], + MutateLogPath: "", + UserInfo: v1beta1.RequestInfo{}, + NamespaceSelectorMap: tc.namespaceSelectorMap, + Rc: rc, + } + ApplyPolicyOnResource(applyPolicyConfig) assert.Equal(t, int64(rc.Pass), int64(tc.result.Pass)) assert.Equal(t, int64(rc.Fail), int64(tc.result.Fail)) // TODO: autogen rules seem to not be present when autogen internals is disabled diff --git a/cmd/cli/kubectl-kyverno/utils/store/store.go b/cmd/cli/kubectl-kyverno/utils/store/store.go index 5db9bc5ccd..93e431f8fd 100644 --- a/cmd/cli/kubectl-kyverno/utils/store/store.go +++ b/cmd/cli/kubectl-kyverno/utils/store/store.go @@ -6,10 +6,12 @@ import ( ) var ( - Mock, RegistryAccess bool - ContextVar Context - ForeachElement int - Subjects Subject + Mock bool + RegistryAccess bool + AllowApiCalls bool + ContextVar Context + ForeachElement int + Subjects Subject ) func SetMock(mock bool) { @@ -95,3 +97,11 @@ func GetSubjects() Subject { type Subject struct { Subject rbacv1.Subject `json:"subject,omitempty" yaml:"subject,omitempty"` } + +func AllowApiCall(allow bool) { + AllowApiCalls = allow +} + +func IsAllowApiCall() bool { + return AllowApiCalls +} diff --git a/pkg/engine/jsonContext.go b/pkg/engine/jsonContext.go index 4830589223..d9e9a2ca42 100644 --- a/pkg/engine/jsonContext.go +++ b/pkg/engine/jsonContext.go @@ -42,6 +42,10 @@ func LoadContext(logger logr.Logger, contextEntries []kyvernov1.ContextEntry, ct if err := loadVariable(logger, entry, ctx); err != nil { return err } + } else if entry.APICall != nil && store.IsAllowApiCall() { + if err := loadAPIData(logger, entry, ctx); err != nil { + return err + } } }