From b55354a2f048f259e3196e212356c160b23cac9b Mon Sep 17 00:00:00 2001 From: Andrej Krejcir Date: Thu, 4 Jan 2024 17:34:07 +0100 Subject: [PATCH] feat: Add a Role that will be used by users This new Role "token.kubevirt.io:generate" will be installed in the cluster and can be bound to user accounts to give them access to the endpoint that generates tokens. Signed-off-by: Andrej Krejcir --- manifests/kustomization.yaml.in | 1 + manifests/user_role.yaml | 11 ++++ tests/user-role_test.go | 96 +++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 manifests/user_role.yaml create mode 100644 tests/user-role_test.go diff --git a/manifests/kustomization.yaml.in b/manifests/kustomization.yaml.in index 201b30d9..9fe52bfd 100644 --- a/manifests/kustomization.yaml.in +++ b/manifests/kustomization.yaml.in @@ -9,6 +9,7 @@ resources: - service.yaml - deployment.yaml - api_service.yaml + - user_role.yaml transformers: - transformer_namespace.yaml diff --git a/manifests/user_role.yaml b/manifests/user_role.yaml new file mode 100644 index 00000000..b05293e0 --- /dev/null +++ b/manifests/user_role.yaml @@ -0,0 +1,11 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: "token.kubevirt.io:generate" +rules: +- apiGroups: + - "token.kubevirt.io" + resources: + - "virtualmachines/vnc" + verbs: + - "get" diff --git a/tests/user-role_test.go b/tests/user-role_test.go new file mode 100644 index 00000000..83e7e683 --- /dev/null +++ b/tests/user-role_test.go @@ -0,0 +1,96 @@ +package tests + +import ( + "context" + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + + authzv1 "k8s.io/api/authorization/v1" + v1 "k8s.io/api/core/v1" + rbac "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + proxy "github.com/kubevirt/vm-console-proxy/api/v1alpha1" +) + +var _ = Describe("Role for token generation", func() { + const ( + clusterRoleName = "token.kubevirt.io:generate" + ) + + It("should exist", func() { + _, err := ApiClient.RbacV1().ClusterRoles().Get(context.TODO(), clusterRoleName, metav1.GetOptions{}) + Expect(err).ToNot(HaveOccurred()) + }) + + It("should be able to access token generation endpoint", func() { + sa := &v1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "token-generator-user", + Namespace: testNamespace, + }, + } + + sa, err := ApiClient.CoreV1().ServiceAccounts(testNamespace).Create(context.TODO(), sa, metav1.CreateOptions{}) + Expect(err).ToNot(HaveOccurred()) + DeferCleanup(func() { + err := ApiClient.CoreV1().ServiceAccounts(testNamespace).Delete(context.TODO(), sa.Name, metav1.DeleteOptions{}) + if err != nil && !errors.IsNotFound(err) { + Expect(err).ToNot(HaveOccurred()) + } + }) + + roleBinding := &rbac.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: sa.Name + "-role-binding", + Namespace: testNamespace, + }, + Subjects: []rbac.Subject{{ + Kind: "ServiceAccount", + Name: sa.Name, + Namespace: testNamespace, + }}, + RoleRef: rbac.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: clusterRoleName, + }, + } + + roleBinding, err = ApiClient.RbacV1().RoleBindings(testNamespace).Create(context.TODO(), roleBinding, metav1.CreateOptions{}) + Expect(err).ToNot(HaveOccurred()) + DeferCleanup(func() { + err := ApiClient.RbacV1().RoleBindings(testNamespace).Delete(context.TODO(), roleBinding.Name, metav1.DeleteOptions{}) + if err != nil && !errors.IsNotFound(err) { + Expect(err).ToNot(HaveOccurred()) + } + }) + + saUserName := fmt.Sprintf("system:serviceaccount:%s:%s", sa.GetNamespace(), sa.GetName()) + + subjectAccessReview := &authzv1.SubjectAccessReview{ + Spec: authzv1.SubjectAccessReviewSpec{ + ResourceAttributes: &authzv1.ResourceAttributes{ + Namespace: testNamespace, + Verb: "get", + Group: proxy.Group, + Version: proxy.Version, + Resource: "virtualmachines", + Subresource: "vnc", + }, + User: saUserName, + Groups: []string{"system:serviceaccounts"}, + }, + } + + subjectAccessReview, err = ApiClient.AuthorizationV1().SubjectAccessReviews().Create(context.TODO(), subjectAccessReview, metav1.CreateOptions{}) + Expect(err).ToNot(HaveOccurred()) + + Expect(subjectAccessReview.Status.Allowed).To(BeTrue(), + fmt.Sprintf("Access is not allowed: %s", subjectAccessReview.Status.Reason), + ) + }) +})