Custom match criteria (#116350)
* Add custom match conditions for CEL admission This PR is based off of, and dependent on the following PR: https://github.com/kubernetes/kubernetes/pull/116261 Signed-off-by: Max Smythe <smythe@google.com> * run `make update` Signed-off-by: Max Smythe <smythe@google.com> * Fix unit tests Signed-off-by: Max Smythe <smythe@google.com> * Fix unit tests Signed-off-by: Max Smythe <smythe@google.com> * Update compatibility test data Signed-off-by: Max Smythe <smythe@google.com> * Revert "Update compatibility test data" This reverts commit 312ba7f9e74e0ec4a7ac1f07bf575479c608af28. * Allow params during validation; make match conditions optional Signed-off-by: Max Smythe <smythe@google.com> * Add conditional ignoring of matcher CEL expression validation on update Signed-off-by: Max Smythe <smythe@google.com> * Run codegen Signed-off-by: Max Smythe <smythe@google.com> * Add more validation tests Signed-off-by: Max Smythe <smythe@google.com> * Short-circuit CEL matcher when no matchers specified Signed-off-by: Max Smythe <smythe@google.com> * Run codegen Signed-off-by: Max Smythe <smythe@google.com> * Address review comments Signed-off-by: Max Smythe <smythe@google.com> --------- Signed-off-by: Max Smythe <smythe@google.com>
This commit is contained in:
@@ -206,6 +206,24 @@ type ValidatingAdmissionPolicySpec struct {
|
||||
// +optional
|
||||
Validations []Validation
|
||||
|
||||
// MatchConditions is a list of conditions that must be met for a request to be validated.
|
||||
// Match conditions filter requests that have already been matched by the rules,
|
||||
// namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests.
|
||||
// There are a maximum of 64 match conditions allowed.
|
||||
//
|
||||
// If a parameter object is provided, it can be accessed via the `params` handle in the same
|
||||
// manner as validation expressions.
|
||||
//
|
||||
// The exact matching logic is (in order):
|
||||
// 1. If ANY matchCondition evaluates to FALSE, the policy is skipped.
|
||||
// 2. If ALL matchConditions evaluate to TRUE, the policy is evaluated.
|
||||
// 3. If any matchCondition evaluates to an error (but none are FALSE):
|
||||
// - If failurePolicy=Fail, reject the request
|
||||
// - If failurePolicy=Ignore, the policy is skipped
|
||||
//
|
||||
// +optional
|
||||
MatchConditions []MatchCondition
|
||||
|
||||
// failurePolicy defines how to handle failures for the admission policy. Failures can
|
||||
// occur from CEL expression parse errors, type check errors, runtime errors and invalid
|
||||
// or mis-configured policy definitions or bindings.
|
||||
|
@@ -59,6 +59,16 @@ func RegisterConversions(s *runtime.Scheme) error {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*v1alpha1.MatchCondition)(nil), (*admissionregistration.MatchCondition)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_MatchCondition_To_admissionregistration_MatchCondition(a.(*v1alpha1.MatchCondition), b.(*admissionregistration.MatchCondition), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*admissionregistration.MatchCondition)(nil), (*v1alpha1.MatchCondition)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_admissionregistration_MatchCondition_To_v1alpha1_MatchCondition(a.(*admissionregistration.MatchCondition), b.(*v1alpha1.MatchCondition), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*v1alpha1.MatchResources)(nil), (*admissionregistration.MatchResources)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_MatchResources_To_admissionregistration_MatchResources(a.(*v1alpha1.MatchResources), b.(*admissionregistration.MatchResources), scope)
|
||||
}); err != nil {
|
||||
@@ -236,6 +246,28 @@ func Convert_admissionregistration_ExpressionWarning_To_v1alpha1_ExpressionWarni
|
||||
return autoConvert_admissionregistration_ExpressionWarning_To_v1alpha1_ExpressionWarning(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_MatchCondition_To_admissionregistration_MatchCondition(in *v1alpha1.MatchCondition, out *admissionregistration.MatchCondition, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Expression = in.Expression
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha1_MatchCondition_To_admissionregistration_MatchCondition is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_MatchCondition_To_admissionregistration_MatchCondition(in *v1alpha1.MatchCondition, out *admissionregistration.MatchCondition, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_MatchCondition_To_admissionregistration_MatchCondition(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_admissionregistration_MatchCondition_To_v1alpha1_MatchCondition(in *admissionregistration.MatchCondition, out *v1alpha1.MatchCondition, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Expression = in.Expression
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_admissionregistration_MatchCondition_To_v1alpha1_MatchCondition is an autogenerated conversion function.
|
||||
func Convert_admissionregistration_MatchCondition_To_v1alpha1_MatchCondition(in *admissionregistration.MatchCondition, out *v1alpha1.MatchCondition, s conversion.Scope) error {
|
||||
return autoConvert_admissionregistration_MatchCondition_To_v1alpha1_MatchCondition(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_MatchResources_To_admissionregistration_MatchResources(in *v1alpha1.MatchResources, out *admissionregistration.MatchResources, s conversion.Scope) error {
|
||||
out.NamespaceSelector = (*v1.LabelSelector)(unsafe.Pointer(in.NamespaceSelector))
|
||||
out.ObjectSelector = (*v1.LabelSelector)(unsafe.Pointer(in.ObjectSelector))
|
||||
@@ -592,6 +624,7 @@ func autoConvert_v1alpha1_ValidatingAdmissionPolicySpec_To_admissionregistration
|
||||
out.Validations = *(*[]admissionregistration.Validation)(unsafe.Pointer(&in.Validations))
|
||||
out.FailurePolicy = (*admissionregistration.FailurePolicyType)(unsafe.Pointer(in.FailurePolicy))
|
||||
out.AuditAnnotations = *(*[]admissionregistration.AuditAnnotation)(unsafe.Pointer(&in.AuditAnnotations))
|
||||
out.MatchConditions = *(*[]admissionregistration.MatchCondition)(unsafe.Pointer(&in.MatchConditions))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -612,6 +645,7 @@ func autoConvert_admissionregistration_ValidatingAdmissionPolicySpec_To_v1alpha1
|
||||
out.MatchConstraints = nil
|
||||
}
|
||||
out.Validations = *(*[]v1alpha1.Validation)(unsafe.Pointer(&in.Validations))
|
||||
out.MatchConditions = *(*[]v1alpha1.MatchCondition)(unsafe.Pointer(&in.MatchConditions))
|
||||
out.FailurePolicy = (*v1alpha1.FailurePolicyType)(unsafe.Pointer(in.FailurePolicy))
|
||||
out.AuditAnnotations = *(*[]v1alpha1.AuditAnnotation)(unsafe.Pointer(&in.AuditAnnotations))
|
||||
return nil
|
||||
|
@@ -213,6 +213,7 @@ func validateAdmissionReviewVersions(versions []string, requireRecognizedAdmissi
|
||||
func ValidateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList {
|
||||
return validateValidatingWebhookConfiguration(e, validationOptions{
|
||||
ignoreMatchConditions: false,
|
||||
allowParamsInMatchConditions: false,
|
||||
requireNoSideEffects: true,
|
||||
requireRecognizedAdmissionReviewVersion: true,
|
||||
requireUniqueWebhookNames: true,
|
||||
@@ -241,6 +242,7 @@ func validateValidatingWebhookConfiguration(e *admissionregistration.ValidatingW
|
||||
func ValidateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration) field.ErrorList {
|
||||
return validateMutatingWebhookConfiguration(e, validationOptions{
|
||||
ignoreMatchConditions: false,
|
||||
allowParamsInMatchConditions: false,
|
||||
requireNoSideEffects: true,
|
||||
requireRecognizedAdmissionReviewVersion: true,
|
||||
requireUniqueWebhookNames: true,
|
||||
@@ -250,6 +252,7 @@ func ValidateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebho
|
||||
|
||||
type validationOptions struct {
|
||||
ignoreMatchConditions bool
|
||||
allowParamsInMatchConditions bool
|
||||
requireNoSideEffects bool
|
||||
requireRecognizedAdmissionReviewVersion bool
|
||||
requireUniqueWebhookNames bool
|
||||
@@ -324,7 +327,7 @@ func validateValidatingWebhook(hook *admissionregistration.ValidatingWebhook, op
|
||||
}
|
||||
|
||||
if !opts.ignoreMatchConditions {
|
||||
allErrors = append(allErrors, validateMatchConditions(hook.MatchConditions, fldPath.Child("matchConditions"))...)
|
||||
allErrors = append(allErrors, validateMatchConditions(hook.MatchConditions, opts, fldPath.Child("matchConditions"))...)
|
||||
}
|
||||
|
||||
return allErrors
|
||||
@@ -382,7 +385,7 @@ func validateMutatingWebhook(hook *admissionregistration.MutatingWebhook, opts v
|
||||
}
|
||||
|
||||
if !opts.ignoreMatchConditions {
|
||||
allErrors = append(allErrors, validateMatchConditions(hook.MatchConditions, fldPath.Child("matchConditions"))...)
|
||||
allErrors = append(allErrors, validateMatchConditions(hook.MatchConditions, opts, fldPath.Child("matchConditions"))...)
|
||||
}
|
||||
|
||||
return allErrors
|
||||
@@ -520,6 +523,17 @@ func ignoreValidatingWebhookMatchConditions(new, old []admissionregistration.Val
|
||||
return true
|
||||
}
|
||||
|
||||
// ignoreValidatingAdmissionPolicyMatchConditions returns true if there have been no updates that could invalidate previously-valid match conditions
|
||||
func ignoreValidatingAdmissionPolicyMatchConditions(new, old *admissionregistration.ValidatingAdmissionPolicy) bool {
|
||||
if !reflect.DeepEqual(new.Spec.ParamKind, old.Spec.ParamKind) {
|
||||
return false
|
||||
}
|
||||
if !reflect.DeepEqual(new.Spec.MatchConditions, old.Spec.MatchConditions) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// mutatingHasUniqueWebhookNames returns true if all webhooks have unique names
|
||||
func mutatingHasUniqueWebhookNames(webhooks []admissionregistration.MutatingWebhook) bool {
|
||||
names := sets.NewString()
|
||||
@@ -610,6 +624,7 @@ func mutatingWebhookHasInvalidLabelValueInSelector(webhooks []admissionregistrat
|
||||
func ValidateValidatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList {
|
||||
return validateValidatingWebhookConfiguration(newC, validationOptions{
|
||||
ignoreMatchConditions: ignoreValidatingWebhookMatchConditions(newC.Webhooks, oldC.Webhooks),
|
||||
allowParamsInMatchConditions: false,
|
||||
requireNoSideEffects: validatingHasNoSideEffects(oldC.Webhooks),
|
||||
requireRecognizedAdmissionReviewVersion: validatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks),
|
||||
requireUniqueWebhookNames: validatingHasUniqueWebhookNames(oldC.Webhooks),
|
||||
@@ -621,6 +636,7 @@ func ValidateValidatingWebhookConfigurationUpdate(newC, oldC *admissionregistrat
|
||||
func ValidateMutatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.MutatingWebhookConfiguration) field.ErrorList {
|
||||
return validateMutatingWebhookConfiguration(newC, validationOptions{
|
||||
ignoreMatchConditions: ignoreMutatingWebhookMatchConditions(newC.Webhooks, oldC.Webhooks),
|
||||
allowParamsInMatchConditions: false,
|
||||
requireNoSideEffects: mutatingHasNoSideEffects(oldC.Webhooks),
|
||||
requireRecognizedAdmissionReviewVersion: mutatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks),
|
||||
requireUniqueWebhookNames: mutatingHasUniqueWebhookNames(oldC.Webhooks),
|
||||
@@ -638,16 +654,16 @@ const (
|
||||
|
||||
// ValidateValidatingAdmissionPolicy validates a ValidatingAdmissionPolicy before creation.
|
||||
func ValidateValidatingAdmissionPolicy(p *admissionregistration.ValidatingAdmissionPolicy) field.ErrorList {
|
||||
return validateValidatingAdmissionPolicy(p)
|
||||
return validateValidatingAdmissionPolicy(p, validationOptions{ignoreMatchConditions: false})
|
||||
}
|
||||
|
||||
func validateValidatingAdmissionPolicy(p *admissionregistration.ValidatingAdmissionPolicy) field.ErrorList {
|
||||
func validateValidatingAdmissionPolicy(p *admissionregistration.ValidatingAdmissionPolicy, opts validationOptions) field.ErrorList {
|
||||
allErrors := genericvalidation.ValidateObjectMeta(&p.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
|
||||
allErrors = append(allErrors, validateValidatingAdmissionPolicySpec(p.ObjectMeta, &p.Spec, field.NewPath("spec"))...)
|
||||
allErrors = append(allErrors, validateValidatingAdmissionPolicySpec(p.ObjectMeta, &p.Spec, opts, field.NewPath("spec"))...)
|
||||
return allErrors
|
||||
}
|
||||
|
||||
func validateValidatingAdmissionPolicySpec(meta metav1.ObjectMeta, spec *admissionregistration.ValidatingAdmissionPolicySpec, fldPath *field.Path) field.ErrorList {
|
||||
func validateValidatingAdmissionPolicySpec(meta metav1.ObjectMeta, spec *admissionregistration.ValidatingAdmissionPolicySpec, opts validationOptions, fldPath *field.Path) field.ErrorList {
|
||||
var allErrors field.ErrorList
|
||||
if spec.FailurePolicy == nil {
|
||||
allErrors = append(allErrors, field.Required(fldPath.Child("failurePolicy"), ""))
|
||||
@@ -655,6 +671,7 @@ func validateValidatingAdmissionPolicySpec(meta metav1.ObjectMeta, spec *admissi
|
||||
allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *spec.FailurePolicy, supportedFailurePolicies.List()))
|
||||
}
|
||||
if spec.ParamKind != nil {
|
||||
opts.allowParamsInMatchConditions = true
|
||||
allErrors = append(allErrors, validateParamKind(*spec.ParamKind, fldPath.Child("paramKind"))...)
|
||||
}
|
||||
if spec.MatchConstraints == nil {
|
||||
@@ -666,6 +683,9 @@ func validateValidatingAdmissionPolicySpec(meta metav1.ObjectMeta, spec *admissi
|
||||
allErrors = append(allErrors, field.Required(fldPath.Child("matchConstraints", "resourceRules"), ""))
|
||||
}
|
||||
}
|
||||
if !opts.ignoreMatchConditions {
|
||||
allErrors = append(allErrors, validateMatchConditions(spec.MatchConditions, opts, fldPath.Child("matchConditions"))...)
|
||||
}
|
||||
if len(spec.Validations) == 0 && len(spec.AuditAnnotations) == 0 {
|
||||
allErrors = append(allErrors, field.Required(fldPath.Child("validations"), "validations or auditAnnotations must contain at least one item"))
|
||||
allErrors = append(allErrors, field.Required(fldPath.Child("auditAnnotations"), "validations or auditAnnotations must contain at least one item"))
|
||||
@@ -822,14 +842,14 @@ func validateNamedRuleWithOperations(n *admissionregistration.NamedRuleWithOpera
|
||||
return allErrors
|
||||
}
|
||||
|
||||
func validateMatchConditions(m []admissionregistration.MatchCondition, fldPath *field.Path) field.ErrorList {
|
||||
func validateMatchConditions(m []admissionregistration.MatchCondition, opts validationOptions, fldPath *field.Path) field.ErrorList {
|
||||
var allErrors field.ErrorList
|
||||
conditionNames := sets.NewString()
|
||||
if len(m) > 64 {
|
||||
allErrors = append(allErrors, field.TooMany(fldPath, len(m), 64))
|
||||
}
|
||||
for i, matchCondition := range m {
|
||||
allErrors = append(allErrors, validateMatchCondition(&matchCondition, fldPath.Index(i))...)
|
||||
allErrors = append(allErrors, validateMatchCondition(&matchCondition, opts, fldPath.Index(i))...)
|
||||
if len(matchCondition.Name) > 0 {
|
||||
if conditionNames.Has(matchCondition.Name) {
|
||||
allErrors = append(allErrors, field.Duplicate(fldPath.Index(i).Child("name"), matchCondition.Name))
|
||||
@@ -841,14 +861,14 @@ func validateMatchConditions(m []admissionregistration.MatchCondition, fldPath *
|
||||
return allErrors
|
||||
}
|
||||
|
||||
func validateMatchCondition(v *admissionregistration.MatchCondition, fldPath *field.Path) field.ErrorList {
|
||||
func validateMatchCondition(v *admissionregistration.MatchCondition, opts validationOptions, fldPath *field.Path) field.ErrorList {
|
||||
var allErrors field.ErrorList
|
||||
trimmedExpression := strings.TrimSpace(v.Expression)
|
||||
if len(trimmedExpression) == 0 {
|
||||
allErrors = append(allErrors, field.Required(fldPath.Child("expression"), ""))
|
||||
} else {
|
||||
allErrors = append(allErrors, validateCELExpression(trimmedExpression, plugincel.OptionalVariableDeclarations{
|
||||
HasParams: false,
|
||||
HasParams: opts.allowParamsInMatchConditions,
|
||||
HasAuthorizer: true,
|
||||
}, fldPath.Child("expression"))...)
|
||||
}
|
||||
@@ -995,7 +1015,7 @@ func validateParamRef(pr *admissionregistration.ParamRef, fldPath *field.Path) f
|
||||
|
||||
// ValidateValidatingAdmissionPolicyUpdate validates update of validating admission policy
|
||||
func ValidateValidatingAdmissionPolicyUpdate(newC, oldC *admissionregistration.ValidatingAdmissionPolicy) field.ErrorList {
|
||||
return validateValidatingAdmissionPolicy(newC)
|
||||
return validateValidatingAdmissionPolicy(newC, validationOptions{ignoreMatchConditions: ignoreValidatingAdmissionPolicyMatchConditions(newC, oldC)})
|
||||
}
|
||||
|
||||
// ValidateValidatingAdmissionPolicyStatusUpdate validates update of status of validating admission policy
|
||||
|
@@ -3150,6 +3150,131 @@ func TestValidateValidatingAdmissionPolicy(t *testing.T) {
|
||||
},
|
||||
expectedError: `spec.auditAnnotations[0].valueExpression: Invalid value: "object.x in [1, 2, ": compilation failed: ERROR: <input>:1:19: Syntax error: missing ']' at '<EOF>`,
|
||||
},
|
||||
{
|
||||
name: "single match condition must have a name",
|
||||
config: &admissionregistration.ValidatingAdmissionPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config",
|
||||
},
|
||||
Spec: admissionregistration.ValidatingAdmissionPolicySpec{
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Expression: "true",
|
||||
},
|
||||
},
|
||||
Validations: []admissionregistration.Validation{
|
||||
{
|
||||
Expression: "object.x < 100",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: `spec.matchConditions[0].name: Required value`,
|
||||
},
|
||||
{
|
||||
name: "match condition with parameters allowed",
|
||||
config: &admissionregistration.ValidatingAdmissionPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config",
|
||||
},
|
||||
Spec: admissionregistration.ValidatingAdmissionPolicySpec{
|
||||
ParamKind: &admissionregistration.ParamKind{
|
||||
Kind: "Foo",
|
||||
APIVersion: "foobar/v1alpha1",
|
||||
},
|
||||
MatchConstraints: &admissionregistration.MatchResources{
|
||||
ResourceRules: []admissionregistration.NamedRuleWithOperations{
|
||||
{
|
||||
RuleWithOperations: admissionregistration.RuleWithOperations{
|
||||
Operations: []admissionregistration.OperationType{"*"},
|
||||
Rule: admissionregistration.Rule{
|
||||
APIGroups: []string{"a"},
|
||||
APIVersions: []string{"a"},
|
||||
Resources: []string{"a"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
NamespaceSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"a": "b"},
|
||||
},
|
||||
ObjectSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"a": "b"},
|
||||
},
|
||||
MatchPolicy: func() *admissionregistration.MatchPolicyType {
|
||||
r := admissionregistration.MatchPolicyType("Exact")
|
||||
return &r
|
||||
}(),
|
||||
},
|
||||
FailurePolicy: func() *admissionregistration.FailurePolicyType {
|
||||
r := admissionregistration.FailurePolicyType("Fail")
|
||||
return &r
|
||||
}(),
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "hasParams",
|
||||
Expression: `params.foo == "okay"`,
|
||||
},
|
||||
},
|
||||
Validations: []admissionregistration.Validation{
|
||||
{
|
||||
Expression: "object.x < 100",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "match condition with parameters not allowed if no param kind",
|
||||
config: &admissionregistration.ValidatingAdmissionPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config",
|
||||
},
|
||||
Spec: admissionregistration.ValidatingAdmissionPolicySpec{
|
||||
MatchConstraints: &admissionregistration.MatchResources{
|
||||
ResourceRules: []admissionregistration.NamedRuleWithOperations{
|
||||
{
|
||||
RuleWithOperations: admissionregistration.RuleWithOperations{
|
||||
Operations: []admissionregistration.OperationType{"*"},
|
||||
Rule: admissionregistration.Rule{
|
||||
APIGroups: []string{"a"},
|
||||
APIVersions: []string{"a"},
|
||||
Resources: []string{"a"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
NamespaceSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"a": "b"},
|
||||
},
|
||||
ObjectSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"a": "b"},
|
||||
},
|
||||
MatchPolicy: func() *admissionregistration.MatchPolicyType {
|
||||
r := admissionregistration.MatchPolicyType("Exact")
|
||||
return &r
|
||||
}(),
|
||||
},
|
||||
FailurePolicy: func() *admissionregistration.FailurePolicyType {
|
||||
r := admissionregistration.FailurePolicyType("Fail")
|
||||
return &r
|
||||
}(),
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "hasParams",
|
||||
Expression: `params.foo == "okay"`,
|
||||
},
|
||||
},
|
||||
Validations: []admissionregistration.Validation{
|
||||
{
|
||||
Expression: "object.x < 100",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: `undeclared reference to 'params'`,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
@@ -3293,6 +3418,202 @@ func TestValidateValidatingAdmissionPolicyUpdate(t *testing.T) {
|
||||
Spec: admissionregistration.ValidatingAdmissionPolicySpec{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "match conditions re-checked if paramKind changes",
|
||||
oldconfig: &admissionregistration.ValidatingAdmissionPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config",
|
||||
},
|
||||
Spec: admissionregistration.ValidatingAdmissionPolicySpec{
|
||||
ParamKind: &admissionregistration.ParamKind{
|
||||
Kind: "Foo",
|
||||
APIVersion: "foobar/v1alpha1",
|
||||
},
|
||||
MatchConstraints: &admissionregistration.MatchResources{
|
||||
ResourceRules: []admissionregistration.NamedRuleWithOperations{
|
||||
{
|
||||
RuleWithOperations: admissionregistration.RuleWithOperations{
|
||||
Operations: []admissionregistration.OperationType{"*"},
|
||||
Rule: admissionregistration.Rule{
|
||||
APIGroups: []string{"a"},
|
||||
APIVersions: []string{"a"},
|
||||
Resources: []string{"a"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
NamespaceSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"a": "b"},
|
||||
},
|
||||
ObjectSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"a": "b"},
|
||||
},
|
||||
MatchPolicy: func() *admissionregistration.MatchPolicyType {
|
||||
r := admissionregistration.MatchPolicyType("Exact")
|
||||
return &r
|
||||
}(),
|
||||
},
|
||||
FailurePolicy: func() *admissionregistration.FailurePolicyType {
|
||||
r := admissionregistration.FailurePolicyType("Fail")
|
||||
return &r
|
||||
}(),
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "hasParams",
|
||||
Expression: `params.foo == "okay"`,
|
||||
},
|
||||
},
|
||||
Validations: []admissionregistration.Validation{
|
||||
{
|
||||
Expression: "object.x < 100",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
config: &admissionregistration.ValidatingAdmissionPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config",
|
||||
},
|
||||
Spec: admissionregistration.ValidatingAdmissionPolicySpec{
|
||||
MatchConstraints: &admissionregistration.MatchResources{
|
||||
ResourceRules: []admissionregistration.NamedRuleWithOperations{
|
||||
{
|
||||
RuleWithOperations: admissionregistration.RuleWithOperations{
|
||||
Operations: []admissionregistration.OperationType{"*"},
|
||||
Rule: admissionregistration.Rule{
|
||||
APIGroups: []string{"a"},
|
||||
APIVersions: []string{"a"},
|
||||
Resources: []string{"a"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
NamespaceSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"a": "b"},
|
||||
},
|
||||
ObjectSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"a": "b"},
|
||||
},
|
||||
MatchPolicy: func() *admissionregistration.MatchPolicyType {
|
||||
r := admissionregistration.MatchPolicyType("Exact")
|
||||
return &r
|
||||
}(),
|
||||
},
|
||||
FailurePolicy: func() *admissionregistration.FailurePolicyType {
|
||||
r := admissionregistration.FailurePolicyType("Fail")
|
||||
return &r
|
||||
}(),
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "hasParams",
|
||||
Expression: `params.foo == "okay"`,
|
||||
},
|
||||
},
|
||||
Validations: []admissionregistration.Validation{
|
||||
{
|
||||
Expression: "object.x < 100",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: `undeclared reference to 'params'`,
|
||||
},
|
||||
{
|
||||
name: "match conditions not re-checked if no change to paramKind or matchConditions",
|
||||
oldconfig: &admissionregistration.ValidatingAdmissionPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config",
|
||||
},
|
||||
Spec: admissionregistration.ValidatingAdmissionPolicySpec{
|
||||
MatchConstraints: &admissionregistration.MatchResources{
|
||||
ResourceRules: []admissionregistration.NamedRuleWithOperations{
|
||||
{
|
||||
RuleWithOperations: admissionregistration.RuleWithOperations{
|
||||
Operations: []admissionregistration.OperationType{"*"},
|
||||
Rule: admissionregistration.Rule{
|
||||
APIGroups: []string{"a"},
|
||||
APIVersions: []string{"a"},
|
||||
Resources: []string{"a"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
NamespaceSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"a": "b"},
|
||||
},
|
||||
ObjectSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"a": "b"},
|
||||
},
|
||||
MatchPolicy: func() *admissionregistration.MatchPolicyType {
|
||||
r := admissionregistration.MatchPolicyType("Exact")
|
||||
return &r
|
||||
}(),
|
||||
},
|
||||
FailurePolicy: func() *admissionregistration.FailurePolicyType {
|
||||
r := admissionregistration.FailurePolicyType("Fail")
|
||||
return &r
|
||||
}(),
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "hasParams",
|
||||
Expression: `params.foo == "okay"`,
|
||||
},
|
||||
},
|
||||
Validations: []admissionregistration.Validation{
|
||||
{
|
||||
Expression: "object.x < 100",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
config: &admissionregistration.ValidatingAdmissionPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config",
|
||||
},
|
||||
Spec: admissionregistration.ValidatingAdmissionPolicySpec{
|
||||
MatchConstraints: &admissionregistration.MatchResources{
|
||||
ResourceRules: []admissionregistration.NamedRuleWithOperations{
|
||||
{
|
||||
RuleWithOperations: admissionregistration.RuleWithOperations{
|
||||
Operations: []admissionregistration.OperationType{"*"},
|
||||
Rule: admissionregistration.Rule{
|
||||
APIGroups: []string{"a"},
|
||||
APIVersions: []string{"a"},
|
||||
Resources: []string{"a"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
NamespaceSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"a": "b"},
|
||||
},
|
||||
ObjectSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"a": "b"},
|
||||
},
|
||||
MatchPolicy: func() *admissionregistration.MatchPolicyType {
|
||||
r := admissionregistration.MatchPolicyType("Exact")
|
||||
return &r
|
||||
}(),
|
||||
},
|
||||
FailurePolicy: func() *admissionregistration.FailurePolicyType {
|
||||
r := admissionregistration.FailurePolicyType("Ignore")
|
||||
return &r
|
||||
}(),
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "hasParams",
|
||||
Expression: `params.foo == "okay"`,
|
||||
},
|
||||
},
|
||||
Validations: []admissionregistration.Validation{
|
||||
{
|
||||
Expression: "object.x < 50",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: "",
|
||||
},
|
||||
// TODO: CustomAuditAnnotations: string valueExpression with {oldObject} is allowed
|
||||
}
|
||||
for _, test := range tests {
|
||||
|
@@ -580,6 +580,11 @@ func (in *ValidatingAdmissionPolicySpec) DeepCopyInto(out *ValidatingAdmissionPo
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.MatchConditions != nil {
|
||||
in, out := &in.MatchConditions, &out.MatchConditions
|
||||
*out = make([]MatchCondition, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.FailurePolicy != nil {
|
||||
in, out := &in.FailurePolicy, &out.FailurePolicy
|
||||
*out = new(FailurePolicyType)
|
||||
|
Reference in New Issue
Block a user