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 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/cli-runtime/pkg/genericiooptions"
29 cliflag "k8s.io/component-base/cli/flag"
30 cmdutil "k8s.io/kubectl/pkg/cmd/util"
31 "k8s.io/kubectl/pkg/scheme"
32 "k8s.io/kubectl/pkg/util"
33 "k8s.io/kubectl/pkg/util/i18n"
34 "k8s.io/kubectl/pkg/util/templates"
35 )
36
37 var (
38 clusterRoleLong = templates.LongDesc(i18n.T(`
39 Create a cluster role.`))
40
41 clusterRoleExample = templates.Examples(i18n.T(`
42 # Create a cluster role named "pod-reader" that allows user to perform "get", "watch" and "list" on pods
43 kubectl create clusterrole pod-reader --verb=get,list,watch --resource=pods
44
45 # Create a cluster role named "pod-reader" with ResourceName specified
46 kubectl create clusterrole pod-reader --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod
47
48 # Create a cluster role named "foo" with API Group specified
49 kubectl create clusterrole foo --verb=get,list,watch --resource=rs.apps
50
51 # Create a cluster role named "foo" with SubResource specified
52 kubectl create clusterrole foo --verb=get,list,watch --resource=pods,pods/status
53
54 # Create a cluster role name "foo" with NonResourceURL specified
55 kubectl create clusterrole "foo" --verb=get --non-resource-url=/logs/*
56
57 # Create a cluster role name "monitoring" with AggregationRule specified
58 kubectl create clusterrole monitoring --aggregation-rule="rbac.example.com/aggregate-to-monitoring=true"`))
59
60
61 validNonResourceVerbs = []string{"*", "get", "post", "put", "delete", "patch", "head", "options"}
62 )
63
64
65 type CreateClusterRoleOptions struct {
66 *CreateRoleOptions
67 NonResourceURLs []string
68 AggregationRule map[string]string
69 FieldManager string
70 }
71
72
73 func NewCmdCreateClusterRole(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
74 c := &CreateClusterRoleOptions{
75 CreateRoleOptions: NewCreateRoleOptions(ioStreams),
76 AggregationRule: map[string]string{},
77 }
78 cmd := &cobra.Command{
79 Use: "clusterrole NAME --verb=verb --resource=resource.group [--resource-name=resourcename] [--dry-run=server|client|none]",
80 DisableFlagsInUseLine: true,
81 Short: i18n.T("Create a cluster role"),
82 Long: clusterRoleLong,
83 Example: clusterRoleExample,
84 Run: func(cmd *cobra.Command, args []string) {
85 cmdutil.CheckErr(c.Complete(f, cmd, args))
86 cmdutil.CheckErr(c.Validate())
87 cmdutil.CheckErr(c.RunCreateRole())
88 },
89 }
90
91 c.PrintFlags.AddFlags(cmd)
92
93 cmdutil.AddApplyAnnotationFlags(cmd)
94 cmdutil.AddValidateFlags(cmd)
95 cmdutil.AddDryRunFlag(cmd)
96 cmd.Flags().StringSliceVar(&c.Verbs, "verb", c.Verbs, "Verb that applies to the resources contained in the rule")
97 cmd.Flags().StringSliceVar(&c.NonResourceURLs, "non-resource-url", c.NonResourceURLs, "A partial url that user should have access to.")
98 cmd.Flags().StringSlice("resource", []string{}, "Resource that the rule applies to")
99 cmd.Flags().StringArrayVar(&c.ResourceNames, "resource-name", c.ResourceNames, "Resource in the white list that the rule applies to, repeat this flag for multiple items")
100 cmd.Flags().Var(cliflag.NewMapStringString(&c.AggregationRule), "aggregation-rule", "An aggregation label selector for combining ClusterRoles.")
101 cmdutil.AddFieldManagerFlagVar(cmd, &c.FieldManager, "kubectl-create")
102
103 return cmd
104 }
105
106
107 func (c *CreateClusterRoleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
108
109 nonResourceURLs := []string{}
110 for _, n := range c.NonResourceURLs {
111 if !arrayContains(nonResourceURLs, n) {
112 nonResourceURLs = append(nonResourceURLs, n)
113 }
114 }
115 c.NonResourceURLs = nonResourceURLs
116
117 return c.CreateRoleOptions.Complete(f, cmd, args)
118 }
119
120
121 func (c *CreateClusterRoleOptions) Validate() error {
122 if c.Name == "" {
123 return fmt.Errorf("name must be specified")
124 }
125
126 if len(c.AggregationRule) > 0 {
127 if len(c.NonResourceURLs) > 0 || len(c.Verbs) > 0 || len(c.Resources) > 0 || len(c.ResourceNames) > 0 {
128 return fmt.Errorf("aggregation rule must be specified without nonResourceURLs, verbs, resources or resourceNames")
129 }
130 return nil
131 }
132
133
134 if len(c.Verbs) == 0 {
135 return fmt.Errorf("at least one verb must be specified")
136 }
137
138 if len(c.Resources) == 0 && len(c.NonResourceURLs) == 0 {
139 return fmt.Errorf("one of resource or nonResourceURL must be specified")
140 }
141
142
143 if len(c.Resources) > 0 {
144 for _, v := range c.Verbs {
145 if !arrayContains(validResourceVerbs, v) {
146 fmt.Fprintf(c.ErrOut, "Warning: '%s' is not a standard resource verb\n", v)
147 }
148 }
149 if err := c.validateResource(); err != nil {
150 return err
151 }
152 }
153
154
155 if len(c.NonResourceURLs) > 0 {
156 for _, v := range c.Verbs {
157 if !arrayContains(validNonResourceVerbs, v) {
158 return fmt.Errorf("invalid verb: '%s' for nonResourceURL", v)
159 }
160 }
161
162 for _, nonResourceURL := range c.NonResourceURLs {
163 if nonResourceURL == "*" {
164 continue
165 }
166
167 if nonResourceURL == "" || !strings.HasPrefix(nonResourceURL, "/") {
168 return fmt.Errorf("nonResourceURL should start with /")
169 }
170
171 if strings.ContainsRune(nonResourceURL[:len(nonResourceURL)-1], '*') {
172 return fmt.Errorf("nonResourceURL only supports wildcard matches when '*' is at the end")
173 }
174 }
175 }
176
177 return nil
178
179 }
180
181
182 func (c *CreateClusterRoleOptions) RunCreateRole() error {
183 clusterRole := &rbacv1.ClusterRole{
184
185 TypeMeta: metav1.TypeMeta{APIVersion: rbacv1.SchemeGroupVersion.String(), Kind: "ClusterRole"},
186 }
187 clusterRole.Name = c.Name
188
189 var err error
190 if len(c.AggregationRule) == 0 {
191 rules, err := generateResourcePolicyRules(c.Mapper, c.Verbs, c.Resources, c.ResourceNames, c.NonResourceURLs)
192 if err != nil {
193 return err
194 }
195 clusterRole.Rules = rules
196 } else {
197 clusterRole.AggregationRule = &rbacv1.AggregationRule{
198 ClusterRoleSelectors: []metav1.LabelSelector{
199 {
200 MatchLabels: c.AggregationRule,
201 },
202 },
203 }
204 }
205
206 if err := util.CreateOrUpdateAnnotation(c.CreateAnnotation, clusterRole, scheme.DefaultJSONEncoder()); err != nil {
207 return err
208 }
209
210
211 if c.DryRunStrategy != cmdutil.DryRunClient {
212 createOptions := metav1.CreateOptions{}
213 if c.FieldManager != "" {
214 createOptions.FieldManager = c.FieldManager
215 }
216 createOptions.FieldValidation = c.ValidationDirective
217 if c.DryRunStrategy == cmdutil.DryRunServer {
218 createOptions.DryRun = []string{metav1.DryRunAll}
219 }
220 clusterRole, err = c.Client.ClusterRoles().Create(context.TODO(), clusterRole, createOptions)
221 if err != nil {
222 return err
223 }
224 }
225
226 return c.PrintObj(clusterRole)
227 }
228
View as plain text