@@ -19,18 +19,25 @@ package auth
19
19
import (
20
20
"errors"
21
21
"fmt"
22
+ "io"
22
23
"io/ioutil"
23
24
"os"
25
+ "sort"
24
26
"strings"
25
27
26
28
"github.com/spf13/cobra"
27
29
28
30
authorizationv1 "k8s.io/api/authorization/v1"
31
+ rbacv1 "k8s.io/api/rbac/v1"
29
32
"k8s.io/apimachinery/pkg/api/meta"
30
33
"k8s.io/apimachinery/pkg/runtime/schema"
34
+ utilerrors "k8s.io/apimachinery/pkg/util/errors"
31
35
"k8s.io/cli-runtime/pkg/genericclioptions"
32
36
authorizationv1client "k8s.io/client-go/kubernetes/typed/authorization/v1"
33
37
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
38
+ describeutil "k8s.io/kubernetes/pkg/kubectl/describe/versioned"
39
+ "k8s.io/kubernetes/pkg/kubectl/util/printers"
40
+ rbacutil "k8s.io/kubernetes/pkg/kubectl/util/rbac"
34
41
"k8s.io/kubernetes/pkg/kubectl/util/templates"
35
42
)
36
43
@@ -39,14 +46,16 @@ import (
39
46
type CanIOptions struct {
40
47
AllNamespaces bool
41
48
Quiet bool
49
+ NoHeaders bool
42
50
Namespace string
43
- SelfSARClient authorizationv1client.SelfSubjectAccessReviewsGetter
51
+ AuthClient authorizationv1client.AuthorizationV1Interface
44
52
45
53
Verb string
46
54
Resource schema.GroupVersionResource
47
55
NonResourceURL string
48
56
Subresource string
49
57
ResourceName string
58
+ List bool
50
59
51
60
genericclioptions.IOStreams
52
61
}
77
86
kubectl auth can-i get pods --subresource=log
78
87
79
88
# Check to see if I can access the URL /logs/
80
- kubectl auth can-i get /logs/` )
89
+ kubectl auth can-i get /logs/
90
+
91
+ # List all allowed actions in namespace "foo"
92
+ kubectl auth can-i --list --namespace=foo` )
81
93
)
82
94
83
95
// NewCmdCanI returns an initialized Command for 'auth can-i' sub command
@@ -95,57 +107,68 @@ func NewCmdCanI(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.C
95
107
Run : func (cmd * cobra.Command , args []string ) {
96
108
cmdutil .CheckErr (o .Complete (f , args ))
97
109
cmdutil .CheckErr (o .Validate ())
98
-
99
- allowed , err := o .RunAccessCheck ()
100
- if err == nil {
101
- if ! allowed {
102
- os .Exit (1 )
110
+ var err error
111
+ if o .List {
112
+ err = o .RunAccessList ()
113
+ } else {
114
+ var allowed bool
115
+ allowed , err = o .RunAccessCheck ()
116
+ if err == nil {
117
+ if ! allowed {
118
+ os .Exit (1 )
119
+ }
103
120
}
104
121
}
105
-
106
122
cmdutil .CheckErr (err )
107
123
},
108
124
}
109
125
110
126
cmd .Flags ().BoolVarP (& o .AllNamespaces , "all-namespaces" , "A" , o .AllNamespaces , "If true, check the specified action in all namespaces." )
111
127
cmd .Flags ().BoolVarP (& o .Quiet , "quiet" , "q" , o .Quiet , "If true, suppress output and just return the exit code." )
112
128
cmd .Flags ().StringVar (& o .Subresource , "subresource" , o .Subresource , "SubResource such as pod/log or deployment/scale" )
129
+ cmd .Flags ().BoolVar (& o .List , "list" , o .List , "If true, prints all allowed actions." )
130
+ cmd .Flags ().BoolVar (& o .NoHeaders , "no-headers" , o .NoHeaders , "If true, prints allowed actions without headers" )
113
131
return cmd
114
132
}
115
133
116
134
// Complete completes all the required options
117
135
func (o * CanIOptions ) Complete (f cmdutil.Factory , args []string ) error {
118
- if o .Quiet {
119
- o .Out = ioutil .Discard
120
- }
121
-
122
- switch len (args ) {
123
- case 2 :
124
- o .Verb = args [0 ]
125
- if strings .HasPrefix (args [1 ], "/" ) {
126
- o .NonResourceURL = args [1 ]
127
- break
136
+ if o .List {
137
+ if len (args ) != 0 {
138
+ return errors .New ("list option must be specified with no arguments" )
128
139
}
129
- resourceTokens := strings .SplitN (args [1 ], "/" , 2 )
130
- restMapper , err := f .ToRESTMapper ()
131
- if err != nil {
132
- return err
140
+ } else {
141
+ if o .Quiet {
142
+ o .Out = ioutil .Discard
133
143
}
134
- o .Resource = o .resourceFor (restMapper , resourceTokens [0 ])
135
- if len (resourceTokens ) > 1 {
136
- o .ResourceName = resourceTokens [1 ]
144
+
145
+ switch len (args ) {
146
+ case 2 :
147
+ o .Verb = args [0 ]
148
+ if strings .HasPrefix (args [1 ], "/" ) {
149
+ o .NonResourceURL = args [1 ]
150
+ break
151
+ }
152
+ resourceTokens := strings .SplitN (args [1 ], "/" , 2 )
153
+ restMapper , err := f .ToRESTMapper ()
154
+ if err != nil {
155
+ return err
156
+ }
157
+ o .Resource = o .resourceFor (restMapper , resourceTokens [0 ])
158
+ if len (resourceTokens ) > 1 {
159
+ o .ResourceName = resourceTokens [1 ]
160
+ }
161
+ default :
162
+ return errors .New ("you must specify two or three arguments: verb, resource, and optional resourceName" )
137
163
}
138
- default :
139
- return errors .New ("you must specify two or three arguments: verb, resource, and optional resourceName" )
140
164
}
141
165
142
166
var err error
143
167
client , err := f .KubernetesClientSet ()
144
168
if err != nil {
145
169
return err
146
170
}
147
- o .SelfSARClient = client .AuthorizationV1 ()
148
-
171
+ o .AuthClient = client .AuthorizationV1 ()
149
172
o .Namespace = ""
150
173
if ! o .AllNamespaces {
151
174
o .Namespace , _ , err = f .ToRawKubeConfigLoader ().Namespace ()
@@ -159,6 +182,13 @@ func (o *CanIOptions) Complete(f cmdutil.Factory, args []string) error {
159
182
160
183
// Validate makes sure provided values for CanIOptions are valid
161
184
func (o * CanIOptions ) Validate () error {
185
+ if o .List {
186
+ if o .Quiet || o .AllNamespaces || o .Subresource != "" {
187
+ return errors .New ("list option can't be specified with neither quiet, all-namespaces nor subresource options" )
188
+ }
189
+ return nil
190
+ }
191
+
162
192
if o .NonResourceURL != "" {
163
193
if o .Subresource != "" {
164
194
return fmt .Errorf ("--subresource can not be used with NonResourceURL" )
@@ -167,9 +197,28 @@ func (o *CanIOptions) Validate() error {
167
197
return fmt .Errorf ("NonResourceURL and ResourceName can not specified together" )
168
198
}
169
199
}
200
+
201
+ if o .NoHeaders {
202
+ return fmt .Errorf ("--no-headers cannot be set without --list specified" )
203
+ }
170
204
return nil
171
205
}
172
206
207
+ // RunAccessList lists all the access current user has
208
+ func (o * CanIOptions ) RunAccessList () error {
209
+ sar := & authorizationv1.SelfSubjectRulesReview {
210
+ Spec : authorizationv1.SelfSubjectRulesReviewSpec {
211
+ Namespace : o .Namespace ,
212
+ },
213
+ }
214
+ response , err := o .AuthClient .SelfSubjectRulesReviews ().Create (sar )
215
+ if err != nil {
216
+ return err
217
+ }
218
+
219
+ return o .printStatus (response .Status )
220
+ }
221
+
173
222
// RunAccessCheck checks if user has access to a certain resource or non resource URL
174
223
func (o * CanIOptions ) RunAccessCheck () (bool , error ) {
175
224
var sar * authorizationv1.SelfSubjectAccessReview
@@ -195,10 +244,9 @@ func (o *CanIOptions) RunAccessCheck() (bool, error) {
195
244
},
196
245
},
197
246
}
198
-
199
247
}
200
248
201
- response , err := o .SelfSARClient .SelfSubjectAccessReviews ().Create (sar )
249
+ response , err := o .AuthClient .SelfSubjectAccessReviews ().Create (sar )
202
250
if err != nil {
203
251
return false , err
204
252
}
@@ -244,3 +292,71 @@ func (o *CanIOptions) resourceFor(mapper meta.RESTMapper, resourceArg string) sc
244
292
245
293
return gvr
246
294
}
295
+
296
+ func (o * CanIOptions ) printStatus (status authorizationv1.SubjectRulesReviewStatus ) error {
297
+ if status .Incomplete {
298
+ fmt .Fprintf (o .ErrOut , "warning: the list may be incomplete: %v\n " , status .EvaluationError )
299
+ }
300
+
301
+ breakdownRules := []rbacv1.PolicyRule {}
302
+ for _ , rule := range convertToPolicyRule (status ) {
303
+ breakdownRules = append (breakdownRules , rbacutil .BreakdownRule (rule )... )
304
+ }
305
+
306
+ compactRules , err := rbacutil .CompactRules (breakdownRules )
307
+ if err != nil {
308
+ return err
309
+ }
310
+ sort .Stable (rbacutil .SortableRuleSlice (compactRules ))
311
+
312
+ w := printers .GetNewTabWriter (o .Out )
313
+ defer w .Flush ()
314
+
315
+ allErrs := []error {}
316
+ if ! o .NoHeaders {
317
+ if err := printAccessHeaders (w ); err != nil {
318
+ allErrs = append (allErrs , err )
319
+ }
320
+ }
321
+
322
+ if err := printAccess (w , compactRules ); err != nil {
323
+ allErrs = append (allErrs , err )
324
+ }
325
+ return utilerrors .NewAggregate (allErrs )
326
+ }
327
+
328
+ func convertToPolicyRule (status authorizationv1.SubjectRulesReviewStatus ) []rbacv1.PolicyRule {
329
+ ret := []rbacv1.PolicyRule {}
330
+ for _ , resource := range status .ResourceRules {
331
+ ret = append (ret , rbacv1.PolicyRule {
332
+ Verbs : resource .Verbs ,
333
+ APIGroups : resource .APIGroups ,
334
+ Resources : resource .Resources ,
335
+ ResourceNames : resource .ResourceNames ,
336
+ })
337
+ }
338
+
339
+ for _ , nonResource := range status .NonResourceRules {
340
+ ret = append (ret , rbacv1.PolicyRule {
341
+ Verbs : nonResource .Verbs ,
342
+ NonResourceURLs : nonResource .NonResourceURLs ,
343
+ })
344
+ }
345
+
346
+ return ret
347
+ }
348
+
349
+ func printAccessHeaders (out io.Writer ) error {
350
+ columnNames := []string {"Resources" , "Non-Resource URLs" , "Resource Names" , "Verbs" }
351
+ _ , err := fmt .Fprintf (out , "%s\n " , strings .Join (columnNames , "\t " ))
352
+ return err
353
+ }
354
+
355
+ func printAccess (out io.Writer , rules []rbacv1.PolicyRule ) error {
356
+ for _ , r := range rules {
357
+ if _ , err := fmt .Fprintf (out , "%s\t %v\t %v\t %v\n " , describeutil .CombineResourceGroup (r .Resources , r .APIGroups ), r .NonResourceURLs , r .ResourceNames , r .Verbs ); err != nil {
358
+ return err
359
+ }
360
+ }
361
+ return nil
362
+ }
0 commit comments