From 93e50b167e20648762e69791830d639d2e1833c6 Mon Sep 17 00:00:00 2001 From: Cao Shufeng Date: Mon, 15 May 2017 14:04:29 +0800 Subject: [PATCH] support NonResourceURL for kubectl create clusterrole --- hack/make-rules/test-cmd-util.sh | 3 ++ hack/verify-flags/known-flags.txt | 1 + pkg/kubectl/cmd/create_clusterrole.go | 65 ++++++++++++++++++++++++++- pkg/kubectl/cmd/create_role.go | 16 +++++-- 4 files changed, 80 insertions(+), 5 deletions(-) diff --git a/hack/make-rules/test-cmd-util.sh b/hack/make-rules/test-cmd-util.sh index 5fe2ee218d5e8..df17b1a38dcf9 100644 --- a/hack/make-rules/test-cmd-util.sh +++ b/hack/make-rules/test-cmd-util.sh @@ -3125,6 +3125,9 @@ runTests() { kube::test::get_object_assert clusterrole/resourcename-reader "{{range.rules}}{{range.resources}}{{.}}:{{end}}{{end}}" 'pods:' kube::test::get_object_assert clusterrole/resourcename-reader "{{range.rules}}{{range.apiGroups}}{{.}}:{{end}}{{end}}" ':' kube::test::get_object_assert clusterrole/resourcename-reader "{{range.rules}}{{range.resourceNames}}{{.}}:{{end}}{{end}}" 'foo:' + kubectl create "${kube_flags[@]}" clusterrole url-reader --verb=get --non-resource-url=/logs/* --non-resource-url=/healthz/* + kube::test::get_object_assert clusterrole/url-reader "{{range.rules}}{{range.verbs}}{{.}}:{{end}}{{end}}" 'get:' + kube::test::get_object_assert clusterrole/url-reader "{{range.rules}}{{range.nonResourceURLs}}{{.}}:{{end}}{{end}}" '/logs/\*:/healthz/\*:' # test `kubectl create rolebinding/clusterrolebinding` # test `kubectl set subject rolebinding/clusterrolebinding` diff --git a/hack/verify-flags/known-flags.txt b/hack/verify-flags/known-flags.txt index cf0b1e31268f8..64f8bc04e1d30 100644 --- a/hack/verify-flags/known-flags.txt +++ b/hack/verify-flags/known-flags.txt @@ -508,6 +508,7 @@ node-sync-period no-headers no-headers non-masquerade-cidr +non-resource-url no-suggestions no-suggestions num-nodes diff --git a/pkg/kubectl/cmd/create_clusterrole.go b/pkg/kubectl/cmd/create_clusterrole.go index a4b27cd4a8851..9ab572871b729 100644 --- a/pkg/kubectl/cmd/create_clusterrole.go +++ b/pkg/kubectl/cmd/create_clusterrole.go @@ -17,6 +17,7 @@ limitations under the License. package cmd import ( + "fmt" "io" "github.com/spf13/cobra" @@ -42,11 +43,18 @@ var ( kubectl create clusterrole foo --verb=get,list,watch --resource=rs.extensions # Create a ClusterRole named "foo" with SubResource specified - kubectl create clusterrole foo --verb=get,list,watch --resource=pods,pods/status`)) + kubectl create clusterrole foo --verb=get,list,watch --resource=pods,pods/status + + # Create a ClusterRole name "foo" with NonResourceURL specified + kubectl create clusterrole "foo" --verb=get --non-resource-url=/logs/*`)) + + // Valid nonResource verb list for validation. + validNonResourceVerbs = []string{"*", "get", "post", "put", "delete", "patch", "head", "options"} ) type CreateClusterRoleOptions struct { *CreateRoleOptions + NonResourceURLs []string } // ClusterRole is a command to ease creating ClusterRoles. @@ -72,16 +80,69 @@ func NewCmdCreateClusterRole(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command cmdutil.AddPrinterFlags(cmd) cmdutil.AddDryRunFlag(cmd) cmd.Flags().StringSliceVar(&c.Verbs, "verb", []string{}, "verb that applies to the resources contained in the rule") + cmd.Flags().StringSliceVar(&c.NonResourceURLs, "non-resource-url", []string{}, "a partial url that user should have access to.") cmd.Flags().StringSlice("resource", []string{}, "resource that the rule applies to") cmd.Flags().StringArrayVar(&c.ResourceNames, "resource-name", []string{}, "resource in the white list that the rule applies to, repeat this flag for multiple items") return cmd } +func (c *CreateClusterRoleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + // Remove duplicate nonResourceURLs + nonResourceURLs := []string{} + for _, n := range c.NonResourceURLs { + if !arrayContains(nonResourceURLs, n) { + nonResourceURLs = append(nonResourceURLs, n) + } + } + c.NonResourceURLs = nonResourceURLs + + return c.CreateRoleOptions.Complete(f, cmd, args) +} + +func (c *CreateClusterRoleOptions) Validate() error { + if c.Name == "" { + return fmt.Errorf("name must be specified") + } + + // validate verbs. + if len(c.Verbs) == 0 { + return fmt.Errorf("at least one verb must be specified") + } + + if len(c.Resources) == 0 && len(c.NonResourceURLs) == 0 { + return fmt.Errorf("one of resource or nonResourceURL must be specified") + } + + // validate resources + if len(c.Resources) > 0 { + for _, v := range c.Verbs { + if !arrayContains(validResourceVerbs, v) { + return fmt.Errorf("invalid verb: '%s'", v) + } + } + if err := c.validateResource(); err != nil { + return err + } + } + + //validate non-resource-url + if len(c.NonResourceURLs) > 0 { + for _, v := range c.Verbs { + if !arrayContains(validNonResourceVerbs, v) { + return fmt.Errorf("invalid verb: '%s' for nonResourceURL", v) + } + } + } + + return nil + +} + func (c *CreateClusterRoleOptions) RunCreateRole() error { clusterRole := &rbac.ClusterRole{} clusterRole.Name = c.Name - rules, err := generateResourcePolicyRules(c.Mapper, c.Verbs, c.Resources, c.ResourceNames) + rules, err := generateResourcePolicyRules(c.Mapper, c.Verbs, c.Resources, c.ResourceNames, c.NonResourceURLs) if err != nil { return err } diff --git a/pkg/kubectl/cmd/create_role.go b/pkg/kubectl/cmd/create_role.go index 4a2408696b674..0baf88ee9bca5 100644 --- a/pkg/kubectl/cmd/create_role.go +++ b/pkg/kubectl/cmd/create_role.go @@ -232,6 +232,10 @@ func (c *CreateRoleOptions) Validate() error { return fmt.Errorf("at least one resource must be specified") } + return c.validateResource() +} + +func (c *CreateRoleOptions) validateResource() error { for _, r := range c.Resources { if len(r.Resource) == 0 { return fmt.Errorf("resource must be specified if apiGroup/subresource specified") @@ -263,14 +267,13 @@ func (c *CreateRoleOptions) Validate() error { return err } } - return nil } func (c *CreateRoleOptions) RunCreateRole() error { role := &rbac.Role{} role.Name = c.Name - rules, err := generateResourcePolicyRules(c.Mapper, c.Verbs, c.Resources, c.ResourceNames) + rules, err := generateResourcePolicyRules(c.Mapper, c.Verbs, c.Resources, c.ResourceNames, []string{}) if err != nil { return err } @@ -301,7 +304,7 @@ func arrayContains(s []string, e string) bool { return false } -func generateResourcePolicyRules(mapper meta.RESTMapper, verbs []string, resources []ResourceOptions, resourceNames []string) ([]rbac.PolicyRule, error) { +func generateResourcePolicyRules(mapper meta.RESTMapper, verbs []string, resources []ResourceOptions, resourceNames []string, nonResourceURLs []string) ([]rbac.PolicyRule, error) { // groupResourceMapping is a apigroup-resource map. The key of this map is api group, while the value // is a string array of resources under this api group. // E.g. groupResourceMapping = {"extensions": ["replicasets", "deployments"], "batch":["jobs"]} @@ -337,5 +340,12 @@ func generateResourcePolicyRules(mapper meta.RESTMapper, verbs []string, resourc rules = append(rules, rule) } + if len(nonResourceURLs) > 0 { + rule := rbac.PolicyRule{} + rule.Verbs = verbs + rule.NonResourceURLs = nonResourceURLs + rules = append(rules, rule) + } + return rules, nil }