KEP-3325: Promote SelfSubjectReview to Beta (#116274)

* Promote SelfSubjectReview to Beta

Signed-off-by: m.nabokikh <maksim.nabokikh@flant.com>

* Fix whoami API

Signed-off-by: m.nabokikh <maksim.nabokikh@flant.com>

* Fixes according to code review

Signed-off-by: m.nabokikh <maksim.nabokikh@flant.com>

---------

Signed-off-by: m.nabokikh <maksim.nabokikh@flant.com>

Kubernetes-commit: c1431af4f83b2b06a581f23806ee12a0663fed3b
This commit is contained in:
Maksim Nabokikh 2023-03-09 00:42:33 +01:00 committed by Kubernetes Publisher
parent 58a69e79d3
commit ea55df59db
5 changed files with 223 additions and 46 deletions

16
go.mod
View File

@ -30,11 +30,11 @@ require (
github.com/stretchr/testify v1.8.1 github.com/stretchr/testify v1.8.1
golang.org/x/sys v0.5.0 golang.org/x/sys v0.5.0
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.0.0-20230307055637-a9e2fb5f094f k8s.io/api v0.0.0-20230308234233-a4afee70a903
k8s.io/apimachinery v0.0.0-20230303235435-f357b1fa74b7 k8s.io/apimachinery v0.0.0-20230303235435-f357b1fa74b7
k8s.io/cli-runtime v0.0.0-20230307041416-a9210da26a77 k8s.io/cli-runtime v0.0.0-20230307162015-1076364b1950
k8s.io/client-go v0.0.0-20230307074101-60e53732bb78 k8s.io/client-go v0.0.0-20230309033544-64e2c7ff167c
k8s.io/component-base v0.0.0-20230304000900-3bf8af940f29 k8s.io/component-base v0.0.0-20230308075123-cfc68dcaff73
k8s.io/component-helpers v0.0.0-20230304001010-94a6fd9a7905 k8s.io/component-helpers v0.0.0-20230304001010-94a6fd9a7905
k8s.io/klog/v2 v2.90.1 k8s.io/klog/v2 v2.90.1
k8s.io/kube-openapi v0.0.0-20230303024457-afdc3dddf62d k8s.io/kube-openapi v0.0.0-20230303024457-afdc3dddf62d
@ -91,12 +91,12 @@ require (
) )
replace ( replace (
k8s.io/api => k8s.io/api v0.0.0-20230307055637-a9e2fb5f094f k8s.io/api => k8s.io/api v0.0.0-20230308234233-a4afee70a903
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20230303235435-f357b1fa74b7 k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20230303235435-f357b1fa74b7
k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20230307041416-a9210da26a77 k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20230307162015-1076364b1950
k8s.io/client-go => k8s.io/client-go v0.0.0-20230307074101-60e53732bb78 k8s.io/client-go => k8s.io/client-go v0.0.0-20230309033544-64e2c7ff167c
k8s.io/code-generator => k8s.io/code-generator v0.0.0-20230303235006-4400d5f574d7 k8s.io/code-generator => k8s.io/code-generator v0.0.0-20230303235006-4400d5f574d7
k8s.io/component-base => k8s.io/component-base v0.0.0-20230304000900-3bf8af940f29 k8s.io/component-base => k8s.io/component-base v0.0.0-20230308075123-cfc68dcaff73
k8s.io/component-helpers => k8s.io/component-helpers v0.0.0-20230304001010-94a6fd9a7905 k8s.io/component-helpers => k8s.io/component-helpers v0.0.0-20230304001010-94a6fd9a7905
k8s.io/metrics => k8s.io/metrics v0.0.0-20230306121834-133605a72766 k8s.io/metrics => k8s.io/metrics v0.0.0-20230306121834-133605a72766
) )

16
go.sum
View File

@ -531,16 +531,16 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.0.0-20230307055637-a9e2fb5f094f h1:kf7PdQ7o4kYpR8wNMDP5BBDX6zN+f7sNbrzz25aL7oE= k8s.io/api v0.0.0-20230308234233-a4afee70a903 h1:TmxUf1tDcGUHE8qZKLRWmn2nr2FypkwvqC2qviOUmQc=
k8s.io/api v0.0.0-20230307055637-a9e2fb5f094f/go.mod h1:esKbT+6XB9TZUHyxlJVQ3zUM0abhQZ81Ic68eirO+xM= k8s.io/api v0.0.0-20230308234233-a4afee70a903/go.mod h1:esKbT+6XB9TZUHyxlJVQ3zUM0abhQZ81Ic68eirO+xM=
k8s.io/apimachinery v0.0.0-20230303235435-f357b1fa74b7 h1:YN43Lvs3Pj9iQmuWGojeBiFdz1mkrxe0EZn7Ba3TMpQ= k8s.io/apimachinery v0.0.0-20230303235435-f357b1fa74b7 h1:YN43Lvs3Pj9iQmuWGojeBiFdz1mkrxe0EZn7Ba3TMpQ=
k8s.io/apimachinery v0.0.0-20230303235435-f357b1fa74b7/go.mod h1:jlJwObMa4oKAEOMnAeEaqeiM+Fwd/CbAwNyQ7OaEwS0= k8s.io/apimachinery v0.0.0-20230303235435-f357b1fa74b7/go.mod h1:jlJwObMa4oKAEOMnAeEaqeiM+Fwd/CbAwNyQ7OaEwS0=
k8s.io/cli-runtime v0.0.0-20230307041416-a9210da26a77 h1:t+fWCmEVuJTApmS651BRSDqIOzfsw5EftlXXY/R7zqY= k8s.io/cli-runtime v0.0.0-20230307162015-1076364b1950 h1:e8AHvyKm96kzxrktgL7dPMY65BA5C2S30X3CPKHKO+E=
k8s.io/cli-runtime v0.0.0-20230307041416-a9210da26a77/go.mod h1:19C5E5NH5eRJBAD9wkz/TV0JUtO1/tGG1z+uTXwW58k= k8s.io/cli-runtime v0.0.0-20230307162015-1076364b1950/go.mod h1:OBJKwPnOnWPdaZpa2ULBQaUf0b2p01bWWddPOpIxtyE=
k8s.io/client-go v0.0.0-20230307074101-60e53732bb78 h1:sC/AUHGA0wfCxQQCzQLYZkyTCtWZf7cZN0nTFSbLlzw= k8s.io/client-go v0.0.0-20230309033544-64e2c7ff167c h1:1IXuG9QQvPMR3GbYgBhOKre47MAIq+U41cWOGoAHpd8=
k8s.io/client-go v0.0.0-20230307074101-60e53732bb78/go.mod h1:p2Qa5FMYfRFhtNUgj4FM9vYyVvKg8PfBqOpUwg4f4zY= k8s.io/client-go v0.0.0-20230309033544-64e2c7ff167c/go.mod h1:hjEB5iFHr17qVb6wnh6w2LQvO5DfoP6rzLN8NAE8K6U=
k8s.io/component-base v0.0.0-20230304000900-3bf8af940f29 h1:26PuXs5/GsCX5EPltZN9KjZqkW/EwYHaebfNmIK3Wxc= k8s.io/component-base v0.0.0-20230308075123-cfc68dcaff73 h1:MEKvhkstqrRFmA9+qQlnkA/jPbZUH/VnMKiEfBeLbf8=
k8s.io/component-base v0.0.0-20230304000900-3bf8af940f29/go.mod h1:1aFiUfjXiy45a88xJmd/nSkReVCoyShEHbGmWJQbp2Q= k8s.io/component-base v0.0.0-20230308075123-cfc68dcaff73/go.mod h1:MB0hQ6Wy3OOZ/dr+sy5FwxCJhDJ4hszX743ar8dd2zE=
k8s.io/component-helpers v0.0.0-20230304001010-94a6fd9a7905 h1:kHWUeYs8Mc9IQpqULpPd2j8G6cqqf/fz5wzvqcYCdSQ= k8s.io/component-helpers v0.0.0-20230304001010-94a6fd9a7905 h1:kHWUeYs8Mc9IQpqULpPd2j8G6cqqf/fz5wzvqcYCdSQ=
k8s.io/component-helpers v0.0.0-20230304001010-94a6fd9a7905/go.mod h1:IrR1jWfgEwsQ2rBPvbuwL8bmskaLy5lDQ8mIPnPXcbA= k8s.io/component-helpers v0.0.0-20230304001010-94a6fd9a7905/go.mod h1:IrR1jWfgEwsQ2rBPvbuwL8bmskaLy5lDQ8mIPnPXcbA=
k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw=

View File

@ -1,8 +1,10 @@
# See the OWNERS docs at https://go.k8s.io/owners # See the OWNERS docs at https://go.k8s.io/owners
approvers: approvers:
- sig-auth-authenticators-approvers
- sig-auth-authorizers-approvers - sig-auth-authorizers-approvers
reviewers: reviewers:
- sig-auth-authenticators-reviewers
- sig-auth-authorizers-reviewers - sig-auth-authorizers-reviewers
labels: labels:
- sig/auth - sig/auth

View File

@ -22,7 +22,9 @@ import (
"io" "io"
"github.com/spf13/cobra" "github.com/spf13/cobra"
authenticationv1 "k8s.io/api/authentication/v1"
authenticationv1alpha1 "k8s.io/api/authentication/v1alpha1" authenticationv1alpha1 "k8s.io/api/authentication/v1alpha1"
authenticationv1beta1 "k8s.io/api/authentication/v1beta1"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -30,6 +32,7 @@ import (
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/printers" "k8s.io/cli-runtime/pkg/printers"
authenticationv1alpha1client "k8s.io/client-go/kubernetes/typed/authentication/v1alpha1" authenticationv1alpha1client "k8s.io/client-go/kubernetes/typed/authentication/v1alpha1"
authenticationv1beta1client "k8s.io/client-go/kubernetes/typed/authentication/v1beta1"
cmdutil "k8s.io/kubectl/pkg/cmd/util" cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/scheme" "k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/templates" "k8s.io/kubectl/pkg/util/templates"
@ -71,7 +74,12 @@ func (flags *WhoAmIFlags) ToOptions(ctx context.Context, args []string) (*WhoAmI
return nil, err return nil, err
} }
w.authClient, err = authenticationv1alpha1client.NewForConfig(clientConfig) w.authV1alpha1Client, err = authenticationv1alpha1client.NewForConfig(clientConfig)
if err != nil {
return nil, err
}
w.authV1beta1Client, err = authenticationv1beta1client.NewForConfig(clientConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -92,8 +100,10 @@ func (flags *WhoAmIFlags) ToOptions(ctx context.Context, args []string) (*WhoAmI
// WhoAmIOptions is the start of the data required to perform the operation. As new fields are added, // WhoAmIOptions 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() // add them here instead of referencing the cmd.Flags()
type WhoAmIOptions struct { type WhoAmIOptions struct {
authClient authenticationv1alpha1client.AuthenticationV1alpha1Interface authV1alpha1Client authenticationv1alpha1client.AuthenticationV1alpha1Interface
ctx context.Context authV1beta1Client authenticationv1beta1client.AuthenticationV1beta1Interface
ctx context.Context
resourcePrinterFunc printers.ResourcePrinterFunc resourcePrinterFunc printers.ResourcePrinterFunc
@ -142,7 +152,7 @@ func NewCmdWhoAmI(restClientGetter genericclioptions.RESTClientGetter, streams g
var ( var (
notEnabledErr = fmt.Errorf( notEnabledErr = fmt.Errorf(
"the selfsubjectreviews API is not enabled in the cluster\n" + "the selfsubjectreviews API is not enabled in the cluster\n" +
"enable APISelfSubjectReview feature gate and authentication.k8s.io/v1alpha1 API") "enable APISelfSubjectReview feature gate and authentication.k8s.io/v1alpha1 or authentication.k8s.io/v1beta1 API")
forbiddenErr = fmt.Errorf( forbiddenErr = fmt.Errorf(
"the selfsubjectreviews API is not enabled in the cluster or you do not have permission to call it") "the selfsubjectreviews API is not enabled in the cluster or you do not have permission to call it")
@ -150,8 +160,20 @@ var (
// Run prints all user attributes. // Run prints all user attributes.
func (o WhoAmIOptions) Run() error { func (o WhoAmIOptions) Run() error {
sar := &authenticationv1alpha1.SelfSubjectReview{} var (
response, err := o.authClient.SelfSubjectReviews().Create(context.TODO(), sar, metav1.CreateOptions{}) res runtime.Object
err error
)
res, err = o.authV1beta1Client.
SelfSubjectReviews().
Create(context.TODO(), &authenticationv1beta1.SelfSubjectReview{}, metav1.CreateOptions{})
if err != nil && errors.IsNotFound(err) {
// Fallback to Alpha API if Beta is not enabled
res, err = o.authV1alpha1Client.
SelfSubjectReviews().
Create(context.TODO(), &authenticationv1alpha1.SelfSubjectReview{}, metav1.CreateOptions{})
}
if err != nil { if err != nil {
switch { switch {
case errors.IsForbidden(err): case errors.IsForbidden(err):
@ -162,25 +184,34 @@ func (o WhoAmIOptions) Run() error {
return err return err
} }
} }
return o.resourcePrinterFunc(response, o.Out) return o.resourcePrinterFunc(res, o.Out)
}
func getUserInfo(obj runtime.Object) (authenticationv1.UserInfo, error) {
switch obj.(type) {
case *authenticationv1alpha1.SelfSubjectReview:
return obj.(*authenticationv1alpha1.SelfSubjectReview).Status.UserInfo, nil
case *authenticationv1beta1.SelfSubjectReview:
return obj.(*authenticationv1beta1.SelfSubjectReview).Status.UserInfo, nil
default:
return authenticationv1.UserInfo{}, fmt.Errorf("unexpected response type %T, expected SelfSubjectReview", obj)
}
} }
func printTableSelfSubjectAccessReview(obj runtime.Object, out io.Writer) error { func printTableSelfSubjectAccessReview(obj runtime.Object, out io.Writer) error {
ssr, ok := obj.(*authenticationv1alpha1.SelfSubjectReview) ui, err := getUserInfo(obj)
if !ok { if err != nil {
return fmt.Errorf("object is not SelfSubjectReview") return err
} }
w := printers.GetNewTabWriter(out) w := printers.GetNewTabWriter(out)
defer w.Flush() defer w.Flush()
_, err := fmt.Fprintf(w, "ATTRIBUTE\tVALUE\n") _, err = fmt.Fprintf(w, "ATTRIBUTE\tVALUE\n")
if err != nil { if err != nil {
return fmt.Errorf("cannot write a header: %w", err) return fmt.Errorf("cannot write a header: %w", err)
} }
ui := ssr.Status.UserInfo
if ui.Username != "" { if ui.Username != "" {
_, err := fmt.Fprintf(w, "Username\t%s\n", ui.Username) _, err := fmt.Fprintf(w, "Username\t%s\n", ui.Username)
if err != nil { if err != nil {

View File

@ -25,6 +25,7 @@ import (
authenticationv1 "k8s.io/api/authentication/v1" authenticationv1 "k8s.io/api/authentication/v1"
authenticationv1alpha1 "k8s.io/api/authentication/v1alpha1" authenticationv1alpha1 "k8s.io/api/authentication/v1alpha1"
authenticationv1beta1 "k8s.io/api/authentication/v1beta1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -37,10 +38,12 @@ import (
func TestWhoAmIRun(t *testing.T) { func TestWhoAmIRun(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
o *WhoAmIOptions o *WhoAmIOptions
args []string args []string
serverErr error serverErr error
alphaDisabled bool
betaDisabled bool
expectedError error expectedError error
expectedBodyStrings []string expectedBodyStrings []string
@ -70,6 +73,116 @@ func TestWhoAmIRun(t *testing.T) {
expectedBodyStrings: []string{ expectedBodyStrings: []string{
`{ `{
"kind": "SelfSubjectReview", "kind": "SelfSubjectReview",
"apiVersion": "authentication.k8s.io/v1beta1",
"metadata": {
"creationTimestamp": null
},
"status": {
"userInfo": {
"username": "jane.doe",
"uid": "uniq-id",
"groups": [
"students",
"teachers"
],
"extra": {
"skills": [
"reading",
"learning"
],
"subjects": [
"math",
"sports"
]
}
}
}
}
`,
},
},
{
name: "success test no alpha",
o: &WhoAmIOptions{
resourcePrinterFunc: printTableSelfSubjectAccessReview,
},
args: []string{},
alphaDisabled: true,
expectedBodyStrings: []string{
`ATTRIBUTE VALUE`,
`Username jane.doe`,
`UID uniq-id`,
`Groups [students teachers]`,
`Extra: skills [reading learning]`,
`Extra: subjects [math sports]`,
``,
},
},
{
name: "JSON test no alpha",
o: &WhoAmIOptions{
resourcePrinterFunc: printers.NewTypeSetter(scheme.Scheme).ToPrinter(&printers.JSONPrinter{}).PrintObj,
},
args: []string{},
alphaDisabled: true,
expectedBodyStrings: []string{
`{
"kind": "SelfSubjectReview",
"apiVersion": "authentication.k8s.io/v1beta1",
"metadata": {
"creationTimestamp": null
},
"status": {
"userInfo": {
"username": "jane.doe",
"uid": "uniq-id",
"groups": [
"students",
"teachers"
],
"extra": {
"skills": [
"reading",
"learning"
],
"subjects": [
"math",
"sports"
]
}
}
}
}
`,
},
},
{
name: "success test no beta",
o: &WhoAmIOptions{
resourcePrinterFunc: printTableSelfSubjectAccessReview,
},
args: []string{},
betaDisabled: true,
expectedBodyStrings: []string{
`ATTRIBUTE VALUE`,
`Username jane.doe`,
`UID uniq-id`,
`Groups [students teachers]`,
`Extra: skills [reading learning]`,
`Extra: subjects [math sports]`,
``,
},
},
{
name: "JSON test no beta",
o: &WhoAmIOptions{
resourcePrinterFunc: printers.NewTypeSetter(scheme.Scheme).ToPrinter(&printers.JSONPrinter{}).PrintObj,
},
args: []string{},
betaDisabled: true,
expectedBodyStrings: []string{
`{
"kind": "SelfSubjectReview",
"apiVersion": "authentication.k8s.io/v1alpha1", "apiVersion": "authentication.k8s.io/v1alpha1",
"metadata": { "metadata": {
"creationTimestamp": null "creationTimestamp": null
@ -98,6 +211,16 @@ func TestWhoAmIRun(t *testing.T) {
`, `,
}, },
}, },
{
name: "both API disabled",
o: &WhoAmIOptions{
resourcePrinterFunc: printTableSelfSubjectAccessReview,
},
args: []string{},
betaDisabled: true,
alphaDisabled: true,
expectedError: notEnabledErr,
},
{ {
name: "Forbidden error", name: "Forbidden error",
o: &WhoAmIOptions{ o: &WhoAmIOptions{
@ -150,22 +273,43 @@ func TestWhoAmIRun(t *testing.T) {
return true, nil, test.serverErr return true, nil, test.serverErr
} }
res := &authenticationv1alpha1.SelfSubjectReview{ ui := authenticationv1.UserInfo{
Status: authenticationv1alpha1.SelfSubjectReviewStatus{ Username: "jane.doe",
UserInfo: authenticationv1.UserInfo{ UID: "uniq-id",
Username: "jane.doe", Groups: []string{"students", "teachers"},
UID: "uniq-id", Extra: map[string]authenticationv1.ExtraValue{
Groups: []string{"students", "teachers"}, "subjects": {"math", "sports"},
Extra: map[string]authenticationv1.ExtraValue{ "skills": {"reading", "learning"},
"subjects": {"math", "sports"},
"skills": {"reading", "learning"},
},
},
}, },
} }
return true, res, nil
switch action.GetResource().GroupVersion().String() {
case "authentication.k8s.io/v1alpha1":
if test.alphaDisabled {
return true, nil, errors.NewNotFound(corev1.Resource("selfsubjectreviews"), "foo")
}
res := &authenticationv1alpha1.SelfSubjectReview{
Status: authenticationv1alpha1.SelfSubjectReviewStatus{
UserInfo: ui,
},
}
return true, res, nil
case "authentication.k8s.io/v1beta1":
if test.betaDisabled {
return true, nil, errors.NewNotFound(corev1.Resource("selfsubjectreviews"), "foo")
}
res := &authenticationv1beta1.SelfSubjectReview{
Status: authenticationv1beta1.SelfSubjectReviewStatus{
UserInfo: ui,
},
}
return true, res, nil
default:
return false, nil, fmt.Errorf("unknown API")
}
}) })
test.o.authClient = fakeAuthClientSet.AuthenticationV1alpha1() test.o.authV1beta1Client = fakeAuthClientSet.AuthenticationV1beta1()
test.o.authV1alpha1Client = fakeAuthClientSet.AuthenticationV1alpha1()
err := test.o.Run() err := test.o.Run()
switch { switch {