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