Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions hack/make-rules/test-cmd-util.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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/*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

surprised the * didn't need quoting

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`
Expand Down
1 change: 1 addition & 0 deletions hack/verify-flags/known-flags.txt
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,7 @@ node-sync-period
no-headers
no-headers
non-masquerade-cidr
non-resource-url
no-suggestions
no-suggestions
num-nodes
Expand Down
65 changes: 63 additions & 2 deletions pkg/kubectl/cmd/create_clusterrole.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package cmd

import (
"fmt"
"io"

"github.com/spf13/cobra"
Expand All @@ -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.
Expand All @@ -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
}
Expand Down
16 changes: 13 additions & 3 deletions pkg/kubectl/cmd/create_role.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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"]}
Expand Down Expand Up @@ -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
}