diff --git a/e2e/namespace_metadata_forbidden_test.go b/e2e/namespace_metadata_forbidden_test.go index 1dbf503b..9c1dca49 100644 --- a/e2e/namespace_metadata_forbidden_test.go +++ b/e2e/namespace_metadata_forbidden_test.go @@ -12,6 +12,7 @@ import ( rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" "github.com/projectcapsule/capsule/pkg/api" @@ -20,6 +21,8 @@ import ( ) var _ = Describe("creating a Namespace with user-specified labels and annotations", Ordered, Label("namespace", "metadata", "forbidden"), func() { + originConfig := &capsulev1beta2.CapsuleConfiguration{} + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "e2e-user-metadata-forbidden", @@ -62,7 +65,14 @@ var _ = Describe("creating a Namespace with user-specified labels and annotation }, } + admin := rbac.UserSpec{ + Name: "admin", + Kind: "User", + } + JustBeforeEach(func() { + Expect(k8sClient.Get(context.Background(), client.ObjectKey{Name: defaultConfigurationName}, originConfig)).To(Succeed()) + EventuallyCreation(func() error { tnt.ResourceVersion = "" @@ -70,13 +80,28 @@ var _ = Describe("creating a Namespace with user-specified labels and annotation }).Should(Succeed()) TenantReady(tnt, metav1.ConditionTrue, defaultTimeoutInterval) + + ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) { + configuration.Spec.Administrators = []rbac.UserSpec{admin} + }) }) JustAfterEach(func() { EventuallyDeletion(tnt) + + Eventually(func() error { + c := &capsulev1beta2.CapsuleConfiguration{} + if err := k8sClient.Get(context.Background(), client.ObjectKey{Name: originConfig.Name}, c); err != nil { + return err + } + c.Spec = originConfig.Spec + return k8sClient.Update(context.Background(), c) + }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) }) It("should allow", func() { + ctx := context.TODO() + By("specifying non-forbidden labels", func() { ns := NewNamespace("", map[string]string{ "bim": "baz", @@ -114,6 +139,58 @@ var _ = Describe("creating a Namespace with user-specified labels and annotation g.Expect(ns.GetAnnotations()).To(HaveKeyWithValue("managed.projectcapsule.dev/source", "capsule")) }, defaultTimeoutInterval, time.Second).Should(Succeed()) }) + + By("creating a Namespace with forbidden labels as an admin", func() { + ns := NewNamespace("", map[string]string{ + "foo": "bar", + "gatsby-custom": "value", + meta.TenantLabel: tnt.GetName(), + }) + + NamespaceCreation(ns, admin, defaultTimeoutInterval).Should(Succeed()) + NamespaceIsPartOfTenant(tnt, ns).Should(Succeed()) + }) + + By("creating a Namespace with forbidden annotations as an admin", func() { + ns := NewNamespace("", map[string]string{ + meta.TenantLabel: tnt.GetName(), + }) + ns.SetAnnotations(map[string]string{"foo": "bar", "gatsby-custom": "value"}) + + NamespaceCreation(ns, admin, defaultTimeoutInterval).Should(Succeed()) + NamespaceIsPartOfTenant(tnt, ns).Should(Succeed()) + }) + + By("updating a Namespace with a new forbidden label as an admin", func() { + ns := NewNamespace("admin-update-labels", map[string]string{ + meta.TenantLabel: tnt.GetName(), + }) + + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) + + original := ns.DeepCopy() + ns.Labels["foo"] = "bar" + ns.Labels["gatsby-custom"] = "value" + + Eventually(func() error { + return impersonationClient(admin.Name, nil).Patch(ctx, ns, client.MergeFrom(original)) + }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) + }) + + By("updating a Namespace with a new forbidden annotation as an admin", func() { + ns := NewNamespace("admin-update-annotations", map[string]string{ + meta.TenantLabel: tnt.GetName(), + }) + + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) + + original := ns.DeepCopy() + ns.SetAnnotations(map[string]string{"foo": "bar", "gatsby-custom": "value"}) + + Eventually(func() error { + return impersonationClient(admin.Name, nil).Patch(ctx, ns, client.MergeFrom(original)) + }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) + }) }) It("should fail when creating a Namespace", func() { diff --git a/internal/webhook/namespace/validation/user_metadata.go b/internal/webhook/namespace/validation/user_metadata.go index 1a07674c..63d6690c 100644 --- a/internal/webhook/namespace/validation/user_metadata.go +++ b/internal/webhook/namespace/validation/user_metadata.go @@ -30,14 +30,14 @@ func UserMetadataHandler() handlers.TypedHandlerWithTenantUser[*corev1.Namespace func (h *userMetadataHandler) OnCreate( _ client.Client, _ client.Reader, - _ users.AdmissionUser, + user users.AdmissionUser, ns *corev1.Namespace, _ admission.Decoder, recorder events.EventRecorder, tnt *capsulev1beta2.Tenant, ) handlers.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - if tnt.Spec.NamespaceOptions != nil { + if tnt.Spec.NamespaceOptions != nil && user.IsCapsule() { labels, annotations, err := userMetadataForValidation(ns, nil, tnt) if err != nil { return ad.ErroredResponse(err) @@ -64,7 +64,7 @@ func (h *userMetadataHandler) OnCreate( func (h *userMetadataHandler) OnUpdate( _ client.Client, _ client.Reader, - _ users.AdmissionUser, + user users.AdmissionUser, newNs *corev1.Namespace, oldNs *corev1.Namespace, _ admission.Decoder, @@ -111,7 +111,7 @@ func (h *userMetadataHandler) OnUpdate( } } - if tnt.Spec.NamespaceOptions != nil { + if tnt.Spec.NamespaceOptions != nil && user.IsCapsule() { labels, annotations, err := userMetadataForValidation(newNs, oldNs, tnt) if err != nil { return ad.ErroredResponse(err)