From 5b89e2e5f80c078dabe21455202aec6e0ac48fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?= <charled.breteche@gmail.com> Date: Fri, 2 Dec 2022 09:14:23 +0100 Subject: [PATCH] refactor: make policy context immutable and fields private (#5523) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: make policy context immutable and fields private Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * refactor: make policy context immutable and fields private 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> Co-authored-by: shuting <shuting@nirmata.com> --- .../kubectl-kyverno/utils/common/common.go | 29 +-- pkg/background/common/context.go | 20 +- pkg/background/generate/generate.go | 11 +- pkg/controllers/report/utils/scanner.go | 28 ++- pkg/cosign/client.go | 1 - pkg/engine/background.go | 36 ++-- pkg/engine/generation.go | 12 +- pkg/engine/imageVerify.go | 40 ++-- pkg/engine/imageVerifyValidate.go | 8 +- pkg/engine/imageVerify_test.go | 12 +- pkg/engine/jsonContext.go | 40 ++-- pkg/engine/k8smanifest.go | 4 +- pkg/engine/k8smanifest_test.go | 24 +-- pkg/engine/loadtargets.go | 16 +- pkg/engine/mutation.go | 38 ++-- pkg/engine/mutation_test.go | 82 ++++---- pkg/engine/policyContext.go | 199 +++++++++++++++--- pkg/engine/utils.go | 4 +- pkg/engine/validation.go | 100 ++++----- pkg/engine/validation_test.go | 112 +++++----- pkg/policy/apply.go | 24 +-- pkg/testrunner/scenario.go | 30 +-- .../resource/generation/generation.go | 9 +- pkg/webhooks/resource/handlers.go | 8 +- .../resource/imageverification/handler.go | 15 +- pkg/webhooks/resource/mutation/mutation.go | 20 +- pkg/webhooks/resource/updaterequest.go | 12 +- .../resource/validation/validation.go | 22 +- pkg/webhooks/resource/validation_test.go | 8 +- pkg/webhooks/utils/policy_context_builder.go | 44 +--- 30 files changed, 538 insertions(+), 470 deletions(-) diff --git a/cmd/cli/kubectl-kyverno/utils/common/common.go b/cmd/cli/kubectl-kyverno/utils/common/common.go index e147418f85..f8113f9904 100644 --- a/cmd/cli/kubectl-kyverno/utils/common/common.go +++ b/cmd/cli/kubectl-kyverno/utils/common/common.go @@ -450,14 +450,12 @@ OuterLoop: log.Log.Error(err, "failed to add image variables to context") } - policyContext := &engine.PolicyContext{ - Policy: c.Policy, - NewResource: *updatedResource, - JSONContext: ctx, - NamespaceLabels: namespaceLabels, - AdmissionInfo: c.UserInfo, - Client: c.Client, - } + policyContext := engine.NewPolicyContextWithJsonContext(ctx). + WithPolicy(c.Policy). + WithNewResource(*updatedResource). + WithNamespaceLabels(namespaceLabels). + WithAdmissionInfo(c.UserInfo). + WithClient(c.Client) mutateResponse := engine.Mutate(policyContext) if mutateResponse != nil { @@ -478,7 +476,7 @@ OuterLoop: } } - policyContext.NewResource = mutateResponse.PatchedResource + policyContext = policyContext.WithNewResource(mutateResponse.PatchedResource) var info Info var validateResponse *response.EngineResponse @@ -505,16 +503,6 @@ OuterLoop: } if policyHasGenerate { - policyContext := &engine.PolicyContext{ - NewResource: *c.Resource, - Policy: c.Policy, - ExcludeGroupRole: []string{}, - ExcludeResourceFunc: func(s1, s2, s3 string) bool { - return false - }, - JSONContext: ctx, - NamespaceLabels: namespaceLabels, - } generateResponse := engine.ApplyBackgroundChecks(policyContext) if generateResponse != nil && !generateResponse.IsEmpty() { newRuleResponse, err := handleGeneratePolicy(generateResponse, *policyContext, c.RuleToCloneSourceResource) @@ -1011,7 +999,8 @@ func initializeMockController(objects []runtime.Object) (*generate.GenerateContr // handleGeneratePolicy returns a new RuleResponse with the Kyverno generated resource configuration by applying the generate rule. func handleGeneratePolicy(generateResponse *response.EngineResponse, policyContext engine.PolicyContext, ruleToCloneSourceResource map[string]string) ([]response.RuleResponse, error) { - objects := []runtime.Object{&policyContext.NewResource} + resource := policyContext.NewResource() + objects := []runtime.Object{&resource} resources := []*unstructured.Unstructured{} for _, rule := range generateResponse.PolicyResponse.Rules { if path, ok := ruleToCloneSourceResource[rule.Name]; ok { diff --git a/pkg/background/common/context.go b/pkg/background/common/context.go index 36538a8b59..8178d630d5 100644 --- a/pkg/background/common/context.go +++ b/pkg/background/common/context.go @@ -77,18 +77,14 @@ func NewBackgroundContext(dclient dclient.Interface, ur *kyvernov1beta1.UpdateRe logger.Error(err, "unable to add image info to variables context") } - policyContext := &engine.PolicyContext{ - NewResource: *trigger, - OldResource: old, - Policy: policy, - AdmissionInfo: ur.Spec.Context.UserRequestInfo, - ExcludeGroupRole: cfg.GetExcludeGroupRole(), - ExcludeResourceFunc: cfg.ToFilter, - JSONContext: ctx, - NamespaceLabels: namespaceLabels, - Client: dclient, - AdmissionOperation: false, - } + policyContext := engine.NewPolicyContextWithJsonContext(ctx). + WithPolicy(policy). + WithNewResource(*trigger). + WithOldResource(old). + WithAdmissionInfo(ur.Spec.Context.UserRequestInfo). + WithConfiguration(cfg). + WithNamespaceLabels(namespaceLabels). + WithClient(dclient) return policyContext, false, nil } diff --git a/pkg/background/generate/generate.go b/pkg/background/generate/generate.go index 03cb69b803..4d65581566 100644 --- a/pkg/background/generate/generate.go +++ b/pkg/background/generate/generate.go @@ -306,13 +306,12 @@ func updateStatus(statusControl common.StatusControlInterface, ur kyvernov1beta1 func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext *engine.PolicyContext, ur kyvernov1beta1.UpdateRequest, applicableRules []string) (genResources []kyvernov1.ResourceSpec, processExisting bool, err error) { // Get the response as the actions to be performed on the resource // - - substitute values - policy := policyContext.Policy - resource := policyContext.NewResource - - jsonContext := policyContext.JSONContext + policy := policyContext.Policy() + resource := policyContext.NewResource() + jsonContext := policyContext.JSONContext() // To manage existing resources, we compare the creation time for the default resource to be generated and policy creation time ruleNameToProcessingTime := make(map[string]time.Duration) - applyRules := policyContext.Policy.GetSpec().GetApplyRules() + applyRules := policy.GetSpec().GetApplyRules() applyCount := 0 for _, rule := range autogen.ComputeRules(policy) { @@ -347,7 +346,7 @@ func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext return nil, processExisting, err } - if rule, err = variables.SubstituteAllInRule(log, policyContext.JSONContext, rule); err != nil { + if rule, err = variables.SubstituteAllInRule(log, policyContext.JSONContext(), rule); err != nil { log.Error(err, "variable substitution failed for rule %s", rule.Name) return nil, processExisting, err } diff --git a/pkg/controllers/report/utils/scanner.go b/pkg/controllers/report/utils/scanner.go index 44e0861967..700dccf206 100644 --- a/pkg/controllers/report/utils/scanner.go +++ b/pkg/controllers/report/utils/scanner.go @@ -75,14 +75,12 @@ func (s *scanner) validateResource(resource unstructured.Unstructured, nsLabels if err := ctx.AddOperation("CREATE"); err != nil { return nil, err } - policyCtx := &engine.PolicyContext{ - Policy: policy, - NewResource: resource, - JSONContext: ctx, - Client: s.client, - NamespaceLabels: nsLabels, - ExcludeGroupRole: s.excludeGroupRole, - } + policyCtx := engine.NewPolicyContextWithJsonContext(ctx). + WithNewResource(resource). + WithPolicy(policy). + WithClient(s.client). + WithNamespaceLabels(nsLabels). + WithExcludeGroupRole(s.excludeGroupRole...) return engine.Validate(policyCtx), nil } @@ -100,14 +98,12 @@ func (s *scanner) validateImages(resource unstructured.Unstructured, nsLabels ma if err := ctx.AddOperation("CREATE"); err != nil { return nil, err } - policyCtx := &engine.PolicyContext{ - Policy: policy, - NewResource: resource, - JSONContext: ctx, - Client: s.client, - NamespaceLabels: nsLabels, - ExcludeGroupRole: s.excludeGroupRole, - } + policyCtx := engine.NewPolicyContextWithJsonContext(ctx). + WithNewResource(resource). + WithPolicy(policy). + WithClient(s.client). + WithNamespaceLabels(nsLabels). + WithExcludeGroupRole(s.excludeGroupRole...) response, _ := engine.VerifyAndPatchImages(policyCtx) if len(response.PolicyResponse.Rules) > 0 { s.logger.Info("validateImages", "policy", policy, "response", response) diff --git a/pkg/cosign/client.go b/pkg/cosign/client.go index 981165025f..dd4a086527 100644 --- a/pkg/cosign/client.go +++ b/pkg/cosign/client.go @@ -12,7 +12,6 @@ var client Cosign = &driver{} type Cosign interface { VerifyImageSignatures(ctx context.Context, signedImgRef name.Reference, co *cosign.CheckOpts) ([]oci.Signature, bool, error) - VerifyImageAttestations(ctx context.Context, signedImgRef name.Reference, co *cosign.CheckOpts) (checkedAttestations []oci.Signature, bundleVerified bool, err error) } diff --git a/pkg/engine/background.go b/pkg/engine/background.go index 73b55c7bb9..3781961a66 100644 --- a/pkg/engine/background.go +++ b/pkg/engine/background.go @@ -22,15 +22,15 @@ func ApplyBackgroundChecks(policyContext *PolicyContext) (resp *response.EngineR } func filterRules(policyContext *PolicyContext, startTime time.Time) *response.EngineResponse { - kind := policyContext.NewResource.GetKind() - name := policyContext.NewResource.GetName() - namespace := policyContext.NewResource.GetNamespace() - apiVersion := policyContext.NewResource.GetAPIVersion() + kind := policyContext.newResource.GetKind() + name := policyContext.newResource.GetName() + namespace := policyContext.newResource.GetNamespace() + apiVersion := policyContext.newResource.GetAPIVersion() resp := &response.EngineResponse{ PolicyResponse: response.PolicyResponse{ Policy: response.PolicySpec{ - Name: policyContext.Policy.GetName(), - Namespace: policyContext.Policy.GetNamespace(), + Name: policyContext.policy.GetName(), + Namespace: policyContext.policy.GetNamespace(), }, PolicyStats: response.PolicyStats{ PolicyExecutionTimestamp: startTime.Unix(), @@ -44,13 +44,13 @@ func filterRules(policyContext *PolicyContext, startTime time.Time) *response.En }, } - if policyContext.ExcludeResourceFunc(kind, namespace, name) { + if policyContext.excludeResourceFunc(kind, namespace, name) { logging.WithName("ApplyBackgroundChecks").Info("resource excluded", "kind", kind, "namespace", namespace, "name", name) return resp } - applyRules := policyContext.Policy.GetSpec().GetApplyRules() - for _, rule := range autogen.ComputeRules(policyContext.Policy) { + applyRules := policyContext.policy.GetSpec().GetApplyRules() + for _, rule := range autogen.ComputeRules(policyContext.policy) { if ruleResp := filterRule(rule, policyContext); ruleResp != nil { resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp) if applyRules == kyvernov1.ApplyOne && ruleResp.Status != response.RuleStatusSkip { @@ -75,13 +75,13 @@ func filterRule(rule kyvernov1.Rule, policyContext *PolicyContext) *response.Rul var err error startTime := time.Now() - policy := policyContext.Policy - newResource := policyContext.NewResource - oldResource := policyContext.OldResource - admissionInfo := policyContext.AdmissionInfo - ctx := policyContext.JSONContext - excludeGroupRole := policyContext.ExcludeGroupRole - namespaceLabels := policyContext.NamespaceLabels + policy := policyContext.policy + newResource := policyContext.newResource + oldResource := policyContext.oldResource + admissionInfo := policyContext.admissionInfo + ctx := policyContext.jsonContext + excludeGroupRole := policyContext.excludeGroupRole + namespaceLabels := policyContext.namespaceLabels logger := logging.WithName(string(ruleType)).WithValues("policy", policy.GetName(), "kind", newResource.GetKind(), "namespace", newResource.GetNamespace(), "name", newResource.GetName()) @@ -105,8 +105,8 @@ func filterRule(rule kyvernov1.Rule, policyContext *PolicyContext) *response.Rul return nil } - policyContext.JSONContext.Checkpoint() - defer policyContext.JSONContext.Restore() + policyContext.jsonContext.Checkpoint() + defer policyContext.jsonContext.Restore() if err = LoadContext(logger, rule.Context, policyContext, rule.Name); err != nil { logger.V(4).Info("cannot add external data to the context", "reason", err.Error()) diff --git a/pkg/engine/generation.go b/pkg/engine/generation.go index c13fe0f8b9..521a6e17fe 100644 --- a/pkg/engine/generation.go +++ b/pkg/engine/generation.go @@ -17,10 +17,10 @@ func GenerateResponse(policyContext *PolicyContext, gr kyvernov1beta1.UpdateRequ } func filterGenerateRules(policyContext *PolicyContext, policyNameKey string, startTime time.Time) *response.EngineResponse { - kind := policyContext.NewResource.GetKind() - name := policyContext.NewResource.GetName() - namespace := policyContext.NewResource.GetNamespace() - apiVersion := policyContext.NewResource.GetAPIVersion() + kind := policyContext.newResource.GetKind() + name := policyContext.newResource.GetName() + namespace := policyContext.newResource.GetNamespace() + apiVersion := policyContext.newResource.GetAPIVersion() pNamespace, pName, err := cache.SplitMetaNamespaceKey(policyNameKey) if err != nil { logging.Error(err, "failed to spilt name and namespace", policyNameKey) @@ -44,12 +44,12 @@ func filterGenerateRules(policyContext *PolicyContext, policyNameKey string, sta }, } - if policyContext.ExcludeResourceFunc(kind, namespace, name) { + if policyContext.excludeResourceFunc(kind, namespace, name) { logging.WithName("Generate").Info("resource excluded", "kind", kind, "namespace", namespace, "name", name) return resp } - for _, rule := range autogen.ComputeRules(policyContext.Policy) { + for _, rule := range autogen.ComputeRules(policyContext.policy) { if ruleResp := filterRule(rule, policyContext); ruleResp != nil { resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp) } diff --git a/pkg/engine/imageVerify.go b/pkg/engine/imageVerify.go index e977df16bc..fd7d87c166 100644 --- a/pkg/engine/imageVerify.go +++ b/pkg/engine/imageVerify.go @@ -48,10 +48,10 @@ func extractMatchingImages(policyContext *PolicyContext, rule *kyvernov1.Rule) ( images map[string]map[string]apiutils.ImageInfo err error ) - images = policyContext.JSONContext.ImageInfo() + images = policyContext.jsonContext.ImageInfo() if rule.ImageExtractors != nil { - images, err = policyContext.JSONContext.GenerateCustomImageInfo( - &policyContext.NewResource, rule.ImageExtractors) + images, err = policyContext.jsonContext.GenerateCustomImageInfo( + &policyContext.newResource, rule.ImageExtractors) if err != nil { // if we get an error while generating custom images from image extractors, // don't check for matching images in imageExtractors @@ -65,8 +65,8 @@ func extractMatchingImages(policyContext *PolicyContext, rule *kyvernov1.Rule) ( func VerifyAndPatchImages(policyContext *PolicyContext) (*response.EngineResponse, *ImageVerificationMetadata) { resp := &response.EngineResponse{} - policy := policyContext.Policy - patchedResource := policyContext.NewResource + policy := policyContext.policy + patchedResource := policyContext.newResource logger := logging.WithName("EngineVerifyImages").WithValues("policy", policy.GetName(), "kind", patchedResource.GetKind(), "namespace", patchedResource.GetNamespace(), "name", patchedResource.GetName()) @@ -78,8 +78,8 @@ func VerifyAndPatchImages(policyContext *PolicyContext) (*response.EngineRespons "applied", resp.PolicyResponse.RulesAppliedCount, "successful", resp.IsSuccessful()) }() - policyContext.JSONContext.Checkpoint() - defer policyContext.JSONContext.Restore() + policyContext.jsonContext.Checkpoint() + defer policyContext.jsonContext.Restore() // update image registry secrets if err := registryclient.DefaultClient.RefreshKeychainPullSecrets(); err != nil { @@ -87,7 +87,7 @@ func VerifyAndPatchImages(policyContext *PolicyContext) (*response.EngineRespons } ivm := &ImageVerificationMetadata{} - rules := autogen.ComputeRules(policyContext.Policy) + rules := autogen.ComputeRules(policyContext.policy) applyRules := policy.GetSpec().GetApplyRules() for i := range rules { @@ -115,13 +115,13 @@ func VerifyAndPatchImages(policyContext *PolicyContext) (*response.EngineRespons continue } - policyContext.JSONContext.Restore() + policyContext.jsonContext.Restore() if err := LoadContext(logger, rule.Context, policyContext, rule.Name); err != nil { appendResponse(resp, rule, fmt.Sprintf("failed to load context: %s", err.Error()), response.RuleStatusError) continue } - ruleCopy, err := substituteVariables(rule, policyContext.JSONContext, logger) + ruleCopy, err := substituteVariables(rule, policyContext.jsonContext, logger) if err != nil { appendResponse(resp, rule, fmt.Sprintf("failed to substitute variables: %s", err.Error()), response.RuleStatusError) continue @@ -201,13 +201,13 @@ func (iv *imageVerifier) verify(imageVerify kyvernov1.ImageVerification, matched } pointer := jsonpointer.ParsePath(imageInfo.Pointer).JMESPath() - changed, err := iv.policyContext.JSONContext.HasChanged(pointer) + changed, err := iv.policyContext.jsonContext.HasChanged(pointer) if err == nil && !changed { iv.logger.V(4).Info("no change in image, skipping check", "image", image) continue } - verified, err := isImageVerified(iv.policyContext.NewResource, image, iv.logger) + verified, err := isImageVerified(iv.policyContext.newResource, image, iv.logger) if err == nil && verified { iv.logger.Info("image was previously verified, skipping check", "image", image) continue @@ -266,14 +266,14 @@ func (iv *imageVerifier) handleMutateDigest(digest string, imageInfo apiutils.Im } func hasImageVerifiedAnnotationChanged(ctx *PolicyContext, log logr.Logger) bool { - if reflect.DeepEqual(ctx.NewResource, unstructured.Unstructured{}) || - reflect.DeepEqual(ctx.OldResource, unstructured.Unstructured{}) { + if reflect.DeepEqual(ctx.newResource, unstructured.Unstructured{}) || + reflect.DeepEqual(ctx.oldResource, unstructured.Unstructured{}) { return false } key := imageVerifyAnnotationKey - newValue := ctx.NewResource.GetAnnotations()[key] - oldValue := ctx.OldResource.GetAnnotations()[key] + newValue := ctx.newResource.GetAnnotations()[key] + oldValue := ctx.oldResource.GetAnnotations()[key] result := newValue != oldValue if result { log.V(2).Info("annotation mismatch", "oldValue", oldValue, "newValue", newValue, "key", key) @@ -301,7 +301,7 @@ func (iv *imageVerifier) verifyImage(imageVerify kyvernov1.ImageVerification, im iv.logger.V(2).Info("verifying image signatures", "image", image, "attestors", len(imageVerify.Attestors), "attestations", len(imageVerify.Attestations)) - if err := iv.policyContext.JSONContext.AddImageInfo(imageInfo); err != nil { + if err := iv.policyContext.jsonContext.AddImageInfo(imageInfo); err != nil { iv.logger.Error(err, "failed to add image to context") msg := fmt.Sprintf("failed to add image to context %s: %s", image, err.Error()) return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusError, nil), "" @@ -628,10 +628,10 @@ func (iv *imageVerifier) checkAttestations(a kyvernov1.Attestation, s map[string return true, nil } - iv.policyContext.JSONContext.Checkpoint() - defer iv.policyContext.JSONContext.Restore() + iv.policyContext.jsonContext.Checkpoint() + defer iv.policyContext.jsonContext.Restore() - return evaluateConditions(a.Conditions, iv.policyContext.JSONContext, s, iv.logger) + return evaluateConditions(a.Conditions, iv.policyContext.jsonContext, s, iv.logger) } func evaluateConditions( diff --git a/pkg/engine/imageVerifyValidate.go b/pkg/engine/imageVerifyValidate.go index 226c4ee5a0..ca1d7fd142 100644 --- a/pkg/engine/imageVerifyValidate.go +++ b/pkg/engine/imageVerifyValidate.go @@ -42,7 +42,7 @@ func processImageValidationRule(log logr.Logger, ctx *PolicyContext, rule *kyver } if !preconditionsPassed { - if ctx.Policy.GetSpec().ValidationFailureAction.Audit() { + if ctx.policy.GetSpec().ValidationFailureAction.Audit() { return nil } @@ -51,7 +51,7 @@ func processImageValidationRule(log logr.Logger, ctx *PolicyContext, rule *kyver for _, v := range rule.VerifyImages { imageVerify := v.Convert() - for _, infoMap := range ctx.JSONContext.ImageInfo() { + for _, infoMap := range ctx.jsonContext.ImageInfo() { for name, imageInfo := range infoMap { image := imageInfo.String() log = log.WithValues("rule", rule.Name) @@ -80,8 +80,8 @@ func validateImage(ctx *PolicyContext, imageVerify *kyvernov1.ImageVerification, return fmt.Errorf("missing digest for %s", image) } - if imageVerify.Required && !reflect.DeepEqual(ctx.NewResource, unstructured.Unstructured{}) { - verified, err := isImageVerified(ctx.NewResource, image, log) + if imageVerify.Required && !reflect.DeepEqual(ctx.newResource, unstructured.Unstructured{}) { + verified, err := isImageVerified(ctx.newResource, image, log) if err != nil { return err } diff --git a/pkg/engine/imageVerify_test.go b/pkg/engine/imageVerify_test.go index d4f03bc247..848dd8627a 100644 --- a/pkg/engine/imageVerify_test.go +++ b/pkg/engine/imageVerify_test.go @@ -181,9 +181,9 @@ func buildContext(t *testing.T, policy, resource string, oldResource string) *Po assert.NilError(t, err) policyContext := &PolicyContext{ - Policy: &cpol, - JSONContext: ctx, - NewResource: *resourceUnstructured, + policy: &cpol, + jsonContext: ctx, + newResource: *resourceUnstructured, } if oldResource != "" { @@ -193,7 +193,7 @@ func buildContext(t *testing.T, policy, resource string, oldResource string) *Po err = context.AddOldResource(ctx, []byte(oldResource)) assert.NilError(t, err) - policyContext.OldResource = *oldResourceUnstructured + policyContext.oldResource = *oldResourceUnstructured } if err := ctx.AddImageInfos(resourceUnstructured); err != nil { @@ -418,7 +418,7 @@ func Test_ConfigMapMissingSuccess(t *testing.T) { func Test_ConfigMapMissingFailure(t *testing.T) { ghcrImage := strings.Replace(testConfigMapMissingResource, "nginx:latest", "ghcr.io/kyverno/test-verify-image:signed", -1) policyContext := buildContext(t, testConfigMapMissing, ghcrImage, "") - policyContext.Client = client.NewEmptyFakeClient() + policyContext.client = client.NewEmptyFakeClient() cosign.ClearMock() err, _ := VerifyAndPatchImages(policyContext) assert.Equal(t, len(err.PolicyResponse.Rules), 1) @@ -499,7 +499,7 @@ func Test_RuleSelectorImageVerify(t *testing.T) { policyContext := buildContext(t, testSampleSingleKeyPolicy, testSampleResource, "") rule := newStaticKeyRule("match-all", "*", testOtherKey) - spec := policyContext.Policy.GetSpec() + spec := policyContext.policy.GetSpec() spec.Rules = append(spec.Rules, *rule) applyAll := kyverno.ApplyAll diff --git a/pkg/engine/jsonContext.go b/pkg/engine/jsonContext.go index 301621263c..5945a42823 100644 --- a/pkg/engine/jsonContext.go +++ b/pkg/engine/jsonContext.go @@ -19,13 +19,13 @@ func LoadContext(logger logr.Logger, contextEntries []kyvernov1.ContextEntry, ct return nil } - policyName := ctx.Policy.GetName() + policyName := ctx.policy.GetName() if store.GetMock() { rule := store.GetPolicyRuleFromContext(policyName, ruleName) if rule != nil && len(rule.Values) > 0 { variables := rule.Values for key, value := range variables { - if err := ctx.JSONContext.AddVariable(key, value); err != nil { + if err := ctx.jsonContext.AddVariable(key, value); err != nil { return err } } @@ -52,7 +52,7 @@ func LoadContext(logger logr.Logger, contextEntries []kyvernov1.ContextEntry, ct if rule != nil && len(rule.ForeachValues) > 0 { for key, value := range rule.ForeachValues { - if err := ctx.JSONContext.AddVariable(key, value[store.ForeachElement]); err != nil { + if err := ctx.jsonContext.AddVariable(key, value[store.ForeachElement]); err != nil { return err } } @@ -84,7 +84,7 @@ func LoadContext(logger logr.Logger, contextEntries []kyvernov1.ContextEntry, ct func loadVariable(logger logr.Logger, entry kyvernov1.ContextEntry, ctx *PolicyContext) (err error) { path := "" if entry.Variable.JMESPath != "" { - jp, err := variables.SubstituteAll(logger, ctx.JSONContext, entry.Variable.JMESPath) + jp, err := variables.SubstituteAll(logger, ctx.jsonContext, entry.Variable.JMESPath) if err != nil { return fmt.Errorf("failed to substitute variables in context entry %s %s: %v", entry.Name, entry.Variable.JMESPath, err) } @@ -97,7 +97,7 @@ func loadVariable(logger logr.Logger, entry kyvernov1.ContextEntry, ctx *PolicyC if err != nil { return fmt.Errorf("invalid default for variable %s", entry.Name) } - defaultValue, err = variables.SubstituteAll(logger, ctx.JSONContext, value) + defaultValue, err = variables.SubstituteAll(logger, ctx.jsonContext, value) if err != nil { return fmt.Errorf("failed to substitute variables in context entry %s %s: %v", entry.Name, entry.Variable.Default, err) } @@ -106,7 +106,7 @@ func loadVariable(logger logr.Logger, entry kyvernov1.ContextEntry, ctx *PolicyC var output interface{} = defaultValue if entry.Variable.Value != nil { value, _ := variables.DocumentToUntyped(entry.Variable.Value) - variable, err := variables.SubstituteAll(logger, ctx.JSONContext, value) + variable, err := variables.SubstituteAll(logger, ctx.jsonContext, value) if err != nil { return fmt.Errorf("failed to substitute variables in context entry %s %s: %v", entry.Name, entry.Variable.Value, err) } @@ -122,7 +122,7 @@ func loadVariable(logger logr.Logger, entry kyvernov1.ContextEntry, ctx *PolicyC } } else { if path != "" { - if variable, err := ctx.JSONContext.Query(path); err == nil { + if variable, err := ctx.jsonContext.Query(path); err == nil { output = variable } else if defaultValue == nil { return fmt.Errorf("failed to apply jmespath %s to variable %v", path, err) @@ -134,7 +134,7 @@ func loadVariable(logger logr.Logger, entry kyvernov1.ContextEntry, ctx *PolicyC return fmt.Errorf("unable to add context entry for variable %s since it evaluated to nil", entry.Name) } if outputBytes, err := json.Marshal(output); err == nil { - return ctx.JSONContext.ReplaceContextEntry(entry.Name, outputBytes) + return ctx.jsonContext.ReplaceContextEntry(entry.Name, outputBytes) } else { return fmt.Errorf("unable to add context entry for variable %s: %w", entry.Name, err) } @@ -152,14 +152,14 @@ func loadImageData(logger logr.Logger, entry kyvernov1.ContextEntry, ctx *Policy if err != nil { return err } - if err := ctx.JSONContext.AddContextEntry(entry.Name, jsonBytes); err != nil { + if err := ctx.jsonContext.AddContextEntry(entry.Name, jsonBytes); err != nil { return fmt.Errorf("failed to add resource data to context: contextEntry: %v, error: %v", entry, err) } return nil } func fetchImageData(logger logr.Logger, entry kyvernov1.ContextEntry, ctx *PolicyContext) (interface{}, error) { - ref, err := variables.SubstituteAll(logger, ctx.JSONContext, entry.ImageRegistry.Reference) + ref, err := variables.SubstituteAll(logger, ctx.jsonContext, entry.ImageRegistry.Reference) if err != nil { return nil, fmt.Errorf("ailed to substitute variables in context entry %s %s: %v", entry.Name, entry.ImageRegistry.Reference, err) } @@ -167,7 +167,7 @@ func fetchImageData(logger logr.Logger, entry kyvernov1.ContextEntry, ctx *Polic if !ok { return nil, fmt.Errorf("invalid image reference %s, image reference must be a string", ref) } - path, err := variables.SubstituteAll(logger, ctx.JSONContext, entry.ImageRegistry.JMESPath) + path, err := variables.SubstituteAll(logger, ctx.jsonContext, entry.ImageRegistry.JMESPath) if err != nil { return nil, fmt.Errorf("failed to substitute variables in context entry %s %s: %v", entry.Name, entry.ImageRegistry.JMESPath, err) } @@ -246,7 +246,7 @@ func loadAPIData(logger logr.Logger, entry kyvernov1.ContextEntry, ctx *PolicyCo } if entry.APICall.JMESPath == "" { - err = ctx.JSONContext.AddContextEntry(entry.Name, jsonData) + err = ctx.jsonContext.AddContextEntry(entry.Name, jsonData) if err != nil { return fmt.Errorf("failed to add resource data to context: contextEntry: %v, error: %v", entry, err) } @@ -254,7 +254,7 @@ func loadAPIData(logger logr.Logger, entry kyvernov1.ContextEntry, ctx *PolicyCo return nil } - path, err := variables.SubstituteAll(logger, ctx.JSONContext, entry.APICall.JMESPath) + path, err := variables.SubstituteAll(logger, ctx.jsonContext, entry.APICall.JMESPath) if err != nil { return fmt.Errorf("failed to substitute variables in context entry %s %s: %v", entry.Name, entry.APICall.JMESPath, err) } @@ -269,7 +269,7 @@ func loadAPIData(logger logr.Logger, entry kyvernov1.ContextEntry, ctx *PolicyCo return fmt.Errorf("failed to marshall data %v for context entry %v: %v", contextData, entry, err) } - err = ctx.JSONContext.AddContextEntry(entry.Name, contextData) + err = ctx.jsonContext.AddContextEntry(entry.Name, contextData) if err != nil { return fmt.Errorf("failed to add JMESPath (%s) results to context, error: %v", entry.APICall.JMESPath, err) } @@ -301,7 +301,7 @@ func fetchAPIData(log logr.Logger, entry kyvernov1.ContextEntry, ctx *PolicyCont return nil, fmt.Errorf("missing APICall in context entry %s %v", entry.Name, entry.APICall) } - path, err := variables.SubstituteAll(log, ctx.JSONContext, entry.APICall.URLPath) + path, err := variables.SubstituteAll(log, ctx.jsonContext, entry.APICall.URLPath) if err != nil { return nil, fmt.Errorf("failed to substitute variables in context entry %s %s: %v", entry.Name, entry.APICall.URLPath, err) } @@ -317,7 +317,7 @@ func fetchAPIData(log logr.Logger, entry kyvernov1.ContextEntry, ctx *PolicyCont } func getResource(ctx *PolicyContext, p string) ([]byte, error) { - return ctx.Client.RawAbsPath(context.TODO(), p) + return ctx.client.RawAbsPath(context.TODO(), p) } func loadConfigMap(logger logr.Logger, entry kyvernov1.ContextEntry, ctx *PolicyContext) error { @@ -326,7 +326,7 @@ func loadConfigMap(logger logr.Logger, entry kyvernov1.ContextEntry, ctx *Policy return fmt.Errorf("failed to retrieve config map for context entry %s: %v", entry.Name, err) } - err = ctx.JSONContext.AddContextEntry(entry.Name, data) + err = ctx.jsonContext.AddContextEntry(entry.Name, data) if err != nil { return fmt.Errorf("failed to add config map for context entry %s: %v", entry.Name, err) } @@ -337,12 +337,12 @@ func loadConfigMap(logger logr.Logger, entry kyvernov1.ContextEntry, ctx *Policy func fetchConfigMap(logger logr.Logger, entry kyvernov1.ContextEntry, ctx *PolicyContext) ([]byte, error) { contextData := make(map[string]interface{}) - name, err := variables.SubstituteAll(logger, ctx.JSONContext, entry.ConfigMap.Name) + name, err := variables.SubstituteAll(logger, ctx.jsonContext, entry.ConfigMap.Name) if err != nil { return nil, fmt.Errorf("failed to substitute variables in context %s configMap.name %s: %v", entry.Name, entry.ConfigMap.Name, err) } - namespace, err := variables.SubstituteAll(logger, ctx.JSONContext, entry.ConfigMap.Namespace) + namespace, err := variables.SubstituteAll(logger, ctx.jsonContext, entry.ConfigMap.Namespace) if err != nil { return nil, fmt.Errorf("failed to substitute variables in context %s configMap.namespace %s: %v", entry.Name, entry.ConfigMap.Namespace, err) } @@ -351,7 +351,7 @@ func fetchConfigMap(logger logr.Logger, entry kyvernov1.ContextEntry, ctx *Polic namespace = "default" } - obj, err := ctx.Client.GetResource(context.TODO(), "v1", "ConfigMap", namespace.(string), name.(string)) + obj, err := ctx.client.GetResource(context.TODO(), "v1", "ConfigMap", namespace.(string), name.(string)) if err != nil { return nil, fmt.Errorf("failed to get configmap %s/%s : %v", namespace, name, err) } diff --git a/pkg/engine/k8smanifest.go b/pkg/engine/k8smanifest.go index 5c8ce01131..086d091464 100644 --- a/pkg/engine/k8smanifest.go +++ b/pkg/engine/k8smanifest.go @@ -58,7 +58,7 @@ func handleVerifyManifest(ctx *PolicyContext, rule *kyvernov1.Rule, logger logr. func verifyManifest(policyContext *PolicyContext, verifyRule kyvernov1.Manifests, logger logr.Logger) (bool, string, error) { // load AdmissionRequest - request, err := policyContext.JSONContext.Query("request") + request, err := policyContext.jsonContext.Query("request") if err != nil { return false, "", errors.Wrapf(err, "failed to get a request from policyContext") } @@ -106,7 +106,7 @@ func verifyManifest(policyContext *PolicyContext, verifyRule kyvernov1.Manifests } if !vo.DisableDryRun { // check if kyverno can 'create' dryrun resource - ok, err := checkDryRunPermission(policyContext.Client, adreq.Kind.Kind, vo.DryRunNamespace) + ok, err := checkDryRunPermission(policyContext.client, adreq.Kind.Kind, vo.DryRunNamespace) if err != nil { logger.V(1).Info("failed to check permissions to 'create' resource. disabled DryRun option.", "dryrun namespace", vo.DryRunNamespace, "kind", adreq.Kind.Kind, "error", err.Error()) vo.DisableDryRun = true diff --git a/pkg/engine/k8smanifest_test.go b/pkg/engine/k8smanifest_test.go index 3f0e29da27..2e3a85ac54 100644 --- a/pkg/engine/k8smanifest_test.go +++ b/pkg/engine/k8smanifest_test.go @@ -614,8 +614,8 @@ func Test_VerifyManifest_SignedYAML(t *testing.T) { policyContext := buildContext(t, test_policy, signed_resource, "") var request *v1.AdmissionRequest _ = json.Unmarshal([]byte(signed_adreq), &request) - policyContext.JSONContext.AddRequest(request) - policyContext.Policy.SetName("test-policy") + policyContext.jsonContext.AddRequest(request) + policyContext.policy.SetName("test-policy") verifyRule := kyvernov1.Manifests{} verifyRule.Attestors = append(verifyRule.Attestors, kyvernov1.AttestorSet{ Entries: []kyvernov1.Attestor{ @@ -636,8 +636,8 @@ func Test_VerifyManifest_UnsignedYAML(t *testing.T) { policyContext := buildContext(t, test_policy, unsigned_resource, "") var request *v1.AdmissionRequest _ = json.Unmarshal([]byte(unsigned_adreq), &request) - policyContext.JSONContext.AddRequest(request) - policyContext.Policy.SetName("test-policy") + policyContext.jsonContext.AddRequest(request) + policyContext.policy.SetName("test-policy") verifyRule := kyvernov1.Manifests{} verifyRule.Attestors = append(verifyRule.Attestors, kyvernov1.AttestorSet{ Entries: []kyvernov1.Attestor{ @@ -658,8 +658,8 @@ func Test_VerifyManifest_InvalidYAML(t *testing.T) { policyContext := buildContext(t, test_policy, invalid_resource, "") var request *v1.AdmissionRequest _ = json.Unmarshal([]byte(invalid_adreq), &request) - policyContext.JSONContext.AddRequest(request) - policyContext.Policy.SetName("test-policy") + policyContext.jsonContext.AddRequest(request) + policyContext.policy.SetName("test-policy") verifyRule := kyvernov1.Manifests{} verifyRule.Attestors = append(verifyRule.Attestors, kyvernov1.AttestorSet{ Entries: []kyvernov1.Attestor{ @@ -680,8 +680,8 @@ func Test_VerifyManifest_MustAll_InvalidYAML(t *testing.T) { policyContext := buildContext(t, test_policy, multi_sig_resource, "") var request *v1.AdmissionRequest _ = json.Unmarshal([]byte(multi_sig_adreq), &request) - policyContext.JSONContext.AddRequest(request) - policyContext.Policy.SetName("test-policy") + policyContext.jsonContext.AddRequest(request) + policyContext.policy.SetName("test-policy") verifyRule := kyvernov1.Manifests{} verifyRule.Attestors = append(verifyRule.Attestors, kyvernov1.AttestorSet{ Entries: []kyvernov1.Attestor{ @@ -708,8 +708,8 @@ func Test_VerifyManifest_MustAll_ValidYAML(t *testing.T) { policyContext := buildContext(t, test_policy, multi_sig2_resource, "") var request *v1.AdmissionRequest _ = json.Unmarshal([]byte(multi_sig2_adreq), &request) - policyContext.JSONContext.AddRequest(request) - policyContext.Policy.SetName("test-policy") + policyContext.jsonContext.AddRequest(request) + policyContext.policy.SetName("test-policy") verifyRule := kyvernov1.Manifests{} count := 3 verifyRule.Attestors = append(verifyRule.Attestors, kyvernov1.AttestorSet{ @@ -740,8 +740,8 @@ func Test_VerifyManifest_AtLeastOne(t *testing.T) { policyContext := buildContext(t, test_policy, multi_sig_resource, "") var request *v1.AdmissionRequest _ = json.Unmarshal([]byte(multi_sig_adreq), &request) - policyContext.JSONContext.AddRequest(request) - policyContext.Policy.SetName("test-policy") + policyContext.jsonContext.AddRequest(request) + policyContext.policy.SetName("test-policy") verifyRule := kyvernov1.Manifests{} count := 1 verifyRule.Attestors = append(verifyRule.Attestors, kyvernov1.AttestorSet{ diff --git a/pkg/engine/loadtargets.go b/pkg/engine/loadtargets.go index b4015344f6..94ed0b49f3 100644 --- a/pkg/engine/loadtargets.go +++ b/pkg/engine/loadtargets.go @@ -36,22 +36,22 @@ func loadTargets(targets []kyvernov1.ResourceSpec, ctx *PolicyContext, logger lo } func resolveSpec(i int, target kyvernov1.ResourceSpec, ctx *PolicyContext, logger logr.Logger) (kyvernov1.ResourceSpec, error) { - kind, err := variables.SubstituteAll(logger, ctx.JSONContext, target.Kind) + kind, err := variables.SubstituteAll(logger, ctx.jsonContext, target.Kind) if err != nil { return kyvernov1.ResourceSpec{}, fmt.Errorf("failed to substitute variables in target[%d].Kind %s: %v", i, target.Kind, err) } - apiversion, err := variables.SubstituteAll(logger, ctx.JSONContext, target.APIVersion) + apiversion, err := variables.SubstituteAll(logger, ctx.jsonContext, target.APIVersion) if err != nil { return kyvernov1.ResourceSpec{}, fmt.Errorf("failed to substitute variables in target[%d].APIVersion %s: %v", i, target.APIVersion, err) } - namespace, err := variables.SubstituteAll(logger, ctx.JSONContext, target.Namespace) + namespace, err := variables.SubstituteAll(logger, ctx.jsonContext, target.Namespace) if err != nil { return kyvernov1.ResourceSpec{}, fmt.Errorf("failed to substitute variables in target[%d].Namespace %s: %v", i, target.Namespace, err) } - name, err := variables.SubstituteAll(logger, ctx.JSONContext, target.Name) + name, err := variables.SubstituteAll(logger, ctx.jsonContext, target.Name) if err != nil { return kyvernov1.ResourceSpec{}, fmt.Errorf("failed to substitute variables in target[%d].Name %s: %v", i, target.Name, err) } @@ -70,13 +70,13 @@ func getTargets(target kyvernov1.ResourceSpec, ctx *PolicyContext, logger logr.L name := target.Name // if it's namespaced policy, targets has to be loaded only from the policy's namespace - if ctx.Policy.IsNamespaced() { - namespace = ctx.Policy.GetNamespace() + if ctx.policy.IsNamespaced() { + namespace = ctx.policy.GetNamespace() } if namespace != "" && name != "" && !wildcard.ContainsWildcard(namespace) && !wildcard.ContainsWildcard(name) { - obj, err := ctx.Client.GetResource(context.TODO(), target.APIVersion, target.Kind, namespace, name) + obj, err := ctx.client.GetResource(context.TODO(), target.APIVersion, target.Kind, namespace, name) if err != nil { return nil, fmt.Errorf("failed to get target %s/%s %s/%s : %v", target.APIVersion, target.Kind, namespace, name, err) } @@ -85,7 +85,7 @@ func getTargets(target kyvernov1.ResourceSpec, ctx *PolicyContext, logger logr.L } // list all targets if wildcard is specified - objList, err := ctx.Client.ListResource(context.TODO(), target.APIVersion, target.Kind, "", nil) + objList, err := ctx.client.ListResource(context.TODO(), target.APIVersion, target.Kind, "", nil) if err != nil { return nil, err } diff --git a/pkg/engine/mutation.go b/pkg/engine/mutation.go index fcf733f21c..8f53219abd 100644 --- a/pkg/engine/mutation.go +++ b/pkg/engine/mutation.go @@ -19,12 +19,12 @@ import ( // Mutate performs mutation. Overlay first and then mutation patches func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) { startTime := time.Now() - policy := policyContext.Policy + policy := policyContext.policy resp = &response.EngineResponse{ Policy: policy, } - matchedResource := policyContext.NewResource - ctx := policyContext.JSONContext + matchedResource := policyContext.newResource + ctx := policyContext.jsonContext var skippedRules []string logger := logging.WithName("EngineMutate").WithValues("policy", policy.GetName(), "kind", matchedResource.GetKind(), @@ -35,8 +35,8 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) { startMutateResultResponse(resp, policy, matchedResource) defer endMutateResultResponse(logger, resp, startTime) - policyContext.JSONContext.Checkpoint() - defer policyContext.JSONContext.Restore() + policyContext.jsonContext.Checkpoint() + defer policyContext.jsonContext.Restore() var err error applyRules := policy.GetSpec().GetApplyRules() @@ -48,19 +48,19 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) { logger := logger.WithValues("rule", rule.Name) var excludeResource []string - if len(policyContext.ExcludeGroupRole) > 0 { - excludeResource = policyContext.ExcludeGroupRole + if len(policyContext.excludeGroupRole) > 0 { + excludeResource = policyContext.excludeGroupRole } - if err = MatchesResourceDescription(matchedResource, rule, policyContext.AdmissionInfo, excludeResource, policyContext.NamespaceLabels, policyContext.Policy.GetNamespace()); err != nil { + if err = MatchesResourceDescription(matchedResource, rule, policyContext.admissionInfo, excludeResource, policyContext.namespaceLabels, policyContext.policy.GetNamespace()); err != nil { logger.V(4).Info("rule not matched", "reason", err.Error()) skippedRules = append(skippedRules, rule.Name) continue } logger.V(3).Info("processing mutate rule", "applyRules", applyRules) - resource, err := policyContext.JSONContext.Query("request.object") - policyContext.JSONContext.Reset() + resource, err := policyContext.jsonContext.Query("request.object") + policyContext.jsonContext.Reset() if err == nil && resource != nil { if err := ctx.AddResource(resource.(map[string]interface{})); err != nil { logger.Error(err, "unable to update resource object") @@ -80,7 +80,7 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) { ruleCopy := rule.DeepCopy() var patchedResources []unstructured.Unstructured - if !policyContext.AdmissionOperation && rule.IsMutateExisting() { + if !policyContext.admissionOperation && rule.IsMutateExisting() { targets, err := loadTargets(ruleCopy.Mutation.Targets, policyContext, logger) if err != nil { rr := ruleResponse(rule, response.Mutation, err.Error(), response.RuleStatusError, nil) @@ -97,9 +97,9 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) { continue } - if !policyContext.AdmissionOperation && rule.IsMutateExisting() { + if !policyContext.admissionOperation && rule.IsMutateExisting() { policyContext := policyContext.Copy() - if err := policyContext.JSONContext.AddTargetResource(patchedResource.Object); err != nil { + if err := policyContext.jsonContext.AddTargetResource(patchedResource.Object); err != nil { logging.Error(err, "failed to add target resource to the context") continue } @@ -153,7 +153,7 @@ func mutateResource(rule *kyvernov1.Rule, ctx *PolicyContext, resource unstructu return ruleResponse(*rule, response.Mutation, "preconditions not met", response.RuleStatusSkip, &resource), resource } - mutateResp := mutate.Mutate(rule, ctx.JSONContext, resource, logger) + mutateResp := mutate.Mutate(rule, ctx.jsonContext, resource, logger) ruleResp := buildRuleResponse(rule, mutateResp, &mutateResp.PatchedResource) return ruleResp, mutateResp.PatchedResource } @@ -183,7 +183,7 @@ func mutateForEach(rule *kyvernov1.Rule, ctx *PolicyContext, resource unstructur return ruleResponse(*rule, response.Mutation, "preconditions not met", response.RuleStatusSkip, &patchedResource), resource } - elements, err := evaluateList(foreach.List, ctx.JSONContext) + elements, err := evaluateList(foreach.List, ctx.jsonContext) if err != nil { msg := fmt.Sprintf("failed to evaluate list %s", foreach.List) return ruleError(rule, response.Mutation, msg, err), resource @@ -214,8 +214,8 @@ func mutateForEach(rule *kyvernov1.Rule, ctx *PolicyContext, resource unstructur } func mutateElements(name string, foreach kyvernov1.ForEachMutation, ctx *PolicyContext, elements []interface{}, resource unstructured.Unstructured, logger logr.Logger) *mutate.Response { - ctx.JSONContext.Checkpoint() - defer ctx.JSONContext.Restore() + ctx.jsonContext.Checkpoint() + defer ctx.jsonContext.Restore() patchedResource := resource var allPatches [][]byte @@ -227,7 +227,7 @@ func mutateElements(name string, foreach kyvernov1.ForEachMutation, ctx *PolicyC if e == nil { continue } - ctx.JSONContext.Reset() + ctx.jsonContext.Reset() ctx := ctx.Copy() store.SetForeachElement(i) falseVar := false @@ -249,7 +249,7 @@ func mutateElements(name string, foreach kyvernov1.ForEachMutation, ctx *PolicyC continue } - mutateResp := mutate.ForEach(name, foreach, ctx.JSONContext, patchedResource, logger) + mutateResp := mutate.ForEach(name, foreach, ctx.jsonContext, patchedResource, logger) if mutateResp.Status == response.RuleStatusFail || mutateResp.Status == response.RuleStatusError { return mutateResp } diff --git a/pkg/engine/mutation_test.go b/pkg/engine/mutation_test.go index f9e476f6f8..2f18417bfc 100644 --- a/pkg/engine/mutation_test.go +++ b/pkg/engine/mutation_test.go @@ -90,9 +90,9 @@ func Test_VariableSubstitutionPatchStrategicMerge(t *testing.T) { t.Error(err) } policyContext := &PolicyContext{ - Policy: &policy, - JSONContext: ctx, - NewResource: *resourceUnstructured} + policy: &policy, + jsonContext: ctx, + newResource: *resourceUnstructured} er := Mutate(policyContext) t.Log(string(expectedPatch)) @@ -163,9 +163,9 @@ func Test_variableSubstitutionPathNotExist(t *testing.T) { assert.NilError(t, err) policyContext := &PolicyContext{ - Policy: &policy, - JSONContext: ctx, - NewResource: *resourceUnstructured} + policy: &policy, + jsonContext: ctx, + newResource: *resourceUnstructured} er := Mutate(policyContext) assert.Equal(t, len(er.PolicyResponse.Rules), 1) assert.Assert(t, strings.Contains(er.PolicyResponse.Rules[0].Message, "Unknown key \"name1\" in path")) @@ -258,9 +258,9 @@ func Test_variableSubstitutionCLI(t *testing.T) { assert.NilError(t, err) policyContext := &PolicyContext{ - Policy: &policy, - JSONContext: ctx, - NewResource: *resourceUnstructured, + policy: &policy, + jsonContext: ctx, + newResource: *resourceUnstructured, } er := Mutate(policyContext) @@ -361,9 +361,9 @@ func Test_chained_rules(t *testing.T) { assert.NilError(t, err) policyContext := &PolicyContext{ - Policy: &policy, - JSONContext: ctx, - NewResource: *resource, + policy: &policy, + jsonContext: ctx, + newResource: *resource, } err = ctx.AddImageInfos(resource) @@ -455,9 +455,9 @@ func Test_precondition(t *testing.T) { assert.NilError(t, err) policyContext := &PolicyContext{ - Policy: &policy, - JSONContext: ctx, - NewResource: *resourceUnstructured, + policy: &policy, + jsonContext: ctx, + newResource: *resourceUnstructured, } er := Mutate(policyContext) @@ -552,9 +552,9 @@ func Test_nonZeroIndexNumberPatchesJson6902(t *testing.T) { assert.NilError(t, err) policyContext := &PolicyContext{ - Policy: &policy, - JSONContext: ctx, - NewResource: *resourceUnstructured, + policy: &policy, + jsonContext: ctx, + newResource: *resourceUnstructured, } er := Mutate(policyContext) @@ -640,9 +640,9 @@ func Test_foreach(t *testing.T) { assert.NilError(t, err) policyContext := &PolicyContext{ - Policy: &policy, - JSONContext: ctx, - NewResource: *resource, + policy: &policy, + jsonContext: ctx, + newResource: *resource, } err = ctx.AddImageInfos(resource) @@ -747,9 +747,9 @@ func Test_foreach_element_mutation(t *testing.T) { assert.NilError(t, err) policyContext := &PolicyContext{ - Policy: &policy, - JSONContext: ctx, - NewResource: *resource, + policy: &policy, + jsonContext: ctx, + newResource: *resource, } err = ctx.AddImageInfos(resource) @@ -873,9 +873,9 @@ func Test_Container_InitContainer_foreach(t *testing.T) { assert.NilError(t, err) policyContext := &PolicyContext{ - Policy: &policy, - JSONContext: ctx, - NewResource: *resource, + policy: &policy, + jsonContext: ctx, + newResource: *resource, } err = ctx.AddImageInfos(resource) @@ -1000,9 +1000,9 @@ func Test_foreach_order_mutation_(t *testing.T) { assert.NilError(t, err) policyContext := &PolicyContext{ - Policy: &policy, - JSONContext: ctx, - NewResource: *resource, + policy: &policy, + jsonContext: ctx, + newResource: *resource, } err = ctx.AddImageInfos(resource) @@ -1451,10 +1451,10 @@ func Test_mutate_existing_resources(t *testing.T) { assert.NilError(t, err) policyContext = &PolicyContext{ - Client: dclient, - Policy: &policy, - JSONContext: ctx, - NewResource: *trigger, + client: dclient, + policy: &policy, + jsonContext: ctx, + newResource: *trigger, } } er := Mutate(policyContext) @@ -1560,9 +1560,9 @@ func Test_RuleSelectorMutate(t *testing.T) { assert.NilError(t, err) policyContext := &PolicyContext{ - Policy: &policy, - JSONContext: ctx, - NewResource: *resourceUnstructured, + policy: &policy, + jsonContext: ctx, + newResource: *resourceUnstructured, } er := Mutate(policyContext) @@ -1578,7 +1578,7 @@ func Test_RuleSelectorMutate(t *testing.T) { } applyOne := kyverno.ApplyOne - policyContext.Policy.GetSpec().ApplyRules = &applyOne + policyContext.policy.GetSpec().ApplyRules = &applyOne er = Mutate(policyContext) assert.Equal(t, len(er.PolicyResponse.Rules), 1) @@ -1941,9 +1941,9 @@ func Test_SpecialCharacters(t *testing.T) { // Create policy context. policyContext := &PolicyContext{ - Policy: &policy, - JSONContext: ctx, - NewResource: *resource, + policy: &policy, + jsonContext: ctx, + newResource: *resource, } // Mutate and make sure that we got the expected amount of rules. diff --git a/pkg/engine/policyContext.go b/pkg/engine/policyContext.go index 9e22d57290..fcb3e76306 100644 --- a/pkg/engine/policyContext.go +++ b/pkg/engine/policyContext.go @@ -4,55 +4,196 @@ import ( kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1" "github.com/kyverno/kyverno/pkg/clients/dclient" + "github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/engine/context" + enginectx "github.com/kyverno/kyverno/pkg/engine/context" + "github.com/kyverno/kyverno/pkg/utils" + "github.com/pkg/errors" + admissionv1 "k8s.io/api/admission/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) +// ExcludeFunc is a function used to determine if a resource is excluded +type ExcludeFunc = func(kind, namespace, name string) bool + // PolicyContext contains the contexts for engine to process type PolicyContext struct { - // Policy is the policy to be processed - Policy kyvernov1.PolicyInterface + // policy is the policy to be processed + policy kyvernov1.PolicyInterface - // NewResource is the resource to be processed - NewResource unstructured.Unstructured + // newResource is the resource to be processed + newResource unstructured.Unstructured - // OldResource is the prior resource for an update, or nil - OldResource unstructured.Unstructured + // oldResource is the prior resource for an update, or nil + oldResource unstructured.Unstructured - // Element is set when the context is used for processing a foreach loop - Element unstructured.Unstructured + // element is set when the context is used for processing a foreach loop + element unstructured.Unstructured - // AdmissionInfo contains the admission request information - AdmissionInfo kyvernov1beta1.RequestInfo + // admissionInfo contains the admission request information + admissionInfo kyvernov1beta1.RequestInfo // Dynamic client - used for api lookups - Client dclient.Interface + client dclient.Interface // Config handler - ExcludeGroupRole []string + excludeGroupRole []string - ExcludeResourceFunc func(kind, namespace, name string) bool + excludeResourceFunc ExcludeFunc - // JSONContext is the variable context - JSONContext context.Interface + // jsonContext is the variable context + jsonContext context.Interface - // NamespaceLabels stores the label of namespace to be processed by namespace selector - NamespaceLabels map[string]string + // namespaceLabels stores the label of namespace to be processed by namespace selector + namespaceLabels map[string]string - // AdmissionOperation represents if the caller is from the webhook server - AdmissionOperation bool + // admissionOperation represents if the caller is from the webhook server + admissionOperation bool } -func (pc *PolicyContext) Copy() *PolicyContext { +// Getters + +func (c *PolicyContext) Policy() kyvernov1.PolicyInterface { + return c.policy +} + +func (c *PolicyContext) NewResource() unstructured.Unstructured { + return c.newResource +} + +func (c *PolicyContext) OldResource() unstructured.Unstructured { + return c.oldResource +} + +func (c *PolicyContext) AdmissionInfo() kyvernov1beta1.RequestInfo { + return c.admissionInfo +} + +func (c *PolicyContext) JSONContext() context.Interface { + return c.jsonContext +} + +// Mutators + +func (c *PolicyContext) WithPolicy(policy kyvernov1.PolicyInterface) *PolicyContext { + copy := c.Copy() + copy.policy = policy + return copy +} + +func (c *PolicyContext) WithNamespaceLabels(namespaceLabels map[string]string) *PolicyContext { + copy := c.Copy() + copy.namespaceLabels = namespaceLabels + return copy +} + +func (c *PolicyContext) WithAdmissionInfo(admissionInfo kyvernov1beta1.RequestInfo) *PolicyContext { + copy := c.Copy() + copy.admissionInfo = admissionInfo + return copy +} + +func (c *PolicyContext) WithNewResource(resource unstructured.Unstructured) *PolicyContext { + copy := c.Copy() + copy.newResource = resource + return copy +} + +func (c *PolicyContext) WithOldResource(resource unstructured.Unstructured) *PolicyContext { + copy := c.Copy() + copy.oldResource = resource + return copy +} + +func (c *PolicyContext) WithResources(newResource unstructured.Unstructured, oldResource unstructured.Unstructured) *PolicyContext { + return c.WithNewResource(newResource).WithOldResource(oldResource) +} + +func (c *PolicyContext) WithClient(client dclient.Interface) *PolicyContext { + copy := c.Copy() + copy.client = client + return copy +} + +func (c *PolicyContext) WithExcludeGroupRole(excludeGroupRole ...string) *PolicyContext { + copy := c.Copy() + copy.excludeGroupRole = excludeGroupRole + return copy +} + +func (c *PolicyContext) WithExcludeResourceFunc(excludeResourceFunc ExcludeFunc) *PolicyContext { + copy := c.Copy() + copy.excludeResourceFunc = excludeResourceFunc + return copy +} + +func (c *PolicyContext) WithConfiguration(configuration config.Configuration) *PolicyContext { + return c.WithExcludeResourceFunc(configuration.ToFilter).WithExcludeGroupRole(configuration.GetExcludeGroupRole()...) +} + +func (c *PolicyContext) WithAdmissionOperation(admissionOperation bool) *PolicyContext { + copy := c.Copy() + copy.admissionOperation = admissionOperation + return copy +} + +// Constructors + +func NewPolicyContextWithJsonContext(jsonContext context.Interface) *PolicyContext { return &PolicyContext{ - Policy: pc.Policy, - NewResource: pc.NewResource, - OldResource: pc.OldResource, - AdmissionInfo: pc.AdmissionInfo, - Client: pc.Client, - ExcludeGroupRole: pc.ExcludeGroupRole, - ExcludeResourceFunc: pc.ExcludeResourceFunc, - JSONContext: pc.JSONContext, - NamespaceLabels: pc.NamespaceLabels, + jsonContext: jsonContext, + excludeGroupRole: []string{}, + excludeResourceFunc: func(string, string, string) bool { + return false + }, } } + +func NewPolicyContext() *PolicyContext { + return NewPolicyContextWithJsonContext(context.NewContext()) +} + +func NewPolicyContextFromAdmissionRequest( + request *admissionv1.AdmissionRequest, + admissionInfo kyvernov1beta1.RequestInfo, + configuration config.Configuration, + client dclient.Interface, +) (*PolicyContext, error) { + ctx, err := newVariablesContext(request, &admissionInfo) + if err != nil { + return nil, errors.Wrap(err, "failed to create policy rule context") + } + newResource, oldResource, err := utils.ExtractResources(nil, request) + if err != nil { + return nil, errors.Wrap(err, "failed to parse resource") + } + if err := ctx.AddImageInfos(&newResource); err != nil { + return nil, errors.Wrap(err, "failed to add image information to the policy rule context") + } + policyContext := NewPolicyContextWithJsonContext(ctx). + WithNewResource(newResource). + WithOldResource(oldResource). + WithAdmissionInfo(admissionInfo). + WithConfiguration(configuration). + WithClient(client). + WithAdmissionOperation(true) + return policyContext, nil +} + +func (c PolicyContext) Copy() *PolicyContext { + return &c +} + +func newVariablesContext(request *admissionv1.AdmissionRequest, userRequestInfo *kyvernov1beta1.RequestInfo) (enginectx.Interface, error) { + ctx := enginectx.NewContext() + if err := ctx.AddRequest(request); err != nil { + return nil, errors.Wrap(err, "failed to load incoming request in context") + } + if err := ctx.AddUserInfo(*userRequestInfo); err != nil { + return nil, errors.Wrap(err, "failed to load userInfo in context") + } + if err := ctx.AddServiceAccount(userRequestInfo.AdmissionUserInfo.Username); err != nil { + return nil, errors.Wrap(err, "failed to load service account in context") + } + return ctx, nil +} diff --git a/pkg/engine/utils.go b/pkg/engine/utils.go index d9f99b577d..ec1a1eea0f 100644 --- a/pkg/engine/utils.go +++ b/pkg/engine/utils.go @@ -426,7 +426,7 @@ func ManagedPodResource(policy kyvernov1.PolicyInterface, resource unstructured. } func checkPreconditions(logger logr.Logger, ctx *PolicyContext, anyAllConditions apiextensions.JSON) (bool, error) { - preconditions, err := variables.SubstituteAllInPreconditions(logger, ctx.JSONContext, anyAllConditions) + preconditions, err := variables.SubstituteAllInPreconditions(logger, ctx.jsonContext, anyAllConditions) if err != nil { return false, errors.Wrapf(err, "failed to substitute variables in preconditions") } @@ -436,7 +436,7 @@ func checkPreconditions(logger logr.Logger, ctx *PolicyContext, anyAllConditions return false, errors.Wrapf(err, "failed to parse preconditions") } - pass := variables.EvaluateConditions(logger, ctx.JSONContext, typeConditions) + pass := variables.EvaluateConditions(logger, ctx.jsonContext, typeConditions) return pass, nil } diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index 25f78a8dee..ae03700af8 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -46,11 +46,11 @@ func Validate(policyContext *PolicyContext) (resp *response.EngineResponse) { } func buildLogger(ctx *PolicyContext) logr.Logger { - logger := logging.WithName("EngineValidate").WithValues("policy", ctx.Policy.GetName()) - if reflect.DeepEqual(ctx.NewResource, unstructured.Unstructured{}) { - logger = logger.WithValues("kind", ctx.OldResource.GetKind(), "namespace", ctx.OldResource.GetNamespace(), "name", ctx.OldResource.GetName()) + logger := logging.WithName("EngineValidate").WithValues("policy", ctx.policy.GetName()) + if reflect.DeepEqual(ctx.newResource, unstructured.Unstructured{}) { + logger = logger.WithValues("kind", ctx.oldResource.GetKind(), "namespace", ctx.oldResource.GetNamespace(), "name", ctx.oldResource.GetName()) } else { - logger = logger.WithValues("kind", ctx.NewResource.GetKind(), "namespace", ctx.NewResource.GetNamespace(), "name", ctx.NewResource.GetName()) + logger = logger.WithValues("kind", ctx.newResource.GetKind(), "namespace", ctx.newResource.GetNamespace(), "name", ctx.newResource.GetName()) } return logger @@ -63,24 +63,24 @@ func buildResponse(ctx *PolicyContext, resp *response.EngineResponse, startTime if reflect.DeepEqual(resp.PatchedResource, unstructured.Unstructured{}) { // for delete requests patched resource will be oldResource since newResource is empty - resource := ctx.NewResource - if reflect.DeepEqual(ctx.NewResource, unstructured.Unstructured{}) { - resource = ctx.OldResource + resource := ctx.newResource + if reflect.DeepEqual(ctx.newResource, unstructured.Unstructured{}) { + resource = ctx.oldResource } resp.PatchedResource = resource } - resp.Policy = ctx.Policy - resp.PolicyResponse.Policy.Name = ctx.Policy.GetName() - resp.PolicyResponse.Policy.Namespace = ctx.Policy.GetNamespace() + resp.Policy = ctx.policy + resp.PolicyResponse.Policy.Name = ctx.policy.GetName() + resp.PolicyResponse.Policy.Namespace = ctx.policy.GetNamespace() resp.PolicyResponse.Resource.Name = resp.PatchedResource.GetName() resp.PolicyResponse.Resource.Namespace = resp.PatchedResource.GetNamespace() resp.PolicyResponse.Resource.Kind = resp.PatchedResource.GetKind() resp.PolicyResponse.Resource.APIVersion = resp.PatchedResource.GetAPIVersion() - resp.PolicyResponse.ValidationFailureAction = ctx.Policy.GetSpec().ValidationFailureAction + resp.PolicyResponse.ValidationFailureAction = ctx.policy.GetSpec().ValidationFailureAction - for _, v := range ctx.Policy.GetSpec().ValidationFailureActionOverrides { + for _, v := range ctx.policy.GetSpec().ValidationFailureActionOverrides { resp.PolicyResponse.ValidationFailureActionOverrides = append(resp.PolicyResponse.ValidationFailureActionOverrides, response.ValidationFailureActionOverride{Action: v.Action, Namespaces: v.Namespaces}) } @@ -91,19 +91,19 @@ func buildResponse(ctx *PolicyContext, resp *response.EngineResponse, startTime func validateResource(log logr.Logger, ctx *PolicyContext) *response.EngineResponse { resp := &response.EngineResponse{} - ctx.JSONContext.Checkpoint() - defer ctx.JSONContext.Restore() + ctx.jsonContext.Checkpoint() + defer ctx.jsonContext.Restore() - rules := autogen.ComputeRules(ctx.Policy) + rules := autogen.ComputeRules(ctx.policy) matchCount := 0 - applyRules := ctx.Policy.GetSpec().GetApplyRules() + applyRules := ctx.policy.GetSpec().GetApplyRules() - if ctx.Policy.IsNamespaced() { - polNs := ctx.Policy.GetNamespace() - if ctx.NewResource.Object != nil && (ctx.NewResource.GetNamespace() != polNs || ctx.NewResource.GetNamespace() == "") { + if ctx.policy.IsNamespaced() { + polNs := ctx.policy.GetNamespace() + if ctx.newResource.Object != nil && (ctx.newResource.GetNamespace() != polNs || ctx.newResource.GetNamespace() == "") { return resp } - if ctx.OldResource.Object != nil && (ctx.OldResource.GetNamespace() != polNs || ctx.OldResource.GetNamespace() == "") { + if ctx.oldResource.Object != nil && (ctx.oldResource.GetNamespace() != polNs || ctx.oldResource.GetNamespace() == "") { return resp } } @@ -123,7 +123,7 @@ func validateResource(log logr.Logger, ctx *PolicyContext) *response.EngineRespo } log.V(3).Info("processing validation rule", "matchCount", matchCount, "applyRules", applyRules) - ctx.JSONContext.Reset() + ctx.jsonContext.Reset() startTime := time.Now() var ruleResp *response.RuleResponse @@ -148,14 +148,14 @@ func validateResource(log logr.Logger, ctx *PolicyContext) *response.EngineRespo func validateOldObject(log logr.Logger, ctx *PolicyContext, rule *kyvernov1.Rule) (*response.RuleResponse, error) { ctxCopy := ctx.Copy() - ctxCopy.NewResource = *ctxCopy.OldResource.DeepCopy() - ctxCopy.OldResource = unstructured.Unstructured{} + ctxCopy.newResource = *ctxCopy.oldResource.DeepCopy() + ctxCopy.oldResource = unstructured.Unstructured{} - if err := context.ReplaceResource(ctxCopy.JSONContext, ctxCopy.NewResource.Object); err != nil { + if err := context.ReplaceResource(ctxCopy.jsonContext, ctxCopy.newResource.Object); err != nil { return nil, errors.Wrapf(err, "failed to replace object in the JSON context") } - if err := context.ReplaceOldResource(ctxCopy.JSONContext, ctxCopy.OldResource.Object); err != nil { + if err := context.ReplaceOldResource(ctxCopy.jsonContext, ctxCopy.oldResource.Object); err != nil { return nil, errors.Wrapf(err, "failed to replace old object in the JSON context") } @@ -300,7 +300,7 @@ func (v *validator) validateForEach() *response.RuleResponse { } for _, foreach := range foreachList { - elements, err := evaluateList(foreach.List, v.ctx.JSONContext) + elements, err := evaluateList(foreach.List, v.ctx.jsonContext) if err != nil { v.log.V(2).Info("failed to evaluate list", "list", foreach.List, "error", err.Error()) continue @@ -321,8 +321,8 @@ func (v *validator) validateForEach() *response.RuleResponse { } func (v *validator) validateElements(foreach kyvernov1.ForEachValidation, elements []interface{}, elementScope *bool) (*response.RuleResponse, int) { - v.ctx.JSONContext.Checkpoint() - defer v.ctx.JSONContext.Restore() + v.ctx.jsonContext.Checkpoint() + defer v.ctx.jsonContext.Restore() applyCount := 0 for i, e := range elements { @@ -330,7 +330,7 @@ func (v *validator) validateElements(foreach kyvernov1.ForEachValidation, elemen continue } store.SetForeachElement(i) - v.ctx.JSONContext.Reset() + v.ctx.jsonContext.Reset() ctx := v.ctx.Copy() if err := addElementToContext(ctx, e, i, elementScope); err != nil { @@ -369,7 +369,7 @@ func addElementToContext(ctx *PolicyContext, e interface{}, elementIndex int, el if err != nil { return err } - if err := ctx.JSONContext.AddElement(data, elementIndex); err != nil { + if err := ctx.jsonContext.AddElement(data, elementIndex); err != nil { return errors.Wrapf(err, "failed to add element (%v) to JSON context", e) } dataMap, ok := data.(map[string]interface{}) @@ -392,7 +392,7 @@ func addElementToContext(ctx *PolicyContext, e interface{}, elementIndex int, el if scoped { u := unstructured.Unstructured{} u.SetUnstructuredContent(dataMap) - ctx.Element = u + ctx.element = u } return nil } @@ -413,7 +413,7 @@ func (v *validator) loadContext() error { func (v *validator) validateDeny() *response.RuleResponse { anyAllCond := v.deny.GetAnyAllConditions() - anyAllCond, err := variables.SubstituteAll(v.log, v.ctx.JSONContext, anyAllCond) + anyAllCond, err := variables.SubstituteAll(v.log, v.ctx.jsonContext, anyAllCond) if err != nil { return ruleError(v.rule, response.Validation, "failed to substitute variables in deny conditions", err) } @@ -427,7 +427,7 @@ func (v *validator) validateDeny() *response.RuleResponse { return ruleError(v.rule, response.Validation, "invalid deny conditions", err) } - deny := variables.EvaluateConditions(v.log, v.ctx.JSONContext, denyConditions) + deny := variables.EvaluateConditions(v.log, v.ctx.jsonContext, denyConditions) if deny { return ruleResponse(*v.rule, response.Validation, v.getDenyMessage(deny), response.RuleStatusFail, nil) } @@ -445,7 +445,7 @@ func (v *validator) getDenyMessage(deny bool) string { return fmt.Sprintf("validation error: rule %s failed", v.rule.Name) } - raw, err := variables.SubstituteAll(v.log, v.ctx.JSONContext, msg) + raw, err := variables.SubstituteAll(v.log, v.ctx.jsonContext, msg) if err != nil { return msg } @@ -454,12 +454,12 @@ func (v *validator) getDenyMessage(deny bool) string { } func getSpec(v *validator) (podSpec *corev1.PodSpec, metadata *metav1.ObjectMeta, err error) { - kind := v.ctx.NewResource.GetKind() + kind := v.ctx.newResource.GetKind() if kind == "DaemonSet" || kind == "Deployment" || kind == "Job" || kind == "StatefulSet" || kind == "ReplicaSet" || kind == "ReplicationController" { var deployment appsv1.Deployment - resourceBytes, err := v.ctx.NewResource.MarshalJSON() + resourceBytes, err := v.ctx.newResource.MarshalJSON() if err != nil { return nil, nil, err } @@ -473,7 +473,7 @@ func getSpec(v *validator) (podSpec *corev1.PodSpec, metadata *metav1.ObjectMeta } else if kind == "CronJob" { var cronJob batchv1.CronJob - resourceBytes, err := v.ctx.NewResource.MarshalJSON() + resourceBytes, err := v.ctx.newResource.MarshalJSON() if err != nil { return nil, nil, err } @@ -486,7 +486,7 @@ func getSpec(v *validator) (podSpec *corev1.PodSpec, metadata *metav1.ObjectMeta } else if kind == "Pod" { var pod corev1.Pod - resourceBytes, err := v.ctx.NewResource.MarshalJSON() + resourceBytes, err := v.ctx.newResource.MarshalJSON() if err != nil { return nil, nil, err } @@ -531,8 +531,8 @@ func (v *validator) validatePodSecurity() *response.RuleResponse { } func (v *validator) validateResourceWithRule() *response.RuleResponse { - if !isEmptyUnstructured(&v.ctx.Element) { - return v.validatePatterns(v.ctx.Element) + if !isEmptyUnstructured(&v.ctx.element) { + return v.validatePatterns(v.ctx.element) } if isDeleteRequest(v.ctx) { @@ -540,18 +540,18 @@ func (v *validator) validateResourceWithRule() *response.RuleResponse { return nil } - resp := v.validatePatterns(v.ctx.NewResource) + resp := v.validatePatterns(v.ctx.newResource) return resp } func isDeleteRequest(ctx *PolicyContext) bool { // if the OldResource is not empty, and the NewResource is empty, the request is a DELETE - return isEmptyUnstructured(&ctx.NewResource) + return isEmptyUnstructured(&ctx.newResource) } func isUpdateRequest(ctx *PolicyContext) bool { // is the OldObject and NewObject are available, the request is an UPDATE - return !isEmptyUnstructured(&ctx.OldResource) && !isEmptyUnstructured(&ctx.NewResource) + return !isEmptyUnstructured(&ctx.oldResource) && !isEmptyUnstructured(&ctx.newResource) } func isEmptyUnstructured(u *unstructured.Unstructured) bool { @@ -568,13 +568,13 @@ func isEmptyUnstructured(u *unstructured.Unstructured) bool { // matches checks if either the new or old resource satisfies the filter conditions defined in the rule func matches(logger logr.Logger, rule *kyvernov1.Rule, ctx *PolicyContext) bool { - err := MatchesResourceDescription(ctx.NewResource, *rule, ctx.AdmissionInfo, ctx.ExcludeGroupRole, ctx.NamespaceLabels, "") + err := MatchesResourceDescription(ctx.newResource, *rule, ctx.admissionInfo, ctx.excludeGroupRole, ctx.namespaceLabels, "") if err == nil { return true } - if !reflect.DeepEqual(ctx.OldResource, unstructured.Unstructured{}) { - err := MatchesResourceDescription(ctx.OldResource, *rule, ctx.AdmissionInfo, ctx.ExcludeGroupRole, ctx.NamespaceLabels, "") + if !reflect.DeepEqual(ctx.oldResource, unstructured.Unstructured{}) { + err := MatchesResourceDescription(ctx.oldResource, *rule, ctx.admissionInfo, ctx.excludeGroupRole, ctx.namespaceLabels, "") if err == nil { return true } @@ -718,7 +718,7 @@ func (v *validator) buildErrorMessage(err error, path string) string { return fmt.Sprintf("validation error: rule %s execution error: %s", v.rule.Name, err.Error()) } - msgRaw, sErr := variables.SubstituteAll(v.log, v.ctx.JSONContext, v.rule.Validation.Message) + msgRaw, sErr := variables.SubstituteAll(v.log, v.ctx.jsonContext, v.rule.Validation.Message) if sErr != nil { v.log.V(2).Info("failed to substitute variables in message", "error", sErr) return fmt.Sprintf("validation error: variables substitution error in rule %s execution error: %s", v.rule.Name, err.Error()) @@ -749,7 +749,7 @@ func buildAnyPatternErrorMessage(rule *kyvernov1.Rule, errors []string) string { func (v *validator) substitutePatterns() error { if v.pattern != nil { - i, err := variables.SubstituteAll(v.log, v.ctx.JSONContext, v.pattern) + i, err := variables.SubstituteAll(v.log, v.ctx.jsonContext, v.pattern) if err != nil { return err } @@ -759,7 +759,7 @@ func (v *validator) substitutePatterns() error { } if v.anyPattern != nil { - i, err := variables.SubstituteAll(v.log, v.ctx.JSONContext, v.anyPattern) + i, err := variables.SubstituteAll(v.log, v.ctx.jsonContext, v.anyPattern) if err != nil { return err } @@ -776,7 +776,7 @@ func (v *validator) substituteDeny() error { return nil } - i, err := variables.SubstituteAll(v.log, v.ctx.JSONContext, v.deny) + i, err := variables.SubstituteAll(v.log, v.ctx.jsonContext, v.deny) if err != nil { return err } diff --git a/pkg/engine/validation_test.go b/pkg/engine/validation_test.go index 7a5e71d039..0437e2576c 100644 --- a/pkg/engine/validation_test.go +++ b/pkg/engine/validation_test.go @@ -131,7 +131,7 @@ func TestValidate_image_tag_fail(t *testing.T) { "validation error: imagePullPolicy 'Always' required with tag 'latest'. rule validate-latest failed at path /spec/containers/0/imagePullPolicy/", } - er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + er := Validate(&PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: context.NewContext()}) for index, r := range er.PolicyResponse.Rules { assert.Equal(t, r.Message, msgs[index]) } @@ -231,7 +231,7 @@ func TestValidate_image_tag_pass(t *testing.T) { "validation rule 'validate-tag' passed.", "validation rule 'validate-latest' passed.", } - er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + er := Validate(&PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: context.NewContext()}) for index, r := range er.PolicyResponse.Rules { assert.Equal(t, r.Message, msgs[index]) } @@ -305,7 +305,7 @@ func TestValidate_Fail_anyPattern(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + er := Validate(&PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: context.NewContext()}) assert.Assert(t, !er.IsSuccessful()) msgs := []string{"validation error: A namespace is required. rule check-default-namespace[0] failed at path /metadata/namespace/ rule check-default-namespace[1] failed at path /metadata/namespace/"} @@ -388,7 +388,7 @@ func TestValidate_host_network_port(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + er := Validate(&PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: context.NewContext()}) msgs := []string{"validation error: Host network and port are not allowed. rule validate-host-network-port failed at path /spec/containers/0/ports/0/hostPort/"} for index, r := range er.PolicyResponse.Rules { @@ -478,7 +478,7 @@ func TestValidate_anchor_arraymap_pass(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + er := Validate(&PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: context.NewContext()}) msgs := []string{"validation rule 'validate-host-path' passed."} for index, r := range er.PolicyResponse.Rules { @@ -566,7 +566,7 @@ func TestValidate_anchor_arraymap_fail(t *testing.T) { assert.NilError(t, err) resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + er := Validate(&PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: context.NewContext()}) msgs := []string{"validation error: Host path '/var/lib/' is not allowed. rule validate-host-path failed at path /spec/volumes/0/hostPath/path/"} for index, r := range er.PolicyResponse.Rules { @@ -636,7 +636,7 @@ func TestValidate_anchor_map_notfound(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + er := Validate(&PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: context.NewContext()}) msgs := []string{"validation rule 'pod rule 2' passed."} for index, r := range er.PolicyResponse.Rules { @@ -709,7 +709,7 @@ func TestValidate_anchor_map_found_valid(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + er := Validate(&PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: context.NewContext()}) msgs := []string{"validation rule 'pod rule 2' passed."} for index, r := range er.PolicyResponse.Rules { @@ -783,7 +783,7 @@ func TestValidate_inequality_List_Processing(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + er := Validate(&PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: context.NewContext()}) msgs := []string{"validation rule 'pod rule 2' passed."} for index, r := range er.PolicyResponse.Rules { @@ -863,7 +863,7 @@ func TestValidate_inequality_List_ProcessingBrackets(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + er := Validate(&PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: context.NewContext()}) msgs := []string{"validation rule 'pod rule 2' passed."} for index, r := range er.PolicyResponse.Rules { @@ -937,7 +937,7 @@ func TestValidate_anchor_map_found_invalid(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + er := Validate(&PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: context.NewContext()}) msgs := []string{"validation error: pod: validate run as non root user. rule pod rule 2 failed at path /spec/securityContext/runAsNonRoot/"} for index, r := range er.PolicyResponse.Rules { @@ -1012,7 +1012,7 @@ func TestValidate_AnchorList_pass(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + er := Validate(&PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: context.NewContext()}) msgs := []string{"validation rule 'pod image rule' passed."} for index, r := range er.PolicyResponse.Rules { @@ -1087,7 +1087,7 @@ func TestValidate_AnchorList_fail(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + er := Validate(&PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: context.NewContext()}) assert.Assert(t, !er.IsSuccessful()) } @@ -1157,7 +1157,7 @@ func TestValidate_existenceAnchor_fail(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + er := Validate(&PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: context.NewContext()}) assert.Assert(t, !er.IsSuccessful()) } @@ -1227,7 +1227,7 @@ func TestValidate_existenceAnchor_pass(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + er := Validate(&PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: context.NewContext()}) msgs := []string{"validation rule 'pod image rule' passed."} for index, r := range er.PolicyResponse.Rules { @@ -1315,7 +1315,7 @@ func TestValidate_negationAnchor_deny(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + er := Validate(&PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: context.NewContext()}) msgs := []string{"validation error: Host path is not allowed. rule validate-host-path failed at path /spec/volumes/0/hostPath/"} for index, r := range er.PolicyResponse.Rules { @@ -1402,7 +1402,7 @@ func TestValidate_negationAnchor_pass(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + er := Validate(&PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: context.NewContext()}) msgs := []string{"validation rule 'validate-host-path' passed."} for index, r := range er.PolicyResponse.Rules { @@ -1475,9 +1475,9 @@ func Test_VariableSubstitutionPathNotExistInPattern(t *testing.T) { assert.NilError(t, err) policyContext := &PolicyContext{ - Policy: &policy, - JSONContext: ctx, - NewResource: *resourceUnstructured} + policy: &policy, + jsonContext: ctx, + newResource: *resourceUnstructured} er := Validate(policyContext) assert.Equal(t, len(er.PolicyResponse.Rules), 1) @@ -1568,9 +1568,9 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_OnePatternStatisfiesButSu assert.NilError(t, err) policyContext := &PolicyContext{ - Policy: &policy, - JSONContext: ctx, - NewResource: *resourceUnstructured} + policy: &policy, + jsonContext: ctx, + newResource: *resourceUnstructured} er := Validate(policyContext) assert.Equal(t, len(er.PolicyResponse.Rules), 1) @@ -1629,9 +1629,9 @@ func Test_VariableSubstitution_NotOperatorWithStringVariable(t *testing.T) { assert.NilError(t, err) policyContext := &PolicyContext{ - Policy: &policy, - JSONContext: ctx, - NewResource: *resourceUnstructured} + policy: &policy, + jsonContext: ctx, + newResource: *resourceUnstructured} er := Validate(policyContext) assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusFail) assert.Equal(t, er.PolicyResponse.Rules[0].Message, "validation error: rule not-operator-with-variable-should-alway-fail-validation failed at path /spec/content/") @@ -1720,9 +1720,9 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathNotPresent(t *test assert.NilError(t, err) policyContext := &PolicyContext{ - Policy: &policy, - JSONContext: ctx, - NewResource: *resourceUnstructured} + policy: &policy, + jsonContext: ctx, + newResource: *resourceUnstructured} er := Validate(policyContext) assert.Equal(t, len(er.PolicyResponse.Rules), 1) @@ -1813,9 +1813,9 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathPresent_NonePatter assert.NilError(t, err) policyContext := &PolicyContext{ - Policy: &policy, - JSONContext: ctx, - NewResource: *resourceUnstructured} + policy: &policy, + jsonContext: ctx, + newResource: *resourceUnstructured} er := Validate(policyContext) assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusFail) @@ -1918,9 +1918,9 @@ func Test_VariableSubstitutionValidate_VariablesInMessageAreResolved(t *testing. assert.NilError(t, err) policyContext := &PolicyContext{ - Policy: &policy, - JSONContext: ctx, - NewResource: *resourceUnstructured} + policy: &policy, + jsonContext: ctx, + newResource: *resourceUnstructured} er := Validate(policyContext) assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusFail) assert.Equal(t, er.PolicyResponse.Rules[0].Message, "The animal cow is not in the allowed list of animals.") @@ -1971,9 +1971,9 @@ func Test_Flux_Kustomization_PathNotPresent(t *testing.T) { assert.NilError(t, err) policyContext := &PolicyContext{ - Policy: &policy, - JSONContext: ctx, - NewResource: *resourceUnstructured} + policy: &policy, + jsonContext: ctx, + newResource: *resourceUnstructured} er := Validate(policyContext) for i, rule := range er.PolicyResponse.Rules { @@ -2133,11 +2133,11 @@ func executeTest(t *testing.T, test testCase) { } pc := &PolicyContext{ - Policy: &policy, - NewResource: newR, - OldResource: oldR, - AdmissionInfo: userInfo, - JSONContext: ctx, + policy: &policy, + newResource: newR, + oldResource: oldR, + admissionInfo: userInfo, + jsonContext: ctx, } resp := Validate(pc) @@ -2239,7 +2239,7 @@ func TestValidate_context_variable_substitution_CLI(t *testing.T) { msgs := []string{ "restrict pod counts to be no more than 10 on node minikube", } - er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + er := Validate(&PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: context.NewContext()}) for index, r := range er.PolicyResponse.Rules { assert.Equal(t, r.Message, msgs[index]) } @@ -2328,7 +2328,7 @@ func Test_EmptyStringInDenyCondition(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw) assert.NilError(t, err) - er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: ctx}) + er := Validate(&PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: ctx}) assert.Assert(t, !er.IsSuccessful()) } @@ -2417,7 +2417,7 @@ func Test_StringInDenyCondition(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw) assert.NilError(t, err) - er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: ctx}) + er := Validate(&PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: ctx}) assert.Assert(t, er.IsSuccessful()) } @@ -3001,9 +3001,9 @@ func testForEach(t *testing.T, policyraw []byte, resourceRaw []byte, msg string, assert.NilError(t, err) policyContext := &PolicyContext{ - Policy: &policy, - JSONContext: ctx, - NewResource: *resourceUnstructured} + policy: &policy, + jsonContext: ctx, + newResource: *resourceUnstructured} er := Validate(policyContext) assert.Equal(t, er.PolicyResponse.Rules[0].Status, status) @@ -3065,17 +3065,17 @@ func Test_delete_ignore_pattern(t *testing.T) { assert.NilError(t, err) policyContextCreate := &PolicyContext{ - Policy: &policy, - JSONContext: ctx, - NewResource: *resourceUnstructured} + policy: &policy, + jsonContext: ctx, + newResource: *resourceUnstructured} engineResponseCreate := Validate(policyContextCreate) assert.Equal(t, len(engineResponseCreate.PolicyResponse.Rules), 1) assert.Equal(t, engineResponseCreate.PolicyResponse.Rules[0].Status, response.RuleStatusFail) policyContextDelete := &PolicyContext{ - Policy: &policy, - JSONContext: ctx, - OldResource: *resourceUnstructured} + policy: &policy, + jsonContext: ctx, + oldResource: *resourceUnstructured} engineResponseDelete := Validate(policyContextDelete) assert.Equal(t, len(engineResponseDelete.PolicyResponse.Rules), 0) } @@ -3135,7 +3135,7 @@ func Test_ValidatePattern_anyPattern(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(tc.rawResource) assert.NilError(t, err) - er := Validate(&PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + er := Validate(&PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: context.NewContext()}) if tc.expectedFailed { assert.Assert(t, er.IsFailed()) } else if tc.expectedSkipped { diff --git a/pkg/policy/apply.go b/pkg/policy/apply.go index bc16c80439..2e1e5db47a 100644 --- a/pkg/policy/apply.go +++ b/pkg/policy/apply.go @@ -61,14 +61,12 @@ func applyPolicy(policy kyvernov1.PolicyInterface, resource unstructured.Unstruc logger.Error(err, "failed to process mutation rule") } - policyCtx := &engine.PolicyContext{ - Policy: policy, - NewResource: resource, - ExcludeGroupRole: excludeGroupRole, - JSONContext: ctx, - Client: client, - NamespaceLabels: namespaceLabels, - } + policyCtx := engine.NewPolicyContextWithJsonContext(ctx). + WithPolicy(policy). + WithNewResource(resource). + WithNamespaceLabels(namespaceLabels). + WithClient(client). + WithExcludeGroupRole(excludeGroupRole...) engineResponseValidation = engine.Validate(policyCtx) engineResponses = append(engineResponses, mergeRuleRespose(engineResponseMutation, engineResponseValidation)) @@ -77,12 +75,10 @@ func applyPolicy(policy kyvernov1.PolicyInterface, resource unstructured.Unstruc } func mutation(policy kyvernov1.PolicyInterface, resource unstructured.Unstructured, log logr.Logger, jsonContext context.Interface, namespaceLabels map[string]string) (*response.EngineResponse, error) { - policyContext := &engine.PolicyContext{ - Policy: policy, - NewResource: resource, - JSONContext: jsonContext, - NamespaceLabels: namespaceLabels, - } + policyContext := engine.NewPolicyContextWithJsonContext(jsonContext). + WithPolicy(policy). + WithNamespaceLabels(namespaceLabels). + WithNewResource(resource) engineResponse := engine.Mutate(policyContext) if !engineResponse.IsSuccessful() { diff --git a/pkg/testrunner/scenario.go b/pkg/testrunner/scenario.go index 92bcff2d4b..085ecc9eef 100644 --- a/pkg/testrunner/scenario.go +++ b/pkg/testrunner/scenario.go @@ -14,7 +14,6 @@ import ( kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" "github.com/kyverno/kyverno/pkg/clients/dclient" "github.com/kyverno/kyverno/pkg/engine" - enginecontext "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/response" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" @@ -144,14 +143,9 @@ func runTestCase(t *testing.T, tc TestCase) bool { t.FailNow() } - ctx := &engine.PolicyContext{ - Policy: policy, - NewResource: *resource, - ExcludeGroupRole: []string{}, - JSONContext: enginecontext.NewContext(), - } + policyContext := engine.NewPolicyContext().WithPolicy(policy).WithNewResource(*resource) - er := engine.Mutate(ctx) + er := engine.Mutate(policyContext) t.Log("---Mutation---") validateResource(t, er.PatchedResource, tc.Expected.Mutation.PatchedResource) validateResponse(t, er.PolicyResponse, tc.Expected.Mutation.PolicyResponse) @@ -161,14 +155,9 @@ func runTestCase(t *testing.T, tc TestCase) bool { resource = &er.PatchedResource } - ctx = &engine.PolicyContext{ - Policy: policy, - NewResource: *resource, - ExcludeGroupRole: []string{}, - JSONContext: enginecontext.NewContext(), - } + policyContext = policyContext.WithNewResource(*resource) - er = engine.Validate(ctx) + er = engine.Validate(policyContext) t.Log("---Validation---") validateResponse(t, er.PolicyResponse, tc.Expected.Validation.PolicyResponse) @@ -182,16 +171,7 @@ func runTestCase(t *testing.T, tc TestCase) bool { if err := createNamespace(client, resource); err != nil { t.Error(err) } else { - policyContext := &engine.PolicyContext{ - NewResource: *resource, - Policy: policy, - Client: client, - ExcludeGroupRole: []string{}, - ExcludeResourceFunc: func(s1, s2, s3 string) bool { - return false - }, - JSONContext: enginecontext.NewContext(), - } + policyContext := policyContext.WithClient(client) er = engine.ApplyBackgroundChecks(policyContext) t.Log(("---Generation---")) diff --git a/pkg/webhooks/resource/generation/generation.go b/pkg/webhooks/resource/generation/generation.go index ad8850fbc0..1d0ad7660b 100644 --- a/pkg/webhooks/resource/generation/generation.go +++ b/pkg/webhooks/resource/generation/generation.go @@ -88,9 +88,9 @@ func (h *generationHandler) Handle( if (request.Operation == admissionv1.Create || request.Operation == admissionv1.Update) && len(policies) != 0 { for _, policy := range policies { var rules []response.RuleResponse - policyContext.Policy = policy + policyContext := policyContext.WithPolicy(policy) if request.Kind.Kind != "Namespace" && request.Namespace != "" { - policyContext.NamespaceLabels = common.GetNamespaceSelectorsFromNamespaceLister(request.Kind.Kind, request.Namespace, h.nsLister, h.log) + policyContext = policyContext.WithNamespaceLabels(common.GetNamespaceSelectorsFromNamespaceLister(request.Kind.Kind, request.Namespace, h.nsLister, h.log)) } engineResponse := engine.ApplyBackgroundChecks(policyContext) for _, rule := range engineResponse.PolicyResponse.Rules { @@ -113,11 +113,12 @@ func (h *generationHandler) Handle( go webhookutils.RegisterPolicyExecutionDurationMetricGenerate(context.TODO(), h.log, metricsConfig, string(request.Operation), policy, *engineResponse) } - if failedResponse := applyUpdateRequest(request, kyvernov1beta1.Generate, h.urGenerator, policyContext.AdmissionInfo, request.Operation, engineResponses...); failedResponse != nil { + if failedResponse := applyUpdateRequest(request, kyvernov1beta1.Generate, h.urGenerator, policyContext.AdmissionInfo(), request.Operation, engineResponses...); failedResponse != nil { // report failure event for _, failedUR := range failedResponse { err := fmt.Errorf("failed to create Update Request: %v", failedUR.err) - e := event.NewBackgroundFailedEvent(err, failedUR.ur.Policy, "", event.GeneratePolicyController, &policyContext.NewResource) + newResource := policyContext.NewResource() + e := event.NewBackgroundFailedEvent(err, failedUR.ur.Policy, "", event.GeneratePolicyController, &newResource) h.eventGen.Add(e...) } } diff --git a/pkg/webhooks/resource/handlers.go b/pkg/webhooks/resource/handlers.go index 10f35ee836..55c1ff894b 100644 --- a/pkg/webhooks/resource/handlers.go +++ b/pkg/webhooks/resource/handlers.go @@ -117,7 +117,7 @@ func (h *handlers) Validate(ctx context.Context, logger logr.Logger, request *ad logger.V(4).Info("processing policies for validate admission request", "validate", len(policies), "mutate", len(mutatePolicies), "generate", len(generatePolicies)) - policyContext, err := h.pcBuilder.Build(request, generatePolicies...) + policyContext, err := h.pcBuilder.Build(request) if err != nil { return errorResponse(logger, request.UID, err, "failed create policy context") } @@ -152,13 +152,13 @@ func (h *handlers) Mutate(ctx context.Context, logger logr.Logger, request *admi return admissionutils.ResponseSuccess(request.UID) } logger.V(4).Info("processing policies for mutate admission request", "mutatePolicies", len(mutatePolicies), "verifyImagesPolicies", len(verifyImagesPolicies)) - policyContext, err := h.pcBuilder.Build(request, mutatePolicies...) + policyContext, err := h.pcBuilder.Build(request) if err != nil { logger.Error(err, "failed to build policy context") return admissionutils.Response(request.UID, err) } // update container images to a canonical form - if err := enginectx.MutateResourceWithImageInfo(request.Object.Raw, policyContext.JSONContext); err != nil { + if err := enginectx.MutateResourceWithImageInfo(request.Object.Raw, policyContext.JSONContext()); err != nil { logger.Error(err, "failed to patch images info to resource, policies that mutate images may be impacted") } mh := mutation.NewMutationHandler(logger, h.eventGen, h.openApiManager, h.nsLister) @@ -169,7 +169,7 @@ func (h *handlers) Mutate(ctx context.Context, logger logr.Logger, request *admi } newRequest := patchRequest(mutatePatches, request, logger) // rebuild context to process images updated via mutate policies - policyContext, err = h.pcBuilder.Build(newRequest, mutatePolicies...) + policyContext, err = h.pcBuilder.Build(newRequest) if err != nil { logger.Error(err, "failed to build policy context") return admissionutils.Response(request.UID, err) diff --git a/pkg/webhooks/resource/imageverification/handler.go b/pkg/webhooks/resource/imageverification/handler.go index 003aa89996..21199dbd0f 100644 --- a/pkg/webhooks/resource/imageverification/handler.go +++ b/pkg/webhooks/resource/imageverification/handler.go @@ -75,7 +75,7 @@ func (h *imageVerificationHandler) handleVerifyImages(logger logr.Logger, reques var patches [][]byte verifiedImageData := &engine.ImageVerificationMetadata{} for _, p := range policies { - policyContext.Policy = p + policyContext := policyContext.WithPolicy(p) resp, ivm := engine.VerifyAndPatchImages(policyContext) engineResponses = append(engineResponses, resp) @@ -83,7 +83,7 @@ func (h *imageVerificationHandler) handleVerifyImages(logger logr.Logger, reques verifiedImageData.Merge(ivm) } - failurePolicy := policyContext.Policy.GetSpec().GetFailurePolicy() + failurePolicy := policies[0].GetSpec().GetFailurePolicy() blocked := webhookutils.BlockRequest(engineResponses, failurePolicy, logger) if !isResourceDeleted(policyContext) { events := webhookutils.GenerateEvents(engineResponses, blocked) @@ -106,23 +106,26 @@ func (h *imageVerificationHandler) handleVerifyImages(logger logr.Logger, reques } } - go h.handleAudit(policyContext.NewResource, request, nil, engineResponses...) + go h.handleAudit(policyContext.NewResource(), request, nil, engineResponses...) warnings := webhookutils.GetWarningMessages(engineResponses) return true, "", jsonutils.JoinPatches(patches...), warnings } func hasAnnotations(context *engine.PolicyContext) bool { - annotations := context.NewResource.GetAnnotations() + newResource := context.NewResource() + annotations := newResource.GetAnnotations() return len(annotations) != 0 } func isResourceDeleted(policyContext *engine.PolicyContext) bool { var deletionTimeStamp *metav1.Time if reflect.DeepEqual(policyContext.NewResource, unstructured.Unstructured{}) { - deletionTimeStamp = policyContext.NewResource.GetDeletionTimestamp() + resource := policyContext.NewResource() + deletionTimeStamp = resource.GetDeletionTimestamp() } else { - deletionTimeStamp = policyContext.OldResource.GetDeletionTimestamp() + resource := policyContext.OldResource() + deletionTimeStamp = resource.GetDeletionTimestamp() } return deletionTimeStamp != nil } diff --git a/pkg/webhooks/resource/mutation/mutation.go b/pkg/webhooks/resource/mutation/mutation.go index a9c661c3d9..9c09e7ac3b 100644 --- a/pkg/webhooks/resource/mutation/mutation.go +++ b/pkg/webhooks/resource/mutation/mutation.go @@ -100,8 +100,8 @@ func (v *mutationHandler) applyMutations( continue } v.log.V(3).Info("applying policy mutate rules", "policy", policy.GetName()) - policyContext.Policy = policy - engineResponse, policyPatches, err := v.applyMutation(request, policyContext) + currentContext := policyContext.WithPolicy(policy) + engineResponse, policyPatches, err := v.applyMutation(request, currentContext) if err != nil { return nil, nil, fmt.Errorf("mutation policy %s error: %v", policy.GetName(), err) } @@ -114,7 +114,7 @@ func (v *mutationHandler) applyMutations( } } - policyContext.NewResource = engineResponse.PatchedResource + policyContext = currentContext.WithNewResource(engineResponse.PatchedResource) engineResponses = append(engineResponses, engineResponse) // registering the kyverno_policy_results_total metric concurrently @@ -141,20 +141,20 @@ func (v *mutationHandler) applyMutations( func (h *mutationHandler) applyMutation(request *admissionv1.AdmissionRequest, policyContext *engine.PolicyContext) (*response.EngineResponse, [][]byte, error) { if request.Kind.Kind != "Namespace" && request.Namespace != "" { - policyContext.NamespaceLabels = common.GetNamespaceSelectorsFromNamespaceLister(request.Kind.Kind, request.Namespace, h.nsLister, h.log) + policyContext = policyContext.WithNamespaceLabels(common.GetNamespaceSelectorsFromNamespaceLister(request.Kind.Kind, request.Namespace, h.nsLister, h.log)) } engineResponse := engine.Mutate(policyContext) policyPatches := engineResponse.GetPatches() if !engineResponse.IsSuccessful() { - return nil, nil, fmt.Errorf("failed to apply policy %s rules %v", policyContext.Policy.GetName(), engineResponse.GetFailedRules()) + return nil, nil, fmt.Errorf("failed to apply policy %s rules %v", policyContext.Policy().GetName(), engineResponse.GetFailedRules()) } - if policyContext.Policy.ValidateSchema() && engineResponse.PatchedResource.GetKind() != "*" { + if policyContext.Policy().ValidateSchema() && engineResponse.PatchedResource.GetKind() != "*" { err := h.openApiManager.ValidateResource(*engineResponse.PatchedResource.DeepCopy(), engineResponse.PatchedResource.GetAPIVersion(), engineResponse.PatchedResource.GetKind()) if err != nil { - return nil, nil, errors.Wrapf(err, "failed to validate resource mutated by policy %s", policyContext.Policy.GetName()) + return nil, nil, errors.Wrapf(err, "failed to validate resource mutated by policy %s", policyContext.Policy().GetName()) } } @@ -175,9 +175,11 @@ func logMutationResponse(patches [][]byte, engineResponses []*response.EngineRes func isResourceDeleted(policyContext *engine.PolicyContext) bool { var deletionTimeStamp *metav1.Time if reflect.DeepEqual(policyContext.NewResource, unstructured.Unstructured{}) { - deletionTimeStamp = policyContext.NewResource.GetDeletionTimestamp() + resource := policyContext.NewResource() + deletionTimeStamp = resource.GetDeletionTimestamp() } else { - deletionTimeStamp = policyContext.OldResource.GetDeletionTimestamp() + resource := policyContext.OldResource() + deletionTimeStamp = resource.GetDeletionTimestamp() } return deletionTimeStamp != nil } diff --git a/pkg/webhooks/resource/updaterequest.go b/pkg/webhooks/resource/updaterequest.go index cdab47ece0..418b254876 100644 --- a/pkg/webhooks/resource/updaterequest.go +++ b/pkg/webhooks/resource/updaterequest.go @@ -25,10 +25,11 @@ func (h *handlers) createUpdateRequests(logger logr.Logger, request *admissionv1 func (h *handlers) handleMutateExisting(logger logr.Logger, request *admissionv1.AdmissionRequest, policies []kyvernov1.PolicyInterface, policyContext *engine.PolicyContext, admissionRequestTimestamp time.Time) { if request.Operation == admissionv1.Delete { - policyContext.NewResource = policyContext.OldResource + policyContext = policyContext.WithNewResource(policyContext.OldResource()) } - if request.Operation == admissionv1.Update && policyContext.NewResource.GetDeletionTimestamp() != nil { + resource := policyContext.NewResource() + if request.Operation == admissionv1.Update && resource.GetDeletionTimestamp() != nil { logger.V(4).Info("skip creating UR for the trigger resource that is in termination") return } @@ -41,7 +42,7 @@ func (h *handlers) handleMutateExisting(logger logr.Logger, request *admissionv1 logger.V(4).Info("update request for mutateExisting policy") var rules []response.RuleResponse - policyContext.Policy = policy + policyContext := policyContext.WithPolicy(policy) engineResponse := engine.ApplyBackgroundChecks(policyContext) for _, rule := range engineResponse.PolicyResponse.Rules { @@ -61,10 +62,11 @@ func (h *handlers) handleMutateExisting(logger logr.Logger, request *admissionv1 go webhookutils.RegisterPolicyExecutionDurationMetricMutate(context.TODO(), logger, h.metricsConfig, string(request.Operation), policy, *engineResponse) } - if failedResponse := applyUpdateRequest(request, kyvernov1beta1.Mutate, h.urGenerator, policyContext.AdmissionInfo, request.Operation, engineResponses...); failedResponse != nil { + if failedResponse := applyUpdateRequest(request, kyvernov1beta1.Mutate, h.urGenerator, policyContext.AdmissionInfo(), request.Operation, engineResponses...); failedResponse != nil { for _, failedUR := range failedResponse { err := fmt.Errorf("failed to create update request: %v", failedUR.err) - events := event.NewBackgroundFailedEvent(err, failedUR.ur.Policy, "", event.GeneratePolicyController, &policyContext.NewResource) + resource := policyContext.NewResource() + events := event.NewBackgroundFailedEvent(err, failedUR.ur.Policy, "", event.GeneratePolicyController, &resource) h.eventGen.Add(events...) } } diff --git a/pkg/webhooks/resource/validation/validation.go b/pkg/webhooks/resource/validation/validation.go index 1682fe7f7e..63e4cbfb90 100644 --- a/pkg/webhooks/resource/validation/validation.go +++ b/pkg/webhooks/resource/validation/validation.go @@ -67,7 +67,7 @@ func (v *validationHandler) HandleValidation( ) (bool, string, []string) { if len(policies) == 0 { // invoke handleAudit as we may have some policies in audit mode to consider - go v.handleAudit(policyContext.NewResource, request, namespaceLabels) + go v.handleAudit(policyContext.NewResource(), request, namespaceLabels) return true, "", nil } @@ -76,9 +76,11 @@ func (v *validationHandler) HandleValidation( var deletionTimeStamp *metav1.Time if reflect.DeepEqual(policyContext.NewResource, unstructured.Unstructured{}) { - deletionTimeStamp = policyContext.NewResource.GetDeletionTimestamp() + resource := policyContext.NewResource() + deletionTimeStamp = resource.GetDeletionTimestamp() } else { - deletionTimeStamp = policyContext.OldResource.GetDeletionTimestamp() + resource := policyContext.OldResource() + deletionTimeStamp = resource.GetDeletionTimestamp() } if deletionTimeStamp != nil && request.Operation == admissionv1.Update { @@ -88,8 +90,7 @@ func (v *validationHandler) HandleValidation( var engineResponses []*response.EngineResponse failurePolicy := kyvernov1.Ignore for _, policy := range policies { - policyContext.Policy = policy - policyContext.NamespaceLabels = namespaceLabels + policyContext := policyContext.WithPolicy(policy).WithNamespaceLabels(namespaceLabels) if policy.GetSpec().GetFailurePolicy() == kyvernov1.Fail { failurePolicy = kyvernov1.Fail } @@ -101,8 +102,8 @@ func (v *validationHandler) HandleValidation( continue } - go webhookutils.RegisterPolicyResultsMetricValidation(context.TODO(), logger, metricsConfig, string(request.Operation), policyContext.Policy, *engineResponse) - go webhookutils.RegisterPolicyExecutionDurationMetricValidate(context.TODO(), logger, metricsConfig, string(request.Operation), policyContext.Policy, *engineResponse) + go webhookutils.RegisterPolicyResultsMetricValidation(context.TODO(), logger, metricsConfig, string(request.Operation), policyContext.Policy(), *engineResponse) + go webhookutils.RegisterPolicyExecutionDurationMetricValidate(context.TODO(), logger, metricsConfig, string(request.Operation), policyContext.Policy(), *engineResponse) engineResponses = append(engineResponses, engineResponse) if !engineResponse.IsSuccessful() { @@ -126,7 +127,7 @@ func (v *validationHandler) HandleValidation( return false, webhookutils.GetBlockedMessages(engineResponses), nil } - go v.handleAudit(policyContext.NewResource, request, namespaceLabels, engineResponses...) + go v.handleAudit(policyContext.NewResource(), request, namespaceLabels, engineResponses...) warnings := webhookutils.GetWarningMessages(engineResponses) return true, "", warnings @@ -134,14 +135,13 @@ func (v *validationHandler) HandleValidation( func (v *validationHandler) buildAuditResponses(resource unstructured.Unstructured, request *admissionv1.AdmissionRequest, namespaceLabels map[string]string) ([]*response.EngineResponse, error) { policies := v.pCache.GetPolicies(policycache.ValidateAudit, request.Kind.Kind, request.Namespace) - policyContext, err := v.pcBuilder.Build(request, policies...) + policyContext, err := v.pcBuilder.Build(request) if err != nil { return nil, err } var responses []*response.EngineResponse for _, policy := range policies { - policyContext.Policy = policy - policyContext.NamespaceLabels = namespaceLabels + policyContext := policyContext.WithPolicy(policy).WithNamespaceLabels(namespaceLabels) responses = append(responses, engine.Validate(policyContext)) } return responses, nil diff --git a/pkg/webhooks/resource/validation_test.go b/pkg/webhooks/resource/validation_test.go index 2141af6ec4..dd167dc71a 100644 --- a/pkg/webhooks/resource/validation_test.go +++ b/pkg/webhooks/resource/validation_test.go @@ -9,7 +9,6 @@ import ( kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" "github.com/kyverno/kyverno/pkg/engine" - "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/response" "github.com/kyverno/kyverno/pkg/engine/utils" webhookutils "github.com/kyverno/kyverno/pkg/webhooks/utils" @@ -531,7 +530,9 @@ func TestValidate_failure_action_overrides(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(tc.rawResource) assert.NilError(t, err) - er := engine.Validate(&engine.PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}) + er := engine.Validate( + engine.NewPolicyContext().WithPolicy(&policy).WithNewResource(*resourceUnstructured), + ) if tc.blocked && tc.messages != nil { for _, r := range er.PolicyResponse.Rules { msg := tc.messages[r.Name] @@ -589,7 +590,8 @@ func Test_RuleSelector(t *testing.T) { assert.NilError(t, err) assert.Assert(t, resourceUnstructured != nil) - ctx := &engine.PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()} + ctx := engine.NewPolicyContext().WithPolicy(&policy).WithNewResource(*resourceUnstructured) + resp := engine.Validate(ctx) assert.Assert(t, resp.PolicyResponse.RulesAppliedCount == 2) assert.Assert(t, resp.PolicyResponse.RulesErrorCount == 0) diff --git a/pkg/webhooks/utils/policy_context_builder.go b/pkg/webhooks/utils/policy_context_builder.go index 1cca4f730d..8c7d99b1fc 100644 --- a/pkg/webhooks/utils/policy_context_builder.go +++ b/pkg/webhooks/utils/policy_context_builder.go @@ -1,35 +1,18 @@ package utils import ( - kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1" "github.com/kyverno/kyverno/pkg/clients/dclient" "github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/engine" - enginectx "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/userinfo" - "github.com/kyverno/kyverno/pkg/utils" "github.com/pkg/errors" admissionv1 "k8s.io/api/admission/v1" rbacv1listers "k8s.io/client-go/listers/rbac/v1" ) type PolicyContextBuilder interface { - Build(*admissionv1.AdmissionRequest, ...kyvernov1.PolicyInterface) (*engine.PolicyContext, error) -} - -func newVariablesContext(request *admissionv1.AdmissionRequest, userRequestInfo *kyvernov1beta1.RequestInfo) (enginectx.Interface, error) { - ctx := enginectx.NewContext() - if err := ctx.AddRequest(request); err != nil { - return nil, errors.Wrap(err, "failed to load incoming request in context") - } - if err := ctx.AddUserInfo(*userRequestInfo); err != nil { - return nil, errors.Wrap(err, "failed to load userInfo in context") - } - if err := ctx.AddServiceAccount(userRequestInfo.AdmissionUserInfo.Username); err != nil { - return nil, errors.Wrap(err, "failed to load service account in context") - } - return ctx, nil + Build(*admissionv1.AdmissionRequest) (*engine.PolicyContext, error) } type policyContextBuilder struct { @@ -53,7 +36,7 @@ func NewPolicyContextBuilder( } } -func (b *policyContextBuilder) Build(request *admissionv1.AdmissionRequest, policies ...kyvernov1.PolicyInterface) (*engine.PolicyContext, error) { +func (b *policyContextBuilder) Build(request *admissionv1.AdmissionRequest) (*engine.PolicyContext, error) { userRequestInfo := kyvernov1beta1.RequestInfo{ AdmissionUserInfo: *request.UserInfo.DeepCopy(), } @@ -63,26 +46,5 @@ func (b *policyContextBuilder) Build(request *admissionv1.AdmissionRequest, poli userRequestInfo.Roles = roles userRequestInfo.ClusterRoles = clusterRoles } - ctx, err := newVariablesContext(request, &userRequestInfo) - if err != nil { - return nil, errors.Wrap(err, "failed to create policy rule context") - } - newResource, oldResource, err := utils.ExtractResources(nil, request) - if err != nil { - return nil, errors.Wrap(err, "failed to parse resource") - } - if err := ctx.AddImageInfos(&newResource); err != nil { - return nil, errors.Wrap(err, "failed to add image information to the policy rule context") - } - policyContext := &engine.PolicyContext{ - NewResource: newResource, - OldResource: oldResource, - AdmissionInfo: userRequestInfo, - ExcludeGroupRole: b.configuration.GetExcludeGroupRole(), - ExcludeResourceFunc: b.configuration.ToFilter, - JSONContext: ctx, - Client: b.client, - AdmissionOperation: true, - } - return policyContext, nil + return engine.NewPolicyContextFromAdmissionRequest(request, userRequestInfo, b.configuration, b.client) }