1/*
2Copyright 2016 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package rbac
18
19import (
20	"context"
21	"fmt"
22	"strings"
23	"testing"
24
25	rbacv1 "k8s.io/api/rbac/v1"
26	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27	"k8s.io/apiserver/pkg/authentication/user"
28	"k8s.io/apiserver/pkg/authorization/authorizer"
29	rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1"
30	rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
31	"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy"
32)
33
34func newRule(verbs, apiGroups, resources, nonResourceURLs string) rbacv1.PolicyRule {
35	return rbacv1.PolicyRule{
36		Verbs:           strings.Split(verbs, ","),
37		APIGroups:       strings.Split(apiGroups, ","),
38		Resources:       strings.Split(resources, ","),
39		NonResourceURLs: strings.Split(nonResourceURLs, ","),
40	}
41}
42
43func newRole(name, namespace string, rules ...rbacv1.PolicyRule) *rbacv1.Role {
44	return &rbacv1.Role{ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}, Rules: rules}
45}
46
47func newClusterRole(name string, rules ...rbacv1.PolicyRule) *rbacv1.ClusterRole {
48	return &rbacv1.ClusterRole{ObjectMeta: metav1.ObjectMeta{Name: name}, Rules: rules}
49}
50
51const (
52	bindToRole        uint16 = 0x0
53	bindToClusterRole uint16 = 0x1
54)
55
56func newClusterRoleBinding(roleName string, subjects ...string) *rbacv1.ClusterRoleBinding {
57	r := &rbacv1.ClusterRoleBinding{
58		ObjectMeta: metav1.ObjectMeta{},
59		RoleRef: rbacv1.RoleRef{
60			APIGroup: rbacv1.GroupName,
61			Kind:     "ClusterRole", // ClusterRoleBindings can only refer to ClusterRole
62			Name:     roleName,
63		},
64	}
65
66	r.Subjects = make([]rbacv1.Subject, len(subjects))
67	for i, subject := range subjects {
68		split := strings.SplitN(subject, ":", 2)
69		r.Subjects[i].Kind, r.Subjects[i].Name = split[0], split[1]
70
71		switch r.Subjects[i].Kind {
72		case rbacv1.ServiceAccountKind:
73			r.Subjects[i].APIGroup = ""
74		case rbacv1.UserKind, rbacv1.GroupKind:
75			r.Subjects[i].APIGroup = rbacv1.GroupName
76		default:
77			panic(fmt.Errorf("invalid kind %s", r.Subjects[i].Kind))
78		}
79	}
80	return r
81}
82
83func newRoleBinding(namespace, roleName string, bindType uint16, subjects ...string) *rbacv1.RoleBinding {
84	r := &rbacv1.RoleBinding{ObjectMeta: metav1.ObjectMeta{Namespace: namespace}}
85
86	switch bindType {
87	case bindToRole:
88		r.RoleRef = rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "Role", Name: roleName}
89	case bindToClusterRole:
90		r.RoleRef = rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "ClusterRole", Name: roleName}
91	}
92
93	r.Subjects = make([]rbacv1.Subject, len(subjects))
94	for i, subject := range subjects {
95		split := strings.SplitN(subject, ":", 2)
96		r.Subjects[i].Kind, r.Subjects[i].Name = split[0], split[1]
97
98		switch r.Subjects[i].Kind {
99		case rbacv1.ServiceAccountKind:
100			r.Subjects[i].APIGroup = ""
101		case rbacv1.UserKind, rbacv1.GroupKind:
102			r.Subjects[i].APIGroup = rbacv1.GroupName
103		default:
104			panic(fmt.Errorf("invalid kind %s", r.Subjects[i].Kind))
105		}
106	}
107	return r
108}
109
110type defaultAttributes struct {
111	user        string
112	groups      string
113	verb        string
114	resource    string
115	subresource string
116	namespace   string
117	apiGroup    string
118}
119
120func (d *defaultAttributes) String() string {
121	return fmt.Sprintf("user=(%s), groups=(%s), verb=(%s), resource=(%s), namespace=(%s), apiGroup=(%s)",
122		d.user, strings.Split(d.groups, ","), d.verb, d.resource, d.namespace, d.apiGroup)
123}
124
125func (d *defaultAttributes) GetUser() user.Info {
126	return &user.DefaultInfo{Name: d.user, Groups: strings.Split(d.groups, ",")}
127}
128func (d *defaultAttributes) GetVerb() string         { return d.verb }
129func (d *defaultAttributes) IsReadOnly() bool        { return d.verb == "get" || d.verb == "watch" }
130func (d *defaultAttributes) GetNamespace() string    { return d.namespace }
131func (d *defaultAttributes) GetResource() string     { return d.resource }
132func (d *defaultAttributes) GetSubresource() string  { return d.subresource }
133func (d *defaultAttributes) GetName() string         { return "" }
134func (d *defaultAttributes) GetAPIGroup() string     { return d.apiGroup }
135func (d *defaultAttributes) GetAPIVersion() string   { return "" }
136func (d *defaultAttributes) IsResourceRequest() bool { return true }
137func (d *defaultAttributes) GetPath() string         { return "" }
138
139func TestAuthorizer(t *testing.T) {
140	tests := []struct {
141		roles               []*rbacv1.Role
142		roleBindings        []*rbacv1.RoleBinding
143		clusterRoles        []*rbacv1.ClusterRole
144		clusterRoleBindings []*rbacv1.ClusterRoleBinding
145
146		shouldPass []authorizer.Attributes
147		shouldFail []authorizer.Attributes
148	}{
149		{
150			clusterRoles: []*rbacv1.ClusterRole{
151				newClusterRole("admin", newRule("*", "*", "*", "*")),
152			},
153			roleBindings: []*rbacv1.RoleBinding{
154				newRoleBinding("ns1", "admin", bindToClusterRole, "User:admin", "Group:admins"),
155			},
156			shouldPass: []authorizer.Attributes{
157				&defaultAttributes{"admin", "", "get", "Pods", "", "ns1", ""},
158				&defaultAttributes{"admin", "", "watch", "Pods", "", "ns1", ""},
159				&defaultAttributes{"admin", "group1", "watch", "Foobar", "", "ns1", ""},
160				&defaultAttributes{"joe", "admins", "watch", "Foobar", "", "ns1", ""},
161				&defaultAttributes{"joe", "group1,admins", "watch", "Foobar", "", "ns1", ""},
162			},
163			shouldFail: []authorizer.Attributes{
164				&defaultAttributes{"admin", "", "GET", "Pods", "", "ns2", ""},
165				&defaultAttributes{"admin", "", "GET", "Nodes", "", "", ""},
166				&defaultAttributes{"admin", "admins", "GET", "Pods", "", "ns2", ""},
167				&defaultAttributes{"admin", "admins", "GET", "Nodes", "", "", ""},
168			},
169		},
170		{
171			// Non-resource-url tests
172			clusterRoles: []*rbacv1.ClusterRole{
173				newClusterRole("non-resource-url-getter", newRule("get", "", "", "/apis")),
174				newClusterRole("non-resource-url", newRule("*", "", "", "/apis")),
175				newClusterRole("non-resource-url-prefix", newRule("get", "", "", "/apis/*")),
176			},
177			clusterRoleBindings: []*rbacv1.ClusterRoleBinding{
178				newClusterRoleBinding("non-resource-url-getter", "User:foo", "Group:bar"),
179				newClusterRoleBinding("non-resource-url", "User:admin", "Group:admin"),
180				newClusterRoleBinding("non-resource-url-prefix", "User:prefixed", "Group:prefixed"),
181			},
182			shouldPass: []authorizer.Attributes{
183				authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "foo"}, Verb: "get", Path: "/apis"},
184				authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"bar"}}, Verb: "get", Path: "/apis"},
185				authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "admin"}, Verb: "get", Path: "/apis"},
186				authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"admin"}}, Verb: "get", Path: "/apis"},
187				authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "admin"}, Verb: "watch", Path: "/apis"},
188				authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"admin"}}, Verb: "watch", Path: "/apis"},
189
190				authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "prefixed"}, Verb: "get", Path: "/apis/v1"},
191				authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"prefixed"}}, Verb: "get", Path: "/apis/v1"},
192				authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "prefixed"}, Verb: "get", Path: "/apis/v1/foobar"},
193				authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"prefixed"}}, Verb: "get", Path: "/apis/v1/foorbar"},
194			},
195			shouldFail: []authorizer.Attributes{
196				// wrong verb
197				authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "foo"}, Verb: "watch", Path: "/apis"},
198				authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"bar"}}, Verb: "watch", Path: "/apis"},
199
200				// wrong path
201				authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "foo"}, Verb: "get", Path: "/api/v1"},
202				authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"bar"}}, Verb: "get", Path: "/api/v1"},
203				authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "admin"}, Verb: "get", Path: "/api/v1"},
204				authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"admin"}}, Verb: "get", Path: "/api/v1"},
205
206				// not covered by prefix
207				authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "prefixed"}, Verb: "get", Path: "/api/v1"},
208				authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"prefixed"}}, Verb: "get", Path: "/api/v1"},
209			},
210		},
211		{
212			// test subresource resolution
213			clusterRoles: []*rbacv1.ClusterRole{
214				newClusterRole("admin", newRule("*", "*", "pods", "*")),
215			},
216			roleBindings: []*rbacv1.RoleBinding{
217				newRoleBinding("ns1", "admin", bindToClusterRole, "User:admin", "Group:admins"),
218			},
219			shouldPass: []authorizer.Attributes{
220				&defaultAttributes{"admin", "", "get", "pods", "", "ns1", ""},
221			},
222			shouldFail: []authorizer.Attributes{
223				&defaultAttributes{"admin", "", "get", "pods", "status", "ns1", ""},
224			},
225		},
226		{
227			// test subresource resolution
228			clusterRoles: []*rbacv1.ClusterRole{
229				newClusterRole("admin",
230					newRule("*", "*", "pods/status", "*"),
231					newRule("*", "*", "*/scale", "*"),
232				),
233			},
234			roleBindings: []*rbacv1.RoleBinding{
235				newRoleBinding("ns1", "admin", bindToClusterRole, "User:admin", "Group:admins"),
236			},
237			shouldPass: []authorizer.Attributes{
238				&defaultAttributes{"admin", "", "get", "pods", "status", "ns1", ""},
239				&defaultAttributes{"admin", "", "get", "pods", "scale", "ns1", ""},
240				&defaultAttributes{"admin", "", "get", "deployments", "scale", "ns1", ""},
241				&defaultAttributes{"admin", "", "get", "anything", "scale", "ns1", ""},
242			},
243			shouldFail: []authorizer.Attributes{
244				&defaultAttributes{"admin", "", "get", "pods", "", "ns1", ""},
245			},
246		},
247	}
248	for i, tt := range tests {
249		ruleResolver, _ := rbacregistryvalidation.NewTestRuleResolver(tt.roles, tt.roleBindings, tt.clusterRoles, tt.clusterRoleBindings)
250		a := RBACAuthorizer{ruleResolver}
251		for _, attr := range tt.shouldPass {
252			if decision, _, _ := a.Authorize(context.Background(), attr); decision != authorizer.DecisionAllow {
253				t.Errorf("case %d: incorrectly restricted %s", i, attr)
254			}
255		}
256
257		for _, attr := range tt.shouldFail {
258			if decision, _, _ := a.Authorize(context.Background(), attr); decision == authorizer.DecisionAllow {
259				t.Errorf("case %d: incorrectly passed %s", i, attr)
260			}
261		}
262	}
263}
264
265func TestRuleMatches(t *testing.T) {
266	tests := []struct {
267		name string
268		rule rbacv1.PolicyRule
269
270		requestsToExpected map[authorizer.AttributesRecord]bool
271	}{
272		{
273			name: "star verb, exact match other",
274			rule: rbacv1helpers.NewRule("*").Groups("group1").Resources("resource1").RuleOrDie(),
275			requestsToExpected: map[authorizer.AttributesRecord]bool{
276				resourceRequest("verb1").Group("group1").Resource("resource1").New(): true,
277				resourceRequest("verb1").Group("group2").Resource("resource1").New(): false,
278				resourceRequest("verb1").Group("group1").Resource("resource2").New(): false,
279				resourceRequest("verb1").Group("group2").Resource("resource2").New(): false,
280				resourceRequest("verb2").Group("group1").Resource("resource1").New(): true,
281				resourceRequest("verb2").Group("group2").Resource("resource1").New(): false,
282				resourceRequest("verb2").Group("group1").Resource("resource2").New(): false,
283				resourceRequest("verb2").Group("group2").Resource("resource2").New(): false,
284			},
285		},
286		{
287			name: "star group, exact match other",
288			rule: rbacv1helpers.NewRule("verb1").Groups("*").Resources("resource1").RuleOrDie(),
289			requestsToExpected: map[authorizer.AttributesRecord]bool{
290				resourceRequest("verb1").Group("group1").Resource("resource1").New(): true,
291				resourceRequest("verb1").Group("group2").Resource("resource1").New(): true,
292				resourceRequest("verb1").Group("group1").Resource("resource2").New(): false,
293				resourceRequest("verb1").Group("group2").Resource("resource2").New(): false,
294				resourceRequest("verb2").Group("group1").Resource("resource1").New(): false,
295				resourceRequest("verb2").Group("group2").Resource("resource1").New(): false,
296				resourceRequest("verb2").Group("group1").Resource("resource2").New(): false,
297				resourceRequest("verb2").Group("group2").Resource("resource2").New(): false,
298			},
299		},
300		{
301			name: "star resource, exact match other",
302			rule: rbacv1helpers.NewRule("verb1").Groups("group1").Resources("*").RuleOrDie(),
303			requestsToExpected: map[authorizer.AttributesRecord]bool{
304				resourceRequest("verb1").Group("group1").Resource("resource1").New(): true,
305				resourceRequest("verb1").Group("group2").Resource("resource1").New(): false,
306				resourceRequest("verb1").Group("group1").Resource("resource2").New(): true,
307				resourceRequest("verb1").Group("group2").Resource("resource2").New(): false,
308				resourceRequest("verb2").Group("group1").Resource("resource1").New(): false,
309				resourceRequest("verb2").Group("group2").Resource("resource1").New(): false,
310				resourceRequest("verb2").Group("group1").Resource("resource2").New(): false,
311				resourceRequest("verb2").Group("group2").Resource("resource2").New(): false,
312			},
313		},
314		{
315			name: "tuple expansion",
316			rule: rbacv1helpers.NewRule("verb1", "verb2").Groups("group1", "group2").Resources("resource1", "resource2").RuleOrDie(),
317			requestsToExpected: map[authorizer.AttributesRecord]bool{
318				resourceRequest("verb1").Group("group1").Resource("resource1").New(): true,
319				resourceRequest("verb1").Group("group2").Resource("resource1").New(): true,
320				resourceRequest("verb1").Group("group1").Resource("resource2").New(): true,
321				resourceRequest("verb1").Group("group2").Resource("resource2").New(): true,
322				resourceRequest("verb2").Group("group1").Resource("resource1").New(): true,
323				resourceRequest("verb2").Group("group2").Resource("resource1").New(): true,
324				resourceRequest("verb2").Group("group1").Resource("resource2").New(): true,
325				resourceRequest("verb2").Group("group2").Resource("resource2").New(): true,
326			},
327		},
328		{
329			name: "subresource expansion",
330			rule: rbacv1helpers.NewRule("*").Groups("*").Resources("resource1/subresource1").RuleOrDie(),
331			requestsToExpected: map[authorizer.AttributesRecord]bool{
332				resourceRequest("verb1").Group("group1").Resource("resource1").Subresource("subresource1").New(): true,
333				resourceRequest("verb1").Group("group2").Resource("resource1").Subresource("subresource2").New(): false,
334				resourceRequest("verb1").Group("group1").Resource("resource2").Subresource("subresource1").New(): false,
335				resourceRequest("verb1").Group("group2").Resource("resource2").Subresource("subresource1").New(): false,
336				resourceRequest("verb2").Group("group1").Resource("resource1").Subresource("subresource1").New(): true,
337				resourceRequest("verb2").Group("group2").Resource("resource1").Subresource("subresource2").New(): false,
338				resourceRequest("verb2").Group("group1").Resource("resource2").Subresource("subresource1").New(): false,
339				resourceRequest("verb2").Group("group2").Resource("resource2").Subresource("subresource1").New(): false,
340			},
341		},
342		{
343			name: "star nonresource, exact match other",
344			rule: rbacv1helpers.NewRule("verb1").URLs("*").RuleOrDie(),
345			requestsToExpected: map[authorizer.AttributesRecord]bool{
346				nonresourceRequest("verb1").URL("/foo").New():         true,
347				nonresourceRequest("verb1").URL("/foo/bar").New():     true,
348				nonresourceRequest("verb1").URL("/foo/baz").New():     true,
349				nonresourceRequest("verb1").URL("/foo/bar/one").New(): true,
350				nonresourceRequest("verb1").URL("/foo/baz/one").New(): true,
351				nonresourceRequest("verb2").URL("/foo").New():         false,
352				nonresourceRequest("verb2").URL("/foo/bar").New():     false,
353				nonresourceRequest("verb2").URL("/foo/baz").New():     false,
354				nonresourceRequest("verb2").URL("/foo/bar/one").New(): false,
355				nonresourceRequest("verb2").URL("/foo/baz/one").New(): false,
356			},
357		},
358		{
359			name: "star nonresource subpath",
360			rule: rbacv1helpers.NewRule("verb1").URLs("/foo/*").RuleOrDie(),
361			requestsToExpected: map[authorizer.AttributesRecord]bool{
362				nonresourceRequest("verb1").URL("/foo").New():            false,
363				nonresourceRequest("verb1").URL("/foo/bar").New():        true,
364				nonresourceRequest("verb1").URL("/foo/baz").New():        true,
365				nonresourceRequest("verb1").URL("/foo/bar/one").New():    true,
366				nonresourceRequest("verb1").URL("/foo/baz/one").New():    true,
367				nonresourceRequest("verb1").URL("/notfoo").New():         false,
368				nonresourceRequest("verb1").URL("/notfoo/bar").New():     false,
369				nonresourceRequest("verb1").URL("/notfoo/baz").New():     false,
370				nonresourceRequest("verb1").URL("/notfoo/bar/one").New(): false,
371				nonresourceRequest("verb1").URL("/notfoo/baz/one").New(): false,
372			},
373		},
374		{
375			name: "star verb, exact nonresource",
376			rule: rbacv1helpers.NewRule("*").URLs("/foo", "/foo/bar/one").RuleOrDie(),
377			requestsToExpected: map[authorizer.AttributesRecord]bool{
378				nonresourceRequest("verb1").URL("/foo").New():         true,
379				nonresourceRequest("verb1").URL("/foo/bar").New():     false,
380				nonresourceRequest("verb1").URL("/foo/baz").New():     false,
381				nonresourceRequest("verb1").URL("/foo/bar/one").New(): true,
382				nonresourceRequest("verb1").URL("/foo/baz/one").New(): false,
383				nonresourceRequest("verb2").URL("/foo").New():         true,
384				nonresourceRequest("verb2").URL("/foo/bar").New():     false,
385				nonresourceRequest("verb2").URL("/foo/baz").New():     false,
386				nonresourceRequest("verb2").URL("/foo/bar/one").New(): true,
387				nonresourceRequest("verb2").URL("/foo/baz/one").New(): false,
388			},
389		},
390	}
391	for _, tc := range tests {
392		for request, expected := range tc.requestsToExpected {
393			if e, a := expected, RuleAllows(request, &tc.rule); e != a {
394				t.Errorf("%q: expected %v, got %v for %v", tc.name, e, a, request)
395			}
396		}
397	}
398}
399
400type requestAttributeBuilder struct {
401	request authorizer.AttributesRecord
402}
403
404func resourceRequest(verb string) *requestAttributeBuilder {
405	return &requestAttributeBuilder{
406		request: authorizer.AttributesRecord{ResourceRequest: true, Verb: verb},
407	}
408}
409
410func nonresourceRequest(verb string) *requestAttributeBuilder {
411	return &requestAttributeBuilder{
412		request: authorizer.AttributesRecord{ResourceRequest: false, Verb: verb},
413	}
414}
415
416func (r *requestAttributeBuilder) Group(group string) *requestAttributeBuilder {
417	r.request.APIGroup = group
418	return r
419}
420
421func (r *requestAttributeBuilder) Resource(resource string) *requestAttributeBuilder {
422	r.request.Resource = resource
423	return r
424}
425
426func (r *requestAttributeBuilder) Subresource(subresource string) *requestAttributeBuilder {
427	r.request.Subresource = subresource
428	return r
429}
430
431func (r *requestAttributeBuilder) Name(name string) *requestAttributeBuilder {
432	r.request.Name = name
433	return r
434}
435
436func (r *requestAttributeBuilder) URL(url string) *requestAttributeBuilder {
437	r.request.Path = url
438	return r
439}
440
441func (r *requestAttributeBuilder) New() authorizer.AttributesRecord {
442	return r.request
443}
444
445func BenchmarkAuthorize(b *testing.B) {
446	bootstrapRoles := []rbacv1.ClusterRole{}
447	bootstrapRoles = append(bootstrapRoles, bootstrappolicy.ControllerRoles()...)
448	bootstrapRoles = append(bootstrapRoles, bootstrappolicy.ClusterRoles()...)
449
450	bootstrapBindings := []rbacv1.ClusterRoleBinding{}
451	bootstrapBindings = append(bootstrapBindings, bootstrappolicy.ClusterRoleBindings()...)
452	bootstrapBindings = append(bootstrapBindings, bootstrappolicy.ControllerRoleBindings()...)
453
454	clusterRoles := []*rbacv1.ClusterRole{}
455	for i := range bootstrapRoles {
456		clusterRoles = append(clusterRoles, &bootstrapRoles[i])
457	}
458	clusterRoleBindings := []*rbacv1.ClusterRoleBinding{}
459	for i := range bootstrapBindings {
460		clusterRoleBindings = append(clusterRoleBindings, &bootstrapBindings[i])
461	}
462
463	_, resolver := rbacregistryvalidation.NewTestRuleResolver(nil, nil, clusterRoles, clusterRoleBindings)
464
465	authz := New(resolver, resolver, resolver, resolver)
466
467	nodeUser := &user.DefaultInfo{Name: "system:node:node1", Groups: []string{"system:nodes", "system:authenticated"}}
468	requests := []struct {
469		name  string
470		attrs authorizer.Attributes
471	}{
472		{
473			"allow list pods",
474			authorizer.AttributesRecord{
475				ResourceRequest: true,
476				User:            nodeUser,
477				Verb:            "list",
478				Resource:        "pods",
479				Subresource:     "",
480				Name:            "",
481				Namespace:       "",
482				APIGroup:        "",
483				APIVersion:      "v1",
484			},
485		},
486		{
487			"allow update pods/status",
488			authorizer.AttributesRecord{
489				ResourceRequest: true,
490				User:            nodeUser,
491				Verb:            "update",
492				Resource:        "pods",
493				Subresource:     "status",
494				Name:            "mypods",
495				Namespace:       "myns",
496				APIGroup:        "",
497				APIVersion:      "v1",
498			},
499		},
500		{
501			"forbid educate dolphins",
502			authorizer.AttributesRecord{
503				ResourceRequest: true,
504				User:            nodeUser,
505				Verb:            "educate",
506				Resource:        "dolphins",
507				Subresource:     "",
508				Name:            "",
509				Namespace:       "",
510				APIGroup:        "",
511				APIVersion:      "v1",
512			},
513		},
514	}
515
516	b.ResetTimer()
517	for _, request := range requests {
518		b.Run(request.name, func(b *testing.B) {
519			for i := 0; i < b.N; i++ {
520				authz.Authorize(context.Background(), request.attrs)
521			}
522		})
523	}
524}
525