// Copyright 2020-2023 Project Capsule Authors. // SPDX-License-Identifier: Apache-2.0 package e2e import ( "context" "fmt" "math/rand" "sort" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apiserver/pkg/authentication/serviceaccount" "k8s.io/utils/ptr" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" "github.com/projectcapsule/capsule/pkg/api/meta" "github.com/projectcapsule/capsule/pkg/api/rbac" clt "github.com/projectcapsule/capsule/pkg/runtime/client" "github.com/projectcapsule/capsule/pkg/tenant" ) var _ = Describe("creating several Namespaces for a Tenant", Ordered, Label("config", "namespace", "hijack"), func() { administrator := rbac.UserSpec{ Name: "e2e-namespace-hijacking-admin", Kind: rbac.UserOwner, } var originalConfigurationSpec *capsulev1beta2.CapsuleConfigurationSpec t1 := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "e2e-ns-attack-1", Labels: map[string]string{ "env": "e2e", }, }, Spec: capsulev1beta2.TenantSpec{ Owners: rbac.OwnerListSpec{ { CoreOwnerSpec: rbac.CoreOwnerSpec{ UserSpec: rbac.UserSpec{ Name: "gatsby", Kind: "User", }, }, }, { CoreOwnerSpec: rbac.CoreOwnerSpec{ UserSpec: rbac.UserSpec{ Name: "oidc:group", Kind: "Group", }, }, }, { CoreOwnerSpec: rbac.CoreOwnerSpec{ UserSpec: rbac.UserSpec{ Kind: "ServiceAccount", Name: "system:serviceaccount:attacker-system:attacker", }, }, }, }, }, } t2 := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "e2e-ns-attack-2", Labels: map[string]string{ "env": "e2e", }, }, Spec: capsulev1beta2.TenantSpec{ Owners: rbac.OwnerListSpec{ { CoreOwnerSpec: rbac.CoreOwnerSpec{ UserSpec: rbac.UserSpec{ Name: "gatsby", Kind: "User", }, }, }, }, }, } t3 := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "e2e-ns-attack-3", Labels: map[string]string{"env": "e2e"}, }, Spec: capsulev1beta2.TenantSpec{ Owners: rbac.OwnerListSpec{ { CoreOwnerSpec: rbac.CoreOwnerSpec{ UserSpec: rbac.UserSpec{ Name: "different-owner", Kind: "User", }, }, }, }, }, } grantNamespaceSubresourceUpdate := func(name string, subject rbacv1.Subject) { clusterRole := &rbacv1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{ Name: name, }, Rules: []rbacv1.PolicyRule{ { APIGroups: []string{""}, Resources: []string{ "namespaces/status", "namespaces/finalize", }, Verbs: []string{ "get", "update", "patch", }, }, { APIGroups: []string{""}, Resources: []string{ "namespaces", }, Verbs: []string{ "get", "update", "patch", }, }, }, } clusterRoleBinding := &rbacv1.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: name, }, Subjects: []rbacv1.Subject{ subject, }, RoleRef: rbacv1.RoleRef{ APIGroup: rbacv1.GroupName, Kind: "ClusterRole", Name: name, }, } EventuallyCreation(func() error { return k8sClient.Create(context.TODO(), clusterRole) }).Should(Succeed()) EventuallyCreation(func() error { return k8sClient.Create(context.TODO(), clusterRoleBinding) }).Should(Succeed()) } cleanupNamespaceSubresourceGrant := func(name string) { Eventually(func() error { return k8sClient.Delete(context.TODO(), &rbacv1.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: name, }, }) }).Should(SatisfyAny(Succeed(), WithTransform(apierrors.IsNotFound, BeTrue()))) Eventually(func() error { return k8sClient.Delete(context.TODO(), &rbacv1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{ Name: name, }, }) }).Should(SatisfyAny(Succeed(), WithTransform(apierrors.IsNotFound, BeTrue()))) } expectNoTenantHijackPersisted := func(nsName string, originalTenant, attackerTenant *capsulev1beta2.Tenant) { Eventually(func(g Gomega) { current := &corev1.Namespace{} Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: nsName}, current)).Should(Succeed()) g.Expect(current.Labels).To(HaveKeyWithValue(meta.TenantLabel, originalTenant.GetName())) g.Expect(current.Labels).NotTo(HaveKeyWithValue(meta.TenantLabel, attackerTenant.GetName())) g.Expect(hasTenantOwnerReferenceByNameAndUID(current, originalTenant.GetName(), originalTenant.GetUID())). To(BeTrue(), "namespace should keep original tenant ownerReference") g.Expect(hasTenantOwnerReferenceByNameAndUID(current, attackerTenant.GetName(), attackerTenant.GetUID())). To(BeFalse(), "namespace must not gain attacker tenant ownerReference") }).Should(Succeed()) } kubeSystem := &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: "kube-system", }, } getTenant := func(name string) *capsulev1beta2.Tenant { tenant := &capsulev1beta2.Tenant{} Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: name}, tenant)).Should(Succeed()) return tenant } getNamespace := func(name string) *corev1.Namespace { ns := &corev1.Namespace{} Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: name}, ns)).Should(Succeed()) return ns } hasTenantOwnerReference := func(ns *corev1.Namespace, tenant *capsulev1beta2.Tenant) bool { for _, ownerRef := range ns.OwnerReferences { if ownerRef.APIVersion == capsulev1beta2.GroupVersion.String() && ownerRef.Kind == "Tenant" && ownerRef.Name == tenant.GetName() && ownerRef.UID == tenant.GetUID() { return true } } return false } hasTenantOwnerReferenceByNameAndUID := func(ns *corev1.Namespace, name string, uid types.UID) bool { for _, ownerRef := range ns.OwnerReferences { if ownerRef.APIVersion == capsulev1beta2.GroupVersion.String() && ownerRef.Kind == "Tenant" && ownerRef.Name == name && ownerRef.UID == uid { return true } } return false } expectOriginalTenantOwnership := func(nsName string, tenant *capsulev1beta2.Tenant) { retrievedNs := getNamespace(nsName) Expect(retrievedNs.Labels).To(HaveKeyWithValue(meta.TenantLabel, tenant.GetName())) Expect(hasTenantOwnerReference(retrievedNs, tenant)).To(BeTrue(), "Namespace should keep original Tenant ownerReference") } expectNoTenantOwnership := func(nsName string, tenant *capsulev1beta2.Tenant) { retrievedNs := getNamespace(nsName) Expect(retrievedNs.Labels).NotTo(HaveKeyWithValue(meta.TenantLabel, tenant.GetName())) Expect(hasTenantOwnerReference(retrievedNs, tenant)).To(BeFalse(), "Namespace should not have Tenant ownerReference") } randomTenantReference := func() (string, types.UID) { return fmt.Sprintf("random-tenant-%d", rand.Int()), types.UID(fmt.Sprintf("%d", rand.Int())) } createUnmanagedNamespace := func() *corev1.Namespace { ns := NewNamespace("") Expect(k8sClient.Create(context.TODO(), ns)).To(Succeed()) DeferCleanup(func() { EventuallyDeletion(ns) }) return ns } waitForTenantNamespacesDeletion := func(tenantNames ...string) { names := make(map[string]struct{}, len(tenantNames)) for _, name := range tenantNames { names[name] = struct{}{} } Eventually(func(g Gomega) { list := &corev1.NamespaceList{} g.Expect(k8sClient.List(context.TODO(), list)).To(Succeed()) for i := range list.Items { for _, ref := range tenant.TenantOwnerReferences(&list.Items[i]) { _, belongsToTestTenant := names[ref.Name] g.Expect(belongsToTestTenant).To( BeFalse(), "namespace %q still references a previous incarnation of Tenant %q (UID %q)", list.Items[i].Name, ref.Name, ref.UID, ) } } }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) } BeforeAll(func() { configuration := &capsulev1beta2.CapsuleConfiguration{} Expect(k8sClient.Get( context.Background(), types.NamespacedName{Name: defaultConfigurationName}, configuration, )).To(Succeed()) originalConfigurationSpec = configuration.Spec.DeepCopy() ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) { configuration.Spec.Administrators = append(configuration.Spec.Administrators, administrator) }) }) AfterAll(func() { ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) { configuration.Spec = *originalConfigurationSpec }) }) JustBeforeEach(func() { waitForTenantNamespacesDeletion(t1.Name, t2.Name, t3.Name) EventuallyCreation(func() error { t1.ResourceVersion = "" return k8sClient.Create(context.TODO(), t1) }).Should(Succeed()) TenantReady(t1, metav1.ConditionTrue, defaultTimeoutInterval) EventuallyCreation(func() error { t2.ResourceVersion = "" return k8sClient.Create(context.TODO(), t2) }).Should(Succeed()) TenantReady(t2, metav1.ConditionTrue, defaultTimeoutInterval) EventuallyCreation(func() error { t3.ResourceVersion = "" return k8sClient.Create(context.TODO(), t3) }).Should(Succeed()) TenantReady(t3, metav1.ConditionTrue, defaultTimeoutInterval) }) JustAfterEach(func() { EventuallyDeletion(t1) EventuallyDeletion(t2) EventuallyDeletion(t3) waitForTenantNamespacesDeletion(t1.Name, t2.Name, t3.Name) }) It("Owners can not hijack Tenant ownership through namespaces/status", func() { tenantA := getTenant(t1.Name) tenantB := getTenant(t2.Name) owner := t1.Spec.Owners[0].UserSpec cs := ownerClient(owner) grantName := "e2e-ns-status-hijack" grantNamespaceSubresourceUpdate(grantName, rbacv1.Subject{ APIGroup: rbacv1.GroupName, Kind: rbacv1.UserKind, Name: owner.Name, }) DeferCleanup(func() { cleanupNamespaceSubresourceGrant(grantName) }) ns := NewNamespace("", map[string]string{ meta.TenantLabel: tenantA.GetName(), }) NamespaceCreation(ns, owner, defaultTimeoutInterval).Should(Succeed()) NamespaceIsPartOfTenant(t1, ns).Should(Succeed()) current, err := cs.CoreV1().Namespaces().Get( context.TODO(), ns.Name, metav1.GetOptions{}, ) Expect(err).ToNot(HaveOccurred()) hijacked := current.DeepCopy() if hijacked.Labels == nil { hijacked.Labels = map[string]string{} } hijacked.Labels[meta.TenantLabel] = tenantB.GetName() replaced := false for i := range hijacked.OwnerReferences { ref := &hijacked.OwnerReferences[i] if ref.APIVersion != capsulev1beta2.GroupVersion.String() { continue } if ref.Kind != "Tenant" { continue } if ref.Name != tenantA.GetName() { continue } if ref.UID != tenantA.GetUID() { continue } ref.Name = tenantB.GetName() ref.UID = tenantB.GetUID() replaced = true break } Expect(replaced).To(BeTrue(), "expected test namespace to contain Tenant ownerReference for %q", tenantA.GetName()) Expect(hasTenantOwnerReferenceByNameAndUID(hijacked, tenantA.GetName(), tenantA.GetUID())). To(BeFalse(), "hijack payload should no longer contain original Tenant ownerReference") Expect(hasTenantOwnerReferenceByNameAndUID(hijacked, tenantB.GetName(), tenantB.GetUID())). To(BeTrue(), "hijack payload should contain attacker Tenant ownerReference") _, err = cs.CoreV1().Namespaces().UpdateStatus( context.TODO(), hijacked, metav1.UpdateOptions{}, ) if err != nil { By(fmt.Sprintf("namespaces/status hijack attempt was rejected: %v", err)) } expectNoTenantHijackPersisted(ns.Name, tenantA, tenantB) }) It("Owners can not hijack Tenant ownership through namespaces/finalize", func() { tenantA := getTenant(t1.Name) tenantB := getTenant(t2.Name) owner := t1.Spec.Owners[0].UserSpec cs := ownerClient(owner) grantName := "e2e-ns-finalize-hijack" grantNamespaceSubresourceUpdate(grantName, rbacv1.Subject{ APIGroup: rbacv1.GroupName, Kind: rbacv1.UserKind, Name: owner.Name, }) DeferCleanup(func() { cleanupNamespaceSubresourceGrant(grantName) }) ns := NewNamespace("", map[string]string{ meta.TenantLabel: tenantA.GetName(), }) NamespaceCreation(ns, owner, defaultTimeoutInterval).Should(Succeed()) NamespaceIsPartOfTenant(t1, ns).Should(Succeed()) current, err := cs.CoreV1().Namespaces().Get( context.TODO(), ns.Name, metav1.GetOptions{}, ) Expect(err).ToNot(HaveOccurred()) hijacked := current.DeepCopy() if hijacked.Labels == nil { hijacked.Labels = map[string]string{} } hijacked.Labels[meta.TenantLabel] = tenantB.GetName() replaced := false for i := range hijacked.OwnerReferences { ref := &hijacked.OwnerReferences[i] if ref.APIVersion != capsulev1beta2.GroupVersion.String() { continue } if ref.Kind != "Tenant" { continue } if ref.Name != tenantA.GetName() { continue } if ref.UID != tenantA.GetUID() { continue } ref.Name = tenantB.GetName() ref.UID = tenantB.GetUID() replaced = true break } Expect(replaced).To(BeTrue(), "expected test namespace to contain Tenant ownerReference for %q", tenantA.GetName()) Expect(hasTenantOwnerReferenceByNameAndUID(hijacked, tenantA.GetName(), tenantA.GetUID())). To(BeFalse(), "hijack payload should no longer contain original Tenant ownerReference") Expect(hasTenantOwnerReferenceByNameAndUID(hijacked, tenantB.GetName(), tenantB.GetUID())). To(BeTrue(), "hijack payload should contain attacker Tenant ownerReference") _, err = cs.CoreV1().Namespaces().Finalize( context.TODO(), hijacked, metav1.UpdateOptions{}, ) if err != nil { By(fmt.Sprintf("namespaces/finalize hijack attempt was rejected: %v", err)) } expectNoTenantHijackPersisted(ns.Name, tenantA, tenantB) }) It("Owners can not add a second Tenant ownerReference to a managed namespace", func() { tenantA := getTenant(t1.Name) tenantB := getTenant(t2.Name) for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) ns := NewNamespace("", map[string]string{ meta.TenantLabel: tenantA.GetName(), }) NamespaceCreation(ns, owner.UserSpec, defaultTimeoutInterval).Should(Succeed()) NamespaceIsPartOfTenant(t1, ns).Should(Succeed()) current, err := cs.CoreV1().Namespaces().Get( context.TODO(), ns.Name, metav1.GetOptions{}, ) Expect(err).ToNot(HaveOccurred()) current.OwnerReferences = append(current.OwnerReferences, metav1.OwnerReference{ APIVersion: capsulev1beta2.GroupVersion.String(), Kind: "Tenant", Name: tenantB.GetName(), UID: tenantB.GetUID(), }) _, err = cs.CoreV1().Namespaces().Update( context.TODO(), current, metav1.UpdateOptions{}, ) Expect(err).To(HaveOccurred()) Eventually(func(g Gomega) { updated := &corev1.Namespace{} err := k8sClient.Get( context.TODO(), types.NamespacedName{Name: ns.Name}, updated, ) g.Expect(err).ToNot(HaveOccurred()) g.Expect(tenantOwnerReferences(updated)).To(Equal([]string{tenantA.GetName()})) g.Expect(updated.Labels).To(HaveKeyWithValue(meta.TenantLabel, tenantA.GetName())) }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) } }) It("Owners can not hijack unmanaged namespaces with multiple Tenant ownerReferences", func() { tenantA := getTenant(t1.Name) tenantB := getTenant(t2.Name) unmanaged := createUnmanagedNamespace() for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) patch := []byte(fmt.Sprintf(`{ "metadata": { "ownerReferences": [ { "apiVersion": "%s", "kind": "Tenant", "name": "%s", "uid": "%s" }, { "apiVersion": "%s", "kind": "Tenant", "name": "%s", "uid": "%s" } ] } }`, capsulev1beta2.GroupVersion.String(), tenantA.GetName(), tenantA.GetUID(), capsulev1beta2.GroupVersion.String(), tenantB.GetName(), tenantB.GetUID(), )) _, err := cs.CoreV1().Namespaces().Patch( context.TODO(), unmanaged.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}, ) Expect(err).To(HaveOccurred()) Eventually(func(g Gomega) { updated := &corev1.Namespace{} err := k8sClient.Get( context.TODO(), types.NamespacedName{Name: unmanaged.Name}, updated, ) g.Expect(err).ToNot(HaveOccurred()) g.Expect(tenantOwnerReferences(updated)).To(BeEmpty()) g.Expect(updated.Labels).ToNot(HaveKey(meta.TenantLabel)) }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) } }) It("Tenant A owners can not adopt unmanaged namespaces into Tenant B", func() { tenantB := getTenant(t3.Name) unmanaged := createUnmanagedNamespace() for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) patch := []byte(fmt.Sprintf( `{"metadata":{"labels":{"%s":"%s"},"ownerReferences":[{"apiVersion":"%s","kind":"Tenant","name":"%s","uid":"%s"}]}}`, meta.TenantLabel, tenantB.GetName(), capsulev1beta2.GroupVersion.String(), tenantB.GetName(), tenantB.GetUID(), )) _, err := cs.CoreV1().Namespaces().Patch( context.TODO(), unmanaged.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}, ) Expect(err).To(HaveOccurred()) expectNoTenantOwnership(unmanaged.Name, tenantB) } }) It("Owners can not hijack unmanaged namespaces with controller ownerReference flags", func() { tenant := getTenant(t1.Name) unmanaged := createUnmanagedNamespace() for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) patch := []byte(fmt.Sprintf(`{ "metadata":{ "ownerReferences":[{ "apiVersion":"%s", "kind":"Tenant", "name":"%s", "uid":"%s", "controller":true, "blockOwnerDeletion":true }] } }`, capsulev1beta2.GroupVersion.String(), tenant.GetName(), tenant.GetUID())) _, err := cs.CoreV1().Namespaces().Patch( context.TODO(), unmanaged.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}, ) Expect(err).To(HaveOccurred()) expectNoTenantOwnership(unmanaged.Name, tenant) } }) It("Owners can not smuggle Tenant ownerReference beside unrelated ownerReferences", func() { tenant := getTenant(t1.Name) unmanaged := createUnmanagedNamespace() for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) patch := []byte(fmt.Sprintf(`{ "metadata":{ "ownerReferences":[ {"apiVersion":"v1","kind":"ConfigMap","name":"dummy","uid":"%s"}, {"apiVersion":"%s","kind":"Tenant","name":"%s","uid":"%s"} ] } }`, types.UID("12345"), capsulev1beta2.GroupVersion.String(), tenant.GetName(), tenant.GetUID())) _, err := cs.CoreV1().Namespaces().Patch( context.TODO(), unmanaged.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}, ) Expect(err).To(HaveOccurred()) expectNoTenantOwnership(unmanaged.Name, tenant) } }) It("Owners can not create an ownership gap then patch managed namespace metadata", func() { tenant := getTenant(t1.Name) for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) ns := NewNamespace("", map[string]string{meta.TenantLabel: tenant.GetName()}) NamespaceCreation(ns, owner.UserSpec, defaultTimeoutInterval).Should(Succeed()) NamespaceIsPartOfTenant(t1, ns).Should(Succeed()) remove := []byte(fmt.Sprintf(`{"metadata":{"labels":{"%s":null}}}`, meta.TenantLabel)) _, err := cs.CoreV1().Namespaces().Patch( context.TODO(), ns.Name, types.StrategicMergePatchType, remove, metav1.PatchOptions{}, ) Expect(err).ToNot(HaveOccurred()) expectOriginalTenantOwnership(ns.Name, tenant) patch := []byte(`{"metadata":{"labels":{"attacker.example.com/touched":"true"}}}`) _, err = cs.CoreV1().Namespaces().Patch( context.TODO(), ns.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}, ) Expect(err).ToNot(HaveOccurred()) expectOriginalTenantOwnership(ns.Name, tenant) } }) It("Can't hijack offlimits namespace (Ownerreferences)", func() { tenant := getTenant(t1.Name) Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: kubeSystem.GetName()}, kubeSystem)).Should(Succeed()) for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) patch := []byte(fmt.Sprintf( `{"metadata":{"ownerReferences":[{"apiVersion":"%s","kind":"Tenant","name":"%s","uid":"%s"}]}}`, capsulev1beta2.GroupVersion.String(), tenant.GetName(), tenant.GetUID(), )) _, err := cs.CoreV1().Namespaces().Patch(context.TODO(), kubeSystem.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}) Expect(err).To(HaveOccurred()) } }) It("Can't hijack offlimits namespace (Labels)", func() { tenant := getTenant(t1.Name) Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: kubeSystem.GetName()}, kubeSystem)).Should(Succeed()) for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) patch := []byte(fmt.Sprintf( `{"metadata":{"labels":{"%s":"%s"}}}`, meta.TenantLabel, tenant.GetName(), )) _, err := cs.CoreV1().Namespaces().Patch(context.TODO(), kubeSystem.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}) Expect(err).To(HaveOccurred()) } }) It("Can't hijack offlimits namespace (Annotations)", func() { tenant := getTenant(t1.Name) Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: kubeSystem.GetName()}, kubeSystem)).Should(Succeed()) for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) patch := []byte(fmt.Sprintf( `{"metadata":{"annotations":{"%s":"%s"}}}`, meta.TenantLabel, tenant.GetName(), )) _, err := cs.CoreV1().Namespaces().Patch(context.TODO(), kubeSystem.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}) Expect(err).To(HaveOccurred()) } }) It("Owners can not hijack unmanaged namespaces using JSONPatch add label", func() { tenant := getTenant(t1.Name) unmanaged := createUnmanagedNamespace() for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) patch := []byte(fmt.Sprintf(`[ {"op":"add","path":"/metadata/labels","value":{}}, {"op":"add","path":"/metadata/labels/%s","value":"%s"} ]`, clt.EscapeJSONPointer(meta.TenantLabel), tenant.GetName())) _, err := cs.CoreV1().Namespaces().Patch( context.TODO(), unmanaged.Name, types.JSONPatchType, patch, metav1.PatchOptions{}, ) Expect(err).To(HaveOccurred()) expectNoTenantOwnership(unmanaged.Name, tenant) } }) It("Owners can not hijack unmanaged namespaces using JSONPatch add ownerReference", func() { tenant := getTenant(t1.Name) unmanaged := createUnmanagedNamespace() for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) patch := []byte(fmt.Sprintf(`[ {"op":"add","path":"/metadata/ownerReferences","value":[ {"apiVersion":"%s","kind":"Tenant","name":"%s","uid":"%s"} ]} ]`, capsulev1beta2.GroupVersion.String(), tenant.GetName(), tenant.GetUID())) _, err := cs.CoreV1().Namespaces().Patch( context.TODO(), unmanaged.Name, types.JSONPatchType, patch, metav1.PatchOptions{}, ) Expect(err).To(HaveOccurred()) expectNoTenantOwnership(unmanaged.Name, tenant) } }) It("Owners can not hijack unmanaged namespaces with matching label and forged ownerReference UID", func() { tenant := getTenant(t1.Name) unmanaged := createUnmanagedNamespace() for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) _, fakeUID := randomTenantReference() patch := []byte(fmt.Sprintf( `{"metadata":{"labels":{"%s":"%s"},"ownerReferences":[{"apiVersion":"%s","kind":"Tenant","name":"%s","uid":"%s"}]}}`, meta.TenantLabel, tenant.GetName(), capsulev1beta2.GroupVersion.String(), tenant.GetName(), fakeUID, )) _, err := cs.CoreV1().Namespaces().Patch( context.TODO(), unmanaged.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}, ) Expect(err).To(HaveOccurred()) expectNoTenantOwnership(unmanaged.Name, tenant) } }) It("Owners can not hijack unmanaged namespaces using server-side apply", func() { tenant := getTenant(t1.Name) unmanaged := createUnmanagedNamespace() for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) apply := []byte(fmt.Sprintf(`{ "apiVersion":"v1", "kind":"Namespace", "metadata":{ "name":"%s", "labels":{"%s":"%s"}, "ownerReferences":[{ "apiVersion":"%s", "kind":"Tenant", "name":"%s", "uid":"%s" }] } }`, unmanaged.Name, meta.TenantLabel, tenant.GetName(), capsulev1beta2.GroupVersion.String(), tenant.GetName(), tenant.GetUID(), )) _, err := cs.CoreV1().Namespaces().Patch( context.TODO(), unmanaged.Name, types.ApplyPatchType, apply, metav1.PatchOptions{ FieldManager: "attacker", Force: ptr.To(true), }, ) Expect(err).To(HaveOccurred()) expectNoTenantOwnership(unmanaged.Name, tenant) } }) It("Owners can not combine status and metadata patches to adopt unmanaged namespaces", func() { tenant := getTenant(t1.Name) createNamespaceStatusRBACForOwner(tenant) DeferCleanup(func(tnt *capsulev1beta2.Tenant) { deleteNamespaceStatusRBACForOwner(tnt) }, tenant) unmanaged := createUnmanagedNamespace() for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) statusNs, err := cs.CoreV1().Namespaces().Get(context.TODO(), unmanaged.Name, metav1.GetOptions{}) Expect(err).ToNot(HaveOccurred()) if statusNs.Labels == nil { statusNs.Labels = map[string]string{} } statusNs.Labels[meta.TenantLabel] = tenant.GetName() statusNs.OwnerReferences = []metav1.OwnerReference{{ APIVersion: capsulev1beta2.GroupVersion.String(), Kind: "Tenant", Name: tenant.GetName(), UID: tenant.GetUID(), }} _, err = cs.CoreV1().Namespaces().UpdateStatus(context.TODO(), statusNs, metav1.UpdateOptions{}) Expect(err).To(HaveOccurred()) Expect(apierrors.IsForbidden(err)).To(BeTrue()) Expect(err).To(MatchError(ContainSubstring("namespace can not be patched into a tenant"))) patch := []byte(fmt.Sprintf( `{"metadata":{"labels":{"%s":"%s"}}}`, meta.TenantLabel, tenant.GetName(), )) _, err = cs.CoreV1().Namespaces().Patch( context.TODO(), unmanaged.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}, ) Expect(err).To(HaveOccurred()) expectNoTenantOwnership(unmanaged.Name, tenant) } }) It("Owners can not hijack unmanaged namespaces with valid ownerReference and mismatching label", func() { tenant := getTenant(t1.Name) unmanaged := createUnmanagedNamespace() for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) patch := []byte(fmt.Sprintf( `{"metadata":{"labels":{"%s":"not-%s"},"ownerReferences":[{"apiVersion":"%s","kind":"Tenant","name":"%s","uid":"%s"}]}}`, meta.TenantLabel, tenant.GetName(), capsulev1beta2.GroupVersion.String(), tenant.GetName(), tenant.GetUID(), )) _, err := cs.CoreV1().Namespaces().Patch( context.TODO(), unmanaged.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}, ) Expect(err).To(HaveOccurred()) expectNoTenantOwnership(unmanaged.Name, tenant) } }) It("Owners can patch managed namespaces but ownerReference changes should be reverted", func() { tenant := getTenant(t1.Name) for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) ns := NewNamespace("", map[string]string{ meta.TenantLabel: tenant.GetName(), }) NamespaceCreation(ns, owner.UserSpec, defaultTimeoutInterval).Should(Succeed()) NamespaceIsPartOfTenant(t1, ns).Should(Succeed()) randomName, randomUID := randomTenantReference() patch := []byte(fmt.Sprintf( `{"metadata":{"ownerReferences":[{"apiVersion":"%s","kind":"Tenant","name":"%s","uid":"%s"}]}}`, capsulev1beta2.GroupVersion.String(), randomName, randomUID, )) _, err := cs.CoreV1().Namespaces().Patch(context.TODO(), ns.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}) Expect(err).ToNot(HaveOccurred()) retrievedNs := getNamespace(ns.Name) Expect(hasTenantOwnerReferenceByNameAndUID(retrievedNs, randomName, randomUID)).To(BeFalse(), "Namespace should not keep patched Tenant ownerReference") Expect(hasTenantOwnerReference(retrievedNs, tenant)).To(BeTrue(), "Namespace should keep original Tenant ownerReference") } }) It("Owners can patch managed namespaces but tenant label changes should be reverted", func() { tenant := getTenant(t1.Name) for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) ns := NewNamespace("", map[string]string{ meta.TenantLabel: tenant.GetName(), }) NamespaceCreation(ns, owner.UserSpec, defaultTimeoutInterval).Should(Succeed()) NamespaceIsPartOfTenant(t1, ns).Should(Succeed()) randomName, _ := randomTenantReference() patch := []byte(fmt.Sprintf( `{"metadata":{"labels":{"%s":"%s"}}}`, meta.TenantLabel, randomName, )) _, err := cs.CoreV1().Namespaces().Patch(context.TODO(), ns.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}) Expect(err).ToNot(HaveOccurred()) expectOriginalTenantOwnership(ns.Name, tenant) } }) It("Owners can patch managed namespaces but combined ownership changes should be reverted", func() { tenant := getTenant(t1.Name) for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) ns := NewNamespace("", map[string]string{ meta.TenantLabel: tenant.GetName(), }) NamespaceCreation(ns, owner.UserSpec, defaultTimeoutInterval).Should(Succeed()) NamespaceIsPartOfTenant(t1, ns).Should(Succeed()) randomName, randomUID := randomTenantReference() patch := []byte(fmt.Sprintf( `{"metadata":{"labels":{"%s":"%s"},"ownerReferences":[{"apiVersion":"%s","kind":"Tenant","name":"%s","uid":"%s"}]}}`, meta.TenantLabel, randomName, capsulev1beta2.GroupVersion.String(), randomName, randomUID, )) _, err := cs.CoreV1().Namespaces().Patch(context.TODO(), ns.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}) Expect(err).ToNot(HaveOccurred()) retrievedNs := getNamespace(ns.Name) Expect(retrievedNs.Labels).To(HaveKeyWithValue(meta.TenantLabel, tenant.GetName())) Expect(hasTenantOwnerReferenceByNameAndUID(retrievedNs, randomName, randomUID)).To(BeFalse()) Expect(hasTenantOwnerReference(retrievedNs, tenant)).To(BeTrue()) } }) It("Administrators can migrate managed namespaces to another Tenant", func() { tenantA := getTenant(t1.Name) tenantB := getTenant(t2.Name) ns := NewNamespace("", map[string]string{ meta.TenantLabel: tenantA.GetName(), }) NamespaceCreation(ns, t1.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) NamespaceIsPartOfTenant(t1, ns).Should(Succeed()) DeferCleanup(func() { EventuallyDeletion(ns) }) ref, err := GetTenantOwnerReferenceAsPatch(tenantB) Expect(err).NotTo(HaveOccurred()) patch := map[string]interface{}{ "metadata": map[string]interface{}{ "labels": map[string]interface{}{ meta.TenantLabel: tenantB.GetName(), }, "ownerReferences": []map[string]interface{}{ref}, }, } Eventually(func() error { return PatchNamespace(ns, ownerClient(administrator), patch) }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) Eventually(func(g Gomega) { current := getNamespace(ns.Name) g.Expect(current.Labels).To(HaveKeyWithValue(meta.TenantLabel, tenantB.GetName())) g.Expect(tenantOwnerReferences(current)).To(Equal([]string{tenantB.GetName()})) g.Expect(hasTenantOwnerReference(current, tenantB)).To(BeTrue()) g.Expect(hasTenantOwnerReference(current, tenantA)).To(BeFalse()) }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) NamespaceIsNotPartOfTenant(t1, ns).Should(Succeed()) NamespaceIsPartOfTenant(t2, ns).Should(Succeed()) }) It("Administrators can remove complete Tenant ownership while partial removal is reverted", func() { tenant := getTenant(t1.Name) ns := NewNamespace("", map[string]string{ meta.TenantLabel: tenant.GetName(), }) NamespaceCreation(ns, t1.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) NamespaceIsPartOfTenant(t1, ns).Should(Succeed()) DeferCleanup(func() { EventuallyDeletion(ns) }) adminClient := ownerClient(administrator) Eventually(func() error { _, err := adminClient.CoreV1().Namespaces().Patch( context.TODO(), ns.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"e2e.capsule.clastix.io/admin":"true"}}}`), metav1.PatchOptions{}, ) return err }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) Eventually(func() error { _, err := adminClient.CoreV1().Namespaces().Patch( context.TODO(), ns.Name, types.MergePatchType, []byte(fmt.Sprintf(`{"metadata":{"labels":{"%s":null}}}`, meta.TenantLabel)), metav1.PatchOptions{}, ) return err }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) expectOriginalTenantOwnership(ns.Name, tenant) patch := map[string]interface{}{ "metadata": map[string]interface{}{ "labels": map[string]interface{}{ meta.TenantLabel: nil, }, "ownerReferences": []interface{}{}, }, } Expect(PatchNamespace(ns, adminClient, patch)).To(Succeed()) ExpectNamespaceNotAssignedToTenant(context.TODO(), ns.Name) NamespaceIsNotPartOfTenant(t1, ns).Should(Succeed()) }) It("Owners can not migrate managed namespaces to another Tenant", func() { tenantA := getTenant(t1.Name) tenantB := getTenant(t2.Name) for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) ns := NewNamespace("", map[string]string{ meta.TenantLabel: tenantA.GetName(), }) NamespaceCreation(ns, owner.UserSpec, defaultTimeoutInterval).Should(Succeed()) NamespaceIsPartOfTenant(t1, ns).Should(Succeed()) patch := []byte(fmt.Sprintf( `{"metadata":{"labels":{"%s":"%s"},"ownerReferences":[{"apiVersion":"%s","kind":"Tenant","name":"%s","uid":"%s"}]}}`, meta.TenantLabel, tenantB.GetName(), capsulev1beta2.GroupVersion.String(), tenantB.GetName(), tenantB.GetUID(), )) _, err := cs.CoreV1().Namespaces().Patch(context.TODO(), ns.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}) Expect(err).ToNot(HaveOccurred()) retrievedNs := getNamespace(ns.Name) Expect(retrievedNs.Labels).To(HaveKeyWithValue(meta.TenantLabel, tenantA.GetName())) Expect(hasTenantOwnerReference(retrievedNs, tenantA)).To(BeTrue()) Expect(hasTenantOwnerReference(retrievedNs, tenantB)).To(BeFalse()) } }) It("Owners can not remove tenant ownership from managed namespaces", func() { tenant := getTenant(t1.Name) for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) ns := NewNamespace("", map[string]string{ meta.TenantLabel: tenant.GetName(), }) NamespaceCreation(ns, owner.UserSpec, defaultTimeoutInterval).Should(Succeed()) NamespaceIsPartOfTenant(t1, ns).Should(Succeed()) patchRemoveOwnerReferences := []byte(`{"metadata":{"ownerReferences":[]}}`) _, err := cs.CoreV1().Namespaces().Patch(context.TODO(), ns.Name, types.StrategicMergePatchType, patchRemoveOwnerReferences, metav1.PatchOptions{}) Expect(err).ToNot(HaveOccurred()) expectOriginalTenantOwnership(ns.Name, tenant) patchRemoveTenantLabel := []byte(fmt.Sprintf( `{"metadata":{"labels":{"%s":null}}}`, meta.TenantLabel, )) _, err = cs.CoreV1().Namespaces().Patch(context.TODO(), ns.Name, types.StrategicMergePatchType, patchRemoveTenantLabel, metav1.PatchOptions{}) Expect(err).ToNot(HaveOccurred()) expectOriginalTenantOwnership(ns.Name, tenant) patchRemoveOwnership := map[string]interface{}{ "metadata": map[string]interface{}{ "labels": map[string]interface{}{ meta.TenantLabel: nil, }, "ownerReferences": []interface{}{}, }, } Expect(PatchNamespace(ns, cs, patchRemoveOwnership)).ToNot(Succeed()) expectOriginalTenantOwnership(ns.Name, tenant) } }) It("Owners can not patch unmanaged namespaces into a Tenant", func() { tenant := getTenant(t1.Name) unmanaged := createUnmanagedNamespace() for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) patchLabel := []byte(fmt.Sprintf( `{"metadata":{"labels":{"%s":"%s"}}}`, meta.TenantLabel, tenant.GetName(), )) _, err := cs.CoreV1().Namespaces().Patch(context.TODO(), unmanaged.Name, types.StrategicMergePatchType, patchLabel, metav1.PatchOptions{}) Expect(err).To(HaveOccurred()) patchOwnerReference := []byte(fmt.Sprintf( `{"metadata":{"ownerReferences":[{"apiVersion":"%s","kind":"Tenant","name":"%s","uid":"%s"}]}}`, capsulev1beta2.GroupVersion.String(), tenant.GetName(), tenant.GetUID(), )) _, err = cs.CoreV1().Namespaces().Patch(context.TODO(), unmanaged.Name, types.StrategicMergePatchType, patchOwnerReference, metav1.PatchOptions{}) Expect(err).To(HaveOccurred()) expectNoTenantOwnership(unmanaged.Name, tenant) } }) It("Namespace status updates by owners can not change tenant ownerReferences", func() { tenant := getTenant(t1.Name) createNamespaceStatusRBACForOwner(tenant) DeferCleanup(func(tnt *capsulev1beta2.Tenant) { deleteNamespaceStatusRBACForOwner(tnt) }, tenant) for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) ns := NewNamespace("", map[string]string{ meta.TenantLabel: tenant.GetName(), }) NamespaceCreation(ns, owner.UserSpec, defaultTimeoutInterval).Should(Succeed()) NamespaceIsPartOfTenant(t1, ns).Should(Succeed()) randomName, randomUID := randomTenantReference() statusNs, err := cs.CoreV1().Namespaces().Get(context.TODO(), ns.Name, metav1.GetOptions{}) Expect(err).ToNot(HaveOccurred()) statusNs.OwnerReferences = []metav1.OwnerReference{ { APIVersion: capsulev1beta2.GroupVersion.String(), Kind: "Tenant", Name: randomName, UID: randomUID, }, } _, err = cs.CoreV1().Namespaces().UpdateStatus(context.TODO(), statusNs, metav1.UpdateOptions{}) if err != nil { expectOriginalTenantOwnership(ns.Name, tenant) continue } retrievedNs := getNamespace(ns.Name) Expect(hasTenantOwnerReferenceByNameAndUID(retrievedNs, randomName, randomUID)).To(BeFalse(), "Namespace status update must not change Tenant ownerReference") Expect(hasTenantOwnerReference(retrievedNs, tenant)).To(BeTrue(), "Namespace should keep original Tenant ownerReference") } }) It("Namespace status updates by owners can not change tenant labels", func() { tenant := getTenant(t1.Name) createNamespaceStatusRBACForOwner(tenant) DeferCleanup(func(tnt *capsulev1beta2.Tenant) { deleteNamespaceStatusRBACForOwner(tnt) }, tenant) for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) ns := NewNamespace("", map[string]string{ meta.TenantLabel: tenant.GetName(), }) NamespaceCreation(ns, owner.UserSpec, defaultTimeoutInterval).Should(Succeed()) NamespaceIsPartOfTenant(t1, ns).Should(Succeed()) randomName, _ := randomTenantReference() statusNs, err := cs.CoreV1().Namespaces().Get(context.TODO(), ns.Name, metav1.GetOptions{}) Expect(err).ToNot(HaveOccurred()) if statusNs.Labels == nil { statusNs.Labels = map[string]string{} } statusNs.Labels[meta.TenantLabel] = randomName _, err = cs.CoreV1().Namespaces().UpdateStatus(context.TODO(), statusNs, metav1.UpdateOptions{}) if err != nil { expectOriginalTenantOwnership(ns.Name, tenant) continue } expectOriginalTenantOwnership(ns.Name, tenant) } }) It("Namespace status updates by owners can not migrate namespaces to another Tenant", func() { tenantA := getTenant(t1.Name) tenantB := getTenant(t2.Name) createNamespaceStatusRBACForOwner(tenantA) DeferCleanup(func(tnt *capsulev1beta2.Tenant) { deleteNamespaceStatusRBACForOwner(tnt) }, tenantA) createNamespaceStatusRBACForOwner(tenantB) DeferCleanup(func(tnt *capsulev1beta2.Tenant) { deleteNamespaceStatusRBACForOwner(tnt) }, tenantB) for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) ns := NewNamespace("", map[string]string{ meta.TenantLabel: tenantA.GetName(), }) NamespaceCreation(ns, owner.UserSpec, defaultTimeoutInterval).Should(Succeed()) NamespaceIsPartOfTenant(t1, ns).Should(Succeed()) statusNs, err := cs.CoreV1().Namespaces().Get(context.TODO(), ns.Name, metav1.GetOptions{}) Expect(err).ToNot(HaveOccurred()) if statusNs.Labels == nil { statusNs.Labels = map[string]string{} } statusNs.Labels[meta.TenantLabel] = tenantB.GetName() statusNs.OwnerReferences = []metav1.OwnerReference{ { APIVersion: capsulev1beta2.GroupVersion.String(), Kind: "Tenant", Name: tenantB.GetName(), UID: tenantB.GetUID(), }, } _, err = cs.CoreV1().Namespaces().UpdateStatus(context.TODO(), statusNs, metav1.UpdateOptions{}) if err != nil { retrievedNs := getNamespace(ns.Name) Expect(retrievedNs.Labels).To(HaveKeyWithValue(meta.TenantLabel, tenantA.GetName())) Expect(hasTenantOwnerReference(retrievedNs, tenantA)).To(BeTrue()) Expect(hasTenantOwnerReference(retrievedNs, tenantB)).To(BeFalse()) continue } retrievedNs := getNamespace(ns.Name) Expect(retrievedNs.Labels).To(HaveKeyWithValue(meta.TenantLabel, tenantA.GetName())) Expect(hasTenantOwnerReference(retrievedNs, tenantA)).To(BeTrue()) Expect(hasTenantOwnerReference(retrievedNs, tenantB)).To(BeFalse()) } }) It("Namespace status updates by owners can not patch unmanaged namespaces into a Tenant", func() { tenant := getTenant(t1.Name) createNamespaceStatusRBACForOwner(tenant) DeferCleanup(func(tnt *capsulev1beta2.Tenant) { deleteNamespaceStatusRBACForOwner(tnt) }, tenant) unmanaged := createUnmanagedNamespace() for _, owner := range t1.Spec.Owners { cs := ownerClient(owner.UserSpec) statusNs, err := cs.CoreV1().Namespaces().Get(context.TODO(), unmanaged.Name, metav1.GetOptions{}) Expect(err).ToNot(HaveOccurred()) if statusNs.Labels == nil { statusNs.Labels = map[string]string{} } statusNs.Labels[meta.TenantLabel] = tenant.GetName() statusNs.OwnerReferences = []metav1.OwnerReference{ { APIVersion: capsulev1beta2.GroupVersion.String(), Kind: "Tenant", Name: tenant.GetName(), UID: tenant.GetUID(), }, } _, err = cs.CoreV1().Namespaces().UpdateStatus(context.TODO(), statusNs, metav1.UpdateOptions{}) if err != nil { expectNoTenantOwnership(unmanaged.Name, tenant) continue } expectNoTenantOwnership(unmanaged.Name, tenant) } Expect(k8sClient.Delete(context.TODO(), unmanaged)).To(Succeed()) Eventually(func() bool { err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: unmanaged.Name}, &corev1.Namespace{}) return apierrors.IsNotFound(err) }, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue()) }) }) func createNamespaceStatusRBACForOwner(tnt *capsulev1beta2.Tenant) { name := "namespace-status-patch-" + tnt.GetName() clusterRole := &rbacv1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{ Name: name, }, Rules: []rbacv1.PolicyRule{ { APIGroups: []string{""}, Resources: []string{ "namespaces", "namespaces/status", }, Verbs: []string{ "get", "patch", "update", }, }, }, } err := k8sClient.Create(context.TODO(), clusterRole) if err != nil && !apierrors.IsAlreadyExists(err) { Expect(err).NotTo(HaveOccurred()) } clusterRoleBinding := &rbacv1.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: name, }, RoleRef: rbacv1.RoleRef{ APIGroup: rbacv1.GroupName, Kind: "ClusterRole", Name: name, }, } for _, sub := range tnt.Spec.Owners { if sub.Kind != rbac.ServiceAccountOwner { clusterRoleBinding.Subjects = append(clusterRoleBinding.Subjects, rbacv1.Subject{ Kind: string(sub.Kind), Name: sub.Name, }) } else { namespace, name, err := serviceaccount.SplitUsername(sub.Name) Expect(err).NotTo(HaveOccurred()) clusterRoleBinding.Subjects = append(clusterRoleBinding.Subjects, rbacv1.Subject{ Kind: string(sub.Kind), Name: name, Namespace: namespace, }) } } err = k8sClient.Create(context.TODO(), clusterRoleBinding) if err != nil && !apierrors.IsAlreadyExists(err) { Expect(err).NotTo(HaveOccurred()) } } func deleteNamespaceStatusRBACForOwner(tnt *capsulev1beta2.Tenant) { name := "namespace-status-patch-" + tnt.GetName() err := k8sClient.Delete(context.TODO(), &rbacv1.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: name, }, }) if err != nil && !apierrors.IsNotFound(err) { Expect(err).NotTo(HaveOccurred()) } err = k8sClient.Delete(context.TODO(), &rbacv1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{ Name: name, }, }) if err != nil && !apierrors.IsNotFound(err) { Expect(err).NotTo(HaveOccurred()) } } func tenantOwnerReferences(ns *corev1.Namespace) []string { var refs []string for _, ref := range ns.GetOwnerReferences() { if tenant.IsTenantOwnerReference(ref) { refs = append(refs, ref.Name) } } sort.Strings(refs) return refs }