From 0ed74145fb00626ce0e900812a54ca3de5406f2e Mon Sep 17 00:00:00 2001 From: Max Smythe Date: Wed, 1 Feb 2023 17:29:30 -0800 Subject: [PATCH] make CEL admission controller code consumable (#115412) * Make policy decision object public Signed-off-by: Max Smythe * Separate version conversion from validation Signed-off-by: Max Smythe * Address review comments Signed-off-by: Max Smythe * Fix variable name Signed-off-by: Max Smythe --------- Signed-off-by: Max Smythe --- .../admission_test.go | 47 ++++---- .../validatingadmissionpolicy/controller.go | 69 ++++++----- .../validatingadmissionpolicy/interface.go | 9 +- .../policy_decision.go | 34 +++--- .../validatingadmissionpolicy/validator.go | 51 ++++---- .../validator_test.go | 110 +++++++++--------- 6 files changed, 171 insertions(+), 149 deletions(-) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/admission_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/admission_test.go index 7edb06f5f3b..438b408e3e8 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/admission_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/admission_test.go @@ -37,6 +37,7 @@ import ( "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission/initializer" "k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic" + whgeneric "k8s.io/apiserver/pkg/admission/plugin/webhook/generic" "k8s.io/apiserver/pkg/features" dynamicfake "k8s.io/client-go/dynamic/fake" "k8s.io/client-go/informers" @@ -173,11 +174,11 @@ func (f *fakeCompiler) DefinitionMatches(a admission.Attributes, o admission.Obj namespace: namespace, } if fun, ok := f.DefinitionMatchFuncs[key]; ok { - return fun(definition, a), schema.GroupVersionKind{}, nil + return fun(definition, a), a.GetKind(), nil } // Default is match everything - return f.DefaultMatch, schema.GroupVersionKind{}, nil + return f.DefaultMatch, a.GetKind(), nil } // Matches says whether this policy definition matches the provided admission @@ -656,21 +657,21 @@ func TestBasicPolicyDefinitionFailure(t *testing.T) { require.ErrorContains(t, err, `Denied`) } -type validatorFunc func(a admission.Attributes, o admission.ObjectInterfaces, params runtime.Object, matchKind schema.GroupVersionKind) ([]policyDecision, error) +type validatorFunc func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object) ([]PolicyDecision, error) -func (f validatorFunc) Validate(a admission.Attributes, o admission.ObjectInterfaces, params runtime.Object, matchKind schema.GroupVersionKind) ([]policyDecision, error) { - return f(a, o, params, matchKind) +func (f validatorFunc) Validate(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object) ([]PolicyDecision, error) { + return f(versionedAttr, versionedParams) } type testValidator struct { } -func (v testValidator) Validate(a admission.Attributes, o admission.ObjectInterfaces, params runtime.Object, matchKind schema.GroupVersionKind) ([]policyDecision, error) { +func (v testValidator) Validate(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object) ([]PolicyDecision, error) { // Policy always denies - return []policyDecision{ + return []PolicyDecision{ { - action: actionDeny, - message: "Denied", + Action: ActionDeny, + Message: "Denied", }, }, nil } @@ -1142,11 +1143,11 @@ func TestMultiplePoliciesSharedParamType(t *testing.T) { compiler.RegisterDefinition(&policy1, func(vap *v1alpha1.ValidatingAdmissionPolicy) Validator { compiles1.Add(1) - return validatorFunc(func(a admission.Attributes, o admission.ObjectInterfaces, params runtime.Object, matchKind schema.GroupVersionKind) ([]policyDecision, error) { + return validatorFunc(func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object) ([]PolicyDecision, error) { evaluations1.Add(1) - return []policyDecision{ + return []PolicyDecision{ { - action: actionAdmit, + Action: ActionAdmit, }, }, nil }) @@ -1155,12 +1156,12 @@ func TestMultiplePoliciesSharedParamType(t *testing.T) { compiler.RegisterDefinition(&policy2, func(vap *v1alpha1.ValidatingAdmissionPolicy) Validator { compiles2.Add(1) - return validatorFunc(func(a admission.Attributes, o admission.ObjectInterfaces, params runtime.Object, matchKind schema.GroupVersionKind) ([]policyDecision, error) { + return validatorFunc(func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object) ([]PolicyDecision, error) { evaluations2.Add(1) - return []policyDecision{ + return []PolicyDecision{ { - action: actionDeny, - message: "Policy2Denied", + Action: ActionDeny, + Message: "Policy2Denied", }, }, nil }) @@ -1256,22 +1257,22 @@ func TestNativeTypeParam(t *testing.T) { compiler.RegisterDefinition(&nativeTypeParamPolicy, func(vap *v1alpha1.ValidatingAdmissionPolicy) Validator { compiles.Add(1) - return validatorFunc(func(a admission.Attributes, o admission.ObjectInterfaces, params runtime.Object, matchKind schema.GroupVersionKind) ([]policyDecision, error) { + return validatorFunc(func(versionedAttr *whgeneric.VersionedAttributes, params runtime.Object) ([]PolicyDecision, error) { evaluations.Add(1) // show that the passed params was a ConfigMap native type if _, ok := params.(*v1.ConfigMap); ok { - return []policyDecision{ + return []PolicyDecision{ { - action: actionDeny, - message: "correct type", + Action: ActionDeny, + Message: "correct type", }, }, nil } - return []policyDecision{ + return []PolicyDecision{ { - action: actionDeny, - message: "Incorrect param type", + Action: ActionDeny, + Message: "Incorrect param type", }, }, nil }) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/controller.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/controller.go index 795dbad5b9c..dfb3dda21a5 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/controller.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/controller.go @@ -37,6 +37,7 @@ import ( "k8s.io/apiserver/pkg/admission" celmetrics "k8s.io/apiserver/pkg/admission/cel" "k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic" + whgeneric "k8s.io/apiserver/pkg/admission/plugin/webhook/generic" "k8s.io/client-go/dynamic" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" @@ -190,21 +191,21 @@ func (c *celAdmissionController) Validate( message = fmt.Errorf("failed to configure binding: %w", err).Error() } deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{ - policyDecision: policyDecision{ - action: actionDeny, - message: message, + PolicyDecision: PolicyDecision{ + Action: ActionDeny, + Message: message, }, - definition: definition, - binding: binding, + Definition: definition, + Binding: binding, }) default: deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{ - policyDecision: policyDecision{ - action: actionDeny, - message: fmt.Errorf("unrecognized failure policy: '%v'", policy).Error(), + PolicyDecision: PolicyDecision{ + Action: ActionDeny, + Message: fmt.Errorf("unrecognized failure policy: '%v'", policy).Error(), }, - definition: definition, - binding: binding, + Definition: definition, + Binding: binding, }) } } @@ -243,6 +244,12 @@ func (c *celAdmissionController) Validate( var param runtime.Object + // versionedAttributes will be set to non-nil inside of the loop, but + // is scoped outside of the param loop so we only convert once. We defer + // conversion so that it is only performed when we know a policy matches, + // saving the cost of converting non-matching requests. + var versionedAttr *whgeneric.VersionedAttributes + // If definition has paramKind, paramRef is required in binding. // If definition has no paramKind, paramRef set in binding will be ignored. paramKind := definition.Spec.ParamKind @@ -293,7 +300,17 @@ func (c *celAdmissionController) Validate( } } - decisions, err := bindingInfo.validator.Validate(a, o, param, matchKind) + if versionedAttr == nil { + va, err := whgeneric.NewVersionedAttributes(a, matchKind, o) + if err != nil { + wrappedErr := fmt.Errorf("failed to convert object version: %w", err) + addConfigError(wrappedErr, definition, binding) + continue + } + versionedAttr = va + } + + decisions, err := bindingInfo.validator.Validate(versionedAttr, param) if err != nil { // runtime error. Apply failure policy wrappedError := fmt.Errorf("failed to evaluate CEL expression: %w", err) @@ -302,21 +319,21 @@ func (c *celAdmissionController) Validate( } for _, decision := range decisions { - switch decision.action { - case actionAdmit: - if decision.evaluation == evalError { - celmetrics.Metrics.ObserveAdmissionWithError(ctx, decision.elapsed, definition.Name, binding.Name, "active") + switch decision.Action { + case ActionAdmit: + if decision.Evaluation == EvalError { + celmetrics.Metrics.ObserveAdmissionWithError(ctx, decision.Elapsed, definition.Name, binding.Name, "active") } - case actionDeny: + case ActionDeny: deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{ - definition: definition, - binding: binding, - policyDecision: decision, + Definition: definition, + Binding: binding, + PolicyDecision: decision, }) - celmetrics.Metrics.ObserveRejection(ctx, decision.elapsed, definition.Name, binding.Name, "active") + celmetrics.Metrics.ObserveRejection(ctx, decision.Elapsed, definition.Name, binding.Name, "active") default: return fmt.Errorf("unrecognized evaluation decision '%s' for ValidatingAdmissionPolicyBinding '%s' with ValidatingAdmissionPolicy '%s'", - decision.action, binding.Name, definition.Name) + decision.Action, binding.Name, definition.Name) } } } @@ -326,18 +343,18 @@ func (c *celAdmissionController) Validate( // TODO: refactor admission.NewForbidden so the name extraction is reusable but the code/reason is customizable var message string deniedDecision := deniedDecisions[0] - if deniedDecision.binding != nil { - message = fmt.Sprintf("ValidatingAdmissionPolicy '%s' with binding '%s' denied request: %s", deniedDecision.definition.Name, deniedDecision.binding.Name, deniedDecision.message) + if deniedDecision.Binding != nil { + message = fmt.Sprintf("ValidatingAdmissionPolicy '%s' with binding '%s' denied request: %s", deniedDecision.Definition.Name, deniedDecision.Binding.Name, deniedDecision.Message) } else { - message = fmt.Sprintf("ValidatingAdmissionPolicy '%s' denied request: %s", deniedDecision.definition.Name, deniedDecision.message) + message = fmt.Sprintf("ValidatingAdmissionPolicy '%s' denied request: %s", deniedDecision.Definition.Name, deniedDecision.Message) } err := admission.NewForbidden(a, errors.New(message)).(*k8serrors.StatusError) - reason := deniedDecision.reason + reason := deniedDecision.Reason if len(reason) == 0 { reason = metav1.StatusReasonInvalid } err.ErrStatus.Reason = reason - err.ErrStatus.Code = reasonToCode(reason) + err.ErrStatus.Code = ReasonToCode(reason) err.ErrStatus.Details.Causes = append(err.ErrStatus.Details.Causes, metav1.StatusCause{Message: message}) return err } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/interface.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/interface.go index 217d6bfc47c..7cb33b3724e 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/interface.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/interface.go @@ -21,13 +21,14 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/admission/plugin/webhook/generic" ) -// Validator defines the func used to validate the cel expressions -// matchKind provides the GroupVersionKind that the object should be -// validated by CEL expressions as. +// Validator defines the func used to validate an object against the validator's rules. +// It expects the inbound object to already have been converted to the version expected +// by the underlying CEL code (which is indicated by the match criteria of a policy definition). type Validator interface { - Validate(a admission.Attributes, o admission.ObjectInterfaces, versionedParams runtime.Object, matchKind schema.GroupVersionKind) ([]policyDecision, error) + Validate(versionedAttr *generic.VersionedAttributes, versionedParams runtime.Object) ([]PolicyDecision, error) } // ValidatorCompiler is Dependency Injected into the PolicyDefinition's `Compile` diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/policy_decision.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/policy_decision.go index 1018743705b..b13a2ab74a1 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/policy_decision.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/policy_decision.go @@ -24,36 +24,36 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -type policyDecisionAction string +type PolicyDecisionAction string const ( - actionAdmit policyDecisionAction = "admit" - actionDeny policyDecisionAction = "deny" + ActionAdmit PolicyDecisionAction = "admit" + ActionDeny PolicyDecisionAction = "deny" ) -type policyDecisionEvaluation string +type PolicyDecisionEvaluation string const ( - evalAdmit policyDecisionEvaluation = "admit" - evalError policyDecisionEvaluation = "error" - evalDeny policyDecisionEvaluation = "deny" + EvalAdmit PolicyDecisionEvaluation = "admit" + EvalError PolicyDecisionEvaluation = "error" + EvalDeny PolicyDecisionEvaluation = "deny" ) -type policyDecision struct { - action policyDecisionAction - evaluation policyDecisionEvaluation - message string - reason metav1.StatusReason - elapsed time.Duration +type PolicyDecision struct { + Action PolicyDecisionAction + Evaluation PolicyDecisionEvaluation + Message string + Reason metav1.StatusReason + Elapsed time.Duration } type policyDecisionWithMetadata struct { - policyDecision - definition *v1alpha1.ValidatingAdmissionPolicy - binding *v1alpha1.ValidatingAdmissionPolicyBinding + PolicyDecision + Definition *v1alpha1.ValidatingAdmissionPolicy + Binding *v1alpha1.ValidatingAdmissionPolicyBinding } -func reasonToCode(r metav1.StatusReason) int32 { +func ReasonToCode(r metav1.StatusReason) int32 { switch r { case metav1.StatusReasonForbidden: return http.StatusForbidden diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/validator.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/validator.go index 033d2e48e15..f4e84408d2b 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/validator.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/validator.go @@ -157,26 +157,22 @@ func objectToResolveVal(r runtime.Object) (interface{}, error) { return v.Object, nil } -func policyDecisionActionForError(f v1alpha1.FailurePolicyType) policyDecisionAction { +func policyDecisionActionForError(f v1alpha1.FailurePolicyType) PolicyDecisionAction { if f == v1alpha1.Ignore { - return actionAdmit + return ActionAdmit } - return actionDeny + return ActionDeny } // Validate validates all cel expressions in Validator and returns a PolicyDecision for each CEL expression or returns an error. // An error will be returned if failed to convert the object/oldObject/params/request to unstructured. // Each PolicyDecision will have a decision and a message. // policyDecision.message will be empty if the decision is allowed and no error met. -func (v *CELValidator) Validate(a admission.Attributes, o admission.ObjectInterfaces, versionedParams runtime.Object, matchKind schema.GroupVersionKind) ([]policyDecision, error) { +func (v *CELValidator) Validate(versionedAttr *generic.VersionedAttributes, versionedParams runtime.Object) ([]PolicyDecision, error) { // TODO: replace unstructured with ref.Val for CEL variables when native type support is available - - decisions := make([]policyDecision, len(v.compilationResults)) + decisions := make([]PolicyDecision, len(v.compilationResults)) var err error - versionedAttr, err := generic.NewVersionedAttributes(a, matchKind, o) - if err != nil { - return nil, err - } + oldObjectVal, err := objectToResolveVal(versionedAttr.VersionedOldObject) if err != nil { return nil, err @@ -189,6 +185,7 @@ func (v *CELValidator) Validate(a admission.Attributes, o admission.ObjectInterf if err != nil { return nil, err } + request := createAdmissionRequest(versionedAttr.Attributes) requestVal, err := convertObjectToUnstructured(request) if err != nil { @@ -214,41 +211,41 @@ func (v *CELValidator) Validate(a admission.Attributes, o admission.ObjectInterf var policyDecision = &decisions[i] if compilationResult.Error != nil { - policyDecision.action = policyDecisionActionForError(f) - policyDecision.evaluation = evalError - policyDecision.message = fmt.Sprintf("compilation error: %v", compilationResult.Error) + policyDecision.Action = policyDecisionActionForError(f) + policyDecision.Evaluation = EvalError + policyDecision.Message = fmt.Sprintf("compilation error: %v", compilationResult.Error) continue } if compilationResult.Program == nil { - policyDecision.action = policyDecisionActionForError(f) - policyDecision.evaluation = evalError - policyDecision.message = "unexpected internal error compiling expression" + policyDecision.Action = policyDecisionActionForError(f) + policyDecision.Evaluation = EvalError + policyDecision.Message = "unexpected internal error compiling expression" continue } t1 := time.Now() evalResult, _, err := compilationResult.Program.Eval(va) elapsed := time.Since(t1) - policyDecision.elapsed = elapsed + policyDecision.Elapsed = elapsed if err != nil { - policyDecision.action = policyDecisionActionForError(f) - policyDecision.evaluation = evalError - policyDecision.message = fmt.Sprintf("expression '%v' resulted in error: %v", v.policy.Spec.Validations[i].Expression, err) + policyDecision.Action = policyDecisionActionForError(f) + policyDecision.Evaluation = EvalError + policyDecision.Message = fmt.Sprintf("expression '%v' resulted in error: %v", v.policy.Spec.Validations[i].Expression, err) } else if evalResult != celtypes.True { - policyDecision.action = actionDeny + policyDecision.Action = ActionDeny if validation.Reason == nil { - policyDecision.reason = metav1.StatusReasonInvalid + policyDecision.Reason = metav1.StatusReasonInvalid } else { - policyDecision.reason = *validation.Reason + policyDecision.Reason = *validation.Reason } if len(validation.Message) > 0 { - policyDecision.message = strings.TrimSpace(validation.Message) + policyDecision.Message = strings.TrimSpace(validation.Message) } else { - policyDecision.message = fmt.Sprintf("failed expression: %v", strings.TrimSpace(validation.Expression)) + policyDecision.Message = fmt.Sprintf("failed expression: %v", strings.TrimSpace(validation.Expression)) } } else { - policyDecision.action = actionAdmit - policyDecision.evaluation = evalAdmit + policyDecision.Action = ActionAdmit + policyDecision.Evaluation = EvalAdmit } } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/validator_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/validator_test.go index fff818bee94..f71f3c46077 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/validator_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/validator_test.go @@ -31,6 +31,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/admission/plugin/webhook/generic" ) func TestCompile(t *testing.T) { @@ -219,8 +220,8 @@ func getValidPolicy(validations []v1alpha1.Validation, params *v1alpha1.ParamKin } } -func generatedDecision(k policyDecisionAction, m string, r metav1.StatusReason) policyDecision { - return policyDecision{action: k, message: m, reason: r} +func generatedDecision(k PolicyDecisionAction, m string, r metav1.StatusReason) PolicyDecision { + return PolicyDecision{Action: k, Message: m, Reason: r} } func TestValidate(t *testing.T) { @@ -270,7 +271,7 @@ func TestValidate(t *testing.T) { policy *v1alpha1.ValidatingAdmissionPolicy attributes admission.Attributes params runtime.Object - policyDecisions []policyDecision + policyDecisions []PolicyDecision }{ { name: "valid syntax for object", @@ -280,8 +281,8 @@ func TestValidate(t *testing.T) { }, }, nil, nil), attributes: newValidAttribute(nil, false), - policyDecisions: []policyDecision{ - generatedDecision(actionAdmit, "", ""), + policyDecisions: []PolicyDecision{ + generatedDecision(ActionAdmit, "", ""), }, }, { @@ -292,8 +293,8 @@ func TestValidate(t *testing.T) { }, }, nil, nil), attributes: newValidAttribute(nil, false), - policyDecisions: []policyDecision{ - generatedDecision(actionAdmit, "", ""), + policyDecisions: []PolicyDecision{ + generatedDecision(ActionAdmit, "", ""), }, }, { @@ -307,9 +308,9 @@ func TestValidate(t *testing.T) { }, }, nil, nil), attributes: newValidAttribute(nil, false), - policyDecisions: []policyDecision{ - generatedDecision(actionAdmit, "", ""), - generatedDecision(actionAdmit, "", ""), + policyDecisions: []PolicyDecision{ + generatedDecision(ActionAdmit, "", ""), + generatedDecision(ActionAdmit, "", ""), }, }, { @@ -318,8 +319,8 @@ func TestValidate(t *testing.T) { {Expression: "request.operation == 'CREATE'"}, }, nil, nil), attributes: newValidAttribute(nil, false), - policyDecisions: []policyDecision{ - generatedDecision(actionAdmit, "", ""), + policyDecisions: []PolicyDecision{ + generatedDecision(ActionAdmit, "", ""), }, }, { @@ -329,8 +330,8 @@ func TestValidate(t *testing.T) { }, hasParamKind, nil), attributes: newValidAttribute(nil, false), params: configMapParams, - policyDecisions: []policyDecision{ - generatedDecision(actionAdmit, "", ""), + policyDecisions: []PolicyDecision{ + generatedDecision(ActionAdmit, "", ""), }, }, { @@ -347,8 +348,8 @@ func TestValidate(t *testing.T) { "fakeString": "fake", }, }, - policyDecisions: []policyDecision{ - generatedDecision(actionDeny, "failed expression: object.subsets.size() > 2", metav1.StatusReasonInvalid), + policyDecisions: []PolicyDecision{ + generatedDecision(ActionDeny, "failed expression: object.subsets.size() > 2", metav1.StatusReasonInvalid), }, }, { @@ -363,9 +364,9 @@ func TestValidate(t *testing.T) { }, hasParamKind, ignorePolicy), attributes: newValidAttribute(nil, false), params: configMapParams, - policyDecisions: []policyDecision{ - generatedDecision(actionAdmit, "", ""), - generatedDecision(actionDeny, "failed expression: object.subsets.size() > 2", metav1.StatusReasonInvalid), + policyDecisions: []PolicyDecision{ + generatedDecision(ActionAdmit, "", ""), + generatedDecision(ActionDeny, "failed expression: object.subsets.size() > 2", metav1.StatusReasonInvalid), }, }, { @@ -380,9 +381,9 @@ func TestValidate(t *testing.T) { }, hasParamKind, nil), attributes: newValidAttribute(nil, false), params: configMapParams, - policyDecisions: []policyDecision{ - generatedDecision(actionDeny, "failed expression: oldObject != null", metav1.StatusReasonInvalid), - generatedDecision(actionDeny, "failed expression: object.subsets.size() > 2", metav1.StatusReasonInvalid), + policyDecisions: []PolicyDecision{ + generatedDecision(ActionDeny, "failed expression: oldObject != null", metav1.StatusReasonInvalid), + generatedDecision(ActionDeny, "failed expression: object.subsets.size() > 2", metav1.StatusReasonInvalid), }, }, { @@ -397,9 +398,9 @@ func TestValidate(t *testing.T) { }, hasParamKind, nil), attributes: newValidAttribute(nil, true), params: configMapParams, - policyDecisions: []policyDecision{ - generatedDecision(actionAdmit, "", ""), - generatedDecision(actionAdmit, "", ""), + policyDecisions: []PolicyDecision{ + generatedDecision(ActionAdmit, "", ""), + generatedDecision(ActionAdmit, "", ""), }, }, { @@ -412,8 +413,8 @@ func TestValidate(t *testing.T) { }, hasParamKind, nil), attributes: newValidAttribute(nil, true), params: configMapParams, - policyDecisions: []policyDecision{ - generatedDecision(actionDeny, "failed expression: oldObject == null", metav1.StatusReasonForbidden), + policyDecisions: []PolicyDecision{ + generatedDecision(ActionDeny, "failed expression: oldObject == null", metav1.StatusReasonForbidden), }, }, { @@ -427,8 +428,8 @@ func TestValidate(t *testing.T) { }, hasParamKind, nil), attributes: newValidAttribute(nil, true), params: configMapParams, - policyDecisions: []policyDecision{ - generatedDecision(actionDeny, "old object should be present", metav1.StatusReasonForbidden), + policyDecisions: []PolicyDecision{ + generatedDecision(ActionDeny, "old object should be present", metav1.StatusReasonForbidden), }, }, { @@ -440,8 +441,8 @@ func TestValidate(t *testing.T) { }, hasParamKind, nil), attributes: newValidAttribute(nil, true), params: configMapParams, - policyDecisions: []policyDecision{ - generatedDecision(actionDeny, "resulted in error", ""), + policyDecisions: []PolicyDecision{ + generatedDecision(ActionDeny, "resulted in error", ""), }, }, { @@ -453,8 +454,8 @@ func TestValidate(t *testing.T) { }, hasParamKind, nil), attributes: newValidAttribute(nil, false), params: crdParams, - policyDecisions: []policyDecision{ - generatedDecision(actionAdmit, "", ""), + policyDecisions: []PolicyDecision{ + generatedDecision(ActionAdmit, "", ""), }, }, { @@ -469,9 +470,9 @@ func TestValidate(t *testing.T) { }, hasParamKind, nil), attributes: newValidAttribute(nil, false), params: crdParams, - policyDecisions: []policyDecision{ - generatedDecision(actionDeny, "compilation error: compilation failed: ERROR: :1:6: Syntax error:", ""), - generatedDecision(actionDeny, "failed expression: object.subsets.size() > params.spec.testSize", metav1.StatusReasonInvalid), + policyDecisions: []PolicyDecision{ + generatedDecision(ActionDeny, "compilation error: compilation failed: ERROR: :1:6: Syntax error:", ""), + generatedDecision(ActionDeny, "failed expression: object.subsets.size() > params.spec.testSize", metav1.StatusReasonInvalid), }, }, { @@ -486,9 +487,9 @@ func TestValidate(t *testing.T) { }, hasParamKind, ignorePolicy), attributes: newValidAttribute(nil, false), params: crdParams, - policyDecisions: []policyDecision{ - generatedDecision(actionAdmit, "compilation error: compilation failed: ERROR:", ""), - generatedDecision(actionDeny, "failed expression: object.subsets.size() > params.spec.testSize", metav1.StatusReasonInvalid), + policyDecisions: []PolicyDecision{ + generatedDecision(ActionAdmit, "compilation error: compilation failed: ERROR:", ""), + generatedDecision(ActionDeny, "failed expression: object.subsets.size() > params.spec.testSize", metav1.StatusReasonInvalid), }, }, { @@ -500,8 +501,8 @@ func TestValidate(t *testing.T) { }, nil, nil), attributes: newValidAttribute(&podObject, false), params: crdParams, - policyDecisions: []policyDecision{ - generatedDecision(actionAdmit, "", ""), + policyDecisions: []PolicyDecision{ + generatedDecision(ActionAdmit, "", ""), }, }, { @@ -517,8 +518,8 @@ func TestValidate(t *testing.T) { // Simulate a interface holding a nil pointer, since this is how param is passed to Validate // if paramRef is unset on a binding params: runtime.Object(nilUnstructured), - policyDecisions: []policyDecision{ - generatedDecision(actionDeny, "params as required", metav1.StatusReasonForbidden), + policyDecisions: []PolicyDecision{ + generatedDecision(ActionDeny, "params as required", metav1.StatusReasonForbidden), }, }, { @@ -533,8 +534,8 @@ func TestValidate(t *testing.T) { // Simulate a interface holding a nil pointer, since this is how param is passed to Validate // if paramRef is unset on a binding params: runtime.Object(nilUnstructured), - policyDecisions: []policyDecision{ - generatedDecision(actionAdmit, "", ""), + policyDecisions: []PolicyDecision{ + generatedDecision(ActionAdmit, "", ""), }, }, } @@ -550,20 +551,25 @@ func TestValidate(t *testing.T) { CompilationResults := validator.(*CELValidator).compilationResults require.Equal(t, len(validations), len(CompilationResults)) - policyResults, err := validator.Validate(tc.attributes, newObjectInterfacesForTest(), tc.params, tc.attributes.GetKind()) + versionedAttr, err := generic.NewVersionedAttributes(tc.attributes, tc.attributes.GetKind(), newObjectInterfacesForTest()) + if err != nil { + t.Fatalf("unexpected error on conversion: %v", err) + } + + policyResults, err := validator.Validate(versionedAttr, tc.params) if err != nil { t.Fatalf("unexpected error: %v", err) } require.Equal(t, len(policyResults), len(tc.policyDecisions)) for i, policyDecision := range tc.policyDecisions { - if policyDecision.action != policyResults[i].action { - t.Errorf("Expected policy decision kind '%v' but got '%v'", policyDecision.action, policyResults[i].action) + if policyDecision.Action != policyResults[i].Action { + t.Errorf("Expected policy decision kind '%v' but got '%v'", policyDecision.Action, policyResults[i].Action) } - if !strings.Contains(policyResults[i].message, policyDecision.message) { - t.Errorf("Expected policy decision message contains '%v' but got '%v'", policyDecision.message, policyResults[i].message) + if !strings.Contains(policyResults[i].Message, policyDecision.Message) { + t.Errorf("Expected policy decision message contains '%v' but got '%v'", policyDecision.Message, policyResults[i].Message) } - if policyDecision.reason != policyResults[i].reason { - t.Errorf("Expected policy decision reason '%v' but got '%v'", policyDecision.reason, policyResults[i].reason) + if policyDecision.Reason != policyResults[i].Reason { + t.Errorf("Expected policy decision reason '%v' but got '%v'", policyDecision.Reason, policyResults[i].Reason) } } })