From 3d30b97cd8b680b2e3054ef4fa5df63b777f1c12 Mon Sep 17 00:00:00 2001 From: Andrew Sy Kim Date: Tue, 8 Nov 2022 09:41:01 -0500 Subject: [PATCH 1/9] test/integration/apiserver/cel: add Test_PolicyExemption Signed-off-by: Andrew Sy Kim --- .../cel/validatingadmissionpolicy_test.go | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/test/integration/apiserver/cel/validatingadmissionpolicy_test.go b/test/integration/apiserver/cel/validatingadmissionpolicy_test.go index 6b303443ba3..c633bfc47e6 100644 --- a/test/integration/apiserver/cel/validatingadmissionpolicy_test.go +++ b/test/integration/apiserver/cel/validatingadmissionpolicy_test.go @@ -451,6 +451,87 @@ func TestMultiplePolicyBindings(t *testing.T) { checkFailureReason(t, err, metav1.StatusReasonInvalid) } +// Test_PolicyExemption tests that ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding resources +// are exempt from policy rules. +func Test_PolicyExemption(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CELValidatingAdmission, true)() + server, err := apiservertesting.StartTestServer(t, nil, []string{ + "--enable-admission-plugins", "ValidatingAdmissionPolicy", + }, framework.SharedEtcd()) + if err != nil { + t.Fatal(err) + } + defer server.TearDownFn() + + config := server.ClientConfig + + client, err := clientset.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + + policy := makePolicy("test-policy") + policy.Spec.MatchConstraints = &admissionregistrationv1alpha1.MatchResources{ + ResourceRules: []admissionregistrationv1alpha1.NamedRuleWithOperations{ + { + RuleWithOperations: admissionregistrationv1alpha1.RuleWithOperations{ + Operations: []admissionregistrationv1.OperationType{ + "*", + }, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{ + "*", + }, + APIVersions: []string{ + "*", + }, + Resources: []string{ + "*", + }, + }, + }, + }, + }, + } + + policy.Spec.Validations = []admissionregistrationv1alpha1.Validation{{ + Expression: "false", + Message: "marker denied; policy is ready", + }} + + policy, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + policyBinding := makeBinding("test-policy-binding", "test-policy", "") + if err := createAndWaitReady(t, client, policyBinding, nil); err != nil { + t.Fatal(err) + } + + // validate that operations to ValidatingAdmissionPolicy are exempt from an existing policy that catches all resources + policyCopy := policy.DeepCopy() + ignoreFailurePolicy := admissionregistrationv1alpha1.Ignore + policyCopy.Spec.FailurePolicy = &ignoreFailurePolicy + _, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Update(context.TODO(), policyCopy, metav1.UpdateOptions{}) + if err != nil { + t.Error(err) + } + + policyBinding, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings().Get(context.TODO(), policyBinding.Name, metav1.GetOptions{}) + if err != nil { + t.Fatal(err) + } + + // validate that operations to ValidatingAdmissionPolicyBindings are exempt from an existing policy that catches all resources + policyBindingCopy := policyBinding.DeepCopy() + policyBindingCopy.Spec.PolicyName = "different-binding" + _, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings().Update(context.TODO(), policyBindingCopy, metav1.UpdateOptions{}) + if err != nil { + t.Error(err) + } +} + func withWaitReadyConstraintAndExpression(policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy { policy = policy.DeepCopy() policy.Spec.MatchConstraints.ResourceRules = append(policy.Spec.MatchConstraints.ResourceRules, admissionregistrationv1alpha1.NamedRuleWithOperations{ From cbcc22eb9c21ad43ff6907de9cc674767a15d03f Mon Sep 17 00:00:00 2001 From: Andrew Sy Kim Date: Tue, 8 Nov 2022 12:05:12 -0500 Subject: [PATCH 2/9] test/integration/apiserver/cel: add Test_ValidatingAdmissionPolicy_UpdateParamKind Signed-off-by: Andrew Sy Kim --- .../cel/validatingadmissionpolicy_test.go | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/test/integration/apiserver/cel/validatingadmissionpolicy_test.go b/test/integration/apiserver/cel/validatingadmissionpolicy_test.go index c633bfc47e6..d5642de1d62 100644 --- a/test/integration/apiserver/cel/validatingadmissionpolicy_test.go +++ b/test/integration/apiserver/cel/validatingadmissionpolicy_test.go @@ -532,6 +532,146 @@ func Test_PolicyExemption(t *testing.T) { } } +// Test_ValidatingAdmissionPolicy_UpdateParamKind validates the behavior of ValidatingAdmissionPolicy when +// only the ParamKind is updated. This test creates a policy where namespaces must have a prefix that matches +// the ParamKind set in the policy. Switching the ParamKind should result in only namespaces with prefixes matching +// the new ParamKind to be allowed. For example, when Paramkind is v1/ConfigMap, only namespaces prefixed with "configmap" +// is allowed and when ParamKind is updated to v1/Secret, only namespaces prefixed with "secret" is allowed, etc. +func Test_ValidatingAdmissionPolicy_UpdateParamKind(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CELValidatingAdmission, true)() + server, err := apiservertesting.StartTestServer(t, nil, []string{ + "--enable-admission-plugins", "ValidatingAdmissionPolicy", + }, framework.SharedEtcd()) + if err != nil { + t.Fatal(err) + } + defer server.TearDownFn() + + config := server.ClientConfig + + client, err := clientset.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + + allowedPrefixesParamsConfigMap := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "allowed-prefixes", + }, + } + if _, err := client.CoreV1().ConfigMaps("default").Create(context.TODO(), allowedPrefixesParamsConfigMap, metav1.CreateOptions{}); err != nil { + t.Fatal(err) + } + + allowedPrefixesParamSecret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "allowed-prefixes", + }, + } + if _, err := client.CoreV1().Secrets("default").Create(context.TODO(), allowedPrefixesParamSecret, metav1.CreateOptions{}); err != nil { + t.Fatal(err) + } + + paramKind := &admissionregistrationv1alpha1.ParamKind{ + APIVersion: "v1", + Kind: "ConfigMap", + } + + policy := withValidations([]admissionregistrationv1alpha1.Validation{ + { + Expression: "object.metadata.name.startsWith(params.kind.lowerAscii())", + Message: "wrong paramKind", + }, + }, withParams(paramKind, withNamespaceMatch(withFailurePolicy(admissionregistrationv1alpha1.Fail, makePolicy("allowed-prefixes"))))) + policy = withWaitReadyConstraintAndExpression(policy) + policy, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + allowedPrefixesBinding := makeBinding("allowed-prefixes-binding", "allowed-prefixes", "allowed-prefixes") + if err := createAndWaitReady(t, client, allowedPrefixesBinding, nil); err != nil { + t.Fatal(err) + } + + // validate that namespaces starting with "configmap-" are allowed + // and namespaces starting with "secret-" are disallowed + allowedNamespace := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "configmap-", + }, + } + _, err = client.CoreV1().Namespaces().Create(context.TODO(), allowedNamespace, metav1.CreateOptions{}) + if err != nil { + t.Error(err) + } + + disallowedNamespace := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "secret-", + }, + } + _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) + if err == nil { + t.Error("unexpected nil error") + } + if !strings.Contains(err.Error(), "wrong paramKind") { + t.Errorf("unexpected error message: %v", err) + } + checkFailureReason(t, err, metav1.StatusReasonInvalid) + + // update the policy ParamKind to reference a Secret + paramKind = &admissionregistrationv1alpha1.ParamKind{ + APIVersion: "v1", + Kind: "Secret", + } + policyCopy := policy.DeepCopy() + policyCopy.Spec.ParamKind = paramKind + _, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Update(context.TODO(), policyCopy, metav1.UpdateOptions{}) + if err != nil { + t.Error(err) + } + + // validate that namespaces starting with "secret-" are allowed + // and namespaces starting with "configmap-" are disallowed + // wait loop is required here since ConfigMaps were previousy allowed and we need to wait for the new policy + // to be enforced + if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { + disallowedNamespace = &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "configmap-", + }, + } + + _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) + if err == nil { + return false, nil + } + + if strings.Contains(err.Error(), "not yet synced to use for admission") { + return false, nil + } + + if !strings.Contains(err.Error(), "wrong paramKind") { + return false, err + } + + return true, nil + }); waitErr != nil { + t.Errorf("timed out waiting: %v", err) + } + + allowedNamespace = &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "secret-", + }, + } + _, err = client.CoreV1().Namespaces().Create(context.TODO(), allowedNamespace, metav1.CreateOptions{}) + if err != nil { + t.Error(err) + } +} + func withWaitReadyConstraintAndExpression(policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy { policy = policy.DeepCopy() policy.Spec.MatchConstraints.ResourceRules = append(policy.Spec.MatchConstraints.ResourceRules, admissionregistrationv1alpha1.NamedRuleWithOperations{ From e2ce260f7a49498503545e1a4bb86ecab5a920d1 Mon Sep 17 00:00:00 2001 From: Andrew Sy Kim Date: Wed, 9 Nov 2022 16:35:44 -0500 Subject: [PATCH 3/9] test/integration/apiserver/cel: add Test_ValidatingAdmissionPolicy_UpdateParamRef Signed-off-by: Andrew Sy Kim --- .../cel/validatingadmissionpolicy_test.go | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/test/integration/apiserver/cel/validatingadmissionpolicy_test.go b/test/integration/apiserver/cel/validatingadmissionpolicy_test.go index d5642de1d62..ee439a0aa6e 100644 --- a/test/integration/apiserver/cel/validatingadmissionpolicy_test.go +++ b/test/integration/apiserver/cel/validatingadmissionpolicy_test.go @@ -672,6 +672,154 @@ func Test_ValidatingAdmissionPolicy_UpdateParamKind(t *testing.T) { } } +// Test_ValidatingAdmissionPolicy_UpdateParamRef validates the behavior of ValidatingAdmissionPolicy when +// only the ParamRef in the binding is updated. This test creates a policy where namespaces must have a prefix that matches +// the ParamRef set in the policy binding. The paramRef in the binding is then updated to a different object. +func Test_ValidatingAdmissionPolicy_UpdateParamRef(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CELValidatingAdmission, true)() + server, err := apiservertesting.StartTestServer(t, nil, []string{ + "--enable-admission-plugins", "ValidatingAdmissionPolicy", + }, framework.SharedEtcd()) + if err != nil { + t.Fatal(err) + } + defer server.TearDownFn() + + config := server.ClientConfig + + client, err := clientset.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + + allowedPrefixesParamsConfigMap1 := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-1", + Namespace: "default", + }, + } + if _, err := client.CoreV1().ConfigMaps("default").Create(context.TODO(), allowedPrefixesParamsConfigMap1, metav1.CreateOptions{}); err != nil { + t.Fatal(err) + } + + allowedPrefixesParamsConfigMap2 := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-2", + Namespace: "default", + }, + } + if _, err := client.CoreV1().ConfigMaps("default").Create(context.TODO(), allowedPrefixesParamsConfigMap2, metav1.CreateOptions{}); err != nil { + t.Fatal(err) + } + + policy := withValidations([]admissionregistrationv1alpha1.Validation{ + { + Expression: "object.metadata.name.startsWith(params.metadata.name)", + Message: "wrong paramRef", + }, + }, withParams(configParamKind(), withNamespaceMatch(withFailurePolicy(admissionregistrationv1alpha1.Fail, makePolicy("allowed-prefixes"))))) + policy = withWaitReadyConstraintAndExpression(policy) + _, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + // validate that namespaces starting with "test-1" are allowed + // and namespaces starting with "test-2-" are disallowed + allowedPrefixesBinding := makeBinding("allowed-prefixes-binding", "allowed-prefixes", "test-1") + if err := createAndWaitReady(t, client, allowedPrefixesBinding, nil); err != nil { + t.Fatal(err) + } + + if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { + disallowedNamespace := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-2-", + }, + } + + _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) + if err == nil { + return false, nil + } + + if strings.Contains(err.Error(), "not yet synced to use for admission") { + return false, nil + } + + if !strings.Contains(err.Error(), "wrong paramRef") { + return false, err + } + + return true, nil + }); waitErr != nil { + t.Errorf("timed out waiting: %v", err) + } + + allowedNamespace := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-1-", + }, + } + _, err = client.CoreV1().Namespaces().Create(context.TODO(), allowedNamespace, metav1.CreateOptions{}) + if err != nil { + t.Error(err) + } + + // Update the paramRef in the policy binding to use the test-2 ConfigMap + policyBinding, err := client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings().Get(context.TODO(), allowedPrefixesBinding.Name, metav1.GetOptions{}) + if err != nil { + t.Fatal(err) + } + + policyBindingCopy := policyBinding.DeepCopy() + policyBindingCopy.Spec.ParamRef = &admissionregistrationv1alpha1.ParamRef{ + Name: "test-2", + Namespace: "default", + } + _, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings().Update(context.TODO(), policyBindingCopy, metav1.UpdateOptions{}) + if err != nil { + t.Error(err) + } + + // validate that namespaces starting with "test-2" are allowed + // and namespaces starting with "test-1" are disallowed + if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { + disallowedNamespace := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-1-", + }, + } + + _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) + if err == nil { + return false, nil + } + + if strings.Contains(err.Error(), "not yet synced to use for admission") { + return false, nil + } + + if !strings.Contains(err.Error(), "wrong paramRef") { + return false, err + } + + return true, nil + }); waitErr != nil { + t.Errorf("timed out waiting: %v", err) + } + + allowedNamespace = &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-2-", + }, + } + _, err = client.CoreV1().Namespaces().Create(context.TODO(), allowedNamespace, metav1.CreateOptions{}) + if err != nil { + t.Error(err) + } +} + func withWaitReadyConstraintAndExpression(policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy { policy = policy.DeepCopy() policy.Spec.MatchConstraints.ResourceRules = append(policy.Spec.MatchConstraints.ResourceRules, admissionregistrationv1alpha1.NamedRuleWithOperations{ From 894063908f00f9fce6b93a75c4ff5a597c439dac Mon Sep 17 00:00:00 2001 From: Andrew Sy Kim Date: Tue, 8 Nov 2022 12:48:05 -0500 Subject: [PATCH 4/9] test/integration/apiserver/cel: add additional test cases to Test_ValidateNamespace_NoParams for unguarded params Signed-off-by: Andrew Sy Kim --- .../cel/validatingadmissionpolicy_test.go | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/test/integration/apiserver/cel/validatingadmissionpolicy_test.go b/test/integration/apiserver/cel/validatingadmissionpolicy_test.go index ee439a0aa6e..3dff66bc804 100644 --- a/test/integration/apiserver/cel/validatingadmissionpolicy_test.go +++ b/test/integration/apiserver/cel/validatingadmissionpolicy_test.go @@ -162,6 +162,86 @@ func Test_ValidateNamespace_NoParams(t *testing.T) { err: "namespaces \"test-k8s\" is forbidden: ValidatingAdmissionPolicy 'validate-namespace-suffix' with binding 'validate-namespace-suffix-binding' denied request: expression 'object.nonExistentProperty == 'someval'' resulted in error: no such key: nonExistentProperty", failureReason: metav1.StatusReasonInvalid, }, + { + name: "runtime error due to unguarded params", + policy: withValidations([]admissionregistrationv1alpha1.Validation{ + { + Expression: "object.metadata.name.startsWith(params.metadata.name)", + }, + }, withParams(configParamKind(), withFailurePolicy(admissionregistrationv1alpha1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix"))))), + policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""), + namespace: &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-k8s", + }, + }, + err: "namespaces \"test-k8s\" is forbidden: ValidatingAdmissionPolicy 'validate-namespace-suffix' with binding 'validate-namespace-suffix-binding' denied request: expression 'object.metadata.name.startsWith(params.metadata.name)' resulted in error: no such key: metadata", + failureReason: metav1.StatusReasonInvalid, + }, + { + name: "with check against unguarded params using has()", + policy: withValidations([]admissionregistrationv1alpha1.Validation{ + { + Expression: "has(params.metadata) && has(params.metadata.name) && object.metadata.name.endsWith(params.metadata.name)", + }, + }, withParams(configParamKind(), withFailurePolicy(admissionregistrationv1alpha1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix"))))), + policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""), + namespace: &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-k8s", + }, + }, + err: "namespaces \"test-k8s\" is forbidden: ValidatingAdmissionPolicy 'validate-namespace-suffix' with binding 'validate-namespace-suffix-binding' denied request: expression 'has(params.metadata) && has(params.metadata.name) && object.metadata.name.endsWith(params.metadata.name)' resulted in error: invalid type for field selection.", + failureReason: metav1.StatusReasonInvalid, + }, + { + name: "with check against null params", + policy: withValidations([]admissionregistrationv1alpha1.Validation{ + { + Expression: "(params != null && object.metadata.name.endsWith(params.metadata.name))", + }, + }, withParams(configParamKind(), withFailurePolicy(admissionregistrationv1alpha1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix"))))), + policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""), + namespace: &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-k8s", + }, + }, + err: "namespaces \"test-k8s\" is forbidden: ValidatingAdmissionPolicy 'validate-namespace-suffix' with binding 'validate-namespace-suffix-binding' denied request: failed expression: (params != null && object.metadata.name.endsWith(params.metadata.name))", + failureReason: metav1.StatusReasonInvalid, + }, + { + name: "with check against unguarded params using has() and default check", + policy: withValidations([]admissionregistrationv1alpha1.Validation{ + { + Expression: "(has(params.metadata) && has(params.metadata.name) && object.metadata.name.startsWith(params.metadata.name)) || object.metadata.name.endsWith('k8s')", + }, + }, withParams(configParamKind(), withFailurePolicy(admissionregistrationv1alpha1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix"))))), + policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""), + namespace: &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-k8s", + }, + }, + err: "", + failureReason: metav1.StatusReasonInvalid, + }, + { + name: "with check against null params and default check", + policy: withValidations([]admissionregistrationv1alpha1.Validation{ + { + Expression: "(params != null && object.metadata.name.startsWith(params.metadata.name)) || object.metadata.name.endsWith('k8s')", + }, + }, withParams(configParamKind(), withFailurePolicy(admissionregistrationv1alpha1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix"))))), + policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""), + namespace: &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-k8s", + }, + }, + err: "", + failureReason: metav1.StatusReasonInvalid, + }, } for _, testcase := range testcases { From 38d884580babd516e789a9501bc072778720eb8e Mon Sep 17 00:00:00 2001 From: Andrew Sy Kim Date: Thu, 10 Nov 2022 16:13:39 -0500 Subject: [PATCH 5/9] test/integration/apiserver/cel: add tests for match resources and match policy Signed-off-by: Andrew Sy Kim --- .../cel/validatingadmissionpolicy_test.go | 572 +++++++++++++++++- 1 file changed, 571 insertions(+), 1 deletion(-) diff --git a/test/integration/apiserver/cel/validatingadmissionpolicy_test.go b/test/integration/apiserver/cel/validatingadmissionpolicy_test.go index 3dff66bc804..c35ab44fcb1 100644 --- a/test/integration/apiserver/cel/validatingadmissionpolicy_test.go +++ b/test/integration/apiserver/cel/validatingadmissionpolicy_test.go @@ -22,15 +22,22 @@ import ( "testing" "time" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + "k8s.io/apiextensions-apiserver/test/integration/fixtures" genericfeatures "k8s.io/apiserver/pkg/features" utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" + "k8s.io/kubernetes/test/integration/etcd" "k8s.io/kubernetes/test/integration/framework" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/dynamic" clientset "k8s.io/client-go/kubernetes" v1 "k8s.io/api/core/v1" @@ -900,6 +907,484 @@ func Test_ValidatingAdmissionPolicy_UpdateParamRef(t *testing.T) { } } +func Test_ValidatingAdmissionPolicy_MatchByObjectSelector(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CELValidatingAdmission, true)() + server, err := apiservertesting.StartTestServer(t, nil, []string{ + "--enable-admission-plugins", "ValidatingAdmissionPolicy", + }, framework.SharedEtcd()) + if err != nil { + t.Fatal(err) + } + defer server.TearDownFn() + + config := server.ClientConfig + + client, err := clientset.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + + labelSelector := &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, + } + + policy := withValidations([]admissionregistrationv1alpha1.Validation{ + { + Expression: "false", + Message: "matched by object selector!", + }, + }, withConfigMapMatch(withFailurePolicy(admissionregistrationv1alpha1.Fail, makePolicy("match-by-object-selector")))) + policy = withObjectSelector(labelSelector, policy) + policy = withWaitReadyConstraintAndExpression(policy) + _, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + policyBinding := makeBinding("match-by-object-selector-binding", "match-by-object-selector", "") + if err := createAndWaitReady(t, client, policyBinding, map[string]string{"foo": "bar"}); err != nil { + t.Fatal(err) + } + + matchedConfigMap := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "denied", + Namespace: "default", + Labels: map[string]string{ + "foo": "bar", + }, + }, + } + + _, err = client.CoreV1().ConfigMaps(matchedConfigMap.Namespace).Create(context.TODO(), matchedConfigMap, metav1.CreateOptions{}) + if !strings.Contains(err.Error(), "matched by object selector!") { + t.Errorf("unexpected error: %v", err) + } + + allowedConfigMap := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "allowed", + Namespace: "default", + }, + } + + if _, err := client.CoreV1().ConfigMaps(allowedConfigMap.Namespace).Create(context.TODO(), allowedConfigMap, metav1.CreateOptions{}); err != nil { + t.Errorf("unexpected error: %v", err) + } +} + +func Test_ValidatingAdmissionPolicy_MatchByNamespaceSelector(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CELValidatingAdmission, true)() + server, err := apiservertesting.StartTestServer(t, nil, []string{ + "--enable-admission-plugins", "ValidatingAdmissionPolicy", + }, framework.SharedEtcd()) + if err != nil { + t.Fatal(err) + } + defer server.TearDownFn() + + config := server.ClientConfig + + client, err := clientset.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + + // only configmaps in default will be allowed. + labelSelector := &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "kubernetes.io/metadata.name", + Operator: "NotIn", + Values: []string{"default"}, + }, + }, + } + + policy := withValidations([]admissionregistrationv1alpha1.Validation{ + { + Expression: "false", + Message: "matched by namespace selector!", + }, + }, withConfigMapMatch(withFailurePolicy(admissionregistrationv1alpha1.Fail, makePolicy("match-by-namespace-selector")))) + policy = withNamespaceSelector(labelSelector, policy) + policy = withWaitReadyConstraintAndExpression(policy) + _, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + policyBinding := makeBinding("match-by-namespace-selector-binding", "match-by-namespace-selector", "") + _, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings().Create(context.TODO(), policyBinding, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + namespace := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "not-default", + }, + } + if _, err := client.CoreV1().Namespaces().Create(context.TODO(), namespace, metav1.CreateOptions{}); err != nil { + t.Fatal(err) + } + + if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { + matchedConfigMap := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "denied-", + Namespace: "not-default", + }, + } + + _, err := client.CoreV1().ConfigMaps(matchedConfigMap.Namespace).Create(context.TODO(), matchedConfigMap, metav1.CreateOptions{}) + // policy not enforced yet, try again + if err == nil { + return false, nil + } + + if !strings.Contains(err.Error(), "matched by namespace selector!") { + return false, err + } + + return true, nil + + }); waitErr != nil { + t.Errorf("timed out waiting: %v", waitErr) + } + + allowedConfigMap := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "allowed", + Namespace: "default", + }, + } + + if _, err := client.CoreV1().ConfigMaps(allowedConfigMap.Namespace).Create(context.TODO(), allowedConfigMap, metav1.CreateOptions{}); err != nil { + t.Errorf("unexpected error: %v", err) + } +} + +func Test_ValidatingAdmissionPolicy_MatchByResourceNames(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CELValidatingAdmission, true)() + server, err := apiservertesting.StartTestServer(t, nil, []string{ + "--enable-admission-plugins", "ValidatingAdmissionPolicy", + }, framework.SharedEtcd()) + if err != nil { + t.Fatal(err) + } + defer server.TearDownFn() + + config := server.ClientConfig + + client, err := clientset.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + + policy := withValidations([]admissionregistrationv1alpha1.Validation{ + { + Expression: "false", + Message: "matched by resource names!", + }, + }, withConfigMapMatch(withFailurePolicy(admissionregistrationv1alpha1.Fail, makePolicy("match-by-resource-names")))) + policy.Spec.MatchConstraints.ResourceRules[0].ResourceNames = []string{"matched-by-resource-name"} + policy = withWaitReadyConstraintAndExpression(policy) + _, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + policyBinding := makeBinding("match-by-resource-names-binding", "match-by-resource-names", "") + if err := createAndWaitReady(t, client, policyBinding, nil); err != nil { + t.Fatal(err) + } + + matchedConfigMap := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matched-by-resource-name", + Namespace: "default", + }, + } + + _, err = client.CoreV1().ConfigMaps(matchedConfigMap.Namespace).Create(context.TODO(), matchedConfigMap, metav1.CreateOptions{}) + if !strings.Contains(err.Error(), "matched by resource names!") { + t.Errorf("unexpected error: %v", err) + } + + allowedConfigMap := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "not-matched-by-resource-name", + Namespace: "default", + }, + } + + if _, err := client.CoreV1().ConfigMaps(allowedConfigMap.Namespace).Create(context.TODO(), allowedConfigMap, metav1.CreateOptions{}); err != nil { + t.Errorf("unexpected error: %v", err) + } +} + +func Test_ValidatingAdmissionPolicy_MatchWithExcludeResources(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CELValidatingAdmission, true)() + server, err := apiservertesting.StartTestServer(t, nil, []string{ + "--enable-admission-plugins", "ValidatingAdmissionPolicy", + }, framework.SharedEtcd()) + if err != nil { + t.Fatal(err) + } + defer server.TearDownFn() + + config := server.ClientConfig + + client, err := clientset.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + + policy := withValidations([]admissionregistrationv1alpha1.Validation{ + { + Expression: "false", + Message: "not matched by exclude resources!", + }, + }, withPolicyMatch("*", withFailurePolicy(admissionregistrationv1alpha1.Fail, makePolicy("match-by-resource-names")))) + + policy = withExcludePolicyMatch("configmaps", policy) + _, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + policyBinding := makeBinding("match-by-resource-names-binding", "match-by-resource-names", "") + _, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings().Create(context.TODO(), policyBinding, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { + secret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "not-matched-by-exclude-resources", + Namespace: "default", + }, + } + + _, err := client.CoreV1().Secrets(secret.Namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) + // policy not enforced yet, try again + if err == nil { + return false, nil + } + + if !strings.Contains(err.Error(), "not matched by exclude resources!") { + return false, err + } + + return true, nil + + }); waitErr != nil { + t.Errorf("timed out waiting: %v", waitErr) + } + + allowedConfigMap := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matched-by-exclude-resources", + Namespace: "default", + }, + } + + if _, err := client.CoreV1().ConfigMaps(allowedConfigMap.Namespace).Create(context.TODO(), allowedConfigMap, metav1.CreateOptions{}); err != nil { + t.Errorf("unexpected error: %v", err) + } +} + +func Test_ValidatingAdmissionPolicy_MatchWithMatchPolicyEquivalent(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CELValidatingAdmission, true)() + server, err := apiservertesting.StartTestServer(t, nil, []string{ + "--enable-admission-plugins", "ValidatingAdmissionPolicy", + }, framework.SharedEtcd()) + etcd.CreateTestCRDs(t, apiextensionsclientset.NewForConfigOrDie(server.ClientConfig), false, versionedCustomResourceDefinition()) + if err != nil { + t.Fatal(err) + } + defer server.TearDownFn() + + config := server.ClientConfig + + client, err := clientset.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + + policy := withValidations([]admissionregistrationv1alpha1.Validation{ + { + Expression: "false", + Message: "matched by equivalent match policy!", + }, + }, withFailurePolicy(admissionregistrationv1alpha1.Fail, makePolicy("match-by-match-policy-equivalent"))) + policy.Spec.MatchConstraints = &admissionregistrationv1alpha1.MatchResources{ + ResourceRules: []admissionregistrationv1alpha1.NamedRuleWithOperations{ + { + RuleWithOperations: admissionregistrationv1alpha1.RuleWithOperations{ + Operations: []admissionregistrationv1.OperationType{ + "*", + }, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{ + "awesome.bears.com", + }, + APIVersions: []string{ + "v1", + }, + Resources: []string{ + "pandas", + }, + }, + }, + }, + }, + } + policy = withWaitReadyConstraintAndExpression(policy) + if _, err := client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}); err != nil { + t.Fatal(err) + } + + policyBinding := makeBinding("match-by-match-policy-equivalent-binding", "match-by-match-policy-equivalent", "") + if err := createAndWaitReady(t, client, policyBinding, nil); err != nil { + t.Fatal(err) + } + + v1Resource := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "awesome.bears.com" + "/" + "v1", + "kind": "Panda", + "metadata": map[string]interface{}{ + "name": "v1-bears", + }, + }, + } + + v2Resource := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "awesome.bears.com" + "/" + "v2", + "kind": "Panda", + "metadata": map[string]interface{}{ + "name": "v2-bears", + }, + }, + } + + dynamicClient, err := dynamic.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + + _, err = dynamicClient.Resource(schema.GroupVersionResource{Group: "awesome.bears.com", Version: "v1", Resource: "pandas"}).Create(context.TODO(), v1Resource, metav1.CreateOptions{}) + if !strings.Contains(err.Error(), "matched by equivalent match policy!") { + t.Errorf("v1 panadas did not match against policy, err: %v", err) + } + + _, err = dynamicClient.Resource(schema.GroupVersionResource{Group: "awesome.bears.com", Version: "v2", Resource: "pandas"}).Create(context.TODO(), v2Resource, metav1.CreateOptions{}) + if !strings.Contains(err.Error(), "matched by equivalent match policy!") { + t.Errorf("v2 panadas did not match against policy, err: %v", err) + } +} + +func Test_ValidatingAdmissionPolicy_MatchWithMatchPolicyExact(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CELValidatingAdmission, true)() + server, err := apiservertesting.StartTestServer(t, nil, []string{ + "--enable-admission-plugins", "ValidatingAdmissionPolicy", + }, framework.SharedEtcd()) + etcd.CreateTestCRDs(t, apiextensionsclientset.NewForConfigOrDie(server.ClientConfig), false, versionedCustomResourceDefinition()) + if err != nil { + t.Fatal(err) + } + defer server.TearDownFn() + + config := server.ClientConfig + + client, err := clientset.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + + policy := withValidations([]admissionregistrationv1alpha1.Validation{ + { + Expression: "false", + Message: "matched by exact match policy!", + }, + }, withFailurePolicy(admissionregistrationv1alpha1.Fail, makePolicy("match-by-match-policy-exact"))) + matchPolicyExact := admissionregistrationv1alpha1.Exact + policy.Spec.MatchConstraints = &admissionregistrationv1alpha1.MatchResources{ + MatchPolicy: &matchPolicyExact, + ResourceRules: []admissionregistrationv1alpha1.NamedRuleWithOperations{ + { + RuleWithOperations: admissionregistrationv1alpha1.RuleWithOperations{ + Operations: []admissionregistrationv1.OperationType{ + "*", + }, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{ + "awesome.bears.com", + }, + APIVersions: []string{ + "v1", + }, + Resources: []string{ + "pandas", + }, + }, + }, + }, + }, + } + policy = withWaitReadyConstraintAndExpression(policy) + if _, err := client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}); err != nil { + t.Fatal(err) + } + + policyBinding := makeBinding("match-by-match-policy-exact-binding", "match-by-match-policy-exact", "") + if err := createAndWaitReady(t, client, policyBinding, nil); err != nil { + t.Fatal(err) + } + + v1Resource := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "awesome.bears.com" + "/" + "v1", + "kind": "Panda", + "metadata": map[string]interface{}{ + "name": "v1-bears", + }, + }, + } + + v2Resource := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "awesome.bears.com" + "/" + "v2", + "kind": "Panda", + "metadata": map[string]interface{}{ + "name": "v2-bears", + }, + }, + } + + dynamicClient, err := dynamic.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + + _, err = dynamicClient.Resource(schema.GroupVersionResource{Group: "awesome.bears.com", Version: "v1", Resource: "pandas"}).Create(context.TODO(), v1Resource, metav1.CreateOptions{}) + if !strings.Contains(err.Error(), "matched by exact match policy!") { + t.Errorf("v1 panadas did not match against policy, err: %v", err) + } + + // v2 panadas is allowed since policy specificed match policy Exact and only matched against v1 + _, err = dynamicClient.Resource(schema.GroupVersionResource{Group: "awesome.bears.com", Version: "v2", Resource: "pandas"}).Create(context.TODO(), v2Resource, metav1.CreateOptions{}) + if err != nil { + t.Error(err) + } +} + func withWaitReadyConstraintAndExpression(policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy { policy = policy.DeepCopy() policy.Spec.MatchConstraints.ResourceRules = append(policy.Spec.MatchConstraints.ResourceRules, admissionregistrationv1alpha1.NamedRuleWithOperations{ @@ -987,13 +1472,27 @@ func withNamespaceMatch(policy *admissionregistrationv1alpha1.ValidatingAdmissio return withPolicyMatch("namespaces", policy) } +func withConfigMapMatch(policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy { + return withPolicyMatch("configmaps", policy) +} + +func withObjectSelector(labelSelector *metav1.LabelSelector, policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy { + policy.Spec.MatchConstraints.ObjectSelector = labelSelector + return policy +} + +func withNamespaceSelector(labelSelector *metav1.LabelSelector, policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy { + policy.Spec.MatchConstraints.NamespaceSelector = labelSelector + return policy +} + func withPolicyMatch(resource string, policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy { policy.Spec.MatchConstraints = &admissionregistrationv1alpha1.MatchResources{ ResourceRules: []admissionregistrationv1alpha1.NamedRuleWithOperations{ { RuleWithOperations: admissionregistrationv1alpha1.RuleWithOperations{ Operations: []admissionregistrationv1.OperationType{ - "CREATE", + "*", }, Rule: admissionregistrationv1.Rule{ APIGroups: []string{ @@ -1013,6 +1512,30 @@ func withPolicyMatch(resource string, policy *admissionregistrationv1alpha1.Vali return policy } +func withExcludePolicyMatch(resource string, policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy { + policy.Spec.MatchConstraints.ExcludeResourceRules = []admissionregistrationv1alpha1.NamedRuleWithOperations{ + { + RuleWithOperations: admissionregistrationv1alpha1.RuleWithOperations{ + Operations: []admissionregistrationv1.OperationType{ + "*", + }, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{ + "", + }, + APIVersions: []string{ + "*", + }, + Resources: []string{ + resource, + }, + }, + }, + }, + } + return policy +} + func withPolicyExistsLabels(labels []string, policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy { if policy.Spec.MatchConstraints == nil { policy.Spec.MatchConstraints = &admissionregistrationv1alpha1.MatchResources{} @@ -1092,3 +1615,50 @@ func checkFailureReason(t *testing.T, err error, expectedReason metav1.StatusRea t.Error("unexpected error reason") } } + +// Copied from etcd.GetCustomResourceDefinitionData +func versionedCustomResourceDefinition() *apiextensionsv1.CustomResourceDefinition { + return &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pandas.awesome.bears.com", + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "awesome.bears.com", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + { + Name: "v1", + Served: true, + Storage: true, + Schema: fixtures.AllowAllSchema(), + Subresources: &apiextensionsv1.CustomResourceSubresources{ + Status: &apiextensionsv1.CustomResourceSubresourceStatus{}, + Scale: &apiextensionsv1.CustomResourceSubresourceScale{ + SpecReplicasPath: ".spec.replicas", + StatusReplicasPath: ".status.replicas", + LabelSelectorPath: func() *string { path := ".status.selector"; return &path }(), + }, + }, + }, + { + Name: "v2", + Served: true, + Storage: false, + Schema: fixtures.AllowAllSchema(), + Subresources: &apiextensionsv1.CustomResourceSubresources{ + Status: &apiextensionsv1.CustomResourceSubresourceStatus{}, + Scale: &apiextensionsv1.CustomResourceSubresourceScale{ + SpecReplicasPath: ".spec.replicas", + StatusReplicasPath: ".status.replicas", + LabelSelectorPath: func() *string { path := ".status.selector"; return &path }(), + }, + }, + }, + }, + Scope: apiextensionsv1.ClusterScoped, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: "pandas", + Kind: "Panda", + }, + }, + } +} From 3f477f847de4b33f7fb375cd82217b3efe185c1a Mon Sep 17 00:00:00 2001 From: Andrew Sy Kim Date: Thu, 10 Nov 2022 16:46:59 -0500 Subject: [PATCH 6/9] test/integration/apiserver/cel: update feature gate name CELValidatingAdmission -> ValidatingAdmissionPolicy Signed-off-by: Andrew Sy Kim --- .../cel/validatingadmissionpolicy_test.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/integration/apiserver/cel/validatingadmissionpolicy_test.go b/test/integration/apiserver/cel/validatingadmissionpolicy_test.go index c35ab44fcb1..7f438a4b8f8 100644 --- a/test/integration/apiserver/cel/validatingadmissionpolicy_test.go +++ b/test/integration/apiserver/cel/validatingadmissionpolicy_test.go @@ -541,7 +541,7 @@ func TestMultiplePolicyBindings(t *testing.T) { // Test_PolicyExemption tests that ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding resources // are exempt from policy rules. func Test_PolicyExemption(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CELValidatingAdmission, true)() + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() server, err := apiservertesting.StartTestServer(t, nil, []string{ "--enable-admission-plugins", "ValidatingAdmissionPolicy", }, framework.SharedEtcd()) @@ -625,7 +625,7 @@ func Test_PolicyExemption(t *testing.T) { // the new ParamKind to be allowed. For example, when Paramkind is v1/ConfigMap, only namespaces prefixed with "configmap" // is allowed and when ParamKind is updated to v1/Secret, only namespaces prefixed with "secret" is allowed, etc. func Test_ValidatingAdmissionPolicy_UpdateParamKind(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CELValidatingAdmission, true)() + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() server, err := apiservertesting.StartTestServer(t, nil, []string{ "--enable-admission-plugins", "ValidatingAdmissionPolicy", }, framework.SharedEtcd()) @@ -763,7 +763,7 @@ func Test_ValidatingAdmissionPolicy_UpdateParamKind(t *testing.T) { // only the ParamRef in the binding is updated. This test creates a policy where namespaces must have a prefix that matches // the ParamRef set in the policy binding. The paramRef in the binding is then updated to a different object. func Test_ValidatingAdmissionPolicy_UpdateParamRef(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CELValidatingAdmission, true)() + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() server, err := apiservertesting.StartTestServer(t, nil, []string{ "--enable-admission-plugins", "ValidatingAdmissionPolicy", }, framework.SharedEtcd()) @@ -908,7 +908,7 @@ func Test_ValidatingAdmissionPolicy_UpdateParamRef(t *testing.T) { } func Test_ValidatingAdmissionPolicy_MatchByObjectSelector(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CELValidatingAdmission, true)() + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() server, err := apiservertesting.StartTestServer(t, nil, []string{ "--enable-admission-plugins", "ValidatingAdmissionPolicy", }, framework.SharedEtcd()) @@ -976,7 +976,7 @@ func Test_ValidatingAdmissionPolicy_MatchByObjectSelector(t *testing.T) { } func Test_ValidatingAdmissionPolicy_MatchByNamespaceSelector(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CELValidatingAdmission, true)() + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() server, err := apiservertesting.StartTestServer(t, nil, []string{ "--enable-admission-plugins", "ValidatingAdmissionPolicy", }, framework.SharedEtcd()) @@ -1068,7 +1068,7 @@ func Test_ValidatingAdmissionPolicy_MatchByNamespaceSelector(t *testing.T) { } func Test_ValidatingAdmissionPolicy_MatchByResourceNames(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CELValidatingAdmission, true)() + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() server, err := apiservertesting.StartTestServer(t, nil, []string{ "--enable-admission-plugins", "ValidatingAdmissionPolicy", }, framework.SharedEtcd()) @@ -1127,7 +1127,7 @@ func Test_ValidatingAdmissionPolicy_MatchByResourceNames(t *testing.T) { } func Test_ValidatingAdmissionPolicy_MatchWithExcludeResources(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CELValidatingAdmission, true)() + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() server, err := apiservertesting.StartTestServer(t, nil, []string{ "--enable-admission-plugins", "ValidatingAdmissionPolicy", }, framework.SharedEtcd()) @@ -1199,7 +1199,7 @@ func Test_ValidatingAdmissionPolicy_MatchWithExcludeResources(t *testing.T) { } func Test_ValidatingAdmissionPolicy_MatchWithMatchPolicyEquivalent(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CELValidatingAdmission, true)() + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() server, err := apiservertesting.StartTestServer(t, nil, []string{ "--enable-admission-plugins", "ValidatingAdmissionPolicy", }, framework.SharedEtcd()) @@ -1291,7 +1291,7 @@ func Test_ValidatingAdmissionPolicy_MatchWithMatchPolicyEquivalent(t *testing.T) } func Test_ValidatingAdmissionPolicy_MatchWithMatchPolicyExact(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CELValidatingAdmission, true)() + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() server, err := apiservertesting.StartTestServer(t, nil, []string{ "--enable-admission-plugins", "ValidatingAdmissionPolicy", }, framework.SharedEtcd()) From 0fb038fb15265008c3f940f37f07988aece5c7e5 Mon Sep 17 00:00:00 2001 From: Andrew Sy Kim Date: Mon, 14 Nov 2022 12:10:24 -0500 Subject: [PATCH 7/9] test/integration/apiserver/cel: add lifecycle tests for deleting/recreating policy, policy bindings, and param resources Signed-off-by: Andrew Sy Kim --- .../cel/validatingadmissionpolicy_test.go | 364 ++++++++++++++++++ 1 file changed, 364 insertions(+) diff --git a/test/integration/apiserver/cel/validatingadmissionpolicy_test.go b/test/integration/apiserver/cel/validatingadmissionpolicy_test.go index 7f438a4b8f8..4a247460701 100644 --- a/test/integration/apiserver/cel/validatingadmissionpolicy_test.go +++ b/test/integration/apiserver/cel/validatingadmissionpolicy_test.go @@ -1385,6 +1385,370 @@ func Test_ValidatingAdmissionPolicy_MatchWithMatchPolicyExact(t *testing.T) { } } +// Test_ValidatingAdmissionPolicy_PolicyDeletedThenRecreated validates that deleting a ValidatingAdmissionPolicy +// removes the policy from the apiserver admission chain and recreating it re-enables it. +func Test_ValidatingAdmissionPolicy_PolicyDeletedThenRecreated(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() + server, err := apiservertesting.StartTestServer(t, nil, []string{ + "--enable-admission-plugins", "ValidatingAdmissionPolicy", + }, framework.SharedEtcd()) + if err != nil { + t.Fatal(err) + } + defer server.TearDownFn() + + config := server.ClientConfig + + client, err := clientset.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + + policy := withValidations([]admissionregistrationv1alpha1.Validation{ + { + Expression: "object.metadata.name.startsWith('test')", + Message: "wrong prefix", + }, + }, withParams(configParamKind(), withNamespaceMatch(withFailurePolicy(admissionregistrationv1alpha1.Fail, makePolicy("allowed-prefixes"))))) + policy = withWaitReadyConstraintAndExpression(policy) + _, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + // validate that namespaces starting with "test" are allowed + policyBinding := makeBinding("allowed-prefixes-binding", "allowed-prefixes", "") + if err := createAndWaitReady(t, client, policyBinding, nil); err != nil { + t.Fatal(err) + } + + if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { + disallowedNamespace := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "not-test-", + }, + } + + _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) + if err == nil { + return false, nil + } + + if strings.Contains(err.Error(), "not yet synced to use for admission") { + return false, nil + } + + if !strings.Contains(err.Error(), "wrong prefix") { + return false, err + } + + return true, nil + }); waitErr != nil { + t.Errorf("timed out waiting: %v", err) + } + + // delete the binding object and validate that policy is not enforced + if err := client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Delete(context.TODO(), "allowed-prefixes", metav1.DeleteOptions{}); err != nil { + t.Fatal(err) + } + + if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { + allowedNamespace := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "not-test-", + }, + } + _, err = client.CoreV1().Namespaces().Create(context.TODO(), allowedNamespace, metav1.CreateOptions{}) + if err == nil { + return true, nil + } + + // old policy is still enforced, try again + if strings.Contains(err.Error(), "wrong prefix") { + return false, nil + } + + return false, err + }); waitErr != nil { + t.Errorf("timed out waiting: %v", err) + } + + _, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { + disallowedNamespace := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "not-test-", + }, + } + + _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) + if err == nil { + return false, nil + } + + if strings.Contains(err.Error(), "not yet synced to use for admission") { + return false, nil + } + + if !strings.Contains(err.Error(), "wrong prefix") { + return false, err + } + + return true, nil + }); waitErr != nil { + t.Errorf("timed out waiting: %v", err) + } +} + +// Test_ValidatingAdmissionPolicy_BindingDeletedThenRecreated validates that deleting a ValidatingAdmissionPolicyBinding +// removes the policy from the apiserver admission chain and recreating it re-enables it. +func Test_ValidatingAdmissionPolicy_BindingDeletedThenRecreated(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() + server, err := apiservertesting.StartTestServer(t, nil, []string{ + "--enable-admission-plugins", "ValidatingAdmissionPolicy", + }, framework.SharedEtcd()) + if err != nil { + t.Fatal(err) + } + defer server.TearDownFn() + + config := server.ClientConfig + + client, err := clientset.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + + policy := withValidations([]admissionregistrationv1alpha1.Validation{ + { + Expression: "object.metadata.name.startsWith('test')", + Message: "wrong prefix", + }, + }, withParams(configParamKind(), withNamespaceMatch(withFailurePolicy(admissionregistrationv1alpha1.Fail, makePolicy("allowed-prefixes"))))) + policy = withWaitReadyConstraintAndExpression(policy) + _, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + // validate that namespaces starting with "test" are allowed + policyBinding := makeBinding("allowed-prefixes-binding", "allowed-prefixes", "") + if err := createAndWaitReady(t, client, policyBinding, nil); err != nil { + t.Fatal(err) + } + + if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { + disallowedNamespace := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "not-test-", + }, + } + + _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) + if err == nil { + return false, nil + } + + if strings.Contains(err.Error(), "not yet synced to use for admission") { + return false, nil + } + + if !strings.Contains(err.Error(), "wrong prefix") { + return false, err + } + + return true, nil + }); waitErr != nil { + t.Errorf("timed out waiting: %v", err) + } + + // delete the binding object and validate that policy is not enforced + if err := client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings().Delete(context.TODO(), "allowed-prefixes-binding", metav1.DeleteOptions{}); err != nil { + t.Fatal(err) + } + + if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { + allowedNamespace := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "not-test-", + }, + } + _, err = client.CoreV1().Namespaces().Create(context.TODO(), allowedNamespace, metav1.CreateOptions{}) + if err == nil { + return true, nil + } + + // old policy is still enforced, try again + if strings.Contains(err.Error(), "wrong prefix") { + return false, nil + } + + return false, err + }); waitErr != nil { + t.Errorf("timed out waiting: %v", err) + } + + // recreate the policy binding and test that policy is enforced again + _, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings().Create(context.TODO(), policyBinding, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { + disallowedNamespace := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "not-test-", + }, + } + + _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) + if err == nil { + return false, nil + } + + if strings.Contains(err.Error(), "not yet synced to use for admission") { + return false, nil + } + + if !strings.Contains(err.Error(), "wrong prefix") { + return false, err + } + + return true, nil + }); waitErr != nil { + t.Errorf("timed out waiting: %v", err) + } +} + +// Test_ValidatingAdmissionPolicy_ParamResourceDeletedThenRecreated validates that deleting a param resource referenced +// by a binding renders the policy as invalid. Recreating the param resource re-enables the policy. +func Test_ValidatingAdmissionPolicy_ParamResourceDeletedThenRecreated(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() + server, err := apiservertesting.StartTestServer(t, nil, []string{ + "--enable-admission-plugins", "ValidatingAdmissionPolicy", + }, framework.SharedEtcd()) + if err != nil { + t.Fatal(err) + } + defer server.TearDownFn() + + config := server.ClientConfig + + client, err := clientset.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + + param := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + } + if _, err := client.CoreV1().ConfigMaps("default").Create(context.TODO(), param, metav1.CreateOptions{}); err != nil { + t.Fatal(err) + } + + policy := withValidations([]admissionregistrationv1alpha1.Validation{ + { + Expression: "object.metadata.name.startsWith(params.metadata.name)", + Message: "wrong prefix", + }, + }, withParams(configParamKind(), withNamespaceMatch(withFailurePolicy(admissionregistrationv1alpha1.Fail, makePolicy("allowed-prefixes"))))) + policy = withWaitReadyConstraintAndExpression(policy) + _, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + // validate that namespaces starting with "test" are allowed + policyBinding := makeBinding("allowed-prefixes-binding", "allowed-prefixes", "test") + if err := createAndWaitReady(t, client, policyBinding, nil); err != nil { + t.Fatal(err) + } + + if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { + disallowedNamespace := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "not-test-", + }, + } + + _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) + if err == nil { + return false, nil + } + + if strings.Contains(err.Error(), "not yet synced to use for admission") { + return false, nil + } + + if !strings.Contains(err.Error(), "wrong prefix") { + return false, err + } + + return true, nil + }); waitErr != nil { + t.Errorf("timed out waiting: %v", err) + } + + // delete param object and validate that policy is invalid + if err := client.CoreV1().ConfigMaps("default").Delete(context.TODO(), "test", metav1.DeleteOptions{}); err != nil { + t.Fatal(err) + } + + if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { + allowedNamespace := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "not-test-", + }, + } + + _, err = client.CoreV1().Namespaces().Create(context.TODO(), allowedNamespace, metav1.CreateOptions{}) + // old policy is still enforced, try again + if strings.Contains(err.Error(), "wrong prefix") { + return false, nil + } + + if !strings.Contains(err.Error(), "failed to configure binding: test not found") { + return false, err + } + + return true, nil + }); waitErr != nil { + t.Errorf("timed out waiting: %v", err) + } + + // recreate the param resource and validate namespace is disallowed again + if _, err := client.CoreV1().ConfigMaps("default").Create(context.TODO(), param, metav1.CreateOptions{}); err != nil { + t.Fatal(err) + } + + if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { + disallowedNamespace := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "not-test-", + }, + } + + _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) + // cache not synced with new object yet, try again + if strings.Contains(err.Error(), "failed to configure binding: test not found") { + return false, nil + } + + if !strings.Contains(err.Error(), "wrong prefix") { + return false, err + } + + return true, nil + }); waitErr != nil { + t.Errorf("timed out waiting: %v", err) + } +} + func withWaitReadyConstraintAndExpression(policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy { policy = policy.DeepCopy() policy.Spec.MatchConstraints.ResourceRules = append(policy.Spec.MatchConstraints.ResourceRules, admissionregistrationv1alpha1.NamedRuleWithOperations{ From 7127f565f612bb4018bf7edac336b58585d05311 Mon Sep 17 00:00:00 2001 From: Andrew Sy Kim Date: Mon, 14 Nov 2022 12:30:56 -0500 Subject: [PATCH 8/9] test/integration/apiserver/cel: add Test_ValidatingAdmissionPolicy_UpdateParamResource Signed-off-by: Andrew Sy Kim --- .../cel/validatingadmissionpolicy_test.go | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/test/integration/apiserver/cel/validatingadmissionpolicy_test.go b/test/integration/apiserver/cel/validatingadmissionpolicy_test.go index 4a247460701..c06df7a6cc2 100644 --- a/test/integration/apiserver/cel/validatingadmissionpolicy_test.go +++ b/test/integration/apiserver/cel/validatingadmissionpolicy_test.go @@ -907,6 +907,140 @@ func Test_ValidatingAdmissionPolicy_UpdateParamRef(t *testing.T) { } } +// Test_ValidatingAdmissionPolicy_UpdateParamResource validates behavior of a policy after updates to the param resource. +func Test_ValidatingAdmissionPolicy_UpdateParamResource(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() + server, err := apiservertesting.StartTestServer(t, nil, []string{ + "--enable-admission-plugins", "ValidatingAdmissionPolicy", + }, framework.SharedEtcd()) + if err != nil { + t.Fatal(err) + } + defer server.TearDownFn() + + config := server.ClientConfig + + client, err := clientset.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + + paramConfigMap := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "allowed-prefix", + Namespace: "default", + }, + Data: map[string]string{ + "prefix": "test-1", + }, + } + paramConfigMap, err = client.CoreV1().ConfigMaps(paramConfigMap.Namespace).Create(context.TODO(), paramConfigMap, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + policy := withValidations([]admissionregistrationv1alpha1.Validation{ + { + Expression: "object.metadata.name.startsWith(params.data['prefix'])", + Message: "wrong prefix", + }, + }, withParams(configParamKind(), withNamespaceMatch(withFailurePolicy(admissionregistrationv1alpha1.Fail, makePolicy("allowed-prefixes"))))) + policy = withWaitReadyConstraintAndExpression(policy) + _, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + // validate that namespaces starting with "test-1" are allowed + // and namespaces starting with "test-2-" are disallowed + allowedPrefixesBinding := makeBinding("allowed-prefixes-binding", "allowed-prefixes", "allowed-prefix") + if err := createAndWaitReady(t, client, allowedPrefixesBinding, nil); err != nil { + t.Fatal(err) + } + + if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { + disallowedNamespace := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-2-", + }, + } + + _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) + if err == nil { + return false, nil + } + + if strings.Contains(err.Error(), "not yet synced to use for admission") { + return false, nil + } + + if !strings.Contains(err.Error(), "wrong prefix") { + return false, err + } + + return true, nil + }); waitErr != nil { + t.Errorf("timed out waiting: %v", err) + } + + allowedNamespace := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-1-", + }, + } + _, err = client.CoreV1().Namespaces().Create(context.TODO(), allowedNamespace, metav1.CreateOptions{}) + if err != nil { + t.Error(err) + } + + // Update the param resource to use "test-2" as the new allwoed prefix + paramConfigMapCopy := paramConfigMap.DeepCopy() + paramConfigMapCopy.Data = map[string]string{ + "prefix": "test-2", + } + _, err = client.CoreV1().ConfigMaps(paramConfigMapCopy.Namespace).Update(context.TODO(), paramConfigMapCopy, metav1.UpdateOptions{}) + if err != nil { + t.Fatal(err) + } + + // validate that namespaces starting with "test-2" are allowed + // and namespaces starting with "test-1" are disallowed + if waitErr := wait.PollImmediate(time.Millisecond*10, wait.ForeverTestTimeout, func() (bool, error) { + disallowedNamespace := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-1-", + }, + } + + _, err = client.CoreV1().Namespaces().Create(context.TODO(), disallowedNamespace, metav1.CreateOptions{}) + if err == nil { + return false, nil + } + + if strings.Contains(err.Error(), "not yet synced to use for admission") { + return false, nil + } + + if !strings.Contains(err.Error(), "wrong prefix") { + return false, err + } + + return true, nil + }); waitErr != nil { + t.Errorf("timed out waiting: %v", err) + } + + allowedNamespace = &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-2-", + }, + } + _, err = client.CoreV1().Namespaces().Create(context.TODO(), allowedNamespace, metav1.CreateOptions{}) + if err != nil { + t.Error(err) + } +} + func Test_ValidatingAdmissionPolicy_MatchByObjectSelector(t *testing.T) { defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)() server, err := apiservertesting.StartTestServer(t, nil, []string{ From 34a2d265d7de3e06f43366a14db1ebe1664127cc Mon Sep 17 00:00:00 2001 From: Andrew Sy Kim Date: Mon, 14 Nov 2022 22:27:16 -0500 Subject: [PATCH 9/9] test/integration/apiserver/cel: update createAndWaitReady to retry on error including 'not yet synced to use for admission' Signed-off-by: Andrew Sy Kim --- .../apiserver/cel/validatingadmissionpolicy_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/integration/apiserver/cel/validatingadmissionpolicy_test.go b/test/integration/apiserver/cel/validatingadmissionpolicy_test.go index c06df7a6cc2..d5c6feb3463 100644 --- a/test/integration/apiserver/cel/validatingadmissionpolicy_test.go +++ b/test/integration/apiserver/cel/validatingadmissionpolicy_test.go @@ -1933,6 +1933,9 @@ func createAndWaitReady(t *testing.T, client *clientset.Clientset, binding *admi _, err := client.CoreV1().Endpoints("default").Patch(context.TODO(), marker.Name, types.JSONPatchType, []byte("[]"), metav1.PatchOptions{}) if err != nil && strings.Contains(err.Error(), "marker denied; policy is ready") { return true, nil + } else if err != nil && strings.Contains(err.Error(), "not yet synced to use for admission") { + t.Logf("waiting for policy to be ready. Marker: %v. Admission not synced yet: %v", marker, err) + return false, nil } else { t.Logf("waiting for policy to be ready. Marker: %v, Last marker patch response: %v", marker, err) return false, err