From 0fb038fb15265008c3f940f37f07988aece5c7e5 Mon Sep 17 00:00:00 2001 From: Andrew Sy Kim Date: Mon, 14 Nov 2022 12:10:24 -0500 Subject: [PATCH] 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{