// Copyright 2020-2023 Project Capsule Authors. // SPDX-License-Identifier: Apache-2.0 package e2e import ( "context" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" 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" "sigs.k8s.io/controller-runtime/pkg/client" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" "github.com/projectcapsule/capsule/pkg/api/meta" "github.com/projectcapsule/capsule/pkg/api/rbac" "github.com/projectcapsule/capsule/pkg/api/rules" "github.com/projectcapsule/capsule/pkg/utils" ) var _ = Describe("creating a Namespace with an additional Role Binding", Ordered, Label("tenant", "permissions", "rolebindings"), func() { tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "e2e-additional-role-binding", Labels: map[string]string{ "env": "e2e", }, }, Spec: capsulev1beta2.TenantSpec{ Owners: rbac.OwnerListSpec{ { CoreOwnerSpec: rbac.CoreOwnerSpec{ UserSpec: rbac.UserSpec{ Name: "e2e-additional-role-binding", Kind: "User", }, }, }, }, AdditionalRoleBindings: []rbac.AdditionalRoleBindingsSpec{ { ClusterRoleName: "view", Subjects: []rbacv1.Subject{ { Kind: "Group", APIGroup: rbacv1.GroupName, Name: "system:authenticated", }, }, }, }, }, } JustBeforeEach(func() { EventuallyCreation(func() error { tnt.ResourceVersion = "" return k8sClient.Create(context.TODO(), tnt) }).Should(Succeed()) TenantReady(tnt, metav1.ConditionTrue, defaultTimeoutInterval) }) JustAfterEach(func() { EventuallyDeletion(tnt) }) It("should be assigned to each Namespace", func() { ns1 := NewNamespace("", map[string]string{ meta.TenantLabel: tnt.GetName(), }) NamespaceCreation(ns1, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) ns2 := NewNamespace("", map[string]string{ meta.TenantLabel: tnt.GetName(), }) NamespaceCreation(ns2, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) t := &capsulev1beta2.Tenant{} Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.GetName()}, t)).Should(Succeed()) NamespaceIsPartOfTenant(tnt, ns1).Should(Succeed()) NamespaceIsPartOfTenant(tnt, ns2).Should(Succeed()) VerifyTenantRoleBindings(t) }) }) var _ = Describe("creating additional RoleBindings from namespace rules", Ordered, Label("tenant", "rules", "permissions", "rolebindings"), func() { const ( customLabel = "reflection.proxy.projectcapsule.dev/enabled" customAnnotation = "projectcapsule.dev/e2e-rule-binding" selectorLabel = "projectcapsule.dev/e2e-role-binding-environment" ) globalBinding := rbac.AdditionalRoleBindingsSpec{ ClusterRoleName: "view", Subjects: []rbacv1.Subject{ { Kind: rbacv1.GroupKind, APIGroup: rbacv1.GroupName, Name: "system:authenticated", }, }, Labels: map[string]string{customLabel: "true"}, Annotations: map[string]string{customAnnotation: "global"}, } selectedBinding := rbac.AdditionalRoleBindingsSpec{ ClusterRoleName: "edit", Subjects: []rbacv1.Subject{ { Kind: rbacv1.UserKind, APIGroup: rbacv1.GroupName, Name: "e2e-rule-role-binding-user", }, }, Labels: map[string]string{customLabel: "true"}, Annotations: map[string]string{customAnnotation: "selected"}, } tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{Name: "e2e-rule-role-bindings"}, Spec: capsulev1beta2.TenantSpec{ Owners: rbac.OwnerListSpec{ { CoreOwnerSpec: rbac.CoreOwnerSpec{ UserSpec: rbac.UserSpec{ Name: "e2e-rule-role-bindings", Kind: rbac.UserOwner, }, }, }, }, Rules: []*rules.NamespaceRuleBodyTenant{ { Permissions: rules.NamespaceRulePermissionBody{ Bindings: []rbac.AdditionalRoleBindingsSpec{globalBinding}, }, }, { NamespaceSelector: &metav1.LabelSelector{ MatchLabels: map[string]string{selectorLabel: "prod"}, }, Permissions: rules.NamespaceRulePermissionBody{ Bindings: []rbac.AdditionalRoleBindingsSpec{selectedBinding}, }, }, }, }, } JustBeforeEach(func() { EventuallyCreation(func() error { tnt.ResourceVersion = "" return k8sClient.Create(context.TODO(), tnt) }).Should(Succeed()) TenantReady(tnt, metav1.ConditionTrue, defaultTimeoutInterval) }) JustAfterEach(func() { EventuallyDeletion(tnt) }) It("applies bindings according to each rule's namespace selector without mutating their metadata", func() { prod := NewNamespace("", map[string]string{ meta.TenantLabel: tnt.GetName(), selectorLabel: "prod", }) NamespaceCreation(prod, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) dev := NewNamespace("", map[string]string{ meta.TenantLabel: tnt.GetName(), selectorLabel: "dev", }) NamespaceCreation(dev, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) NamespaceIsPartOfTenant(tnt, prod).Should(Succeed()) NamespaceIsPartOfTenant(tnt, dev).Should(Succeed()) assertBinding := func(namespace string, binding rbac.AdditionalRoleBindingsSpec) { Eventually(func(g Gomega) { roleBinding := &rbacv1.RoleBinding{} err := k8sClient.Get(context.Background(), client.ObjectKey{ Namespace: namespace, Name: meta.NameForManagedRoleBindings(utils.RoleBindingHashFunc(binding)), }, roleBinding) g.Expect(err).NotTo(HaveOccurred()) g.Expect(roleBinding.RoleRef.Name).To(Equal(binding.ClusterRoleName)) g.Expect(roleBinding.Subjects).To(ConsistOf(binding.Subjects)) g.Expect(roleBinding.Labels).To(HaveKeyWithValue(customLabel, "true")) g.Expect(roleBinding.Labels).To(HaveKeyWithValue(meta.NewTenantLabel, tnt.Name)) g.Expect(roleBinding.Labels).To(HaveKeyWithValue(meta.NewManagedByCapsuleLabel, meta.ValueController)) g.Expect(roleBinding.Annotations).To(HaveKeyWithValue(customAnnotation, binding.Annotations[customAnnotation])) }).WithTimeout(defaultTimeoutInterval).WithPolling(defaultPollInterval).Should(Succeed()) } assertBinding(prod.Name, globalBinding) assertBinding(dev.Name, globalBinding) assertBinding(prod.Name, selectedBinding) Consistently(func() bool { roleBinding := &rbacv1.RoleBinding{} err := k8sClient.Get(context.Background(), client.ObjectKey{ Namespace: dev.Name, Name: meta.NameForManagedRoleBindings(utils.RoleBindingHashFunc(selectedBinding)), }, roleBinding) return apierrors.IsNotFound(err) }, 2*time.Second, defaultPollInterval).Should(BeTrue()) current := &capsulev1beta2.Tenant{} Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: tnt.Name}, current)).To(Succeed()) Expect(current.Spec.Rules[0].Permissions.Bindings[0].Labels).To(Equal(globalBinding.Labels)) Expect(current.Spec.Rules[0].Permissions.Bindings[0].Annotations).To(Equal(globalBinding.Annotations)) Expect(current.Spec.Rules[1].Permissions.Bindings[0].Labels).To(Equal(selectedBinding.Labels)) Expect(current.Spec.Rules[1].Permissions.Bindings[0].Annotations).To(Equal(selectedBinding.Annotations)) }) })