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  }