1
16
17 package create
18
19 import (
20 "context"
21 "fmt"
22 "strings"
23
24 "github.com/spf13/cobra"
25
26 rbacv1 "k8s.io/api/rbac/v1"
27 "k8s.io/apimachinery/pkg/api/meta"
28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 "k8s.io/apimachinery/pkg/runtime"
30 "k8s.io/apimachinery/pkg/runtime/schema"
31 "k8s.io/apimachinery/pkg/util/sets"
32 "k8s.io/cli-runtime/pkg/genericclioptions"
33 "k8s.io/cli-runtime/pkg/genericiooptions"
34 clientgorbacv1 "k8s.io/client-go/kubernetes/typed/rbac/v1"
35 cmdutil "k8s.io/kubectl/pkg/cmd/util"
36 "k8s.io/kubectl/pkg/scheme"
37 "k8s.io/kubectl/pkg/util"
38 "k8s.io/kubectl/pkg/util/i18n"
39 "k8s.io/kubectl/pkg/util/templates"
40 )
41
42 var (
43 roleLong = templates.LongDesc(i18n.T(`
44 Create a role with single rule.`))
45
46 roleExample = templates.Examples(i18n.T(`
47 # Create a role named "pod-reader" that allows user to perform "get", "watch" and "list" on pods
48 kubectl create role pod-reader --verb=get --verb=list --verb=watch --resource=pods
49
50 # Create a role named "pod-reader" with ResourceName specified
51 kubectl create role pod-reader --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod
52
53 # Create a role named "foo" with API Group specified
54 kubectl create role foo --verb=get,list,watch --resource=rs.apps
55
56 # Create a role named "foo" with SubResource specified
57 kubectl create role foo --verb=get,list,watch --resource=pods,pods/status`))
58
59
60 validResourceVerbs = []string{"*", "get", "delete", "list", "create", "update", "patch", "watch", "proxy", "deletecollection", "use", "bind", "escalate", "impersonate"}
61
62
63 specialVerbs = map[string][]schema.GroupResource{
64 "use": {
65 {
66 Group: "policy",
67 Resource: "podsecuritypolicies",
68 },
69 {
70 Group: "extensions",
71 Resource: "podsecuritypolicies",
72 },
73 },
74 "bind": {
75 {
76 Group: "rbac.authorization.k8s.io",
77 Resource: "roles",
78 },
79 {
80 Group: "rbac.authorization.k8s.io",
81 Resource: "clusterroles",
82 },
83 },
84 "escalate": {
85 {
86 Group: "rbac.authorization.k8s.io",
87 Resource: "roles",
88 },
89 {
90 Group: "rbac.authorization.k8s.io",
91 Resource: "clusterroles",
92 },
93 },
94 "impersonate": {
95 {
96 Group: "",
97 Resource: "users",
98 },
99 {
100 Group: "",
101 Resource: "serviceaccounts",
102 },
103 {
104 Group: "",
105 Resource: "groups",
106 },
107 {
108 Group: "authentication.k8s.io",
109 Resource: "userextras",
110 },
111 },
112 }
113 )
114
115
116 func AddSpecialVerb(verb string, gr schema.GroupResource) {
117 resources, ok := specialVerbs[verb]
118 if !ok {
119 resources = make([]schema.GroupResource, 1)
120 }
121 resources = append(resources, gr)
122 specialVerbs[verb] = resources
123 }
124
125
126 type ResourceOptions struct {
127 Group string
128 Resource string
129 SubResource string
130 }
131
132
133 type CreateRoleOptions struct {
134 PrintFlags *genericclioptions.PrintFlags
135
136 Name string
137 Verbs []string
138 Resources []ResourceOptions
139 ResourceNames []string
140
141 DryRunStrategy cmdutil.DryRunStrategy
142 ValidationDirective string
143 OutputFormat string
144 Namespace string
145 EnforceNamespace bool
146 Client clientgorbacv1.RbacV1Interface
147 Mapper meta.RESTMapper
148 PrintObj func(obj runtime.Object) error
149 FieldManager string
150 CreateAnnotation bool
151
152 genericiooptions.IOStreams
153 }
154
155
156 func NewCreateRoleOptions(ioStreams genericiooptions.IOStreams) *CreateRoleOptions {
157 return &CreateRoleOptions{
158 PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
159
160 IOStreams: ioStreams,
161 }
162 }
163
164
165 func NewCmdCreateRole(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
166 o := NewCreateRoleOptions(ioStreams)
167
168 cmd := &cobra.Command{
169 Use: "role NAME --verb=verb --resource=resource.group/subresource [--resource-name=resourcename] [--dry-run=server|client|none]",
170 DisableFlagsInUseLine: true,
171 Short: i18n.T("Create a role with single rule"),
172 Long: roleLong,
173 Example: roleExample,
174 Run: func(cmd *cobra.Command, args []string) {
175 cmdutil.CheckErr(o.Complete(f, cmd, args))
176 cmdutil.CheckErr(o.Validate())
177 cmdutil.CheckErr(o.RunCreateRole())
178 },
179 }
180
181 o.PrintFlags.AddFlags(cmd)
182
183 cmdutil.AddApplyAnnotationFlags(cmd)
184 cmdutil.AddValidateFlags(cmd)
185 cmdutil.AddDryRunFlag(cmd)
186 cmd.Flags().StringSliceVar(&o.Verbs, "verb", o.Verbs, "Verb that applies to the resources contained in the rule")
187 cmd.Flags().StringSlice("resource", []string{}, "Resource that the rule applies to")
188 cmd.Flags().StringArrayVar(&o.ResourceNames, "resource-name", o.ResourceNames, "Resource in the white list that the rule applies to, repeat this flag for multiple items")
189 cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
190 return cmd
191 }
192
193
194 func (o *CreateRoleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
195 name, err := NameFromCommandArgs(cmd, args)
196 if err != nil {
197 return err
198 }
199 o.Name = name
200
201
202 verbs := []string{}
203 for _, v := range o.Verbs {
204
205 if v == "*" {
206 verbs = []string{"*"}
207 break
208 }
209 if !arrayContains(verbs, v) {
210 verbs = append(verbs, v)
211 }
212 }
213 o.Verbs = verbs
214
215
216
217 resources := cmdutil.GetFlagStringSlice(cmd, "resource")
218 for _, r := range resources {
219 sections := strings.SplitN(r, "/", 2)
220
221 resource := &ResourceOptions{}
222 if len(sections) == 2 {
223 resource.SubResource = sections[1]
224 }
225
226 parts := strings.SplitN(sections[0], ".", 2)
227 if len(parts) == 2 {
228 resource.Group = parts[1]
229 }
230 resource.Resource = parts[0]
231
232 if resource.Resource == "*" && len(parts) == 1 && len(sections) == 1 {
233 o.Resources = []ResourceOptions{*resource}
234 break
235 }
236
237 o.Resources = append(o.Resources, *resource)
238 }
239
240
241 resourceNames := []string{}
242 for _, n := range o.ResourceNames {
243 if !arrayContains(resourceNames, n) {
244 resourceNames = append(resourceNames, n)
245 }
246 }
247 o.ResourceNames = resourceNames
248
249
250 o.Mapper, err = f.ToRESTMapper()
251 if err != nil {
252 return err
253 }
254
255 o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
256 if err != nil {
257 return err
258 }
259 o.OutputFormat = cmdutil.GetFlagString(cmd, "output")
260 o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
261
262 cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
263 printer, err := o.PrintFlags.ToPrinter()
264 if err != nil {
265 return err
266 }
267 o.PrintObj = func(obj runtime.Object) error {
268 return printer.PrintObj(obj, o.Out)
269 }
270
271 o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
272 if err != nil {
273 return err
274 }
275
276 o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
277 if err != nil {
278 return err
279 }
280
281 clientset, err := f.KubernetesClientSet()
282 if err != nil {
283 return err
284 }
285 o.Client = clientset.RbacV1()
286
287 return nil
288 }
289
290
291 func (o *CreateRoleOptions) Validate() error {
292 if o.Name == "" {
293 return fmt.Errorf("name must be specified")
294 }
295
296
297 if len(o.Verbs) == 0 {
298 return fmt.Errorf("at least one verb must be specified")
299 }
300
301 for _, v := range o.Verbs {
302 if !arrayContains(validResourceVerbs, v) {
303 fmt.Fprintf(o.ErrOut, "Warning: '%s' is not a standard resource verb\n", v)
304 }
305 }
306
307
308 if len(o.Resources) == 0 {
309 return fmt.Errorf("at least one resource must be specified")
310 }
311
312 return o.validateResource()
313 }
314
315 func (o *CreateRoleOptions) validateResource() error {
316 for _, r := range o.Resources {
317 if len(r.Resource) == 0 {
318 return fmt.Errorf("resource must be specified if apiGroup/subresource specified")
319 }
320 if r.Resource == "*" {
321 return nil
322 }
323
324 resource := schema.GroupVersionResource{Resource: r.Resource, Group: r.Group}
325 groupVersionResource, err := o.Mapper.ResourceFor(schema.GroupVersionResource{Resource: r.Resource, Group: r.Group})
326 if err == nil {
327 resource = groupVersionResource
328 }
329
330 for _, v := range o.Verbs {
331 if groupResources, ok := specialVerbs[v]; ok {
332 match := false
333 for _, extra := range groupResources {
334 if resource.Resource == extra.Resource && resource.Group == extra.Group {
335 match = true
336 err = nil
337 break
338 }
339 }
340 if !match {
341 return fmt.Errorf("can not perform '%s' on '%s' in group '%s'", v, resource.Resource, resource.Group)
342 }
343 }
344 }
345
346 if err != nil {
347 return err
348 }
349 }
350 return nil
351 }
352
353
354 func (o *CreateRoleOptions) RunCreateRole() error {
355 role := &rbacv1.Role{
356
357 TypeMeta: metav1.TypeMeta{APIVersion: rbacv1.SchemeGroupVersion.String(), Kind: "Role"},
358 }
359 role.Name = o.Name
360 rules, err := generateResourcePolicyRules(o.Mapper, o.Verbs, o.Resources, o.ResourceNames, []string{})
361 if err != nil {
362 return err
363 }
364 role.Rules = rules
365 if o.EnforceNamespace {
366 role.Namespace = o.Namespace
367 }
368
369 if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, role, scheme.DefaultJSONEncoder()); err != nil {
370 return err
371 }
372
373
374 if o.DryRunStrategy != cmdutil.DryRunClient {
375 createOptions := metav1.CreateOptions{}
376 if o.FieldManager != "" {
377 createOptions.FieldManager = o.FieldManager
378 }
379 createOptions.FieldValidation = o.ValidationDirective
380 if o.DryRunStrategy == cmdutil.DryRunServer {
381 createOptions.DryRun = []string{metav1.DryRunAll}
382 }
383 role, err = o.Client.Roles(o.Namespace).Create(context.TODO(), role, createOptions)
384 if err != nil {
385 return err
386 }
387 }
388
389 return o.PrintObj(role)
390 }
391
392 func arrayContains(s []string, e string) bool {
393 for _, a := range s {
394 if a == e {
395 return true
396 }
397 }
398 return false
399 }
400
401 func generateResourcePolicyRules(mapper meta.RESTMapper, verbs []string, resources []ResourceOptions, resourceNames []string, nonResourceURLs []string) ([]rbacv1.PolicyRule, error) {
402
403
404
405 groupResourceMapping := map[string][]string{}
406
407
408
409
410
411 for _, r := range resources {
412 resource := schema.GroupVersionResource{Resource: r.Resource, Group: r.Group}
413 groupVersionResource, err := mapper.ResourceFor(schema.GroupVersionResource{Resource: r.Resource, Group: r.Group})
414 if err == nil {
415 resource = groupVersionResource
416 }
417
418 if len(r.SubResource) > 0 {
419 resource.Resource = resource.Resource + "/" + r.SubResource
420 }
421 if !arrayContains(groupResourceMapping[resource.Group], resource.Resource) {
422 groupResourceMapping[resource.Group] = append(groupResourceMapping[resource.Group], resource.Resource)
423 }
424 }
425
426
427 rules := []rbacv1.PolicyRule{}
428 for _, g := range sets.StringKeySet(groupResourceMapping).List() {
429 rule := rbacv1.PolicyRule{}
430 rule.Verbs = verbs
431 rule.Resources = groupResourceMapping[g]
432 rule.APIGroups = []string{g}
433 rule.ResourceNames = resourceNames
434 rules = append(rules, rule)
435 }
436
437 if len(nonResourceURLs) > 0 {
438 rule := rbacv1.PolicyRule{}
439 rule.Verbs = verbs
440 rule.NonResourceURLs = nonResourceURLs
441 rules = append(rules, rule)
442 }
443
444 return rules, nil
445 }
446
View as plain text