k8s.io/kubernetes@v1.29.3/test/integration/auth/rbac_test.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  	"io"
    23  	"net/http"
    24  	"net/http/httputil"
    25  	gopath "path"
    26  	"reflect"
    27  	"strings"
    28  	"testing"
    29  	"time"
    30  
    31  	rbacapi "k8s.io/api/rbac/v1"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/runtime/schema"
    34  	"k8s.io/apimachinery/pkg/types"
    35  	"k8s.io/apimachinery/pkg/watch"
    36  	"k8s.io/apiserver/pkg/authentication/group"
    37  	"k8s.io/apiserver/pkg/authentication/request/bearertoken"
    38  	unionauthn "k8s.io/apiserver/pkg/authentication/request/union"
    39  	"k8s.io/apiserver/pkg/authentication/token/tokenfile"
    40  	"k8s.io/apiserver/pkg/authentication/user"
    41  	"k8s.io/apiserver/pkg/authorization/authorizer"
    42  	unionauthz "k8s.io/apiserver/pkg/authorization/union"
    43  	"k8s.io/apiserver/pkg/registry/generic"
    44  	clientset "k8s.io/client-go/kubernetes"
    45  	restclient "k8s.io/client-go/rest"
    46  	watchtools "k8s.io/client-go/tools/watch"
    47  	"k8s.io/client-go/transport"
    48  	"k8s.io/klog/v2"
    49  	"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
    50  	rbachelper "k8s.io/kubernetes/pkg/apis/rbac/v1"
    51  	"k8s.io/kubernetes/pkg/controlplane"
    52  	"k8s.io/kubernetes/pkg/registry/rbac/clusterrole"
    53  	clusterrolestore "k8s.io/kubernetes/pkg/registry/rbac/clusterrole/storage"
    54  	"k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding"
    55  	clusterrolebindingstore "k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding/storage"
    56  	"k8s.io/kubernetes/pkg/registry/rbac/role"
    57  	rolestore "k8s.io/kubernetes/pkg/registry/rbac/role/storage"
    58  	"k8s.io/kubernetes/pkg/registry/rbac/rolebinding"
    59  	rolebindingstore "k8s.io/kubernetes/pkg/registry/rbac/rolebinding/storage"
    60  	"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac"
    61  	"k8s.io/kubernetes/test/integration/framework"
    62  	"k8s.io/kubernetes/test/utils/ktesting"
    63  )
    64  
    65  func clientForToken(user string, rt http.RoundTripper) *http.Client {
    66  	return &http.Client{
    67  		Transport: transport.NewBearerAuthRoundTripper(
    68  			user,
    69  			transport.DebugWrappers(rt),
    70  		),
    71  	}
    72  }
    73  
    74  func clientsetForToken(user string, config *restclient.Config) (clientset.Interface, clientset.Interface) {
    75  	configCopy := *config
    76  	configCopy.BearerToken = user
    77  	return clientset.NewForConfigOrDie(&configCopy), clientset.NewForConfigOrDie(&configCopy)
    78  }
    79  
    80  type testRESTOptionsGetter struct {
    81  	config *controlplane.Config
    82  }
    83  
    84  func (getter *testRESTOptionsGetter) GetRESTOptions(resource schema.GroupResource) (generic.RESTOptions, error) {
    85  	storageConfig, err := getter.config.ExtraConfig.StorageFactory.NewConfig(resource)
    86  	if err != nil {
    87  		return generic.RESTOptions{}, fmt.Errorf("failed to get storage: %v", err)
    88  	}
    89  	return generic.RESTOptions{StorageConfig: storageConfig, Decorator: generic.UndecoratedStorage, ResourcePrefix: resource.Resource}, nil
    90  }
    91  
    92  func newRBACAuthorizer(t *testing.T, config *controlplane.Config) (authorizer.Authorizer, func()) {
    93  	optsGetter := &testRESTOptionsGetter{config}
    94  	roleRest, err := rolestore.NewREST(optsGetter)
    95  	if err != nil {
    96  		t.Fatalf("unexpected error from REST storage: %v", err)
    97  	}
    98  	roleRegistry := role.AuthorizerAdapter{Registry: role.NewRegistry(roleRest)}
    99  	rolebindingRest, err := rolebindingstore.NewREST(optsGetter)
   100  	if err != nil {
   101  		t.Fatalf("unexpected error from REST storage: %v", err)
   102  	}
   103  	roleBindingRegistry := rolebinding.AuthorizerAdapter{Registry: rolebinding.NewRegistry(rolebindingRest)}
   104  	clusterroleRest, err := clusterrolestore.NewREST(optsGetter)
   105  	if err != nil {
   106  		t.Fatalf("unexpected error from REST storage: %v", err)
   107  	}
   108  	clusterRoleRegistry := clusterrole.AuthorizerAdapter{Registry: clusterrole.NewRegistry(clusterroleRest)}
   109  	clusterrolebindingRest, err := clusterrolebindingstore.NewREST(optsGetter)
   110  	if err != nil {
   111  		t.Fatalf("unexpected error from REST storage: %v", err)
   112  	}
   113  	clusterRoleBindingRegistry := clusterrolebinding.AuthorizerAdapter{Registry: clusterrolebinding.NewRegistry(clusterrolebindingRest)}
   114  
   115  	tearDownFn := func() {
   116  		roleRest.Destroy()
   117  		rolebindingRest.Destroy()
   118  		clusterroleRest.Destroy()
   119  		clusterrolebindingRest.Destroy()
   120  	}
   121  	return rbac.New(roleRegistry, roleBindingRegistry, clusterRoleRegistry, clusterRoleBindingRegistry), tearDownFn
   122  }
   123  
   124  // bootstrapRoles are a set of RBAC roles which will be populated before the test.
   125  type bootstrapRoles struct {
   126  	roles               []rbacapi.Role
   127  	roleBindings        []rbacapi.RoleBinding
   128  	clusterRoles        []rbacapi.ClusterRole
   129  	clusterRoleBindings []rbacapi.ClusterRoleBinding
   130  }
   131  
   132  // bootstrap uses the provided client to create the bootstrap roles and role bindings.
   133  //
   134  // client should be authenticated as the RBAC super user.
   135  func (b bootstrapRoles) bootstrap(client clientset.Interface) error {
   136  	for _, r := range b.clusterRoles {
   137  		_, err := client.RbacV1().ClusterRoles().Create(context.TODO(), &r, metav1.CreateOptions{})
   138  		if err != nil {
   139  			return fmt.Errorf("failed to make request: %v", err)
   140  		}
   141  	}
   142  	for _, r := range b.roles {
   143  		_, err := client.RbacV1().Roles(r.Namespace).Create(context.TODO(), &r, metav1.CreateOptions{})
   144  		if err != nil {
   145  			return fmt.Errorf("failed to make request: %v", err)
   146  		}
   147  	}
   148  	for _, r := range b.clusterRoleBindings {
   149  		_, err := client.RbacV1().ClusterRoleBindings().Create(context.TODO(), &r, metav1.CreateOptions{})
   150  		if err != nil {
   151  			return fmt.Errorf("failed to make request: %v", err)
   152  		}
   153  	}
   154  	for _, r := range b.roleBindings {
   155  		_, err := client.RbacV1().RoleBindings(r.Namespace).Create(context.TODO(), &r, metav1.CreateOptions{})
   156  		if err != nil {
   157  			return fmt.Errorf("failed to make request: %v", err)
   158  		}
   159  	}
   160  
   161  	return nil
   162  }
   163  
   164  // request is a test case which can.
   165  type request struct {
   166  	// The bearer token sent as part of the request
   167  	token string
   168  
   169  	// Resource metadata
   170  	verb      string
   171  	apiGroup  string
   172  	resource  string
   173  	namespace string
   174  	name      string
   175  
   176  	// The actual resource.
   177  	body string
   178  
   179  	// The expected return status of this request.
   180  	expectedStatus int
   181  }
   182  
   183  func (r request) String() string {
   184  	return fmt.Sprintf("%s %s %s", r.token, r.verb, r.resource)
   185  }
   186  
   187  type statusCode int
   188  
   189  func (s statusCode) String() string {
   190  	return fmt.Sprintf("%d %s", int(s), http.StatusText(int(s)))
   191  }
   192  
   193  // Declare a set of raw objects to use.
   194  var (
   195  	writeJobsRoleBinding = `
   196  {
   197    "apiVersion": "rbac.authorization.k8s.io/v1",
   198    "kind": "RoleBinding",
   199    "metadata": {
   200      "name": "pi"%s
   201    },
   202    "roleRef": {
   203      "apiGroup": "rbac.authorization.k8s.io",
   204      "kind": "ClusterRole",
   205      "name": "write-jobs"
   206    },
   207    "subjects": [{
   208      "apiGroup": "rbac.authorization.k8s.io",
   209      "kind": "User",
   210      "name": "admin"
   211    }]
   212  }`
   213  
   214  	aJob = `
   215  {
   216    "apiVersion": "batch/v1",
   217    "kind": "Job",
   218    "metadata": {
   219      "name": "pi"%s
   220    },
   221    "spec": {
   222      "template": {
   223        "metadata": {
   224          "name": "a",
   225          "labels": {
   226            "name": "pijob"
   227          }
   228        },
   229        "spec": {
   230          "containers": [
   231            {
   232              "name": "pi",
   233              "image": "perl",
   234              "command": [
   235                "perl",
   236                "-Mbignum=bpi",
   237                "-wle",
   238                "print bpi(2000)"
   239              ]
   240            }
   241          ],
   242          "restartPolicy": "Never"
   243        }
   244      }
   245    }
   246  }
   247  `
   248  	aLimitRange = `
   249  {
   250    "apiVersion": "v1",
   251    "kind": "LimitRange",
   252    "metadata": {
   253      "name": "a"%s
   254    }
   255  }
   256  `
   257  	podNamespace = `
   258  {
   259    "apiVersion": "v1",
   260    "kind": "Namespace",
   261    "metadata": {
   262  	"name": "pod-namespace"%s
   263    }
   264  }
   265  `
   266  	jobNamespace = `
   267  {
   268    "apiVersion": "v1",
   269    "kind": "Namespace",
   270    "metadata": {
   271  	"name": "job-namespace"%s
   272    }
   273  }
   274  `
   275  	forbiddenNamespace = `
   276  {
   277    "apiVersion": "v1",
   278    "kind": "Namespace",
   279    "metadata": {
   280  	"name": "forbidden-namespace"%s
   281    }
   282  }
   283  `
   284  	limitRangeNamespace = `
   285  {
   286    "apiVersion": "v1",
   287    "kind": "Namespace",
   288    "metadata": {
   289  	"name": "limitrange-namespace"%s
   290    }
   291  }
   292  `
   293  )
   294  
   295  // Declare some PolicyRules beforehand.
   296  var (
   297  	ruleAllowAll  = rbachelper.NewRule("*").Groups("*").Resources("*").RuleOrDie()
   298  	ruleReadPods  = rbachelper.NewRule("list", "get", "watch").Groups("").Resources("pods").RuleOrDie()
   299  	ruleWriteJobs = rbachelper.NewRule("*").Groups("batch").Resources("*").RuleOrDie()
   300  )
   301  
   302  func TestRBAC(t *testing.T) {
   303  	superUser := "admin/system:masters"
   304  
   305  	tests := []struct {
   306  		bootstrapRoles bootstrapRoles
   307  
   308  		requests []request
   309  	}{
   310  		{
   311  			bootstrapRoles: bootstrapRoles{
   312  				clusterRoles: []rbacapi.ClusterRole{
   313  					{
   314  						ObjectMeta: metav1.ObjectMeta{Name: "allow-all"},
   315  						Rules:      []rbacapi.PolicyRule{ruleAllowAll},
   316  					},
   317  					{
   318  						ObjectMeta: metav1.ObjectMeta{Name: "read-pods"},
   319  						Rules:      []rbacapi.PolicyRule{ruleReadPods},
   320  					},
   321  				},
   322  				clusterRoleBindings: []rbacapi.ClusterRoleBinding{
   323  					{
   324  						ObjectMeta: metav1.ObjectMeta{Name: "read-pods"},
   325  						Subjects: []rbacapi.Subject{
   326  							{Kind: "User", Name: "pod-reader"},
   327  						},
   328  						RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "read-pods"},
   329  					},
   330  				},
   331  			},
   332  			requests: []request{
   333  				// Create the namespace used later in the test
   334  				{superUser, "POST", "", "namespaces", "", "", podNamespace, http.StatusCreated},
   335  
   336  				{superUser, "GET", "", "pods", "", "", "", http.StatusOK},
   337  				{superUser, "GET", "", "pods", "pod-namespace", "a", "", http.StatusNotFound},
   338  				{superUser, "POST", "", "pods", "pod-namespace", "", aPod, http.StatusCreated},
   339  				{superUser, "GET", "", "pods", "pod-namespace", "a", "", http.StatusOK},
   340  
   341  				{"bob", "GET", "", "pods", "", "", "", http.StatusForbidden},
   342  				{"bob", "GET", "", "pods", "pod-namespace", "a", "", http.StatusForbidden},
   343  
   344  				{"pod-reader", "GET", "", "pods", "", "", "", http.StatusOK},
   345  				{"pod-reader", "POST", "", "pods", "pod-namespace", "", aPod, http.StatusForbidden},
   346  			},
   347  		},
   348  		{
   349  			bootstrapRoles: bootstrapRoles{
   350  				clusterRoles: []rbacapi.ClusterRole{
   351  					{
   352  						ObjectMeta: metav1.ObjectMeta{Name: "write-jobs"},
   353  						Rules:      []rbacapi.PolicyRule{ruleWriteJobs},
   354  					},
   355  					{
   356  						ObjectMeta: metav1.ObjectMeta{Name: "create-rolebindings"},
   357  						Rules: []rbacapi.PolicyRule{
   358  							rbachelper.NewRule("create").Groups("rbac.authorization.k8s.io").Resources("rolebindings").RuleOrDie(),
   359  						},
   360  					},
   361  					{
   362  						ObjectMeta: metav1.ObjectMeta{Name: "bind-any-clusterrole"},
   363  						Rules: []rbacapi.PolicyRule{
   364  							rbachelper.NewRule("bind").Groups("rbac.authorization.k8s.io").Resources("clusterroles").RuleOrDie(),
   365  						},
   366  					},
   367  				},
   368  				clusterRoleBindings: []rbacapi.ClusterRoleBinding{
   369  					{
   370  						ObjectMeta: metav1.ObjectMeta{Name: "write-jobs"},
   371  						Subjects:   []rbacapi.Subject{{Kind: "User", Name: "job-writer"}},
   372  						RoleRef:    rbacapi.RoleRef{Kind: "ClusterRole", Name: "write-jobs"},
   373  					},
   374  					{
   375  						ObjectMeta: metav1.ObjectMeta{Name: "create-rolebindings"},
   376  						Subjects: []rbacapi.Subject{
   377  							{Kind: "User", Name: "job-writer"},
   378  							{Kind: "User", Name: "nonescalating-rolebinding-writer"},
   379  							{Kind: "User", Name: "any-rolebinding-writer"},
   380  						},
   381  						RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "create-rolebindings"},
   382  					},
   383  					{
   384  						ObjectMeta: metav1.ObjectMeta{Name: "bind-any-clusterrole"},
   385  						Subjects:   []rbacapi.Subject{{Kind: "User", Name: "any-rolebinding-writer"}},
   386  						RoleRef:    rbacapi.RoleRef{Kind: "ClusterRole", Name: "bind-any-clusterrole"},
   387  					},
   388  				},
   389  				roleBindings: []rbacapi.RoleBinding{
   390  					{
   391  						ObjectMeta: metav1.ObjectMeta{Name: "write-jobs", Namespace: "job-namespace"},
   392  						Subjects:   []rbacapi.Subject{{Kind: "User", Name: "job-writer-namespace"}},
   393  						RoleRef:    rbacapi.RoleRef{Kind: "ClusterRole", Name: "write-jobs"},
   394  					},
   395  					{
   396  						ObjectMeta: metav1.ObjectMeta{Name: "create-rolebindings", Namespace: "job-namespace"},
   397  						Subjects: []rbacapi.Subject{
   398  							{Kind: "User", Name: "job-writer-namespace"},
   399  							{Kind: "User", Name: "any-rolebinding-writer-namespace"},
   400  						},
   401  						RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "create-rolebindings"},
   402  					},
   403  					{
   404  						ObjectMeta: metav1.ObjectMeta{Name: "bind-any-clusterrole", Namespace: "job-namespace"},
   405  						Subjects:   []rbacapi.Subject{{Kind: "User", Name: "any-rolebinding-writer-namespace"}},
   406  						RoleRef:    rbacapi.RoleRef{Kind: "ClusterRole", Name: "bind-any-clusterrole"},
   407  					},
   408  				},
   409  			},
   410  			requests: []request{
   411  				// Create the namespace used later in the test
   412  				{superUser, "POST", "", "namespaces", "", "", jobNamespace, http.StatusCreated},
   413  				{superUser, "POST", "", "namespaces", "", "", forbiddenNamespace, http.StatusCreated},
   414  
   415  				{"user-with-no-permissions", "POST", "batch", "jobs", "job-namespace", "", aJob, http.StatusForbidden},
   416  				{"user-with-no-permissions", "GET", "batch", "jobs", "job-namespace", "pi", "", http.StatusForbidden},
   417  
   418  				// job-writer-namespace cannot write to the "forbidden-namespace"
   419  				{"job-writer-namespace", "GET", "batch", "jobs", "forbidden-namespace", "", "", http.StatusForbidden},
   420  				{"job-writer-namespace", "GET", "batch", "jobs", "forbidden-namespace", "pi", "", http.StatusForbidden},
   421  				{"job-writer-namespace", "POST", "batch", "jobs", "forbidden-namespace", "", aJob, http.StatusForbidden},
   422  				{"job-writer-namespace", "GET", "batch", "jobs", "forbidden-namespace", "pi", "", http.StatusForbidden},
   423  
   424  				// job-writer can write to any namespace
   425  				{"job-writer", "GET", "batch", "jobs", "forbidden-namespace", "", "", http.StatusOK},
   426  				{"job-writer", "GET", "batch", "jobs", "forbidden-namespace", "pi", "", http.StatusNotFound},
   427  				{"job-writer", "POST", "batch", "jobs", "forbidden-namespace", "", aJob, http.StatusCreated},
   428  				{"job-writer", "GET", "batch", "jobs", "forbidden-namespace", "pi", "", http.StatusOK},
   429  
   430  				{"job-writer-namespace", "GET", "batch", "jobs", "job-namespace", "", "", http.StatusOK},
   431  				{"job-writer-namespace", "GET", "batch", "jobs", "job-namespace", "pi", "", http.StatusNotFound},
   432  				{"job-writer-namespace", "POST", "batch", "jobs", "job-namespace", "", aJob, http.StatusCreated},
   433  				{"job-writer-namespace", "GET", "batch", "jobs", "job-namespace", "pi", "", http.StatusOK},
   434  
   435  				// cannot bind role anywhere
   436  				{"user-with-no-permissions", "POST", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "", writeJobsRoleBinding, http.StatusForbidden},
   437  				// can only bind role in namespace where they have explicit bind permission
   438  				{"any-rolebinding-writer-namespace", "POST", "rbac.authorization.k8s.io", "rolebindings", "forbidden-namespace", "", writeJobsRoleBinding, http.StatusForbidden},
   439  				// can only bind role in namespace where they have covering permissions
   440  				{"job-writer-namespace", "POST", "rbac.authorization.k8s.io", "rolebindings", "forbidden-namespace", "", writeJobsRoleBinding, http.StatusForbidden},
   441  				{"job-writer-namespace", "POST", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "", writeJobsRoleBinding, http.StatusCreated},
   442  				{superUser, "DELETE", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "pi", "", http.StatusOK},
   443  				// can bind role in any namespace where they have covering permissions
   444  				{"job-writer", "POST", "rbac.authorization.k8s.io", "rolebindings", "forbidden-namespace", "", writeJobsRoleBinding, http.StatusCreated},
   445  				{superUser, "DELETE", "rbac.authorization.k8s.io", "rolebindings", "forbidden-namespace", "pi", "", http.StatusOK},
   446  				// cannot bind role because they don't have covering permissions
   447  				{"nonescalating-rolebinding-writer", "POST", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "", writeJobsRoleBinding, http.StatusForbidden},
   448  				// can bind role because they have explicit bind permission
   449  				{"any-rolebinding-writer", "POST", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "", writeJobsRoleBinding, http.StatusCreated},
   450  				{superUser, "DELETE", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "pi", "", http.StatusOK},
   451  				{"any-rolebinding-writer-namespace", "POST", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "", writeJobsRoleBinding, http.StatusCreated},
   452  				{superUser, "DELETE", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "pi", "", http.StatusOK},
   453  			},
   454  		},
   455  		{
   456  			bootstrapRoles: bootstrapRoles{
   457  				clusterRoles: []rbacapi.ClusterRole{
   458  					{
   459  						ObjectMeta: metav1.ObjectMeta{Name: "allow-all"},
   460  						Rules:      []rbacapi.PolicyRule{ruleAllowAll},
   461  					},
   462  					{
   463  						ObjectMeta: metav1.ObjectMeta{Name: "update-limitranges"},
   464  						Rules: []rbacapi.PolicyRule{
   465  							rbachelper.NewRule("update").Groups("").Resources("limitranges").RuleOrDie(),
   466  						},
   467  					},
   468  				},
   469  				clusterRoleBindings: []rbacapi.ClusterRoleBinding{
   470  					{
   471  						ObjectMeta: metav1.ObjectMeta{Name: "update-limitranges"},
   472  						Subjects: []rbacapi.Subject{
   473  							{Kind: "User", Name: "limitrange-updater"},
   474  						},
   475  						RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "update-limitranges"},
   476  					},
   477  				},
   478  			},
   479  			requests: []request{
   480  				// Create the namespace used later in the test
   481  				{superUser, "POST", "", "namespaces", "", "", limitRangeNamespace, http.StatusCreated},
   482  
   483  				{"limitrange-updater", "PUT", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusForbidden},
   484  				{superUser, "PUT", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusCreated},
   485  				{superUser, "PUT", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK},
   486  				{"limitrange-updater", "PUT", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK},
   487  			},
   488  		},
   489  		{
   490  			bootstrapRoles: bootstrapRoles{
   491  				clusterRoles: []rbacapi.ClusterRole{
   492  					{
   493  						ObjectMeta: metav1.ObjectMeta{Name: "allow-all"},
   494  						Rules:      []rbacapi.PolicyRule{ruleAllowAll},
   495  					},
   496  					{
   497  						ObjectMeta: metav1.ObjectMeta{Name: "patch-limitranges"},
   498  						Rules: []rbacapi.PolicyRule{
   499  							rbachelper.NewRule("patch").Groups("").Resources("limitranges").RuleOrDie(),
   500  						},
   501  					},
   502  				},
   503  				clusterRoleBindings: []rbacapi.ClusterRoleBinding{
   504  					{
   505  						ObjectMeta: metav1.ObjectMeta{Name: "patch-limitranges"},
   506  						Subjects: []rbacapi.Subject{
   507  							{Kind: "User", Name: "limitrange-patcher"},
   508  						},
   509  						RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "patch-limitranges"},
   510  					},
   511  				},
   512  			},
   513  			requests: []request{
   514  				// Create the namespace used later in the test
   515  				{superUser, "POST", "", "namespaces", "", "", limitRangeNamespace, http.StatusCreated},
   516  
   517  				{"limitrange-patcher", "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusForbidden},
   518  				{superUser, "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusCreated},
   519  				{superUser, "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK},
   520  				{"limitrange-patcher", "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK},
   521  			},
   522  		},
   523  	}
   524  
   525  	for i, tc := range tests {
   526  		t.Run(fmt.Sprintf("case-%d", i), func(t *testing.T) {
   527  			authenticator := group.NewAuthenticatedGroupAdder(bearertoken.New(tokenfile.New(map[string]*user.DefaultInfo{
   528  				superUser:                          {Name: "admin", Groups: []string{"system:masters"}},
   529  				"any-rolebinding-writer":           {Name: "any-rolebinding-writer"},
   530  				"any-rolebinding-writer-namespace": {Name: "any-rolebinding-writer-namespace"},
   531  				"bob":                              {Name: "bob"},
   532  				"job-writer":                       {Name: "job-writer"},
   533  				"job-writer-namespace":             {Name: "job-writer-namespace"},
   534  				"nonescalating-rolebinding-writer": {Name: "nonescalating-rolebinding-writer"},
   535  				"pod-reader":                       {Name: "pod-reader"},
   536  				"limitrange-updater":               {Name: "limitrange-updater"},
   537  				"limitrange-patcher":               {Name: "limitrange-patcher"},
   538  				"user-with-no-permissions":         {Name: "user-with-no-permissions"},
   539  			})))
   540  
   541  			_, ctx := ktesting.NewTestContext(t)
   542  			ctx, cancel := context.WithCancel(ctx)
   543  			defer cancel()
   544  
   545  			var tearDownAuthorizerFn func()
   546  			defer func() {
   547  				if tearDownAuthorizerFn != nil {
   548  					tearDownAuthorizerFn()
   549  				}
   550  			}()
   551  
   552  			_, kubeConfig, tearDownFn := framework.StartTestServer(ctx, t, framework.TestServerSetup{
   553  				ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
   554  					// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
   555  					// Also disable namespace lifecycle to workaroung the test limitation that first creates
   556  					// roles/rolebindings and only then creates corresponding namespaces.
   557  					opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount", "NamespaceLifecycle"}
   558  					// Disable built-in authorizers
   559  					opts.Authorization.Modes = []string{"AlwaysDeny"}
   560  				},
   561  				ModifyServerConfig: func(config *controlplane.Config) {
   562  					// Append our custom test authenticator
   563  					config.GenericConfig.Authentication.Authenticator = unionauthn.New(config.GenericConfig.Authentication.Authenticator, authenticator)
   564  					// Append our custom test authorizer
   565  					var rbacAuthz authorizer.Authorizer
   566  					rbacAuthz, tearDownAuthorizerFn = newRBACAuthorizer(t, config)
   567  					config.GenericConfig.Authorization.Authorizer = unionauthz.New(config.GenericConfig.Authorization.Authorizer, rbacAuthz)
   568  				},
   569  			})
   570  			defer tearDownFn()
   571  
   572  			transport, err := restclient.TransportFor(kubeConfig)
   573  			if err != nil {
   574  				t.Fatal(err)
   575  			}
   576  
   577  			// Bootstrap the API Server with the test case's initial roles.
   578  			superuserClient, _ := clientsetForToken(superUser, kubeConfig)
   579  			if err := tc.bootstrapRoles.bootstrap(superuserClient); err != nil {
   580  				t.Errorf("case %d: failed to apply initial roles: %v", i, err)
   581  				return
   582  			}
   583  			previousResourceVersion := make(map[string]float64)
   584  
   585  			for j, r := range tc.requests {
   586  				path := "/"
   587  				if r.apiGroup == "" {
   588  					path = gopath.Join(path, "api/v1")
   589  				} else {
   590  					path = gopath.Join(path, "apis", r.apiGroup, "v1")
   591  				}
   592  				if r.namespace != "" {
   593  					path = gopath.Join(path, "namespaces", r.namespace)
   594  				}
   595  				if r.resource != "" {
   596  					path = gopath.Join(path, r.resource)
   597  				}
   598  				if r.name != "" {
   599  					path = gopath.Join(path, r.name)
   600  				}
   601  
   602  				var body io.Reader
   603  				if r.body != "" {
   604  					sub := ""
   605  					if r.verb == "PUT" {
   606  						// For update operations, insert previous resource version
   607  						if resVersion := previousResourceVersion[getPreviousResourceVersionKey(path, "")]; resVersion != 0 {
   608  							sub += fmt.Sprintf(",\"resourceVersion\": \"%v\"", resVersion)
   609  						}
   610  					}
   611  					body = strings.NewReader(fmt.Sprintf(r.body, sub))
   612  				}
   613  
   614  				req, err := http.NewRequest(r.verb, kubeConfig.Host+path, body)
   615  				if r.verb == "PATCH" {
   616  					// For patch operations, use the apply content type
   617  					req.Header.Add("Content-Type", string(types.ApplyPatchType))
   618  					q := req.URL.Query()
   619  					q.Add("fieldManager", "rbac_test")
   620  					req.URL.RawQuery = q.Encode()
   621  				}
   622  
   623  				if err != nil {
   624  					t.Fatalf("failed to create request: %v", err)
   625  				}
   626  
   627  				func() {
   628  					reqDump, err := httputil.DumpRequest(req, true)
   629  					if err != nil {
   630  						t.Fatalf("failed to dump request: %v", err)
   631  						return
   632  					}
   633  
   634  					resp, err := clientForToken(r.token, transport).Do(req)
   635  					if err != nil {
   636  						t.Errorf("case %d, req %d: failed to make request: %v", i, j, err)
   637  						return
   638  					}
   639  					defer resp.Body.Close()
   640  
   641  					respDump, err := httputil.DumpResponse(resp, true)
   642  					if err != nil {
   643  						t.Fatalf("failed to dump response: %v", err)
   644  						return
   645  					}
   646  
   647  					if resp.StatusCode != r.expectedStatus {
   648  						// When debugging is on, dump the entire request and response. Very helpful for
   649  						// debugging malformed test cases.
   650  						//
   651  						// To turn on debugging, use the '-args' flag.
   652  						//
   653  						//    go test -v -tags integration -run RBAC -args -v 10
   654  						//
   655  						klog.V(8).Infof("case %d, req %d: %s\n%s\n", i, j, reqDump, respDump)
   656  						t.Errorf("case %d, req %d: %s expected %q got %q", i, j, r, statusCode(r.expectedStatus), statusCode(resp.StatusCode))
   657  					}
   658  
   659  					b, _ := io.ReadAll(resp.Body)
   660  
   661  					if r.verb == "POST" && (resp.StatusCode/100) == 2 {
   662  						// For successful create operations, extract resourceVersion
   663  						id, currentResourceVersion, err := parseResourceVersion(b)
   664  						if err == nil {
   665  							key := getPreviousResourceVersionKey(path, id)
   666  							previousResourceVersion[key] = currentResourceVersion
   667  						} else {
   668  							t.Logf("error in trying to extract resource version: %s", err)
   669  						}
   670  					}
   671  				}()
   672  			}
   673  		})
   674  	}
   675  }
   676  
   677  func TestBootstrapping(t *testing.T) {
   678  	_, ctx := ktesting.NewTestContext(t)
   679  	ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
   680  	defer cancel()
   681  
   682  	clientset, _, tearDownFn := framework.StartTestServer(ctx, t, framework.TestServerSetup{
   683  		ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
   684  			opts.Authorization.Modes = []string{"RBAC"}
   685  		},
   686  	})
   687  	defer tearDownFn()
   688  
   689  	watcher, err := clientset.RbacV1().ClusterRoles().Watch(ctx, metav1.ListOptions{ResourceVersion: "0"})
   690  	if err != nil {
   691  		t.Fatalf("unexpected error: %v", err)
   692  	}
   693  
   694  	_, err = watchtools.UntilWithoutRetry(ctx, watcher, func(event watch.Event) (bool, error) {
   695  		if event.Type != watch.Added {
   696  			return false, nil
   697  		}
   698  		return true, nil
   699  	})
   700  	if err != nil {
   701  		t.Fatalf("unexpected error: %v", err)
   702  	}
   703  
   704  	clusterRoles, err := clientset.RbacV1().ClusterRoles().List(ctx, metav1.ListOptions{})
   705  	if err != nil {
   706  		t.Fatalf("unexpected error: %v", err)
   707  	}
   708  	if len(clusterRoles.Items) == 0 {
   709  		t.Fatalf("missing cluster roles")
   710  	}
   711  
   712  	for _, clusterRole := range clusterRoles.Items {
   713  		if clusterRole.Name == "cluster-admin" {
   714  			return
   715  		}
   716  	}
   717  
   718  	t.Errorf("missing cluster-admin: %v", clusterRoles)
   719  
   720  	healthBytes, err := clientset.Discovery().RESTClient().Get().AbsPath("/healthz/poststarthook/rbac/bootstrap-roles").DoRaw(ctx)
   721  	if err != nil {
   722  		t.Error(err)
   723  	}
   724  	t.Errorf("error bootstrapping roles: %s", string(healthBytes))
   725  }
   726  
   727  // TestDiscoveryUpgradeBootstrapping is primarily meant to test the behavior of
   728  // primePublicInfoClusterRoleBinding in storage_rbac.go during cluster upgrades.
   729  func TestDiscoveryUpgradeBootstrapping(t *testing.T) {
   730  	var tearDownFn func()
   731  	defer func() {
   732  		if tearDownFn != nil {
   733  			tearDownFn()
   734  		}
   735  	}()
   736  
   737  	etcdConfig := framework.SharedEtcd()
   738  
   739  	_, ctx := ktesting.NewTestContext(t)
   740  	ctx, cancel := context.WithCancel(ctx)
   741  	defer cancel()
   742  
   743  	client, _, tearDownFn := framework.StartTestServer(ctx, t, framework.TestServerSetup{
   744  		ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
   745  			// Ensure we're using the same etcd across apiserver restarts.
   746  			opts.Etcd.StorageConfig = *etcdConfig
   747  			opts.Authorization.Modes = []string{"RBAC"}
   748  		},
   749  	})
   750  
   751  	// Modify the default RBAC discovery ClusterRoleBidnings to look more like the defaults that
   752  	// existed prior to v1.14, but with user modifications.
   753  	t.Logf("Modifying default `system:discovery` ClusterRoleBinding")
   754  	discRoleBinding, err := client.RbacV1().ClusterRoleBindings().Get(ctx, "system:discovery", metav1.GetOptions{})
   755  	if err != nil {
   756  		t.Fatalf("Failed to get `system:discovery` ClusterRoleBinding: %v", err)
   757  	}
   758  	discRoleBinding.Annotations["rbac.authorization.kubernetes.io/autoupdate"] = "false"
   759  	discRoleBinding.Annotations["rbac-discovery-upgrade-test"] = "pass"
   760  	discRoleBinding.Subjects = []rbacapi.Subject{
   761  		{
   762  			Name:     "system:authenticated",
   763  			Kind:     "Group",
   764  			APIGroup: "rbac.authorization.k8s.io",
   765  		},
   766  	}
   767  	if discRoleBinding, err = client.RbacV1().ClusterRoleBindings().Update(ctx, discRoleBinding, metav1.UpdateOptions{}); err != nil {
   768  		t.Fatalf("Failed to update `system:discovery` ClusterRoleBinding: %v", err)
   769  	}
   770  	t.Logf("Modifying default `system:basic-user` ClusterRoleBinding")
   771  	basicUserRoleBinding, err := client.RbacV1().ClusterRoleBindings().Get(ctx, "system:basic-user", metav1.GetOptions{})
   772  	if err != nil {
   773  		t.Fatalf("Failed to get `system:basic-user` ClusterRoleBinding: %v", err)
   774  	}
   775  	basicUserRoleBinding.Annotations["rbac.authorization.kubernetes.io/autoupdate"] = "false"
   776  	basicUserRoleBinding.Annotations["rbac-discovery-upgrade-test"] = "pass"
   777  	if basicUserRoleBinding, err = client.RbacV1().ClusterRoleBindings().Update(ctx, basicUserRoleBinding, metav1.UpdateOptions{}); err != nil {
   778  		t.Fatalf("Failed to update `system:basic-user` ClusterRoleBinding: %v", err)
   779  	}
   780  	t.Logf("Deleting default `system:public-info-viewer` ClusterRoleBinding")
   781  	if err = client.RbacV1().ClusterRoleBindings().Delete(ctx, "system:public-info-viewer", metav1.DeleteOptions{}); err != nil {
   782  		t.Fatalf("Failed to delete `system:public-info-viewer` ClusterRoleBinding: %v", err)
   783  	}
   784  
   785  	// Stop the first API server.
   786  	tearDownFn()
   787  	tearDownFn = nil
   788  
   789  	// Check that upgraded API servers inherit `system:public-info-viewer` settings from
   790  	// `system:discovery`, and respect auto-reconciliation annotations.
   791  	client, _, tearDownFn = framework.StartTestServer(ctx, t, framework.TestServerSetup{
   792  		ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
   793  			// Ensure we're using the same etcd across apiserver restarts.
   794  			opts.Etcd.StorageConfig = *etcdConfig
   795  			opts.Authorization.Modes = []string{"RBAC"}
   796  		},
   797  	})
   798  
   799  	newDiscRoleBinding, err := client.RbacV1().ClusterRoleBindings().Get(ctx, "system:discovery", metav1.GetOptions{})
   800  	if err != nil {
   801  		t.Fatalf("Failed to get `system:discovery` ClusterRoleBinding: %v", err)
   802  	}
   803  	if !reflect.DeepEqual(newDiscRoleBinding, discRoleBinding) {
   804  		t.Errorf("`system:discovery` should have been unmodified. Wanted: %v, got %v", discRoleBinding, newDiscRoleBinding)
   805  	}
   806  	newBasicUserRoleBinding, err := client.RbacV1().ClusterRoleBindings().Get(ctx, "system:basic-user", metav1.GetOptions{})
   807  	if err != nil {
   808  		t.Fatalf("Failed to get `system:basic-user` ClusterRoleBinding: %v", err)
   809  	}
   810  	if !reflect.DeepEqual(newBasicUserRoleBinding, basicUserRoleBinding) {
   811  		t.Errorf("`system:basic-user` should have been unmodified. Wanted: %v, got %v", basicUserRoleBinding, newBasicUserRoleBinding)
   812  	}
   813  	publicInfoViewerRoleBinding, err := client.RbacV1().ClusterRoleBindings().Get(ctx, "system:public-info-viewer", metav1.GetOptions{})
   814  	if err != nil {
   815  		t.Fatalf("Failed to get `system:public-info-viewer` ClusterRoleBinding: %v", err)
   816  	}
   817  	if publicInfoViewerRoleBinding.Annotations["rbac.authorization.kubernetes.io/autoupdate"] != "false" {
   818  		t.Errorf("publicInfoViewerRoleBinding.Annotations[\"rbac.authorization.kubernetes.io/autoupdate\"] should be %v, got %v", publicInfoViewerRoleBinding.Annotations["rbac.authorization.kubernetes.io/autoupdate"], "false")
   819  	}
   820  	if publicInfoViewerRoleBinding.Annotations["rbac-discovery-upgrade-test"] != "pass" {
   821  		t.Errorf("publicInfoViewerRoleBinding.Annotations[\"rbac-discovery-upgrade-test\"] should be %v, got %v", publicInfoViewerRoleBinding.Annotations["rbac-discovery-upgrade-test"], "pass")
   822  	}
   823  	if !reflect.DeepEqual(publicInfoViewerRoleBinding.Subjects, newDiscRoleBinding.Subjects) {
   824  		t.Errorf("`system:public-info-viewer` should have inherited Subjects from `system:discovery` Wanted: %v, got %v", newDiscRoleBinding.Subjects, publicInfoViewerRoleBinding.Subjects)
   825  	}
   826  }