mirror of https://github.com/k3s-io/k3s
Merge pull request #64820 from WanLinghao/ctl_selfsubjectrulesreview_support
Add `kubectl auth can-i --list` option which could help users know what actions they can do in specific namespacepull/564/head
commit
a92729a301
|
@ -17,7 +17,10 @@ go_library(
|
||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
"//pkg/kubectl/cmd/util:go_default_library",
|
"//pkg/kubectl/cmd/util:go_default_library",
|
||||||
|
"//pkg/kubectl/describe/versioned:go_default_library",
|
||||||
"//pkg/kubectl/scheme:go_default_library",
|
"//pkg/kubectl/scheme:go_default_library",
|
||||||
|
"//pkg/kubectl/util/printers:go_default_library",
|
||||||
|
"//pkg/kubectl/util/rbac:go_default_library",
|
||||||
"//pkg/kubectl/util/templates:go_default_library",
|
"//pkg/kubectl/util/templates:go_default_library",
|
||||||
"//pkg/registry/rbac/reconciliation:go_default_library",
|
"//pkg/registry/rbac/reconciliation:go_default_library",
|
||||||
"//staging/src/k8s.io/api/authorization/v1:go_default_library",
|
"//staging/src/k8s.io/api/authorization/v1:go_default_library",
|
||||||
|
@ -27,6 +30,7 @@ go_library(
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||||
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
|
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
|
||||||
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions/printers:go_default_library",
|
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions/printers:go_default_library",
|
||||||
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource:go_default_library",
|
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource:go_default_library",
|
||||||
|
@ -60,7 +64,10 @@ go_test(
|
||||||
deps = [
|
deps = [
|
||||||
"//pkg/kubectl/cmd/testing:go_default_library",
|
"//pkg/kubectl/cmd/testing:go_default_library",
|
||||||
"//pkg/kubectl/scheme:go_default_library",
|
"//pkg/kubectl/scheme:go_default_library",
|
||||||
|
"//staging/src/k8s.io/api/authorization/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/rest:go_default_library",
|
"//staging/src/k8s.io/client-go/rest:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/rest/fake:go_default_library",
|
"//staging/src/k8s.io/client-go/rest/fake:go_default_library",
|
||||||
],
|
],
|
||||||
|
|
|
@ -19,18 +19,25 @@ package auth
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
authorizationv1 "k8s.io/api/authorization/v1"
|
authorizationv1 "k8s.io/api/authorization/v1"
|
||||||
|
rbacv1 "k8s.io/api/rbac/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
authorizationv1client "k8s.io/client-go/kubernetes/typed/authorization/v1"
|
authorizationv1client "k8s.io/client-go/kubernetes/typed/authorization/v1"
|
||||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||||
|
describeutil "k8s.io/kubernetes/pkg/kubectl/describe/versioned"
|
||||||
|
"k8s.io/kubernetes/pkg/kubectl/util/printers"
|
||||||
|
rbacutil "k8s.io/kubernetes/pkg/kubectl/util/rbac"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/util/templates"
|
"k8s.io/kubernetes/pkg/kubectl/util/templates"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -39,14 +46,16 @@ import (
|
||||||
type CanIOptions struct {
|
type CanIOptions struct {
|
||||||
AllNamespaces bool
|
AllNamespaces bool
|
||||||
Quiet bool
|
Quiet bool
|
||||||
|
NoHeaders bool
|
||||||
Namespace string
|
Namespace string
|
||||||
SelfSARClient authorizationv1client.SelfSubjectAccessReviewsGetter
|
AuthClient authorizationv1client.AuthorizationV1Interface
|
||||||
|
|
||||||
Verb string
|
Verb string
|
||||||
Resource schema.GroupVersionResource
|
Resource schema.GroupVersionResource
|
||||||
NonResourceURL string
|
NonResourceURL string
|
||||||
Subresource string
|
Subresource string
|
||||||
ResourceName string
|
ResourceName string
|
||||||
|
List bool
|
||||||
|
|
||||||
genericclioptions.IOStreams
|
genericclioptions.IOStreams
|
||||||
}
|
}
|
||||||
|
@ -77,7 +86,10 @@ var (
|
||||||
kubectl auth can-i get pods --subresource=log
|
kubectl auth can-i get pods --subresource=log
|
||||||
|
|
||||||
# Check to see if I can access the URL /logs/
|
# Check to see if I can access the URL /logs/
|
||||||
kubectl auth can-i get /logs/`)
|
kubectl auth can-i get /logs/
|
||||||
|
|
||||||
|
# List all allowed actions in namespace "foo"
|
||||||
|
kubectl auth can-i --list --namespace=foo`)
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewCmdCanI returns an initialized Command for 'auth can-i' sub command
|
// NewCmdCanI returns an initialized Command for 'auth can-i' sub command
|
||||||
|
@ -95,14 +107,18 @@ func NewCmdCanI(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.C
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
cmdutil.CheckErr(o.Complete(f, args))
|
cmdutil.CheckErr(o.Complete(f, args))
|
||||||
cmdutil.CheckErr(o.Validate())
|
cmdutil.CheckErr(o.Validate())
|
||||||
|
var err error
|
||||||
allowed, err := o.RunAccessCheck()
|
if o.List {
|
||||||
if err == nil {
|
err = o.RunAccessList()
|
||||||
if !allowed {
|
} else {
|
||||||
os.Exit(1)
|
var allowed bool
|
||||||
|
allowed, err = o.RunAccessCheck()
|
||||||
|
if err == nil {
|
||||||
|
if !allowed {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdutil.CheckErr(err)
|
cmdutil.CheckErr(err)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -110,33 +126,41 @@ func NewCmdCanI(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.C
|
||||||
cmd.Flags().BoolVarP(&o.AllNamespaces, "all-namespaces", "A", o.AllNamespaces, "If true, check the specified action in all namespaces.")
|
cmd.Flags().BoolVarP(&o.AllNamespaces, "all-namespaces", "A", o.AllNamespaces, "If true, check the specified action in all namespaces.")
|
||||||
cmd.Flags().BoolVarP(&o.Quiet, "quiet", "q", o.Quiet, "If true, suppress output and just return the exit code.")
|
cmd.Flags().BoolVarP(&o.Quiet, "quiet", "q", o.Quiet, "If true, suppress output and just return the exit code.")
|
||||||
cmd.Flags().StringVar(&o.Subresource, "subresource", o.Subresource, "SubResource such as pod/log or deployment/scale")
|
cmd.Flags().StringVar(&o.Subresource, "subresource", o.Subresource, "SubResource such as pod/log or deployment/scale")
|
||||||
|
cmd.Flags().BoolVar(&o.List, "list", o.List, "If true, prints all allowed actions.")
|
||||||
|
cmd.Flags().BoolVar(&o.NoHeaders, "no-headers", o.NoHeaders, "If true, prints allowed actions without headers")
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complete completes all the required options
|
// Complete completes all the required options
|
||||||
func (o *CanIOptions) Complete(f cmdutil.Factory, args []string) error {
|
func (o *CanIOptions) Complete(f cmdutil.Factory, args []string) error {
|
||||||
if o.Quiet {
|
if o.List {
|
||||||
o.Out = ioutil.Discard
|
if len(args) != 0 {
|
||||||
}
|
return errors.New("list option must be specified with no arguments")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if o.Quiet {
|
||||||
|
o.Out = ioutil.Discard
|
||||||
|
}
|
||||||
|
|
||||||
switch len(args) {
|
switch len(args) {
|
||||||
case 2:
|
case 2:
|
||||||
o.Verb = args[0]
|
o.Verb = args[0]
|
||||||
if strings.HasPrefix(args[1], "/") {
|
if strings.HasPrefix(args[1], "/") {
|
||||||
o.NonResourceURL = args[1]
|
o.NonResourceURL = args[1]
|
||||||
break
|
break
|
||||||
|
}
|
||||||
|
resourceTokens := strings.SplitN(args[1], "/", 2)
|
||||||
|
restMapper, err := f.ToRESTMapper()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
o.Resource = o.resourceFor(restMapper, resourceTokens[0])
|
||||||
|
if len(resourceTokens) > 1 {
|
||||||
|
o.ResourceName = resourceTokens[1]
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.New("you must specify two or three arguments: verb, resource, and optional resourceName")
|
||||||
}
|
}
|
||||||
resourceTokens := strings.SplitN(args[1], "/", 2)
|
|
||||||
restMapper, err := f.ToRESTMapper()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
o.Resource = o.resourceFor(restMapper, resourceTokens[0])
|
|
||||||
if len(resourceTokens) > 1 {
|
|
||||||
o.ResourceName = resourceTokens[1]
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.New("you must specify two or three arguments: verb, resource, and optional resourceName")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
@ -144,8 +168,7 @@ func (o *CanIOptions) Complete(f cmdutil.Factory, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
o.SelfSARClient = client.AuthorizationV1()
|
o.AuthClient = client.AuthorizationV1()
|
||||||
|
|
||||||
o.Namespace = ""
|
o.Namespace = ""
|
||||||
if !o.AllNamespaces {
|
if !o.AllNamespaces {
|
||||||
o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
|
o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
|
||||||
|
@ -159,6 +182,13 @@ func (o *CanIOptions) Complete(f cmdutil.Factory, args []string) error {
|
||||||
|
|
||||||
// Validate makes sure provided values for CanIOptions are valid
|
// Validate makes sure provided values for CanIOptions are valid
|
||||||
func (o *CanIOptions) Validate() error {
|
func (o *CanIOptions) Validate() error {
|
||||||
|
if o.List {
|
||||||
|
if o.Quiet || o.AllNamespaces || o.Subresource != "" {
|
||||||
|
return errors.New("list option can't be specified with neither quiet, all-namespaces nor subresource options")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if o.NonResourceURL != "" {
|
if o.NonResourceURL != "" {
|
||||||
if o.Subresource != "" {
|
if o.Subresource != "" {
|
||||||
return fmt.Errorf("--subresource can not be used with NonResourceURL")
|
return fmt.Errorf("--subresource can not be used with NonResourceURL")
|
||||||
|
@ -167,9 +197,28 @@ func (o *CanIOptions) Validate() error {
|
||||||
return fmt.Errorf("NonResourceURL and ResourceName can not specified together")
|
return fmt.Errorf("NonResourceURL and ResourceName can not specified together")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if o.NoHeaders {
|
||||||
|
return fmt.Errorf("--no-headers cannot be set without --list specified")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunAccessList lists all the access current user has
|
||||||
|
func (o *CanIOptions) RunAccessList() error {
|
||||||
|
sar := &authorizationv1.SelfSubjectRulesReview{
|
||||||
|
Spec: authorizationv1.SelfSubjectRulesReviewSpec{
|
||||||
|
Namespace: o.Namespace,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
response, err := o.AuthClient.SelfSubjectRulesReviews().Create(sar)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return o.printStatus(response.Status)
|
||||||
|
}
|
||||||
|
|
||||||
// RunAccessCheck checks if user has access to a certain resource or non resource URL
|
// RunAccessCheck checks if user has access to a certain resource or non resource URL
|
||||||
func (o *CanIOptions) RunAccessCheck() (bool, error) {
|
func (o *CanIOptions) RunAccessCheck() (bool, error) {
|
||||||
var sar *authorizationv1.SelfSubjectAccessReview
|
var sar *authorizationv1.SelfSubjectAccessReview
|
||||||
|
@ -195,10 +244,9 @@ func (o *CanIOptions) RunAccessCheck() (bool, error) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := o.SelfSARClient.SelfSubjectAccessReviews().Create(sar)
|
response, err := o.AuthClient.SelfSubjectAccessReviews().Create(sar)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -244,3 +292,71 @@ func (o *CanIOptions) resourceFor(mapper meta.RESTMapper, resourceArg string) sc
|
||||||
|
|
||||||
return gvr
|
return gvr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *CanIOptions) printStatus(status authorizationv1.SubjectRulesReviewStatus) error {
|
||||||
|
if status.Incomplete {
|
||||||
|
fmt.Fprintf(o.ErrOut, "warning: the list may be incomplete: %v\n", status.EvaluationError)
|
||||||
|
}
|
||||||
|
|
||||||
|
breakdownRules := []rbacv1.PolicyRule{}
|
||||||
|
for _, rule := range convertToPolicyRule(status) {
|
||||||
|
breakdownRules = append(breakdownRules, rbacutil.BreakdownRule(rule)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
compactRules, err := rbacutil.CompactRules(breakdownRules)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sort.Stable(rbacutil.SortableRuleSlice(compactRules))
|
||||||
|
|
||||||
|
w := printers.GetNewTabWriter(o.Out)
|
||||||
|
defer w.Flush()
|
||||||
|
|
||||||
|
allErrs := []error{}
|
||||||
|
if !o.NoHeaders {
|
||||||
|
if err := printAccessHeaders(w); err != nil {
|
||||||
|
allErrs = append(allErrs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := printAccess(w, compactRules); err != nil {
|
||||||
|
allErrs = append(allErrs, err)
|
||||||
|
}
|
||||||
|
return utilerrors.NewAggregate(allErrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertToPolicyRule(status authorizationv1.SubjectRulesReviewStatus) []rbacv1.PolicyRule {
|
||||||
|
ret := []rbacv1.PolicyRule{}
|
||||||
|
for _, resource := range status.ResourceRules {
|
||||||
|
ret = append(ret, rbacv1.PolicyRule{
|
||||||
|
Verbs: resource.Verbs,
|
||||||
|
APIGroups: resource.APIGroups,
|
||||||
|
Resources: resource.Resources,
|
||||||
|
ResourceNames: resource.ResourceNames,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, nonResource := range status.NonResourceRules {
|
||||||
|
ret = append(ret, rbacv1.PolicyRule{
|
||||||
|
Verbs: nonResource.Verbs,
|
||||||
|
NonResourceURLs: nonResource.NonResourceURLs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func printAccessHeaders(out io.Writer) error {
|
||||||
|
columnNames := []string{"Resources", "Non-Resource URLs", "Resource Names", "Verbs"}
|
||||||
|
_, err := fmt.Fprintf(out, "%s\n", strings.Join(columnNames, "\t"))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func printAccess(out io.Writer, rules []rbacv1.PolicyRule) error {
|
||||||
|
for _, r := range rules {
|
||||||
|
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 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -24,7 +24,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
authorizationv1 "k8s.io/api/authorization/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
"k8s.io/client-go/rest/fake"
|
"k8s.io/client-go/rest/fake"
|
||||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||||
|
@ -181,3 +184,69 @@ func TestRunAccessCheck(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunAccessList(t *testing.T) {
|
||||||
|
t.Run("test access list", func(t *testing.T) {
|
||||||
|
options := &CanIOptions{List: true}
|
||||||
|
expectedOutput := "Resources Non-Resource URLs Resource Names Verbs\n" +
|
||||||
|
"job.* [] [test-resource] [get list]\n" +
|
||||||
|
"pod.* [] [test-resource] [get list]\n" +
|
||||||
|
" [/apis/*] [] [get]\n" +
|
||||||
|
" [/version] [] [get]\n"
|
||||||
|
|
||||||
|
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||||
|
defer tf.Cleanup()
|
||||||
|
|
||||||
|
ns := scheme.Codecs
|
||||||
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
|
|
||||||
|
tf.Client = &fake.RESTClient{
|
||||||
|
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
|
||||||
|
NegotiatedSerializer: ns,
|
||||||
|
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
switch req.URL.Path {
|
||||||
|
case "/apis/authorization.k8s.io/v1/selfsubjectrulesreviews":
|
||||||
|
body := ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, getSelfSubjectRulesReview()))))
|
||||||
|
return &http.Response{StatusCode: http.StatusOK, Body: body}, nil
|
||||||
|
default:
|
||||||
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||||
|
options.IOStreams = ioStreams
|
||||||
|
if err := options.Complete(tf, []string{}); err != nil {
|
||||||
|
t.Errorf("got unexpected error when do Complete(): %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := options.RunAccessList()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("got unexpected error when do RunAccessList(): %v", err)
|
||||||
|
} else if buf.String() != expectedOutput {
|
||||||
|
t.Errorf("expected %v\n but got %v\n", expectedOutput, buf.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSelfSubjectRulesReview() *authorizationv1.SelfSubjectRulesReview {
|
||||||
|
return &authorizationv1.SelfSubjectRulesReview{
|
||||||
|
Status: authorizationv1.SubjectRulesReviewStatus{
|
||||||
|
ResourceRules: []authorizationv1.ResourceRule{
|
||||||
|
{
|
||||||
|
Verbs: []string{"get", "list"},
|
||||||
|
APIGroups: []string{"*"},
|
||||||
|
Resources: []string{"pod", "job"},
|
||||||
|
ResourceNames: []string{"test-resource"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NonResourceRules: []authorizationv1.NonResourceRule{
|
||||||
|
{
|
||||||
|
Verbs: []string{"get"},
|
||||||
|
NonResourceURLs: []string{"/apis/*", "/version"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2677,7 +2677,7 @@ func (d *RoleDescriber) Describe(namespace, name string, describerSettings descr
|
||||||
w.Write(LEVEL_1, "Resources\tNon-Resource URLs\tResource Names\tVerbs\n")
|
w.Write(LEVEL_1, "Resources\tNon-Resource URLs\tResource Names\tVerbs\n")
|
||||||
w.Write(LEVEL_1, "---------\t-----------------\t--------------\t-----\n")
|
w.Write(LEVEL_1, "---------\t-----------------\t--------------\t-----\n")
|
||||||
for _, r := range compactRules {
|
for _, r := range compactRules {
|
||||||
w.Write(LEVEL_1, "%s\t%v\t%v\t%v\n", combineResourceGroup(r.Resources, r.APIGroups), r.NonResourceURLs, r.ResourceNames, r.Verbs)
|
w.Write(LEVEL_1, "%s\t%v\t%v\t%v\n", CombineResourceGroup(r.Resources, r.APIGroups), r.NonResourceURLs, r.ResourceNames, r.Verbs)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -2716,14 +2716,14 @@ func (d *ClusterRoleDescriber) Describe(namespace, name string, describerSetting
|
||||||
w.Write(LEVEL_1, "Resources\tNon-Resource URLs\tResource Names\tVerbs\n")
|
w.Write(LEVEL_1, "Resources\tNon-Resource URLs\tResource Names\tVerbs\n")
|
||||||
w.Write(LEVEL_1, "---------\t-----------------\t--------------\t-----\n")
|
w.Write(LEVEL_1, "---------\t-----------------\t--------------\t-----\n")
|
||||||
for _, r := range compactRules {
|
for _, r := range compactRules {
|
||||||
w.Write(LEVEL_1, "%s\t%v\t%v\t%v\n", combineResourceGroup(r.Resources, r.APIGroups), r.NonResourceURLs, r.ResourceNames, r.Verbs)
|
w.Write(LEVEL_1, "%s\t%v\t%v\t%v\n", CombineResourceGroup(r.Resources, r.APIGroups), r.NonResourceURLs, r.ResourceNames, r.Verbs)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func combineResourceGroup(resource, group []string) string {
|
func CombineResourceGroup(resource, group []string) string {
|
||||||
if len(resource) == 0 {
|
if len(resource) == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue