diff --git a/e2e/additional_role_bindings_test.go b/e2e/additional_role_bindings_test.go index 6d9c1f4b..60437188 100644 --- a/e2e/additional_role_bindings_test.go +++ b/e2e/additional_role_bindings_test.go @@ -1,4 +1,5 @@ -//+build e2e +//go:build e2e +// +build e2e // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 @@ -35,7 +36,7 @@ var _ = Describe("creating a Namespace with an additional Role Binding", func() Subjects: []rbacv1.Subject{ { Kind: "Group", - APIGroup: "rbac.authorization.k8s.io", + APIGroup: rbacv1.GroupName, Name: "system:authenticated", }, }, @@ -64,7 +65,7 @@ var _ = Describe("creating a Namespace with an additional Role Binding", func() Eventually(func() (err error) { cs := ownerClient(tnt.Spec.Owners[0]) - rb, err = cs.RbacV1().RoleBindings(ns.Name).Get(context.Background(), fmt.Sprintf("capsule-%s-0-%s", tnt.Name, "crds-rolebinding"), metav1.GetOptions{}) + rb, err = cs.RbacV1().RoleBindings(ns.Name).Get(context.Background(), fmt.Sprintf("capsule-%s-2-%s", tnt.Name, "crds-rolebinding"), metav1.GetOptions{}) return err }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) Expect(rb.RoleRef.Name).Should(Equal(tnt.Spec.AdditionalRoleBindings[0].ClusterRoleName)) diff --git a/e2e/dynamic_tenant_owner_clusterroles_test.go b/e2e/dynamic_tenant_owner_clusterroles_test.go new file mode 100644 index 00000000..b7338875 --- /dev/null +++ b/e2e/dynamic_tenant_owner_clusterroles_test.go @@ -0,0 +1,64 @@ +//go:build e2e +// +build e2e + +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "context" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" +) + +var _ = Describe("defining dynamic Tenant Owner Cluster Roles", func() { + tnt := &capsulev1beta1.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dynamic-tenant-owner-clusterroles", + Annotations: map[string]string{ + "clusterrolenames.capsule.clastix.io/user.michonne": "editor,manager", + "clusterrolenames.capsule.clastix.io/group.kingdom": "readonly", + }, + }, + Spec: capsulev1beta1.TenantSpec{ + Owners: capsulev1beta1.OwnerListSpec{ + { + Name: "michonne", + Kind: "User", + }, + { + Name: "kingdom", + Kind: "Group", + }, + }, + }, + } + + JustBeforeEach(func() { + EventuallyCreation(func() error { + tnt.ResourceVersion = "" + + return k8sClient.Create(context.TODO(), tnt) + }).Should(Succeed()) + }) + + JustAfterEach(func() { + Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed()) + }) + + It("namespace should contains the dynamic rolebindings", func() { + for _, ns := range []string{"dynamnic-roles-1", "dynamnic-roles-2", "dynamnic-roles-3"} { + ns := NewNamespace(ns) + NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + + Eventually(CheckForOwnerRoleBindings(ns, tnt.Spec.Owners[0], map[string]bool{"editor": false, "manager": false}), defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) + Eventually(CheckForOwnerRoleBindings(ns, tnt.Spec.Owners[1], map[string]bool{"readonly": false}), defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) + } + }) +}) diff --git a/e2e/new_namespace_test.go b/e2e/new_namespace_test.go index a23a721f..df526e0f 100644 --- a/e2e/new_namespace_test.go +++ b/e2e/new_namespace_test.go @@ -1,4 +1,5 @@ -//+build e2e +//go:build e2e +// +build e2e // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 @@ -7,7 +8,6 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -48,28 +48,31 @@ var _ = Describe("creating a Namespaces as different type of Tenant owners", fun Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed()) }) - It("should be available in Tenant namespaces list and rolebindigs should present when created as User", func() { + It("should be available in Tenant namespaces list and RoleBindings should be present when created", func() { ns := NewNamespace("new-namespace-user") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElements(ns.GetName())) - for _, a := range KindInTenantRoleBindingAssertions(ns, defaultTimeoutInterval) { - a.Should(ContainElements("User", "Group", "ServiceAccount")) + + for _, owner := range tnt.Spec.Owners { + Eventually(CheckForOwnerRoleBindings(ns, owner, nil), defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) } }) - It("should be available in Tenant namespaces list and rolebindigs should present when created as Group", func() { + It("should be available in Tenant namespaces list and RoleBindings should present when created as Group", func() { ns := NewNamespace("new-namespace-group") NamespaceCreation(ns, tnt.Spec.Owners[1], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElements(ns.GetName())) - for _, a := range KindInTenantRoleBindingAssertions(ns, defaultTimeoutInterval) { - a.Should(ContainElements("User", "Group", "ServiceAccount")) + + for _, owner := range tnt.Spec.Owners { + Eventually(CheckForOwnerRoleBindings(ns, owner, nil), defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) } }) - It("should be available in Tenant namespaces list and rolebindigs should present when created as ServiceAccount", func() { + It("should be available in Tenant namespaces list and RoleBindings should present when created as ServiceAccount", func() { ns := NewNamespace("new-namespace-sa") NamespaceCreation(ns, tnt.Spec.Owners[2], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElements(ns.GetName())) - for _, a := range KindInTenantRoleBindingAssertions(ns, defaultTimeoutInterval) { - a.Should(ContainElements("User", "Group", "ServiceAccount")) + + for _, owner := range tnt.Spec.Owners { + Eventually(CheckForOwnerRoleBindings(ns, owner, nil), defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) } }) }) diff --git a/e2e/node_user_metadata_test.go b/e2e/node_user_metadata_test.go index 8ee2fdc3..ac3ca1df 100644 --- a/e2e/node_user_metadata_test.go +++ b/e2e/node_user_metadata_test.go @@ -9,15 +9,15 @@ package e2e import ( "context" + "fmt" capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/webhook/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/clastix/capsule/pkg/webhook/utils" - "fmt" ) var _ = Describe("modifying node labels and annotations", func() { @@ -55,12 +55,12 @@ var _ = Describe("modifying node labels and annotations", func() { RoleRef: rbacv1.RoleRef{ Kind: "ClusterRole", Name: "node-modifier", - APIGroup: "rbac.authorization.k8s.io", + APIGroup: rbacv1.GroupName, }, Subjects: []rbacv1.Subject{ { Kind: rbacv1.UserKind, - APIGroup: "rbac.authorization.k8s.io", + APIGroup: rbacv1.GroupName, Name: "gatsby", }, }, diff --git a/e2e/utils_test.go b/e2e/utils_test.go index cb599d8e..8251ecb2 100644 --- a/e2e/utils_test.go +++ b/e2e/utils_test.go @@ -1,4 +1,5 @@ -//+build e2e +//go:build e2e +// +build e2e // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 @@ -7,6 +8,9 @@ package e2e import ( "context" + "fmt" + "sigs.k8s.io/controller-runtime/pkg/client" + "strings" "time" . "github.com/onsi/gomega" @@ -73,21 +77,54 @@ func ModifyCapsuleConfigurationOpts(fn func(configuration *capsulev1alpha1.Capsu time.Sleep(1 * time.Second) } -func KindInTenantRoleBindingAssertions(ns *corev1.Namespace, timeout time.Duration) (out []AsyncAssertion) { - for _, rbn := range tenantRoleBindingNames { - rb := &rbacv1.RoleBinding{} - out = append(out, Eventually(func() []string { - if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: rbn, Namespace: ns.GetName()}, rb); err != nil { - return nil +func CheckForOwnerRoleBindings(ns *corev1.Namespace, owner capsulev1beta1.OwnerSpec, roles map[string]bool) func() error { + if roles == nil { + roles = map[string]bool{ + "admin": false, + "capsule-namespace-deleter": false, + } + } + + return func() (err error) { + roleBindings := &rbacv1.RoleBindingList{} + + if err = k8sClient.List(context.Background(), roleBindings, client.InNamespace(ns.GetName())); err != nil { + return fmt.Errorf("cannot retrieve list of rolebindings: %w", err) + } + + var ownerName string + + if owner.Kind == capsulev1beta1.ServiceAccountOwner { + parts := strings.Split(owner.Name, ":") + + ownerName = parts[3] + } else { + ownerName = owner.Name + } + + for _, roleBinding := range roleBindings.Items { + _, ok := roles[roleBinding.RoleRef.Name] + if !ok { + continue } - var subjects []string - for _, subject := range rb.Subjects { - subjects = append(subjects, subject.Kind) + + subject := roleBinding.Subjects[0] + + if subject.Name != ownerName { + continue } - return subjects - }, timeout, defaultPollInterval)) + + roles[roleBinding.RoleRef.Name] = true + } + + for role, found := range roles { + if !found { + return fmt.Errorf("role %s for %s.%s has not been reconciled", role, owner.Kind.String(), owner.Name) + } + } + + return nil } - return } func GetKubernetesVersion() *versionUtil.Version { @@ -105,6 +142,5 @@ func GetKubernetesVersion() *versionUtil.Version { ver, err = versionUtil.ParseGeneric(serverVersion.String()) Expect(err).ToNot(HaveOccurred()) - return ver }