mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 09:22:44 +00:00
Merge pull request #41077 from deads2k/cli-01-cani
Automatic merge from submit-queue (batch tested with PRs 41814, 41922, 41957, 41406, 41077) add kubectl can-i to see if you can perform an action Adds `kubectl auth can-i <verb> <resource> [<name>]` so that a user can see if they are allowed to perform an action. @kubernetes/sig-cli-pr-reviews @fabianofranz This particular command satisfies the immediate need of knowing if you can perform an action without trying that action. When using RBAC in a script that is adding permissions, there is a lag between adding the permission and the permission being realized in the RBAC cache. As a user on the CLI, you almost never see it, but as a script adding a binding and then using that new power, you hit it quite often. There are natural follow-ons to the same area (hence the `auth` subcommand) to figure out if someone else can perform an action, what actions you can perform in total, and who can perform a given action. Someone else is an API we have already, what-can-i-do was a proposed API a while back and a very useful one for interfaces, and who-can is common question if someone is administering a namespace.
This commit is contained in:
commit
a1490926d6
@ -15,6 +15,8 @@ docs/man/man1/kubectl-api-versions.1
|
|||||||
docs/man/man1/kubectl-apply-view-last-applied.1
|
docs/man/man1/kubectl-apply-view-last-applied.1
|
||||||
docs/man/man1/kubectl-apply.1
|
docs/man/man1/kubectl-apply.1
|
||||||
docs/man/man1/kubectl-attach.1
|
docs/man/man1/kubectl-attach.1
|
||||||
|
docs/man/man1/kubectl-auth-can-i.1
|
||||||
|
docs/man/man1/kubectl-auth.1
|
||||||
docs/man/man1/kubectl-autoscale.1
|
docs/man/man1/kubectl-autoscale.1
|
||||||
docs/man/man1/kubectl-certificate-approve.1
|
docs/man/man1/kubectl-certificate-approve.1
|
||||||
docs/man/man1/kubectl-certificate-deny.1
|
docs/man/man1/kubectl-certificate-deny.1
|
||||||
@ -101,6 +103,8 @@ docs/user-guide/kubectl/kubectl_api-versions.md
|
|||||||
docs/user-guide/kubectl/kubectl_apply.md
|
docs/user-guide/kubectl/kubectl_apply.md
|
||||||
docs/user-guide/kubectl/kubectl_apply_view-last-applied.md
|
docs/user-guide/kubectl/kubectl_apply_view-last-applied.md
|
||||||
docs/user-guide/kubectl/kubectl_attach.md
|
docs/user-guide/kubectl/kubectl_attach.md
|
||||||
|
docs/user-guide/kubectl/kubectl_auth.md
|
||||||
|
docs/user-guide/kubectl/kubectl_auth_can-i.md
|
||||||
docs/user-guide/kubectl/kubectl_autoscale.md
|
docs/user-guide/kubectl/kubectl_autoscale.md
|
||||||
docs/user-guide/kubectl/kubectl_certificate.md
|
docs/user-guide/kubectl/kubectl_certificate.md
|
||||||
docs/user-guide/kubectl/kubectl_certificate_approve.md
|
docs/user-guide/kubectl/kubectl_certificate_approve.md
|
||||||
@ -183,6 +187,7 @@ docs/yaml/kubectl/kubectl_annotate.yaml
|
|||||||
docs/yaml/kubectl/kubectl_api-versions.yaml
|
docs/yaml/kubectl/kubectl_api-versions.yaml
|
||||||
docs/yaml/kubectl/kubectl_apply.yaml
|
docs/yaml/kubectl/kubectl_apply.yaml
|
||||||
docs/yaml/kubectl/kubectl_attach.yaml
|
docs/yaml/kubectl/kubectl_attach.yaml
|
||||||
|
docs/yaml/kubectl/kubectl_auth.yaml
|
||||||
docs/yaml/kubectl/kubectl_autoscale.yaml
|
docs/yaml/kubectl/kubectl_autoscale.yaml
|
||||||
docs/yaml/kubectl/kubectl_certificate.yaml
|
docs/yaml/kubectl/kubectl_certificate.yaml
|
||||||
docs/yaml/kubectl/kubectl_cluster-info.yaml
|
docs/yaml/kubectl/kubectl_cluster-info.yaml
|
||||||
|
3
docs/man/man1/kubectl-auth-can-i.1
Normal file
3
docs/man/man1/kubectl-auth-can-i.1
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
This file is autogenerated, but we've stopped checking such files into the
|
||||||
|
repository to reduce the need for rebases. Please run hack/generate-docs.sh to
|
||||||
|
populate this file.
|
3
docs/man/man1/kubectl-auth.1
Normal file
3
docs/man/man1/kubectl-auth.1
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
This file is autogenerated, but we've stopped checking such files into the
|
||||||
|
repository to reduce the need for rebases. Please run hack/generate-docs.sh to
|
||||||
|
populate this file.
|
3
docs/user-guide/kubectl/kubectl_auth.md
Normal file
3
docs/user-guide/kubectl/kubectl_auth.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
This file is autogenerated, but we've stopped checking such files into the
|
||||||
|
repository to reduce the need for rebases. Please run hack/generate-docs.sh to
|
||||||
|
populate this file.
|
3
docs/user-guide/kubectl/kubectl_auth_can-i.md
Normal file
3
docs/user-guide/kubectl/kubectl_auth_can-i.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
This file is autogenerated, but we've stopped checking such files into the
|
||||||
|
repository to reduce the need for rebases. Please run hack/generate-docs.sh to
|
||||||
|
populate this file.
|
3
docs/yaml/kubectl/kubectl_auth.yaml
Normal file
3
docs/yaml/kubectl/kubectl_auth.yaml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
This file is autogenerated, but we've stopped checking such files into the
|
||||||
|
repository to reduce the need for rebases. Please run hack/generate-docs.sh to
|
||||||
|
populate this file.
|
@ -510,6 +510,7 @@ pv-recycler-pod-template-filepath-hostpath
|
|||||||
pv-recycler-pod-template-filepath-nfs
|
pv-recycler-pod-template-filepath-nfs
|
||||||
pv-recycler-timeout-increment-hostpath
|
pv-recycler-timeout-increment-hostpath
|
||||||
pvclaimbinder-sync-period
|
pvclaimbinder-sync-period
|
||||||
|
quiet
|
||||||
read-only-port
|
read-only-port
|
||||||
really-crash-for-testing
|
really-crash-for-testing
|
||||||
reconcile-cidr
|
reconcile-cidr
|
||||||
|
@ -80,6 +80,7 @@ go_library(
|
|||||||
"//pkg/client/unversioned:go_default_library",
|
"//pkg/client/unversioned:go_default_library",
|
||||||
"//pkg/client/unversioned/remotecommand:go_default_library",
|
"//pkg/client/unversioned/remotecommand:go_default_library",
|
||||||
"//pkg/kubectl:go_default_library",
|
"//pkg/kubectl:go_default_library",
|
||||||
|
"//pkg/kubectl/cmd/auth:go_default_library",
|
||||||
"//pkg/kubectl/cmd/config:go_default_library",
|
"//pkg/kubectl/cmd/config:go_default_library",
|
||||||
"//pkg/kubectl/cmd/rollout:go_default_library",
|
"//pkg/kubectl/cmd/rollout:go_default_library",
|
||||||
"//pkg/kubectl/cmd/set:go_default_library",
|
"//pkg/kubectl/cmd/set:go_default_library",
|
||||||
@ -243,6 +244,7 @@ filegroup(
|
|||||||
name = "all-srcs",
|
name = "all-srcs",
|
||||||
srcs = [
|
srcs = [
|
||||||
":package-srcs",
|
":package-srcs",
|
||||||
|
"//pkg/kubectl/cmd/auth:all-srcs",
|
||||||
"//pkg/kubectl/cmd/config:all-srcs",
|
"//pkg/kubectl/cmd/config:all-srcs",
|
||||||
"//pkg/kubectl/cmd/rollout:all-srcs",
|
"//pkg/kubectl/cmd/rollout:all-srcs",
|
||||||
"//pkg/kubectl/cmd/set:all-srcs",
|
"//pkg/kubectl/cmd/set:all-srcs",
|
||||||
|
54
pkg/kubectl/cmd/auth/BUILD
Normal file
54
pkg/kubectl/cmd/auth/BUILD
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_library",
|
||||||
|
"go_test",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"auth.go",
|
||||||
|
"cani.go",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/apis/authorization:go_default_library",
|
||||||
|
"//pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion:go_default_library",
|
||||||
|
"//pkg/kubectl/cmd/templates:go_default_library",
|
||||||
|
"//pkg/kubectl/cmd/util:go_default_library",
|
||||||
|
"//vendor:github.com/spf13/cobra",
|
||||||
|
"//vendor:k8s.io/apimachinery/pkg/api/meta",
|
||||||
|
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||||
|
"//vendor:k8s.io/apimachinery/pkg/util/errors",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["cani_test.go"],
|
||||||
|
library = ":go_default_library",
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api:go_default_library",
|
||||||
|
"//pkg/kubectl/cmd/testing:go_default_library",
|
||||||
|
"//vendor:k8s.io/client-go/rest",
|
||||||
|
"//vendor:k8s.io/client-go/rest/fake",
|
||||||
|
],
|
||||||
|
)
|
39
pkg/kubectl/cmd/auth/auth.go
Normal file
39
pkg/kubectl/cmd/auth/auth.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCmdAuth(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
|
||||||
|
// Parent command to which all subcommands are added.
|
||||||
|
cmds := &cobra.Command{
|
||||||
|
Use: "auth",
|
||||||
|
Short: "Inspect authorization",
|
||||||
|
Long: `Inspect authorization`,
|
||||||
|
Run: cmdutil.DefaultSubCommandRun(errOut),
|
||||||
|
}
|
||||||
|
|
||||||
|
cmds.AddCommand(NewCmdCanI(f, out, errOut))
|
||||||
|
|
||||||
|
return cmds
|
||||||
|
}
|
196
pkg/kubectl/cmd/auth/cani.go
Normal file
196
pkg/kubectl/cmd/auth/cani.go
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||||
|
authorizationapi "k8s.io/kubernetes/pkg/apis/authorization"
|
||||||
|
internalauthorizationclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion"
|
||||||
|
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||||
|
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CanIOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
|
||||||
|
// referencing the cmd.Flags()
|
||||||
|
type CanIOptions struct {
|
||||||
|
AllNamespaces bool
|
||||||
|
Quiet bool
|
||||||
|
Namespace string
|
||||||
|
SelfSARClient internalauthorizationclient.SelfSubjectAccessReviewsGetter
|
||||||
|
|
||||||
|
Verb string
|
||||||
|
Resource schema.GroupVersionResource
|
||||||
|
ResourceName string
|
||||||
|
|
||||||
|
Out io.Writer
|
||||||
|
Err io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
canILong = templates.LongDesc(`
|
||||||
|
Check whether an action is allowed.
|
||||||
|
|
||||||
|
VERB is a logical Kubernetes API verb like 'get', 'list', 'watch', 'delete', etc.
|
||||||
|
TYPE is a Kubernetes resource. Shortcuts and groups will be resolved.
|
||||||
|
NAME is the name of a particular Kubernetes resource.`)
|
||||||
|
|
||||||
|
canIExample = templates.Examples(`
|
||||||
|
# Check to see if I can create pods in any namespace
|
||||||
|
kubectl auth can-i create pods --all-namespaces
|
||||||
|
|
||||||
|
# Check to see if I can list deployments in my current namespace
|
||||||
|
kubectl auth can-i list deployments.extensions
|
||||||
|
|
||||||
|
# Check to see if I can get the job named "bar" in namespace "foo"
|
||||||
|
kubectl auth can-i list jobs.batch/bar -n foo`)
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCmdCanI(f cmdutil.Factory, out, err io.Writer) *cobra.Command {
|
||||||
|
o := &CanIOptions{
|
||||||
|
Out: out,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "can-i VERB [TYPE | TYPE/NAME]",
|
||||||
|
Short: "Check whether an action is allowed",
|
||||||
|
Long: canILong,
|
||||||
|
Example: canIExample,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
cmdutil.CheckErr(o.Complete(f, args))
|
||||||
|
cmdutil.CheckErr(o.Validate())
|
||||||
|
|
||||||
|
allowed, err := o.RunAccessCheck()
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.Quiet && !allowed {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdutil.CheckErr(err)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().BoolVar(&o.AllNamespaces, "all-namespaces", 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.")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *CanIOptions) Complete(f cmdutil.Factory, args []string) error {
|
||||||
|
switch len(args) {
|
||||||
|
case 2:
|
||||||
|
resourceTokens := strings.SplitN(args[1], "/", 2)
|
||||||
|
restMapper, _ := f.Object()
|
||||||
|
o.Verb = args[0]
|
||||||
|
o.Resource = 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
|
||||||
|
client, err := f.ClientSet()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
o.SelfSARClient = client.Authorization()
|
||||||
|
|
||||||
|
o.Namespace = ""
|
||||||
|
if !o.AllNamespaces {
|
||||||
|
o.Namespace, _, err = f.DefaultNamespace()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.Quiet {
|
||||||
|
o.Out = ioutil.Discard
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *CanIOptions) Validate() error {
|
||||||
|
errors := []error{}
|
||||||
|
return utilerrors.NewAggregate(errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *CanIOptions) RunAccessCheck() (bool, error) {
|
||||||
|
sar := &authorizationapi.SelfSubjectAccessReview{
|
||||||
|
Spec: authorizationapi.SelfSubjectAccessReviewSpec{
|
||||||
|
ResourceAttributes: &authorizationapi.ResourceAttributes{
|
||||||
|
Namespace: o.Namespace,
|
||||||
|
Verb: o.Verb,
|
||||||
|
Group: o.Resource.Group,
|
||||||
|
Resource: o.Resource.Resource,
|
||||||
|
Name: o.ResourceName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := o.SelfSARClient.SelfSubjectAccessReviews().Create(sar)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.Status.Allowed {
|
||||||
|
fmt.Fprintln(o.Out, "yes")
|
||||||
|
} else {
|
||||||
|
fmt.Fprint(o.Out, "no")
|
||||||
|
if len(response.Status.Reason) > 0 {
|
||||||
|
fmt.Fprintf(o.Out, " - %v", response.Status.Reason)
|
||||||
|
}
|
||||||
|
if len(response.Status.EvaluationError) > 0 {
|
||||||
|
fmt.Fprintf(o.Out, " - %v", response.Status.EvaluationError)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(o.Out)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Status.Allowed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceFor(mapper meta.RESTMapper, resourceArg string) schema.GroupVersionResource {
|
||||||
|
fullySpecifiedGVR, groupResource := schema.ParseResourceArg(strings.ToLower(resourceArg))
|
||||||
|
gvr := schema.GroupVersionResource{}
|
||||||
|
if fullySpecifiedGVR != nil {
|
||||||
|
gvr, _ = mapper.ResourceFor(*fullySpecifiedGVR)
|
||||||
|
}
|
||||||
|
if gvr.Empty() {
|
||||||
|
var err error
|
||||||
|
gvr, err = mapper.ResourceFor(groupResource.WithVersion(""))
|
||||||
|
if err != nil {
|
||||||
|
return schema.GroupVersionResource{Resource: resourceArg}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gvr
|
||||||
|
}
|
155
pkg/kubectl/cmd/auth/cani_test.go
Normal file
155
pkg/kubectl/cmd/auth/cani_test.go
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
restclient "k8s.io/client-go/rest"
|
||||||
|
"k8s.io/client-go/rest/fake"
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRunAccessCheck(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
o *CanIOptions
|
||||||
|
args []string
|
||||||
|
allowed bool
|
||||||
|
serverErr error
|
||||||
|
|
||||||
|
expectedBodyStrings []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "restmapping for args",
|
||||||
|
o: &CanIOptions{},
|
||||||
|
args: []string{"get", "replicaset"},
|
||||||
|
allowed: true,
|
||||||
|
expectedBodyStrings: []string{
|
||||||
|
`{"resourceAttributes":{"namespace":"test","verb":"get","group":"extensions","resource":"replicasets"}}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "simple success",
|
||||||
|
o: &CanIOptions{},
|
||||||
|
args: []string{"get", "deployments.extensions/foo"},
|
||||||
|
allowed: true,
|
||||||
|
expectedBodyStrings: []string{
|
||||||
|
`{"resourceAttributes":{"namespace":"test","verb":"get","group":"extensions","resource":"deployments","name":"foo"}}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all namespaces",
|
||||||
|
o: &CanIOptions{
|
||||||
|
AllNamespaces: true,
|
||||||
|
},
|
||||||
|
args: []string{"get", "deployments.extensions/foo"},
|
||||||
|
allowed: true,
|
||||||
|
expectedBodyStrings: []string{
|
||||||
|
`{"resourceAttributes":{"verb":"get","group":"extensions","resource":"deployments","name":"foo"}}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "disallowed",
|
||||||
|
o: &CanIOptions{
|
||||||
|
AllNamespaces: true,
|
||||||
|
},
|
||||||
|
args: []string{"get", "deployments.extensions/foo"},
|
||||||
|
allowed: false,
|
||||||
|
expectedBodyStrings: []string{
|
||||||
|
`{"resourceAttributes":{"verb":"get","group":"extensions","resource":"deployments","name":"foo"}}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forcedError",
|
||||||
|
o: &CanIOptions{
|
||||||
|
AllNamespaces: true,
|
||||||
|
},
|
||||||
|
args: []string{"get", "deployments.extensions/foo"},
|
||||||
|
allowed: false,
|
||||||
|
serverErr: fmt.Errorf("forcedError"),
|
||||||
|
expectedBodyStrings: []string{
|
||||||
|
`{"resourceAttributes":{"verb":"get","group":"extensions","resource":"deployments","name":"foo"}}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
test.o.Out = ioutil.Discard
|
||||||
|
test.o.Err = ioutil.Discard
|
||||||
|
|
||||||
|
f, tf, _, ns := cmdtesting.NewAPIFactory()
|
||||||
|
tf.Client = &fake.RESTClient{
|
||||||
|
APIRegistry: api.Registry,
|
||||||
|
NegotiatedSerializer: ns,
|
||||||
|
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
if req.URL.Path != "/apis/authorization.k8s.io/v1/selfsubjectaccessreviews" {
|
||||||
|
t.Errorf("%s: %v", test.name, req.URL.Path)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
bodyBits, err := ioutil.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: %v", test.name, err)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
body := string(bodyBits)
|
||||||
|
|
||||||
|
for _, expectedBody := range test.expectedBodyStrings {
|
||||||
|
if !strings.Contains(body, expectedBody) {
|
||||||
|
t.Errorf("%s expecting %s in %s", test.name, expectedBody, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: ioutil.NopCloser(bytes.NewBufferString(
|
||||||
|
fmt.Sprintf(`{"kind":"SelfSubjectAccessReview","apiVersion":"authorization.k8s.io/v1","status":{"allowed":%v}}`, test.allowed),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
test.serverErr
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
tf.Namespace = "test"
|
||||||
|
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}}
|
||||||
|
|
||||||
|
if err := test.o.Complete(f, test.args); err != nil {
|
||||||
|
t.Errorf("%s: %v", test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
actualAllowed, err := test.o.RunAccessCheck()
|
||||||
|
switch {
|
||||||
|
case test.serverErr == nil && err == nil:
|
||||||
|
// pass
|
||||||
|
case err != nil && test.serverErr != nil && strings.Contains(err.Error(), test.serverErr.Error()):
|
||||||
|
// pass
|
||||||
|
default:
|
||||||
|
t.Errorf("%s: expected %v, got %v", test.name, test.serverErr, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if actualAllowed != test.allowed {
|
||||||
|
t.Errorf("%s: expected %v, got %v", test.name, test.allowed, actualAllowed)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,7 @@ import (
|
|||||||
|
|
||||||
"k8s.io/apiserver/pkg/util/flag"
|
"k8s.io/apiserver/pkg/util/flag"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
|
"k8s.io/kubernetes/pkg/kubectl/cmd/auth"
|
||||||
cmdconfig "k8s.io/kubernetes/pkg/kubectl/cmd/config"
|
cmdconfig "k8s.io/kubernetes/pkg/kubectl/cmd/config"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/rollout"
|
"k8s.io/kubernetes/pkg/kubectl/cmd/rollout"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/set"
|
"k8s.io/kubernetes/pkg/kubectl/cmd/set"
|
||||||
@ -286,6 +287,7 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob
|
|||||||
NewCmdPortForward(f, out, err),
|
NewCmdPortForward(f, out, err),
|
||||||
NewCmdProxy(f, out),
|
NewCmdProxy(f, out),
|
||||||
NewCmdCp(f, in, out, err),
|
NewCmdCp(f, in, out, err),
|
||||||
|
auth.NewCmdAuth(f, out, err),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -261,16 +261,7 @@ func (f *FakeFactory) FlagSet() *pflag.FlagSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeFactory) Object() (meta.RESTMapper, runtime.ObjectTyper) {
|
func (f *FakeFactory) Object() (meta.RESTMapper, runtime.ObjectTyper) {
|
||||||
priorityRESTMapper := meta.PriorityRESTMapper{
|
return api.Registry.RESTMapper(), f.tf.Typer
|
||||||
Delegate: f.tf.Mapper,
|
|
||||||
ResourcePriority: []schema.GroupVersionResource{
|
|
||||||
{Group: meta.AnyGroup, Version: "v1", Resource: meta.AnyResource},
|
|
||||||
},
|
|
||||||
KindPriority: []schema.GroupVersionKind{
|
|
||||||
{Group: meta.AnyGroup, Version: "v1", Kind: meta.AnyKind},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return priorityRESTMapper, f.tf.Typer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeFactory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper, error) {
|
func (f *FakeFactory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper, error) {
|
||||||
|
Loading…
Reference in New Issue
Block a user