mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 19:01:49 +00:00
make CEL admission controller code consumable (#115412)
* Make policy decision object public Signed-off-by: Max Smythe <smythe@google.com> * Separate version conversion from validation Signed-off-by: Max Smythe <smythe@google.com> * Address review comments Signed-off-by: Max Smythe <smythe@google.com> * Fix variable name Signed-off-by: Max Smythe <smythe@google.com> --------- Signed-off-by: Max Smythe <smythe@google.com>
This commit is contained in:
parent
d475085776
commit
0ed74145fb
@ -37,6 +37,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
"k8s.io/apiserver/pkg/admission/initializer"
|
"k8s.io/apiserver/pkg/admission/initializer"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic"
|
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic"
|
||||||
|
whgeneric "k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||||
"k8s.io/apiserver/pkg/features"
|
"k8s.io/apiserver/pkg/features"
|
||||||
dynamicfake "k8s.io/client-go/dynamic/fake"
|
dynamicfake "k8s.io/client-go/dynamic/fake"
|
||||||
"k8s.io/client-go/informers"
|
"k8s.io/client-go/informers"
|
||||||
@ -173,11 +174,11 @@ func (f *fakeCompiler) DefinitionMatches(a admission.Attributes, o admission.Obj
|
|||||||
namespace: namespace,
|
namespace: namespace,
|
||||||
}
|
}
|
||||||
if fun, ok := f.DefinitionMatchFuncs[key]; ok {
|
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
|
// 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
|
// Matches says whether this policy definition matches the provided admission
|
||||||
@ -656,21 +657,21 @@ func TestBasicPolicyDefinitionFailure(t *testing.T) {
|
|||||||
require.ErrorContains(t, err, `Denied`)
|
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) {
|
func (f validatorFunc) Validate(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object) ([]PolicyDecision, error) {
|
||||||
return f(a, o, params, matchKind)
|
return f(versionedAttr, versionedParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
type testValidator struct {
|
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
|
// Policy always denies
|
||||||
return []policyDecision{
|
return []PolicyDecision{
|
||||||
{
|
{
|
||||||
action: actionDeny,
|
Action: ActionDeny,
|
||||||
message: "Denied",
|
Message: "Denied",
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -1142,11 +1143,11 @@ func TestMultiplePoliciesSharedParamType(t *testing.T) {
|
|||||||
compiler.RegisterDefinition(&policy1, func(vap *v1alpha1.ValidatingAdmissionPolicy) Validator {
|
compiler.RegisterDefinition(&policy1, func(vap *v1alpha1.ValidatingAdmissionPolicy) Validator {
|
||||||
compiles1.Add(1)
|
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)
|
evaluations1.Add(1)
|
||||||
return []policyDecision{
|
return []PolicyDecision{
|
||||||
{
|
{
|
||||||
action: actionAdmit,
|
Action: ActionAdmit,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
})
|
})
|
||||||
@ -1155,12 +1156,12 @@ func TestMultiplePoliciesSharedParamType(t *testing.T) {
|
|||||||
compiler.RegisterDefinition(&policy2, func(vap *v1alpha1.ValidatingAdmissionPolicy) Validator {
|
compiler.RegisterDefinition(&policy2, func(vap *v1alpha1.ValidatingAdmissionPolicy) Validator {
|
||||||
compiles2.Add(1)
|
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)
|
evaluations2.Add(1)
|
||||||
return []policyDecision{
|
return []PolicyDecision{
|
||||||
{
|
{
|
||||||
action: actionDeny,
|
Action: ActionDeny,
|
||||||
message: "Policy2Denied",
|
Message: "Policy2Denied",
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
})
|
})
|
||||||
@ -1256,22 +1257,22 @@ func TestNativeTypeParam(t *testing.T) {
|
|||||||
compiler.RegisterDefinition(&nativeTypeParamPolicy, func(vap *v1alpha1.ValidatingAdmissionPolicy) Validator {
|
compiler.RegisterDefinition(&nativeTypeParamPolicy, func(vap *v1alpha1.ValidatingAdmissionPolicy) Validator {
|
||||||
compiles.Add(1)
|
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)
|
evaluations.Add(1)
|
||||||
|
|
||||||
// show that the passed params was a ConfigMap native type
|
// show that the passed params was a ConfigMap native type
|
||||||
if _, ok := params.(*v1.ConfigMap); ok {
|
if _, ok := params.(*v1.ConfigMap); ok {
|
||||||
return []policyDecision{
|
return []PolicyDecision{
|
||||||
{
|
{
|
||||||
action: actionDeny,
|
Action: ActionDeny,
|
||||||
message: "correct type",
|
Message: "correct type",
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return []policyDecision{
|
return []PolicyDecision{
|
||||||
{
|
{
|
||||||
action: actionDeny,
|
Action: ActionDeny,
|
||||||
message: "Incorrect param type",
|
Message: "Incorrect param type",
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
})
|
})
|
||||||
|
@ -37,6 +37,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
celmetrics "k8s.io/apiserver/pkg/admission/cel"
|
celmetrics "k8s.io/apiserver/pkg/admission/cel"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic"
|
"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/dynamic"
|
||||||
"k8s.io/client-go/informers"
|
"k8s.io/client-go/informers"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
@ -190,21 +191,21 @@ func (c *celAdmissionController) Validate(
|
|||||||
message = fmt.Errorf("failed to configure binding: %w", err).Error()
|
message = fmt.Errorf("failed to configure binding: %w", err).Error()
|
||||||
}
|
}
|
||||||
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
|
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
|
||||||
policyDecision: policyDecision{
|
PolicyDecision: PolicyDecision{
|
||||||
action: actionDeny,
|
Action: ActionDeny,
|
||||||
message: message,
|
Message: message,
|
||||||
},
|
},
|
||||||
definition: definition,
|
Definition: definition,
|
||||||
binding: binding,
|
Binding: binding,
|
||||||
})
|
})
|
||||||
default:
|
default:
|
||||||
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
|
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
|
||||||
policyDecision: policyDecision{
|
PolicyDecision: PolicyDecision{
|
||||||
action: actionDeny,
|
Action: ActionDeny,
|
||||||
message: fmt.Errorf("unrecognized failure policy: '%v'", policy).Error(),
|
Message: fmt.Errorf("unrecognized failure policy: '%v'", policy).Error(),
|
||||||
},
|
},
|
||||||
definition: definition,
|
Definition: definition,
|
||||||
binding: binding,
|
Binding: binding,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -243,6 +244,12 @@ func (c *celAdmissionController) Validate(
|
|||||||
|
|
||||||
var param runtime.Object
|
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 paramKind, paramRef is required in binding.
|
||||||
// If definition has no paramKind, paramRef set in binding will be ignored.
|
// If definition has no paramKind, paramRef set in binding will be ignored.
|
||||||
paramKind := definition.Spec.ParamKind
|
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 {
|
if err != nil {
|
||||||
// runtime error. Apply failure policy
|
// runtime error. Apply failure policy
|
||||||
wrappedError := fmt.Errorf("failed to evaluate CEL expression: %w", err)
|
wrappedError := fmt.Errorf("failed to evaluate CEL expression: %w", err)
|
||||||
@ -302,21 +319,21 @@ func (c *celAdmissionController) Validate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, decision := range decisions {
|
for _, decision := range decisions {
|
||||||
switch decision.action {
|
switch decision.Action {
|
||||||
case actionAdmit:
|
case ActionAdmit:
|
||||||
if decision.evaluation == evalError {
|
if decision.Evaluation == EvalError {
|
||||||
celmetrics.Metrics.ObserveAdmissionWithError(ctx, decision.elapsed, definition.Name, binding.Name, "active")
|
celmetrics.Metrics.ObserveAdmissionWithError(ctx, decision.Elapsed, definition.Name, binding.Name, "active")
|
||||||
}
|
}
|
||||||
case actionDeny:
|
case ActionDeny:
|
||||||
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
|
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
|
||||||
definition: definition,
|
Definition: definition,
|
||||||
binding: binding,
|
Binding: binding,
|
||||||
policyDecision: decision,
|
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:
|
default:
|
||||||
return fmt.Errorf("unrecognized evaluation decision '%s' for ValidatingAdmissionPolicyBinding '%s' with ValidatingAdmissionPolicy '%s'",
|
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
|
// TODO: refactor admission.NewForbidden so the name extraction is reusable but the code/reason is customizable
|
||||||
var message string
|
var message string
|
||||||
deniedDecision := deniedDecisions[0]
|
deniedDecision := deniedDecisions[0]
|
||||||
if deniedDecision.binding != nil {
|
if deniedDecision.Binding != nil {
|
||||||
message = fmt.Sprintf("ValidatingAdmissionPolicy '%s' with binding '%s' denied request: %s", deniedDecision.definition.Name, deniedDecision.binding.Name, deniedDecision.message)
|
message = fmt.Sprintf("ValidatingAdmissionPolicy '%s' with binding '%s' denied request: %s", deniedDecision.Definition.Name, deniedDecision.Binding.Name, deniedDecision.Message)
|
||||||
} else {
|
} 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)
|
err := admission.NewForbidden(a, errors.New(message)).(*k8serrors.StatusError)
|
||||||
reason := deniedDecision.reason
|
reason := deniedDecision.Reason
|
||||||
if len(reason) == 0 {
|
if len(reason) == 0 {
|
||||||
reason = metav1.StatusReasonInvalid
|
reason = metav1.StatusReasonInvalid
|
||||||
}
|
}
|
||||||
err.ErrStatus.Reason = reason
|
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})
|
err.ErrStatus.Details.Causes = append(err.ErrStatus.Details.Causes, metav1.StatusCause{Message: message})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -21,13 +21,14 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Validator defines the func used to validate the cel expressions
|
// Validator defines the func used to validate an object against the validator's rules.
|
||||||
// matchKind provides the GroupVersionKind that the object should be
|
// It expects the inbound object to already have been converted to the version expected
|
||||||
// validated by CEL expressions as.
|
// by the underlying CEL code (which is indicated by the match criteria of a policy definition).
|
||||||
type Validator interface {
|
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`
|
// ValidatorCompiler is Dependency Injected into the PolicyDefinition's `Compile`
|
||||||
|
@ -24,36 +24,36 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type policyDecisionAction string
|
type PolicyDecisionAction string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
actionAdmit policyDecisionAction = "admit"
|
ActionAdmit PolicyDecisionAction = "admit"
|
||||||
actionDeny policyDecisionAction = "deny"
|
ActionDeny PolicyDecisionAction = "deny"
|
||||||
)
|
)
|
||||||
|
|
||||||
type policyDecisionEvaluation string
|
type PolicyDecisionEvaluation string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
evalAdmit policyDecisionEvaluation = "admit"
|
EvalAdmit PolicyDecisionEvaluation = "admit"
|
||||||
evalError policyDecisionEvaluation = "error"
|
EvalError PolicyDecisionEvaluation = "error"
|
||||||
evalDeny policyDecisionEvaluation = "deny"
|
EvalDeny PolicyDecisionEvaluation = "deny"
|
||||||
)
|
)
|
||||||
|
|
||||||
type policyDecision struct {
|
type PolicyDecision struct {
|
||||||
action policyDecisionAction
|
Action PolicyDecisionAction
|
||||||
evaluation policyDecisionEvaluation
|
Evaluation PolicyDecisionEvaluation
|
||||||
message string
|
Message string
|
||||||
reason metav1.StatusReason
|
Reason metav1.StatusReason
|
||||||
elapsed time.Duration
|
Elapsed time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
type policyDecisionWithMetadata struct {
|
type policyDecisionWithMetadata struct {
|
||||||
policyDecision
|
PolicyDecision
|
||||||
definition *v1alpha1.ValidatingAdmissionPolicy
|
Definition *v1alpha1.ValidatingAdmissionPolicy
|
||||||
binding *v1alpha1.ValidatingAdmissionPolicyBinding
|
Binding *v1alpha1.ValidatingAdmissionPolicyBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
func reasonToCode(r metav1.StatusReason) int32 {
|
func ReasonToCode(r metav1.StatusReason) int32 {
|
||||||
switch r {
|
switch r {
|
||||||
case metav1.StatusReasonForbidden:
|
case metav1.StatusReasonForbidden:
|
||||||
return http.StatusForbidden
|
return http.StatusForbidden
|
||||||
|
@ -157,26 +157,22 @@ func objectToResolveVal(r runtime.Object) (interface{}, error) {
|
|||||||
return v.Object, nil
|
return v.Object, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func policyDecisionActionForError(f v1alpha1.FailurePolicyType) policyDecisionAction {
|
func policyDecisionActionForError(f v1alpha1.FailurePolicyType) PolicyDecisionAction {
|
||||||
if f == v1alpha1.Ignore {
|
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.
|
// 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.
|
// 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.
|
// Each PolicyDecision will have a decision and a message.
|
||||||
// policyDecision.message will be empty if the decision is allowed and no error met.
|
// 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
|
// 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
|
var err error
|
||||||
versionedAttr, err := generic.NewVersionedAttributes(a, matchKind, o)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
oldObjectVal, err := objectToResolveVal(versionedAttr.VersionedOldObject)
|
oldObjectVal, err := objectToResolveVal(versionedAttr.VersionedOldObject)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -189,6 +185,7 @@ func (v *CELValidator) Validate(a admission.Attributes, o admission.ObjectInterf
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
request := createAdmissionRequest(versionedAttr.Attributes)
|
request := createAdmissionRequest(versionedAttr.Attributes)
|
||||||
requestVal, err := convertObjectToUnstructured(request)
|
requestVal, err := convertObjectToUnstructured(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -214,41 +211,41 @@ func (v *CELValidator) Validate(a admission.Attributes, o admission.ObjectInterf
|
|||||||
var policyDecision = &decisions[i]
|
var policyDecision = &decisions[i]
|
||||||
|
|
||||||
if compilationResult.Error != nil {
|
if compilationResult.Error != nil {
|
||||||
policyDecision.action = policyDecisionActionForError(f)
|
policyDecision.Action = policyDecisionActionForError(f)
|
||||||
policyDecision.evaluation = evalError
|
policyDecision.Evaluation = EvalError
|
||||||
policyDecision.message = fmt.Sprintf("compilation error: %v", compilationResult.Error)
|
policyDecision.Message = fmt.Sprintf("compilation error: %v", compilationResult.Error)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if compilationResult.Program == nil {
|
if compilationResult.Program == nil {
|
||||||
policyDecision.action = policyDecisionActionForError(f)
|
policyDecision.Action = policyDecisionActionForError(f)
|
||||||
policyDecision.evaluation = evalError
|
policyDecision.Evaluation = EvalError
|
||||||
policyDecision.message = "unexpected internal error compiling expression"
|
policyDecision.Message = "unexpected internal error compiling expression"
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
t1 := time.Now()
|
t1 := time.Now()
|
||||||
evalResult, _, err := compilationResult.Program.Eval(va)
|
evalResult, _, err := compilationResult.Program.Eval(va)
|
||||||
elapsed := time.Since(t1)
|
elapsed := time.Since(t1)
|
||||||
policyDecision.elapsed = elapsed
|
policyDecision.Elapsed = elapsed
|
||||||
if err != nil {
|
if err != nil {
|
||||||
policyDecision.action = policyDecisionActionForError(f)
|
policyDecision.Action = policyDecisionActionForError(f)
|
||||||
policyDecision.evaluation = evalError
|
policyDecision.Evaluation = EvalError
|
||||||
policyDecision.message = fmt.Sprintf("expression '%v' resulted in error: %v", v.policy.Spec.Validations[i].Expression, err)
|
policyDecision.Message = fmt.Sprintf("expression '%v' resulted in error: %v", v.policy.Spec.Validations[i].Expression, err)
|
||||||
} else if evalResult != celtypes.True {
|
} else if evalResult != celtypes.True {
|
||||||
policyDecision.action = actionDeny
|
policyDecision.Action = ActionDeny
|
||||||
if validation.Reason == nil {
|
if validation.Reason == nil {
|
||||||
policyDecision.reason = metav1.StatusReasonInvalid
|
policyDecision.Reason = metav1.StatusReasonInvalid
|
||||||
} else {
|
} else {
|
||||||
policyDecision.reason = *validation.Reason
|
policyDecision.Reason = *validation.Reason
|
||||||
}
|
}
|
||||||
if len(validation.Message) > 0 {
|
if len(validation.Message) > 0 {
|
||||||
policyDecision.message = strings.TrimSpace(validation.Message)
|
policyDecision.Message = strings.TrimSpace(validation.Message)
|
||||||
} else {
|
} else {
|
||||||
policyDecision.message = fmt.Sprintf("failed expression: %v", strings.TrimSpace(validation.Expression))
|
policyDecision.Message = fmt.Sprintf("failed expression: %v", strings.TrimSpace(validation.Expression))
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
policyDecision.action = actionAdmit
|
policyDecision.Action = ActionAdmit
|
||||||
policyDecision.evaluation = evalAdmit
|
policyDecision.Evaluation = EvalAdmit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCompile(t *testing.T) {
|
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 {
|
func generatedDecision(k PolicyDecisionAction, m string, r metav1.StatusReason) PolicyDecision {
|
||||||
return policyDecision{action: k, message: m, reason: r}
|
return PolicyDecision{Action: k, Message: m, Reason: r}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidate(t *testing.T) {
|
func TestValidate(t *testing.T) {
|
||||||
@ -270,7 +271,7 @@ func TestValidate(t *testing.T) {
|
|||||||
policy *v1alpha1.ValidatingAdmissionPolicy
|
policy *v1alpha1.ValidatingAdmissionPolicy
|
||||||
attributes admission.Attributes
|
attributes admission.Attributes
|
||||||
params runtime.Object
|
params runtime.Object
|
||||||
policyDecisions []policyDecision
|
policyDecisions []PolicyDecision
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "valid syntax for object",
|
name: "valid syntax for object",
|
||||||
@ -280,8 +281,8 @@ func TestValidate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}, nil, nil),
|
}, nil, nil),
|
||||||
attributes: newValidAttribute(nil, false),
|
attributes: newValidAttribute(nil, false),
|
||||||
policyDecisions: []policyDecision{
|
policyDecisions: []PolicyDecision{
|
||||||
generatedDecision(actionAdmit, "", ""),
|
generatedDecision(ActionAdmit, "", ""),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -292,8 +293,8 @@ func TestValidate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}, nil, nil),
|
}, nil, nil),
|
||||||
attributes: newValidAttribute(nil, false),
|
attributes: newValidAttribute(nil, false),
|
||||||
policyDecisions: []policyDecision{
|
policyDecisions: []PolicyDecision{
|
||||||
generatedDecision(actionAdmit, "", ""),
|
generatedDecision(ActionAdmit, "", ""),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -307,9 +308,9 @@ func TestValidate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}, nil, nil),
|
}, nil, nil),
|
||||||
attributes: newValidAttribute(nil, false),
|
attributes: newValidAttribute(nil, false),
|
||||||
policyDecisions: []policyDecision{
|
policyDecisions: []PolicyDecision{
|
||||||
generatedDecision(actionAdmit, "", ""),
|
generatedDecision(ActionAdmit, "", ""),
|
||||||
generatedDecision(actionAdmit, "", ""),
|
generatedDecision(ActionAdmit, "", ""),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -318,8 +319,8 @@ func TestValidate(t *testing.T) {
|
|||||||
{Expression: "request.operation == 'CREATE'"},
|
{Expression: "request.operation == 'CREATE'"},
|
||||||
}, nil, nil),
|
}, nil, nil),
|
||||||
attributes: newValidAttribute(nil, false),
|
attributes: newValidAttribute(nil, false),
|
||||||
policyDecisions: []policyDecision{
|
policyDecisions: []PolicyDecision{
|
||||||
generatedDecision(actionAdmit, "", ""),
|
generatedDecision(ActionAdmit, "", ""),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -329,8 +330,8 @@ func TestValidate(t *testing.T) {
|
|||||||
}, hasParamKind, nil),
|
}, hasParamKind, nil),
|
||||||
attributes: newValidAttribute(nil, false),
|
attributes: newValidAttribute(nil, false),
|
||||||
params: configMapParams,
|
params: configMapParams,
|
||||||
policyDecisions: []policyDecision{
|
policyDecisions: []PolicyDecision{
|
||||||
generatedDecision(actionAdmit, "", ""),
|
generatedDecision(ActionAdmit, "", ""),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -347,8 +348,8 @@ func TestValidate(t *testing.T) {
|
|||||||
"fakeString": "fake",
|
"fakeString": "fake",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
policyDecisions: []policyDecision{
|
policyDecisions: []PolicyDecision{
|
||||||
generatedDecision(actionDeny, "failed expression: object.subsets.size() > 2", metav1.StatusReasonInvalid),
|
generatedDecision(ActionDeny, "failed expression: object.subsets.size() > 2", metav1.StatusReasonInvalid),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -363,9 +364,9 @@ func TestValidate(t *testing.T) {
|
|||||||
}, hasParamKind, ignorePolicy),
|
}, hasParamKind, ignorePolicy),
|
||||||
attributes: newValidAttribute(nil, false),
|
attributes: newValidAttribute(nil, false),
|
||||||
params: configMapParams,
|
params: configMapParams,
|
||||||
policyDecisions: []policyDecision{
|
policyDecisions: []PolicyDecision{
|
||||||
generatedDecision(actionAdmit, "", ""),
|
generatedDecision(ActionAdmit, "", ""),
|
||||||
generatedDecision(actionDeny, "failed expression: object.subsets.size() > 2", metav1.StatusReasonInvalid),
|
generatedDecision(ActionDeny, "failed expression: object.subsets.size() > 2", metav1.StatusReasonInvalid),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -380,9 +381,9 @@ func TestValidate(t *testing.T) {
|
|||||||
}, hasParamKind, nil),
|
}, hasParamKind, nil),
|
||||||
attributes: newValidAttribute(nil, false),
|
attributes: newValidAttribute(nil, false),
|
||||||
params: configMapParams,
|
params: configMapParams,
|
||||||
policyDecisions: []policyDecision{
|
policyDecisions: []PolicyDecision{
|
||||||
generatedDecision(actionDeny, "failed expression: oldObject != null", metav1.StatusReasonInvalid),
|
generatedDecision(ActionDeny, "failed expression: oldObject != null", metav1.StatusReasonInvalid),
|
||||||
generatedDecision(actionDeny, "failed expression: object.subsets.size() > 2", metav1.StatusReasonInvalid),
|
generatedDecision(ActionDeny, "failed expression: object.subsets.size() > 2", metav1.StatusReasonInvalid),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -397,9 +398,9 @@ func TestValidate(t *testing.T) {
|
|||||||
}, hasParamKind, nil),
|
}, hasParamKind, nil),
|
||||||
attributes: newValidAttribute(nil, true),
|
attributes: newValidAttribute(nil, true),
|
||||||
params: configMapParams,
|
params: configMapParams,
|
||||||
policyDecisions: []policyDecision{
|
policyDecisions: []PolicyDecision{
|
||||||
generatedDecision(actionAdmit, "", ""),
|
generatedDecision(ActionAdmit, "", ""),
|
||||||
generatedDecision(actionAdmit, "", ""),
|
generatedDecision(ActionAdmit, "", ""),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -412,8 +413,8 @@ func TestValidate(t *testing.T) {
|
|||||||
}, hasParamKind, nil),
|
}, hasParamKind, nil),
|
||||||
attributes: newValidAttribute(nil, true),
|
attributes: newValidAttribute(nil, true),
|
||||||
params: configMapParams,
|
params: configMapParams,
|
||||||
policyDecisions: []policyDecision{
|
policyDecisions: []PolicyDecision{
|
||||||
generatedDecision(actionDeny, "failed expression: oldObject == null", metav1.StatusReasonForbidden),
|
generatedDecision(ActionDeny, "failed expression: oldObject == null", metav1.StatusReasonForbidden),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -427,8 +428,8 @@ func TestValidate(t *testing.T) {
|
|||||||
}, hasParamKind, nil),
|
}, hasParamKind, nil),
|
||||||
attributes: newValidAttribute(nil, true),
|
attributes: newValidAttribute(nil, true),
|
||||||
params: configMapParams,
|
params: configMapParams,
|
||||||
policyDecisions: []policyDecision{
|
policyDecisions: []PolicyDecision{
|
||||||
generatedDecision(actionDeny, "old object should be present", metav1.StatusReasonForbidden),
|
generatedDecision(ActionDeny, "old object should be present", metav1.StatusReasonForbidden),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -440,8 +441,8 @@ func TestValidate(t *testing.T) {
|
|||||||
}, hasParamKind, nil),
|
}, hasParamKind, nil),
|
||||||
attributes: newValidAttribute(nil, true),
|
attributes: newValidAttribute(nil, true),
|
||||||
params: configMapParams,
|
params: configMapParams,
|
||||||
policyDecisions: []policyDecision{
|
policyDecisions: []PolicyDecision{
|
||||||
generatedDecision(actionDeny, "resulted in error", ""),
|
generatedDecision(ActionDeny, "resulted in error", ""),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -453,8 +454,8 @@ func TestValidate(t *testing.T) {
|
|||||||
}, hasParamKind, nil),
|
}, hasParamKind, nil),
|
||||||
attributes: newValidAttribute(nil, false),
|
attributes: newValidAttribute(nil, false),
|
||||||
params: crdParams,
|
params: crdParams,
|
||||||
policyDecisions: []policyDecision{
|
policyDecisions: []PolicyDecision{
|
||||||
generatedDecision(actionAdmit, "", ""),
|
generatedDecision(ActionAdmit, "", ""),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -469,9 +470,9 @@ func TestValidate(t *testing.T) {
|
|||||||
}, hasParamKind, nil),
|
}, hasParamKind, nil),
|
||||||
attributes: newValidAttribute(nil, false),
|
attributes: newValidAttribute(nil, false),
|
||||||
params: crdParams,
|
params: crdParams,
|
||||||
policyDecisions: []policyDecision{
|
policyDecisions: []PolicyDecision{
|
||||||
generatedDecision(actionDeny, "compilation error: compilation failed: ERROR: <input>:1:6: Syntax error:", ""),
|
generatedDecision(ActionDeny, "compilation error: compilation failed: ERROR: <input>:1:6: Syntax error:", ""),
|
||||||
generatedDecision(actionDeny, "failed expression: object.subsets.size() > params.spec.testSize", metav1.StatusReasonInvalid),
|
generatedDecision(ActionDeny, "failed expression: object.subsets.size() > params.spec.testSize", metav1.StatusReasonInvalid),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -486,9 +487,9 @@ func TestValidate(t *testing.T) {
|
|||||||
}, hasParamKind, ignorePolicy),
|
}, hasParamKind, ignorePolicy),
|
||||||
attributes: newValidAttribute(nil, false),
|
attributes: newValidAttribute(nil, false),
|
||||||
params: crdParams,
|
params: crdParams,
|
||||||
policyDecisions: []policyDecision{
|
policyDecisions: []PolicyDecision{
|
||||||
generatedDecision(actionAdmit, "compilation error: compilation failed: ERROR:", ""),
|
generatedDecision(ActionAdmit, "compilation error: compilation failed: ERROR:", ""),
|
||||||
generatedDecision(actionDeny, "failed expression: object.subsets.size() > params.spec.testSize", metav1.StatusReasonInvalid),
|
generatedDecision(ActionDeny, "failed expression: object.subsets.size() > params.spec.testSize", metav1.StatusReasonInvalid),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -500,8 +501,8 @@ func TestValidate(t *testing.T) {
|
|||||||
}, nil, nil),
|
}, nil, nil),
|
||||||
attributes: newValidAttribute(&podObject, false),
|
attributes: newValidAttribute(&podObject, false),
|
||||||
params: crdParams,
|
params: crdParams,
|
||||||
policyDecisions: []policyDecision{
|
policyDecisions: []PolicyDecision{
|
||||||
generatedDecision(actionAdmit, "", ""),
|
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
|
// Simulate a interface holding a nil pointer, since this is how param is passed to Validate
|
||||||
// if paramRef is unset on a binding
|
// if paramRef is unset on a binding
|
||||||
params: runtime.Object(nilUnstructured),
|
params: runtime.Object(nilUnstructured),
|
||||||
policyDecisions: []policyDecision{
|
policyDecisions: []PolicyDecision{
|
||||||
generatedDecision(actionDeny, "params as required", metav1.StatusReasonForbidden),
|
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
|
// Simulate a interface holding a nil pointer, since this is how param is passed to Validate
|
||||||
// if paramRef is unset on a binding
|
// if paramRef is unset on a binding
|
||||||
params: runtime.Object(nilUnstructured),
|
params: runtime.Object(nilUnstructured),
|
||||||
policyDecisions: []policyDecision{
|
policyDecisions: []PolicyDecision{
|
||||||
generatedDecision(actionAdmit, "", ""),
|
generatedDecision(ActionAdmit, "", ""),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -550,20 +551,25 @@ func TestValidate(t *testing.T) {
|
|||||||
CompilationResults := validator.(*CELValidator).compilationResults
|
CompilationResults := validator.(*CELValidator).compilationResults
|
||||||
require.Equal(t, len(validations), len(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 {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
require.Equal(t, len(policyResults), len(tc.policyDecisions))
|
require.Equal(t, len(policyResults), len(tc.policyDecisions))
|
||||||
for i, policyDecision := range tc.policyDecisions {
|
for i, policyDecision := range tc.policyDecisions {
|
||||||
if 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)
|
t.Errorf("Expected policy decision kind '%v' but got '%v'", policyDecision.Action, policyResults[i].Action)
|
||||||
}
|
}
|
||||||
if !strings.Contains(policyResults[i].message, policyDecision.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)
|
t.Errorf("Expected policy decision message contains '%v' but got '%v'", policyDecision.Message, policyResults[i].Message)
|
||||||
}
|
}
|
||||||
if 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)
|
t.Errorf("Expected policy decision reason '%v' but got '%v'", policyDecision.Reason, policyResults[i].Reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user