k8s.io/kubernetes@v1.29.3/test/e2e/framework/auth/helpers.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package auth 18 19 import ( 20 "context" 21 "fmt" 22 "sync" 23 "time" 24 25 authorizationv1 "k8s.io/api/authorization/v1" 26 rbacv1 "k8s.io/api/rbac/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/runtime/schema" 29 "k8s.io/apimachinery/pkg/util/wait" 30 v1authorization "k8s.io/client-go/kubernetes/typed/authorization/v1" 31 v1rbac "k8s.io/client-go/kubernetes/typed/rbac/v1" 32 "k8s.io/kubernetes/test/e2e/framework" 33 ) 34 35 const ( 36 policyCachePollInterval = 100 * time.Millisecond 37 policyCachePollTimeout = 5 * time.Second 38 ) 39 40 type bindingsGetter interface { 41 v1rbac.RoleBindingsGetter 42 v1rbac.ClusterRoleBindingsGetter 43 v1rbac.ClusterRolesGetter 44 } 45 46 // WaitForAuthorizationUpdate checks if the given user can perform the named verb and action. 47 // If policyCachePollTimeout is reached without the expected condition matching, an error is returned 48 func WaitForAuthorizationUpdate(ctx context.Context, c v1authorization.SubjectAccessReviewsGetter, user, namespace, verb string, resource schema.GroupResource, allowed bool) error { 49 return WaitForNamedAuthorizationUpdate(ctx, c, user, namespace, verb, "", resource, allowed) 50 } 51 52 // WaitForNamedAuthorizationUpdate checks if the given user can perform the named verb and action on the named resource. 53 // If policyCachePollTimeout is reached without the expected condition matching, an error is returned 54 func WaitForNamedAuthorizationUpdate(ctx context.Context, c v1authorization.SubjectAccessReviewsGetter, user, namespace, verb, resourceName string, resource schema.GroupResource, allowed bool) error { 55 review := &authorizationv1.SubjectAccessReview{ 56 Spec: authorizationv1.SubjectAccessReviewSpec{ 57 ResourceAttributes: &authorizationv1.ResourceAttributes{ 58 Group: resource.Group, 59 Verb: verb, 60 Resource: resource.Resource, 61 Namespace: namespace, 62 Name: resourceName, 63 }, 64 User: user, 65 }, 66 } 67 68 err := wait.PollWithContext(ctx, policyCachePollInterval, policyCachePollTimeout, func(ctx context.Context) (bool, error) { 69 response, err := c.SubjectAccessReviews().Create(ctx, review, metav1.CreateOptions{}) 70 if err != nil { 71 return false, err 72 } 73 if response.Status.Allowed != allowed { 74 return false, nil 75 } 76 return true, nil 77 }) 78 return err 79 } 80 81 // BindClusterRole binds the cluster role at the cluster scope. If RBAC is not enabled, nil 82 // is returned with no action. 83 func BindClusterRole(ctx context.Context, c bindingsGetter, clusterRole, ns string, subjects ...rbacv1.Subject) error { 84 if !IsRBACEnabled(ctx, c) { 85 return nil 86 } 87 88 // Since the namespace names are unique, we can leave this lying around so we don't have to race any caches 89 _, err := c.ClusterRoleBindings().Create(ctx, &rbacv1.ClusterRoleBinding{ 90 ObjectMeta: metav1.ObjectMeta{ 91 Name: ns + "--" + clusterRole, 92 }, 93 RoleRef: rbacv1.RoleRef{ 94 APIGroup: "rbac.authorization.k8s.io", 95 Kind: "ClusterRole", 96 Name: clusterRole, 97 }, 98 Subjects: subjects, 99 }, metav1.CreateOptions{}) 100 101 if err != nil { 102 return fmt.Errorf("binding clusterrole/%s for %q for %v: %w", clusterRole, ns, subjects, err) 103 } 104 105 return nil 106 } 107 108 // BindClusterRoleInNamespace binds the cluster role at the namespace scope. If RBAC is not enabled, nil 109 // is returned with no action. 110 func BindClusterRoleInNamespace(ctx context.Context, c bindingsGetter, clusterRole, ns string, subjects ...rbacv1.Subject) error { 111 return bindInNamespace(ctx, c, "ClusterRole", clusterRole, ns, subjects...) 112 } 113 114 // BindRoleInNamespace binds the role at the namespace scope. If RBAC is not enabled, nil 115 // is returned with no action. 116 func BindRoleInNamespace(ctx context.Context, c bindingsGetter, role, ns string, subjects ...rbacv1.Subject) error { 117 return bindInNamespace(ctx, c, "Role", role, ns, subjects...) 118 } 119 120 func bindInNamespace(ctx context.Context, c bindingsGetter, roleType, role, ns string, subjects ...rbacv1.Subject) error { 121 if !IsRBACEnabled(ctx, c) { 122 return nil 123 } 124 125 // Since the namespace names are unique, we can leave this lying around so we don't have to race any caches 126 _, err := c.RoleBindings(ns).Create(ctx, &rbacv1.RoleBinding{ 127 ObjectMeta: metav1.ObjectMeta{ 128 Name: ns + "--" + role, 129 }, 130 RoleRef: rbacv1.RoleRef{ 131 APIGroup: "rbac.authorization.k8s.io", 132 Kind: roleType, 133 Name: role, 134 }, 135 Subjects: subjects, 136 }, metav1.CreateOptions{}) 137 138 if err != nil { 139 return fmt.Errorf("binding %s/%s into %q for %v: %w", roleType, role, ns, subjects, err) 140 } 141 142 return nil 143 } 144 145 var ( 146 isRBACEnabledOnce sync.Once 147 isRBACEnabled bool 148 ) 149 150 // IsRBACEnabled returns true if RBAC is enabled. Otherwise false. 151 func IsRBACEnabled(ctx context.Context, crGetter v1rbac.ClusterRolesGetter) bool { 152 isRBACEnabledOnce.Do(func() { 153 crs, err := crGetter.ClusterRoles().List(ctx, metav1.ListOptions{}) 154 if err != nil { 155 framework.Logf("Error listing ClusterRoles; assuming RBAC is disabled: %v", err) 156 isRBACEnabled = false 157 } else if crs == nil || len(crs.Items) == 0 { 158 framework.Logf("No ClusterRoles found; assuming RBAC is disabled.") 159 isRBACEnabled = false 160 } else { 161 framework.Logf("Found ClusterRoles; assuming RBAC is enabled.") 162 isRBACEnabled = true 163 } 164 }) 165 166 return isRBACEnabled 167 }