From 3957a1400e424d37e5ed1975b803b572d02cbff7 Mon Sep 17 00:00:00 2001 From: Jim Bugwadia Date: Mon, 27 Sep 2021 23:40:05 -0700 Subject: [PATCH] fix deny check and fmt Signed-off-by: Jim Bugwadia --- .../kyverno/v1alpha1/zz_generated.deepcopy.go | 1 + .../v1alpha1/zz_generated.deepcopy.go | 1 + pkg/engine/common/utils.go | 2 +- pkg/engine/context/context.go | 18 ++--- pkg/engine/generation.go | 8 +- pkg/engine/mutate/strategicPreprocessing.go | 4 +- pkg/engine/mutation.go | 2 +- pkg/engine/policyContext.go | 20 ++--- pkg/engine/response/response.go | 4 +- pkg/engine/response/response_test.go | 2 +- pkg/engine/response/status.go | 31 ++++---- pkg/engine/validate/common.go | 1 - pkg/engine/validate/validate.go | 2 +- pkg/engine/validate/validate_test.go | 11 ++- pkg/engine/validation.go | 78 +++++++++++-------- pkg/engine/validation_test.go | 50 ++++++------ pkg/engine/variables/vars.go | 4 +- pkg/testrunner/scenario_test.go | 1 - 18 files changed, 128 insertions(+), 112 deletions(-) diff --git a/pkg/api/kyverno/v1alpha1/zz_generated.deepcopy.go b/pkg/api/kyverno/v1alpha1/zz_generated.deepcopy.go index c9ffcd64d3..9effd366a1 100644 --- a/pkg/api/kyverno/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/api/kyverno/v1alpha1/zz_generated.deepcopy.go @@ -1,3 +1,4 @@ +//go:build !ignore_autogenerated // +build !ignore_autogenerated /* diff --git a/pkg/api/policyreport/v1alpha1/zz_generated.deepcopy.go b/pkg/api/policyreport/v1alpha1/zz_generated.deepcopy.go index 683557bf02..c189f46034 100644 --- a/pkg/api/policyreport/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/api/policyreport/v1alpha1/zz_generated.deepcopy.go @@ -1,3 +1,4 @@ +//go:build !ignore_autogenerated // +build !ignore_autogenerated /* diff --git a/pkg/engine/common/utils.go b/pkg/engine/common/utils.go index 3e8f98b774..5d0147ef3f 100644 --- a/pkg/engine/common/utils.go +++ b/pkg/engine/common/utils.go @@ -37,4 +37,4 @@ func ToMap(data interface{}) (map[string]interface{}, error) { } return mapData, nil -} \ No newline at end of file +} diff --git a/pkg/engine/context/context.go b/pkg/engine/context/context.go index 408dc15ed8..2389e0c26d 100644 --- a/pkg/engine/context/context.go +++ b/pkg/engine/context/context.go @@ -53,21 +53,21 @@ type EvalInterface interface { //Context stores the data resources as JSON type Context struct { - mutex sync.RWMutex - jsonRaw []byte + mutex sync.RWMutex + jsonRaw []byte jsonRawCheckpoints [][]byte - builtInVars []string - images *Images - log logr.Logger + builtInVars []string + images *Images + log logr.Logger } //NewContext returns a new context // builtInVars is the list of known variables (e.g. serviceAccountName) func NewContext(builtInVars ...string) *Context { ctx := Context{ - jsonRaw: []byte(`{}`), // empty json struct - builtInVars: builtInVars, - log: log.Log.WithName("context"), + jsonRaw: []byte(`{}`), // empty json struct + builtInVars: builtInVars, + log: log.Log.WithName("context"), jsonRawCheckpoints: make([][]byte, 0), } @@ -334,7 +334,7 @@ func (ctx *Context) reset(remove bool) { ctx.mutex.Lock() defer ctx.mutex.Unlock() - if len(ctx.jsonRawCheckpoints) == 0 { + if len(ctx.jsonRawCheckpoints) == 0 { return } diff --git a/pkg/engine/generation.go b/pkg/engine/generation.go index 8df53e34a2..1ef5dbc6cb 100644 --- a/pkg/engine/generation.go +++ b/pkg/engine/generation.go @@ -80,8 +80,8 @@ func filterRule(rule kyverno.Rule, policyContext *PolicyContext) *response.RuleR // if the oldResource matched, return "false" to delete GR for it if err = MatchesResourceDescription(oldResource, rule, admissionInfo, excludeGroupRole, namespaceLabels); err == nil { return &response.RuleResponse{ - Name: rule.Name, - Type: "Generation", + Name: rule.Name, + Type: "Generation", Status: response.RuleStatusFail, RuleStats: response.RuleStats{ ProcessingTime: time.Since(startTime), @@ -123,8 +123,8 @@ func filterRule(rule kyverno.Rule, policyContext *PolicyContext) *response.RuleR // build rule Response return &response.RuleResponse{ - Name: ruleCopy.Name, - Type: "Generation", + Name: ruleCopy.Name, + Type: "Generation", Status: response.RuleStatusPass, RuleStats: response.RuleStats{ ProcessingTime: time.Since(startTime), diff --git a/pkg/engine/mutate/strategicPreprocessing.go b/pkg/engine/mutate/strategicPreprocessing.go index 6bb9c9ec00..4e78a6b5c3 100644 --- a/pkg/engine/mutate/strategicPreprocessing.go +++ b/pkg/engine/mutate/strategicPreprocessing.go @@ -325,7 +325,7 @@ func convertRNodeToInterface(document *yaml.RNode) (interface{}, error) { } func checkCondition(logger logr.Logger, pattern *yaml.RNode, resource *yaml.RNode) error { - patternInterface, err := convertRNodeToInterface(pattern); + patternInterface, err := convertRNodeToInterface(pattern) if err != nil { return err } @@ -336,7 +336,7 @@ func checkCondition(logger logr.Logger, pattern *yaml.RNode, resource *yaml.RNod } err, _ = validate.MatchPattern(logger, resourceInterface, patternInterface) - if err != nil{ + if err != nil { return err } diff --git a/pkg/engine/mutation.go b/pkg/engine/mutation.go index f453e9173e..fe178a623e 100644 --- a/pkg/engine/mutation.go +++ b/pkg/engine/mutation.go @@ -118,7 +118,7 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) { Name: ruleCopy.Name, Type: utils.Validation.String(), Message: fmt.Sprintf("variable substitution failed: %s", err.Error()), - Status: response.RuleStatusPass, + Status: response.RuleStatusPass, } incrementAppliedCount(resp) diff --git a/pkg/engine/policyContext.go b/pkg/engine/policyContext.go index 1d55ef0b0e..dd23dad881 100644 --- a/pkg/engine/policyContext.go +++ b/pkg/engine/policyContext.go @@ -43,15 +43,15 @@ type PolicyContext struct { func (pc *PolicyContext) Copy() *PolicyContext { return &PolicyContext{ - Policy: pc.Policy, - NewResource: pc.NewResource, - OldResource: pc.OldResource, - AdmissionInfo: pc.AdmissionInfo, - Client: pc.Client, - ExcludeGroupRole: pc.ExcludeGroupRole, + Policy: pc.Policy, + NewResource: pc.NewResource, + OldResource: pc.OldResource, + AdmissionInfo: pc.AdmissionInfo, + Client: pc.Client, + ExcludeGroupRole: pc.ExcludeGroupRole, ExcludeResourceFunc: pc.ExcludeResourceFunc, - ResourceCache: pc.ResourceCache, - JSONContext: pc.JSONContext, - NamespaceLabels: pc.NamespaceLabels, + ResourceCache: pc.ResourceCache, + JSONContext: pc.JSONContext, + NamespaceLabels: pc.NamespaceLabels, } -} \ No newline at end of file +} diff --git a/pkg/engine/response/response.go b/pkg/engine/response/response.go index 8a640e0291..28e6cdfc08 100644 --- a/pkg/engine/response/response.go +++ b/pkg/engine/response/response.go @@ -87,7 +87,7 @@ type RuleResponse struct { Status RuleStatus `json:"status"` // statistics - RuleStats `json:",inline"` + RuleStats `json:",inline"` } //ToString ... @@ -161,7 +161,7 @@ func (er EngineResponse) GetResourceSpec() ResourceSpec { func (er EngineResponse) getRules(status RuleStatus) []string { var rules []string for _, r := range er.PolicyResponse.Rules { - if r.Status == status { + if r.Status == status { rules = append(rules, r.Name) } } diff --git a/pkg/engine/response/response_test.go b/pkg/engine/response/response_test.go index 09687a4087..caf15d8876 100644 --- a/pkg/engine/response/response_test.go +++ b/pkg/engine/response/response_test.go @@ -27,4 +27,4 @@ func Test_parse_yaml(t *testing.T) { } assert.Equal(t, 1, len(pr.Rules)) assert.Equal(t, RuleStatusFail, pr.Rules[0].Status) -} \ No newline at end of file +} diff --git a/pkg/engine/response/status.go b/pkg/engine/response/status.go index 627597d568..a35f9623f8 100644 --- a/pkg/engine/response/status.go +++ b/pkg/engine/response/status.go @@ -9,16 +9,21 @@ import ( // RuleStatus represents the status of rule execution type RuleStatus int +// RuleStatusPass is used to report the result of processing a rule. const ( - // RuleStatusPass indicates that the policy rule requirements are met + // RuleStatusPass indicates that the resources meets the policy rule requirements RuleStatusPass RuleStatus = iota - // Fail indicates that the policy rule requirements are not met + // Fail indicates that the resource does not meet the policy rule requirements RuleStatusFail - // Warn indicates that the policy rule requirements are not met, and the policy is not scored + // Warn indicates that the the resource does not meet the policy rule requirements, but the policy is not scored RuleStatusWarn - // Error indicates that the policy rule could not be evaluated due to a processing error + // Error indicates that the policy rule could not be evaluated due to a processing error, for + // example when a variable cannot be resolved in the policy rule definition. Note that variables + // that cannot be resolved in preconditions are replaced with empty values to allow existence + // checks. RuleStatusError - // Skip indicates that the policy rule was not selected based on user inputs or applicability + // Skip indicates that the policy rule was not selected based on user inputs or applicability, for example + // when preconditions are not met, or when conditional or global anchors are not satistied. RuleStatusSkip ) @@ -28,18 +33,18 @@ func (s *RuleStatus) String() string { var toString = map[RuleStatus]string{ RuleStatusPass: "Pass", - RuleStatusFail: "Fail", - RuleStatusWarn: "Warning", + RuleStatusFail: "Fail", + RuleStatusWarn: "Warning", RuleStatusError: "Error", - RuleStatusSkip: "Skip", + RuleStatusSkip: "Skip", } var toID = map[string]RuleStatus{ - "Pass": RuleStatusPass, - "Fail": RuleStatusFail, + "Pass": RuleStatusPass, + "Fail": RuleStatusFail, "Warning": RuleStatusWarn, - "Error": RuleStatusError, - "Skip": RuleStatusSkip, + "Error": RuleStatusError, + "Skip": RuleStatusSkip, } // MarshalJSON marshals the enum as a quoted json string @@ -66,7 +71,7 @@ func (s *RuleStatus) UnmarshalJSON(b []byte) error { return nil } -func getRuleStatus(s string) (*RuleStatus, error){ +func getRuleStatus(s string) (*RuleStatus, error) { for k, v := range toID { if s == k { return &v, nil diff --git a/pkg/engine/validate/common.go b/pkg/engine/validate/common.go index 9ec9910d2f..3009fc3fb7 100644 --- a/pkg/engine/validate/common.go +++ b/pkg/engine/validate/common.go @@ -40,4 +40,3 @@ func getRawKeyIfWrappedWithAttributes(str string) string { return str } } - diff --git a/pkg/engine/validate/validate.go b/pkg/engine/validate/validate.go index fb19489e18..7750e6118e 100644 --- a/pkg/engine/validate/validate.go +++ b/pkg/engine/validate/validate.go @@ -49,7 +49,7 @@ func validateResourceElement(log logr.Logger, resourceElement, patternElement, o log.V(4).Info("Pattern and resource have different structures.", "path", path, "expected", fmt.Sprintf("%T", patternElement), "current", fmt.Sprintf("%T", resourceElement)) return path, fmt.Errorf("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, patternElement, resourceElement) } - // CheckAnchorInResource - check anchor anchor key exists in resource and update the AnchorKey fields. + // CheckAnchorInResource - check anchor key exists in resource and update the AnchorKey fields. ac.CheckAnchorInResource(typedPatternElement, typedResourceElement) return validateMap(log, typedResourceElement, typedPatternElement, originPattern, path, ac) // array diff --git a/pkg/engine/validate/validate_test.go b/pkg/engine/validate/validate_test.go index 0fd3b5e35b..292f954c92 100644 --- a/pkg/engine/validate/validate_test.go +++ b/pkg/engine/validate/validate_test.go @@ -1516,7 +1516,7 @@ func Test_global_anchor(t *testing.T) { pattern []byte resource []byte nilErr bool - } { + }{ { name: "check global anchor_skip", pattern: []byte(`{"spec": {"containers": [{"name": "*","<(image)": "*:latest","imagePullPolicy": "!Always"}]}}`), @@ -1535,7 +1535,12 @@ func Test_global_anchor(t *testing.T) { testMatchPattern(t, testCases[1]) } -func testMatchPattern(t *testing.T, testCase struct {name string;pattern []byte;resource []byte;nilErr bool}) { +func testMatchPattern(t *testing.T, testCase struct { + name string + pattern []byte + resource []byte + nilErr bool +}) { var pattern, resource interface{} err := json.Unmarshal(testCase.pattern, &pattern) assert.NilError(t, err) @@ -1550,4 +1555,4 @@ func testMatchPattern(t *testing.T, testCase struct {name string;pattern []byte; err != nil, fmt.Sprintf("\ntest: %s\npattern: %s\nresource: %s\nmsg: %v", testCase.name, pattern, resource, err)) } -} \ No newline at end of file +} diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index 0e1865c26d..9c5fcaef8f 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -116,12 +116,11 @@ func validateResource(log logr.Logger, ctx *PolicyContext) *response.EngineRespo } func processValidationRule(log logr.Logger, ctx *PolicyContext, rule *kyverno.Rule) *response.RuleResponse { + v := newValidator(log, ctx, rule) if rule.Validation.ForEachValidation != nil { - v := newValidator(log, ctx, rule) return v.validateForEach() } - v := newValidator(log, ctx, rule) return v.validate() } @@ -140,41 +139,48 @@ func addRuleResponse(log logr.Logger, resp *response.EngineResponse, ruleResp *r } type validator struct { - log logr.Logger - ctx *PolicyContext - rule *kyverno.Rule - contextEntries []kyverno.ContextEntry + log logr.Logger + ctx *PolicyContext + rule *kyverno.Rule + contextEntries []kyverno.ContextEntry anyAllConditions apiextensions.JSON - pattern apiextensions.JSON - anyPattern apiextensions.JSON - deny *kyverno.Deny + pattern apiextensions.JSON + anyPattern apiextensions.JSON + deny *kyverno.Deny } func newValidator(log logr.Logger, ctx *PolicyContext, rule *kyverno.Rule) *validator { ruleCopy := rule.DeepCopy() return &validator{ - log: log, - rule: ruleCopy, - ctx: ctx, - contextEntries: ruleCopy.Context, + log: log, + rule: ruleCopy, + ctx: ctx, + contextEntries: ruleCopy.Context, anyAllConditions: ruleCopy.AnyAllConditions, - pattern: ruleCopy.Validation.Pattern, - anyPattern: ruleCopy.Validation.AnyPattern, - deny: ruleCopy.Validation.Deny, + pattern: ruleCopy.Validation.Pattern, + anyPattern: ruleCopy.Validation.AnyPattern, + deny: ruleCopy.Validation.Deny, } } func newForeachValidator(log logr.Logger, ctx *PolicyContext, rule *kyverno.Rule) *validator { ruleCopy := rule.DeepCopy() + + // Variable substitution expects JSON data, so we convert to a map + anyAllConditions, err := common.ToMap(ruleCopy.Validation.ForEachValidation.AnyAllConditions) + if err != nil { + log.Error(err, "failed to convert ruleCopy.Validation.ForEachValidation.AnyAllConditions") + } + return &validator{ - log: log, - ctx: ctx, - rule: ruleCopy, - contextEntries: ruleCopy.Validation.ForEachValidation.Context, - anyAllConditions: ruleCopy.Validation.ForEachValidation.AnyAllConditions, - pattern: ruleCopy.Validation.ForEachValidation.Pattern, - anyPattern: ruleCopy.Validation.ForEachValidation.AnyPattern, - deny: ruleCopy.Validation.ForEachValidation.Deny, + log: log, + ctx: ctx, + rule: ruleCopy, + contextEntries: ruleCopy.Validation.ForEachValidation.Context, + anyAllConditions: anyAllConditions, + pattern: ruleCopy.Validation.ForEachValidation.Pattern, + anyPattern: ruleCopy.Validation.ForEachValidation.AnyPattern, + deny: ruleCopy.Validation.ForEachValidation.Deny, } } @@ -233,6 +239,7 @@ func (v *validator) validateForEach() *response.RuleResponse { v.ctx.JSONContext.Checkpoint() defer v.ctx.JSONContext.Restore() + applyCount := 0 for _, e := range elements { v.ctx.JSONContext.Reset() @@ -246,14 +253,20 @@ func (v *validator) validateForEach() *response.RuleResponse { r := foreachValidator.validate() if r == nil { v.log.Info("skipping rule due to empty result") - + continue } else if r.Status == response.RuleStatusSkip { v.log.Info("skipping rule as preconditions were not met") - + continue } else if r.Status != response.RuleStatusPass { - msg := fmt.Sprintf("validation failed in foreach rule for %v", e) + msg := fmt.Sprintf("validation failed in foreach rule for %v", r.Message) return ruleResponse(v.rule, msg, r.Status) } + + applyCount++ + } + + if applyCount == 0 { + return ruleResponse(v.rule, "", response.RuleStatusSkip) } return ruleResponse(v.rule, "", response.RuleStatusPass) @@ -290,7 +303,6 @@ func (v *validator) evaluateList(jmesPath string) ([]interface{}, error) { return l, nil } - func (v *validator) loadContext() error { if err := LoadContext(v.log, v.contextEntries, v.ctx.ResourceCache, v.ctx, v.rule.Name); err != nil { if _, ok := err.(gojmespath.NotFoundError); ok { @@ -481,7 +493,7 @@ func (v *validator) validatePatterns(resource unstructured.Unstructured) *respon return ruleResponse(v.rule, v.rule.Validation.Message, response.RuleStatusPass) } -func deserializeAnyPattern(anyPattern apiextensions.JSON ) ([]interface{}, error) { +func deserializeAnyPattern(anyPattern apiextensions.JSON) ([]interface{}, error) { if anyPattern == nil { return nil, nil } @@ -538,7 +550,7 @@ func buildAnyPatternErrorMessage(rule *kyverno.Rule, errors []string) string { return fmt.Sprintf("validation error: %s. %s", rule.Validation.Message, errStr) } -func (v *validator) substitutePatterns() error { +func (v *validator) substitutePatterns() error { if v.pattern != nil { i, err := variables.SubstituteAll(v.log, v.ctx.JSONContext, v.pattern) if err != nil { @@ -562,7 +574,7 @@ func (v *validator) substitutePatterns() error { return nil } -func (v *validator) substituteDeny() error { +func (v *validator) substituteDeny() error { if v.deny == nil { return nil } @@ -586,6 +598,6 @@ func ruleResponse(rule *kyverno.Rule, msg string, status response.RuleStatus) *r Name: rule.Name, Type: utils.Validation.String(), Message: msg, - Status: status, + Status: status, } -} \ No newline at end of file +} diff --git a/pkg/engine/validation_test.go b/pkg/engine/validation_test.go index 54e7c667ad..259d3c1666 100644 --- a/pkg/engine/validation_test.go +++ b/pkg/engine/validation_test.go @@ -1921,9 +1921,9 @@ func Test_VariableSubstitutionValidate_VariablesInMessageAreResolved(t *testing. func Test_Flux_Kustomization_PathNotPresent(t *testing.T) { tests := []struct { - name string - policyRaw []byte - resourceRaw []byte + name string + policyRaw []byte + resourceRaw []byte expectedResults []response.RuleStatus expectedMessages []string }{ @@ -1931,7 +1931,7 @@ func Test_Flux_Kustomization_PathNotPresent(t *testing.T) { name: "path-not-present", policyRaw: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"flux-multi-tenancy"},"spec":{"validationFailureAction":"enforce","rules":[{"name":"serviceAccountName","exclude":{"resources":{"namespaces":["flux-system"]}},"match":{"resources":{"kinds":["Kustomization","HelmRelease"]}},"validate":{"message":".spec.serviceAccountName is required","pattern":{"spec":{"serviceAccountName":"?*"}}}},{"name":"sourceRefNamespace","exclude":{"resources":{"namespaces":["flux-system"]}},"match":{"resources":{"kinds":["Kustomization","HelmRelease"]}},"validate":{"message":"spec.sourceRef.namespace must be the same as metadata.namespace","deny":{"conditions":[{"key":"{{request.object.spec.sourceRef.namespace}}","operator":"NotEquals","value":"{{request.object.metadata.namespace}}"}]}}}]}}`), // referred variable path not present - resourceRaw: []byte(`{"apiVersion":"kustomize.toolkit.fluxcd.io/v1beta1","kind":"Kustomization","metadata":{"name":"dev-team","namespace":"apps"},"spec":{"serviceAccountName":"dev-team","interval":"5m","sourceRef":{"kind":"GitRepository","name":"dev-team"},"prune":true,"validation":"client"}}`), + resourceRaw: []byte(`{"apiVersion":"kustomize.toolkit.fluxcd.io/v1beta1","kind":"Kustomization","metadata":{"name":"dev-team","namespace":"apps"},"spec":{"serviceAccountName":"dev-team","interval":"5m","sourceRef":{"kind":"GitRepository","name":"dev-team"},"prune":true,"validation":"client"}}`), expectedResults: []response.RuleStatus{response.RuleStatusPass, response.RuleStatusError}, expectedMessages: []string{"validation rule 'serviceAccountName' passed.", "failed to substitute variables in deny conditions: Unknown key \"namespace\" in path"}, }, @@ -1939,7 +1939,7 @@ func Test_Flux_Kustomization_PathNotPresent(t *testing.T) { name: "resource-with-violation", policyRaw: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"flux-multi-tenancy"},"spec":{"validationFailureAction":"enforce","rules":[{"name":"serviceAccountName","exclude":{"resources":{"namespaces":["flux-system"]}},"match":{"resources":{"kinds":["Kustomization","HelmRelease"]}},"validate":{"message":".spec.serviceAccountName is required","pattern":{"spec":{"serviceAccountName":"?*"}}}},{"name":"sourceRefNamespace","exclude":{"resources":{"namespaces":["flux-system"]}},"match":{"resources":{"kinds":["Kustomization","HelmRelease"]}},"validate":{"message":"spec.sourceRef.namespace {{request.object.spec.sourceRef.namespace}} must be the same as metadata.namespace {{request.object.metadata.namespace}}","deny":{"conditions":[{"key":"{{request.object.spec.sourceRef.namespace}}","operator":"NotEquals","value":"{{request.object.metadata.namespace}}"}]}}}]}}`), // referred variable path present with different value - resourceRaw: []byte(`{"apiVersion":"kustomize.toolkit.fluxcd.io/v1beta1","kind":"Kustomization","metadata":{"name":"dev-team","namespace":"apps"},"spec":{"serviceAccountName":"dev-team","interval":"5m","sourceRef":{"kind":"GitRepository","name":"dev-team","namespace":"default"},"prune":true,"validation":"client"}}`), + resourceRaw: []byte(`{"apiVersion":"kustomize.toolkit.fluxcd.io/v1beta1","kind":"Kustomization","metadata":{"name":"dev-team","namespace":"apps"},"spec":{"serviceAccountName":"dev-team","interval":"5m","sourceRef":{"kind":"GitRepository","name":"dev-team","namespace":"default"},"prune":true,"validation":"client"}}`), expectedResults: []response.RuleStatus{response.RuleStatusPass, response.RuleStatusFail}, expectedMessages: []string{"validation rule 'serviceAccountName' passed.", "spec.sourceRef.namespace default must be the same as metadata.namespace apps"}, }, @@ -1947,7 +1947,7 @@ func Test_Flux_Kustomization_PathNotPresent(t *testing.T) { name: "resource-comply", policyRaw: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"flux-multi-tenancy"},"spec":{"validationFailureAction":"enforce","rules":[{"name":"serviceAccountName","exclude":{"resources":{"namespaces":["flux-system"]}},"match":{"resources":{"kinds":["Kustomization","HelmRelease"]}},"validate":{"message":".spec.serviceAccountName is required","pattern":{"spec":{"serviceAccountName":"?*"}}}},{"name":"sourceRefNamespace","exclude":{"resources":{"namespaces":["flux-system"]}},"match":{"resources":{"kinds":["Kustomization","HelmRelease"]}},"validate":{"message":"spec.sourceRef.namespace must be the same as metadata.namespace","deny":{"conditions":[{"key":"{{request.object.spec.sourceRef.namespace}}","operator":"NotEquals","value":"{{request.object.metadata.namespace}}"}]}}}]}}`), // referred variable path present with same value - validate passes - resourceRaw: []byte(`{"apiVersion":"kustomize.toolkit.fluxcd.io/v1beta1","kind":"Kustomization","metadata":{"name":"dev-team","namespace":"apps"},"spec":{"serviceAccountName":"dev-team","interval":"5m","sourceRef":{"kind":"GitRepository","name":"dev-team","namespace":"apps"},"prune":true,"validation":"client"}}`), + resourceRaw: []byte(`{"apiVersion":"kustomize.toolkit.fluxcd.io/v1beta1","kind":"Kustomization","metadata":{"name":"dev-team","namespace":"apps"},"spec":{"serviceAccountName":"dev-team","interval":"5m","sourceRef":{"kind":"GitRepository","name":"dev-team","namespace":"apps"},"prune":true,"validation":"client"}}`), expectedResults: []response.RuleStatus{response.RuleStatusPass, response.RuleStatusPass}, expectedMessages: []string{"validation rule 'serviceAccountName' passed.", "validation rule 'sourceRefNamespace' passed."}, }, @@ -2603,10 +2603,8 @@ func Test_foreach_context_preconditions(t *testing.T) { "metadata": {"name": "test"}, "spec": { "template": { "spec": { "containers": [ - {"name": "pod1-valid", "image": "nginx/nginx:v1"}, - {"name": "pod2-valid", "image": "nginx/nginx:v2"}, - {"name": "pod3-valid", "image": "nginx/nginx:v3"}, - {"name": "pod4-valid", "image": "nginx/nginx:v4"} + {"name": "podvalid", "image": "nginx/nginx:v1"}, + {"name": "podinvalid", "image": "nginx/nginx:v2"} ] }}}}`) @@ -2622,17 +2620,17 @@ func Test_foreach_context_preconditions(t *testing.T) { "validate": { "foreach": { "list": "request.object.spec.template.spec.containers", - "context": [{"name": "tags", "configMap": {"name": "mycmap", "namespace": "default"}}], + "context": [{"name": "img", "configMap": {"name": "mycmap", "namespace": "default"}}], "preconditions": { "all": [ { "key": "{{request.object.name}}", - "operator": "Equals", - "value": "pod1-valid | pod2-valid | pod3-valid" + "operator": "In", + "value": ["podvalid"] } ]}, "deny": { "conditions": [ - {"key": "images.{{ request.object.name }}.tag", "operator": "NotEquals", "value": "{{ tags.data.{{ request.object.name }} }}"} + {"key": "{{ request.object.image }}", "operator": "NotEquals", "value": "{{ img.data.{{ request.object.name }} }}"} ] } } @@ -2646,9 +2644,8 @@ func Test_foreach_context_preconditions(t *testing.T) { { Name: "test", Values: map[string]string{ - "tags.data.pod1-valid": "v1", - "tags.data.pod2-valid": "v2", - "tags.data.pod3-valid": "v3", + "img.data.podvalid": "nginx/nginx:v1", + "img.data.podinvalid": "nginx/nginx:v2", }, }, }, @@ -2670,10 +2667,8 @@ func Test_foreach_context_preconditions_fail(t *testing.T) { "metadata": {"name": "test"}, "spec": { "template": { "spec": { "containers": [ - {"name": "pod1-valid", "image": "nginx/nginx:v1"}, - {"name": "pod2-valid", "image": "nginx/nginx:v2"}, - {"name": "pod3-valid", "image": "nginx/nginx:v3"}, - {"name": "pod4-valid", "image": "nginx/nginx:v4"} + {"name": "podvalid", "image": "nginx/nginx:v1"}, + {"name": "podinvalid", "image": "nginx/nginx:v2"} ] }}}}`) @@ -2689,17 +2684,17 @@ func Test_foreach_context_preconditions_fail(t *testing.T) { "validate": { "foreach": { "list": "request.object.spec.template.spec.containers", - "context": [{"name": "tags", "configMap": {"name": "mycmap", "namespace": "default"}}], + "context": [{"name": "img", "configMap": {"name": "mycmap", "namespace": "default"}}], "preconditions": { "all": [ { "key": "{{request.object.name}}", - "operator": "Equals", - "value": "pod1-valid | pod2-valid | pod3-valid" + "operator": "In", + "value": ["podvalid", "podinvalid"] } ]}, "deny": { "conditions": [ - {"key": "images.{{ request.object.name }}.tag", "operator": "NotEquals", "value": "{{ tags.data.{{ request.object.name }} }}"} + {"key": "{{ request.object.image }}", "operator": "NotEquals", "value": "{{ img.data.{{ request.object.name }} }}"} ] } } @@ -2713,9 +2708,8 @@ func Test_foreach_context_preconditions_fail(t *testing.T) { { Name: "test", Values: map[string]string{ - "tags.data.pod1-valid": "v1", - "tags.data.pod2-valid": "v22", - "tags.data.pod3-valid": "v3", + "img.data.podvalid": "nginx/nginx:v1", + "img.data.podinvalid": "nginx/nginx:v1", }, }, }, diff --git a/pkg/engine/variables/vars.go b/pkg/engine/variables/vars.go index dece35cdb3..8a7bebe020 100644 --- a/pkg/engine/variables/vars.go +++ b/pkg/engine/variables/vars.go @@ -52,6 +52,8 @@ func newPreconditionsVariableResolver(log logr.Logger) VariableResolver { } } +// SubstituteAll substitutes variables and references in the document. The document must be JSON data +// i.e. string, []interface{}, map[string]interface{} func SubstituteAll(log logr.Logger, ctx context.EvalInterface, document interface{}) (_ interface{}, err error) { return substituteAll(log, ctx, document, DefaultVariableResolver) } @@ -139,8 +141,6 @@ func SubstituteAllForceMutate(log logr.Logger, ctx context.EvalInterface, typedR return UntypedToRule(rule) } -//SubstituteVars replaces the variables with the values defined in the context -// - if any variable is invalid or has nil value, it is considered as a failed variable substitution func substituteVars(log logr.Logger, ctx context.EvalInterface, rule interface{}, vr VariableResolver) (interface{}, error) { return jsonUtils.NewTraversal(rule, substituteVariablesIfAny(log, ctx, vr)).TraverseJSON() } diff --git a/pkg/testrunner/scenario_test.go b/pkg/testrunner/scenario_test.go index 001d4d4ae1..ee2f420e92 100644 --- a/pkg/testrunner/scenario_test.go +++ b/pkg/testrunner/scenario_test.go @@ -51,7 +51,6 @@ func Test_parse_file(t *testing.T) { assert.Equal(t, response.RuleStatusFail, s.TestCases[0].Expected.Validation.PolicyResponse.Rules[0].Status, "invalid status") } - func Test_parse_file2(t *testing.T) { path := getRelativePath("test/scenarios/samples/best_practices/disallow_bind_mounts_fail.yaml") data, err := ioutil.ReadFile(path)