Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
K
k3s
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
k3s
Commits
a92729a3
Unverified
Commit
a92729a3
authored
Feb 12, 2019
by
Kubernetes Prow Robot
Committed by
GitHub
Feb 12, 2019
Browse files
Options
Browse Files
Download
Plain Diff
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 namespace
parents
5442980d
d4f5228b
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
226 additions
and
34 deletions
+226
-34
BUILD
pkg/kubectl/cmd/auth/BUILD
+7
-0
cani.go
pkg/kubectl/cmd/auth/cani.go
+147
-31
cani_test.go
pkg/kubectl/cmd/auth/cani_test.go
+69
-0
describe.go
pkg/kubectl/describe/versioned/describe.go
+3
-3
No files found.
pkg/kubectl/cmd/auth/BUILD
View file @
a92729a3
...
...
@@ -17,7 +17,10 @@ go_library(
],
deps = [
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/describe/versioned: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/registry/rbac/reconciliation: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/runtime: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/printers:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource:go_default_library",
...
...
@@ -60,7 +64,10 @@ go_test(
deps = [
"//pkg/kubectl/cmd/testing: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/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/rest/fake:go_default_library",
],
...
...
pkg/kubectl/cmd/auth/cani.go
View file @
a92729a3
...
...
@@ -19,18 +19,25 @@ package auth
import
(
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"sort"
"strings"
"github.com/spf13/cobra"
authorizationv1
"k8s.io/api/authorization/v1"
rbacv1
"k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
utilerrors
"k8s.io/apimachinery/pkg/util/errors"
"k8s.io/cli-runtime/pkg/genericclioptions"
authorizationv1client
"k8s.io/client-go/kubernetes/typed/authorization/v1"
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"
)
...
...
@@ -39,14 +46,16 @@ import (
type
CanIOptions
struct
{
AllNamespaces
bool
Quiet
bool
NoHeaders
bool
Namespace
string
SelfSARClient
authorizationv1client
.
SelfSubjectAccessReviewsGetter
AuthClient
authorizationv1client
.
AuthorizationV1Interface
Verb
string
Resource
schema
.
GroupVersionResource
NonResourceURL
string
Subresource
string
ResourceName
string
List
bool
genericclioptions
.
IOStreams
}
...
...
@@ -77,7 +86,10 @@ var (
kubectl auth can-i get pods --subresource=log
# 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
...
...
@@ -95,14 +107,18 @@ func NewCmdCanI(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.C
Run
:
func
(
cmd
*
cobra
.
Command
,
args
[]
string
)
{
cmdutil
.
CheckErr
(
o
.
Complete
(
f
,
args
))
cmdutil
.
CheckErr
(
o
.
Validate
())
allowed
,
err
:=
o
.
RunAccessCheck
()
if
err
==
nil
{
if
!
allowed
{
os
.
Exit
(
1
)
var
err
error
if
o
.
List
{
err
=
o
.
RunAccessList
()
}
else
{
var
allowed
bool
allowed
,
err
=
o
.
RunAccessCheck
()
if
err
==
nil
{
if
!
allowed
{
os
.
Exit
(
1
)
}
}
}
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
.
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
()
.
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
}
// Complete completes all the required options
func
(
o
*
CanIOptions
)
Complete
(
f
cmdutil
.
Factory
,
args
[]
string
)
error
{
if
o
.
Quiet
{
o
.
Out
=
ioutil
.
Discard
}
switch
len
(
args
)
{
case
2
:
o
.
Verb
=
args
[
0
]
if
strings
.
HasPrefix
(
args
[
1
],
"/"
)
{
o
.
NonResourceURL
=
args
[
1
]
break
if
o
.
List
{
if
len
(
args
)
!=
0
{
return
errors
.
New
(
"list option must be specified with no arguments"
)
}
resourceTokens
:=
strings
.
SplitN
(
args
[
1
],
"/"
,
2
)
restMapper
,
err
:=
f
.
ToRESTMapper
()
if
err
!=
nil
{
return
err
}
else
{
if
o
.
Quiet
{
o
.
Out
=
ioutil
.
Discard
}
o
.
Resource
=
o
.
resourceFor
(
restMapper
,
resourceTokens
[
0
])
if
len
(
resourceTokens
)
>
1
{
o
.
ResourceName
=
resourceTokens
[
1
]
switch
len
(
args
)
{
case
2
:
o
.
Verb
=
args
[
0
]
if
strings
.
HasPrefix
(
args
[
1
],
"/"
)
{
o
.
NonResourceURL
=
args
[
1
]
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"
)
}
default
:
return
errors
.
New
(
"you must specify two or three arguments: verb, resource, and optional resourceName"
)
}
var
err
error
...
...
@@ -144,8 +168,7 @@ func (o *CanIOptions) Complete(f cmdutil.Factory, args []string) error {
if
err
!=
nil
{
return
err
}
o
.
SelfSARClient
=
client
.
AuthorizationV1
()
o
.
AuthClient
=
client
.
AuthorizationV1
()
o
.
Namespace
=
""
if
!
o
.
AllNamespaces
{
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
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
.
Subresource
!=
""
{
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"
)
}
}
if
o
.
NoHeaders
{
return
fmt
.
Errorf
(
"--no-headers cannot be set without --list specified"
)
}
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
func
(
o
*
CanIOptions
)
RunAccessCheck
()
(
bool
,
error
)
{
var
sar
*
authorizationv1
.
SelfSubjectAccessReview
...
...
@@ -195,10 +244,9 @@ func (o *CanIOptions) RunAccessCheck() (bool, error) {
},
},
}
}
response
,
err
:=
o
.
SelfSAR
Client
.
SelfSubjectAccessReviews
()
.
Create
(
sar
)
response
,
err
:=
o
.
Auth
Client
.
SelfSubjectAccessReviews
()
.
Create
(
sar
)
if
err
!=
nil
{
return
false
,
err
}
...
...
@@ -244,3 +292,71 @@ func (o *CanIOptions) resourceFor(mapper meta.RESTMapper, resourceArg string) sc
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
}
pkg/kubectl/cmd/auth/cani_test.go
View file @
a92729a3
...
...
@@ -24,7 +24,10 @@ import (
"strings"
"testing"
authorizationv1
"k8s.io/api/authorization/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
restclient
"k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
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"
},
},
},
},
}
}
pkg/kubectl/describe/versioned/describe.go
View file @
a92729a3
...
...
@@ -2677,7 +2677,7 @@ func (d *RoleDescriber) Describe(namespace, name string, describerSettings descr
w
.
Write
(
LEVEL_1
,
"Resources
\t
Non-Resource URLs
\t
Resource Names
\t
Verbs
\n
"
)
w
.
Write
(
LEVEL_1
,
"---------
\t
-----------------
\t
--------------
\t
-----
\n
"
)
for
_
,
r
:=
range
compactRules
{
w
.
Write
(
LEVEL_1
,
"%s
\t
%v
\t
%v
\t
%v
\n
"
,
c
ombineResourceGroup
(
r
.
Resources
,
r
.
APIGroups
),
r
.
NonResourceURLs
,
r
.
ResourceNames
,
r
.
Verbs
)
w
.
Write
(
LEVEL_1
,
"%s
\t
%v
\t
%v
\t
%v
\n
"
,
C
ombineResourceGroup
(
r
.
Resources
,
r
.
APIGroups
),
r
.
NonResourceURLs
,
r
.
ResourceNames
,
r
.
Verbs
)
}
return
nil
...
...
@@ -2716,14 +2716,14 @@ func (d *ClusterRoleDescriber) Describe(namespace, name string, describerSetting
w
.
Write
(
LEVEL_1
,
"Resources
\t
Non-Resource URLs
\t
Resource Names
\t
Verbs
\n
"
)
w
.
Write
(
LEVEL_1
,
"---------
\t
-----------------
\t
--------------
\t
-----
\n
"
)
for
_
,
r
:=
range
compactRules
{
w
.
Write
(
LEVEL_1
,
"%s
\t
%v
\t
%v
\t
%v
\n
"
,
c
ombineResourceGroup
(
r
.
Resources
,
r
.
APIGroups
),
r
.
NonResourceURLs
,
r
.
ResourceNames
,
r
.
Verbs
)
w
.
Write
(
LEVEL_1
,
"%s
\t
%v
\t
%v
\t
%v
\n
"
,
C
ombineResourceGroup
(
r
.
Resources
,
r
.
APIGroups
),
r
.
NonResourceURLs
,
r
.
ResourceNames
,
r
.
Verbs
)
}
return
nil
})
}
func
c
ombineResourceGroup
(
resource
,
group
[]
string
)
string
{
func
C
ombineResourceGroup
(
resource
,
group
[]
string
)
string
{
if
len
(
resource
)
==
0
{
return
""
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment