@@ -49,16 +49,15 @@ NONRESOURCEURL is a partial URL that starts with "/".`
49
49
50
50
# List who can access the URL /logs/
51
51
kubectl who-can get /logs`
52
- )
53
-
54
- type role struct {
55
- name string
56
- isClusterRole bool
57
- }
58
52
59
- type roles map [role ]struct {}
53
+ // RoleKind is the RoleRef's Kind referencing a Role.
54
+ RoleKind = "Role"
55
+ // ClusterRoleKind is the RoleRef's Kind referencing a ClusterRole.
56
+ ClusterRoleKind = "ClusterRole"
57
+ )
60
58
61
- type whoCan struct {
59
+ // Action represents an action a subject can be given permission to.
60
+ type Action struct {
62
61
verb string
63
62
resource string
64
63
nonResourceURL string
@@ -67,6 +66,16 @@ type whoCan struct {
67
66
68
67
namespace string
69
68
allNamespaces bool
69
+ }
70
+
71
+ // roles is a set of Role names matching the specified Action.
72
+ type roles map [string ]struct {}
73
+
74
+ // clusterRoles is a set of ClusterRole names matching the specified Action.
75
+ type clusterRoles map [string ]struct {}
76
+
77
+ type whoCan struct {
78
+ Action
70
79
71
80
configFlags * clioptions.ConfigFlags
72
81
clientConfig clientcmd.ClientConfig
@@ -76,8 +85,7 @@ type whoCan struct {
76
85
namespaceValidator NamespaceValidator
77
86
resourceResolver ResourceResolver
78
87
accessChecker AccessChecker
79
-
80
- r roles
88
+ policyRuleMatcher PolicyRuleMatcher
81
89
82
90
clioptions.IOStreams
83
91
}
@@ -89,6 +97,7 @@ func NewWhoCanOptions(configFlags *clioptions.ConfigFlags,
89
97
namespaceValidator NamespaceValidator ,
90
98
resourceResolver ResourceResolver ,
91
99
accessChecker AccessChecker ,
100
+ policyRuleMatcher PolicyRuleMatcher ,
92
101
streams clioptions.IOStreams ) * whoCan {
93
102
return & whoCan {
94
103
configFlags : configFlags ,
@@ -98,6 +107,7 @@ func NewWhoCanOptions(configFlags *clioptions.ConfigFlags,
98
107
namespaceValidator : namespaceValidator ,
99
108
resourceResolver : resourceResolver ,
100
109
accessChecker : accessChecker ,
110
+ policyRuleMatcher : policyRuleMatcher ,
101
111
IOStreams : streams ,
102
112
}
103
113
}
@@ -132,6 +142,7 @@ func NewCmdWhoCan(streams clioptions.IOStreams) (*cobra.Command, error) {
132
142
namespaceValidator ,
133
143
resourceResolver ,
134
144
accessChecker ,
145
+ NewPolicyRuleMatcher (),
135
146
streams )
136
147
137
148
cmd := & cobra.Command {
@@ -179,6 +190,7 @@ func (w *whoCan) Complete(args []string) error {
179
190
if err != nil {
180
191
return fmt .Errorf ("resolving resource: %v" , err )
181
192
}
193
+ glog .V (3 ).Infof ("Resolved resource `%s`" , w .resource )
182
194
}
183
195
184
196
err = w .resolveNamespace ()
@@ -197,11 +209,13 @@ func (w *whoCan) resolveArgs(args []string) error {
197
209
w .verb = args [0 ]
198
210
if strings .HasPrefix (args [1 ], "/" ) {
199
211
w .nonResourceURL = args [1 ]
212
+ glog .V (3 ).Infof ("Resolved nonResourceURL `%s`" , w .nonResourceURL )
200
213
} else {
201
214
resourceTokens := strings .SplitN (args [1 ], "/" , 2 )
202
215
w .resource = resourceTokens [0 ]
203
216
if len (resourceTokens ) > 1 {
204
217
w .resourceName = resourceTokens [1 ]
218
+ glog .V (3 ).Infof ("Resolved resourceName `%s`" , w .resourceName )
205
219
}
206
220
}
207
221
return nil
@@ -250,28 +264,26 @@ func (w *whoCan) Check() error {
250
264
return fmt .Errorf ("checking API access: %v" , err )
251
265
}
252
266
253
- w .r = make (map [role ]struct {}, 10 )
254
-
255
267
// Get the Roles that relate to the Verbs and Resources we are interested in
256
- err = w .getRoles ( )
268
+ roleNames , err : = w .GetRolesFor ( w . Action )
257
269
if err != nil {
258
270
return fmt .Errorf ("getting Roles: %v" , err )
259
271
}
260
272
261
- // Get the RoleBindings that relate to this set of Roles
262
- roleBindings , err := w .getRoleBindings ( )
273
+ // Get the ClusterRoles that relate to the verbs and resources we are interested in
274
+ clusterRoleNames , err := w .GetClusterRolesFor ( w . Action )
263
275
if err != nil {
264
- return fmt .Errorf ("getting RoleBindings : %v" , err )
276
+ return fmt .Errorf ("getting ClusterRoles : %v" , err )
265
277
}
266
278
267
- // Get the ClusterRoles that relate to the verbs and resources we are interested in
268
- err = w .getClusterRoles ( )
279
+ // Get the RoleBindings that relate to this set of Roles or ClusterRoles
280
+ roleBindings , err : = w .GetRoleBindings ( roleNames , clusterRoleNames )
269
281
if err != nil {
270
- return fmt .Errorf ("getting ClusterRoles : %v" , err )
282
+ return fmt .Errorf ("getting RoleBindings : %v" , err )
271
283
}
272
284
273
285
// Get the ClusterRoleBindings that relate to this set of ClusterRoles
274
- clusterRoleBindings , err := w .getClusterRoleBindings ( )
286
+ clusterRoleBindings , err := w .GetClusterRoleBindings ( clusterRoleNames )
275
287
if err != nil {
276
288
return fmt .Errorf ("getting ClusterRoleBindings: %v" , err )
277
289
}
@@ -344,167 +356,95 @@ func (w *whoCan) printAPIAccessWarnings(warnings []string) {
344
356
}
345
357
}
346
358
347
- func (w * whoCan ) getRoles () error {
359
+ // GetRolesFor returns a set of names of Roles matching the specified Action.
360
+ func (w * whoCan ) GetRolesFor (action Action ) (roles , error ) {
348
361
rl , err := w .clientRBAC .Roles (w .namespace ).List (meta.ListOptions {})
349
362
if err != nil {
350
- return err
363
+ return nil , err
351
364
}
352
365
353
- w .filterRoles (rl )
354
- return nil
355
- }
356
-
357
- func (w * whoCan ) filterRoles (roles * rbac.RoleList ) {
358
- for _ , item := range roles .Items {
359
- for _ , rule := range item .Rules {
360
- if ! w .policyRuleMatches (rule ) {
361
- glog .V (3 ).Infof ("Role [%s] doesn't match policy filter" , item .Name )
362
- continue
363
- }
366
+ roleNames := make (map [string ]struct {}, 10 )
364
367
365
- newRole := role {
366
- name : item .Name ,
367
- isClusterRole : false ,
368
- }
369
- if _ , ok := w .r [newRole ]; ! ok {
370
- w .r [newRole ] = struct {}{}
368
+ for _ , item := range rl .Items {
369
+ if w .policyRuleMatcher .MatchesRole (item , action ) {
370
+ if _ , ok := roleNames [item .Name ]; ! ok {
371
+ roleNames [item .Name ] = struct {}{}
371
372
}
372
-
373
373
}
374
374
}
375
+
376
+ return roleNames , nil
375
377
}
376
378
377
- func (w * whoCan ) getClusterRoles () error {
379
+ // GetClusterRolesFor returns a set of names of ClusterRoles matching the specified Action.
380
+ func (w * whoCan ) GetClusterRolesFor (action Action ) (clusterRoles , error ) {
378
381
crl , err := w .clientRBAC .ClusterRoles ().List (meta.ListOptions {})
379
382
if err != nil {
380
- return err
383
+ return nil , err
381
384
}
382
385
383
- w .filterClusterRoles (crl )
384
- return nil
385
- }
386
-
387
- func (w * whoCan ) filterClusterRoles (roles * rbac.ClusterRoleList ) {
388
- for _ , item := range roles .Items {
389
- for _ , rule := range item .Rules {
390
- if ! w .policyRuleMatches (rule ) {
391
- glog .V (3 ).Infof ("ClusterRole [%s] doesn't match policy filter" , item .Name )
392
- continue
393
- }
386
+ cr := make (map [string ]struct {}, 10 )
394
387
395
- newRole := role {
396
- name : item .Name ,
397
- isClusterRole : true ,
388
+ for _ , item := range crl .Items {
389
+ if w .policyRuleMatcher .MatchesClusterRole (item , action ) {
390
+ if _ , ok := cr [item .Name ]; ! ok {
391
+ cr [item .Name ] = struct {}{}
398
392
}
399
- if _ , ok := w .r [newRole ]; ! ok {
400
- w .r [newRole ] = struct {}{}
401
- }
402
- }
403
- }
404
- }
405
-
406
- func (w * whoCan ) policyRuleMatches (rule rbac.PolicyRule ) bool {
407
- if w .nonResourceURL != "" {
408
- return w .matchesVerb (rule ) &&
409
- w .matchesNonResourceURL (rule )
410
- }
411
-
412
- return w .matchesVerb (rule ) &&
413
- w .matchesResource (rule ) &&
414
- w .matchesResourceName (rule )
415
- }
416
-
417
- func (w * whoCan ) matchesVerb (rule rbac.PolicyRule ) bool {
418
- for _ , verb := range rule .Verbs {
419
- if verb == rbac .VerbAll || verb == w .verb {
420
- return true
421
- }
422
- }
423
- return false
424
- }
425
-
426
- func (w * whoCan ) matchesResource (rule rbac.PolicyRule ) bool {
427
- for _ , resource := range rule .Resources {
428
- if resource == rbac .ResourceAll || resource == w .resource {
429
- return true
430
393
}
431
394
}
432
- return false
395
+ return cr , nil
433
396
}
434
397
435
- func (w * whoCan ) matchesResourceName (rule rbac.PolicyRule ) bool {
436
- if w .resourceName == "" && len (rule .ResourceNames ) == 0 {
437
- return true
438
- }
439
- if len (rule .ResourceNames ) == 0 {
440
- return true
441
- }
442
- for _ , name := range rule .ResourceNames {
443
- if name == w .resourceName {
444
- return true
445
- }
446
- }
447
- return false
448
- }
449
-
450
- func (w * whoCan ) matchesNonResourceURL (rule rbac.PolicyRule ) bool {
451
- for _ , URL := range rule .NonResourceURLs {
452
- if URL == w .nonResourceURL {
453
- return true
454
- }
398
+ // GetRoleBindings returns the RoleBindings that refer to the given set of Role names or ClusterRole names.
399
+ func (w * whoCan ) GetRoleBindings (roleNames roles , clusterRoleNames clusterRoles ) (roleBindings []rbac.RoleBinding , err error ) {
400
+ // TODO I'm wondering if GetRoleBindings should be invoked at all when the --all-namespaces flag is specified?
401
+ if w .namespace == core .NamespaceAll {
402
+ return
455
403
}
456
- return false
457
- }
458
404
459
- func (w * whoCan ) getRoleBindings () (roleBindings []rbac.RoleBinding , err error ) {
460
- rbl , err := w .clientRBAC .RoleBindings (w .namespace ).List (meta.ListOptions {})
405
+ list , err := w .clientRBAC .RoleBindings (w .namespace ).List (meta.ListOptions {})
461
406
if err != nil {
462
407
return
463
408
}
464
409
465
- for _ , roleBinding := range rbl .Items {
466
- if w .r .match (& roleBinding .RoleRef ) {
467
- glog .V (1 ).Info (fmt .Sprintf ("Match found: roleRef: %v" , roleBinding .RoleRef ))
468
- roleBindings = append (roleBindings , roleBinding )
410
+ for _ , roleBinding := range list .Items {
411
+ if roleBinding .RoleRef .Kind == RoleKind {
412
+ if _ , ok := roleNames [roleBinding .RoleRef .Name ]; ok {
413
+ roleBindings = append (roleBindings , roleBinding )
414
+ }
415
+ } else if roleBinding .RoleRef .Kind == ClusterRoleKind {
416
+ if _ , ok := clusterRoleNames [roleBinding .RoleRef .Name ]; ok {
417
+ roleBindings = append (roleBindings , roleBinding )
418
+ }
419
+ } else {
420
+ _ , _ = fmt .Fprintf (w .Out , "Warning: Unrecognized RoleRef kind `%s` found in `%s` RoleBinding\n " , roleBinding .RoleRef .Kind , roleBinding .Name )
469
421
}
470
422
}
471
423
472
424
return
473
425
}
474
426
475
- func (w * whoCan ) getClusterRoleBindings () (clusterRoleBindings []rbac.ClusterRoleBinding , err error ) {
476
- rbl , err := w .clientRBAC .ClusterRoleBindings ().List (meta.ListOptions {})
427
+ // GetClusterRoleBindings returns the ClusterRoleBindings that refer to the given sef of ClusterRole names.
428
+ func (w * whoCan ) GetClusterRoleBindings (clusterRoleNames clusterRoles ) (clusterRoleBindings []rbac.ClusterRoleBinding , err error ) {
429
+ list , err := w .clientRBAC .ClusterRoleBindings ().List (meta.ListOptions {})
477
430
if err != nil {
478
431
return
479
432
}
480
433
481
- for _ , roleBinding := range rbl .Items {
482
- if w .r .match (& roleBinding .RoleRef ) {
483
- glog .V (1 ).Info (fmt .Sprintf ("Match found: roleRef: %v" , roleBinding .RoleRef ))
434
+ for _ , roleBinding := range list .Items {
435
+ if _ , ok := clusterRoleNames [roleBinding .RoleRef .Name ]; ok {
484
436
clusterRoleBindings = append (clusterRoleBindings , roleBinding )
485
437
}
486
438
}
487
439
488
440
return
489
441
}
490
442
491
- func (r roles ) match (roleRef * rbac.RoleRef ) bool {
492
- tempRole := role {
493
- name : roleRef .Name ,
494
- isClusterRole : (roleRef .Kind == "ClusterRole" ),
495
- }
496
-
497
- glog .V (3 ).Info (fmt .Sprintf ("Testing against roleRef: %v" , tempRole ))
498
-
499
- _ , ok := r [tempRole ]
500
- return ok
501
- }
502
-
503
443
func (w * whoCan ) output (roleBindings []rbac.RoleBinding , clusterRoleBindings []rbac.ClusterRoleBinding ) {
504
444
wr := new (tabwriter.Writer )
505
445
wr .Init (w .Out , 0 , 8 , 2 , ' ' , 0 )
506
446
507
- action := w .prettyPrintAction ()
447
+ action := w .Action . PrettyPrint ()
508
448
509
449
if w .resource != "" {
510
450
// NonResourceURL permissions can only be granted through ClusterRoles. Hence no point in printing RoleBindings section.
@@ -535,7 +475,7 @@ func (w *whoCan) output(roleBindings []rbac.RoleBinding, clusterRoleBindings []r
535
475
wr .Flush ()
536
476
}
537
477
538
- func (w * whoCan ) prettyPrintAction () string {
478
+ func (w Action ) PrettyPrint () string {
539
479
if w .nonResourceURL != "" {
540
480
return fmt .Sprintf ("%s %s" , w .verb , w .nonResourceURL )
541
481
}
0 commit comments