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
golang.org/x/sys v0.5.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/cli-runtime v0.0.0-20230307041416-a9210da26a77
k8s.io/client-go v0.0.0-20230307074101-60e53732bb78
k8s.io/component-base v0.0.0-20230304000900-3bf8af940f29
k8s.io/cli-runtime v0.0.0-20230307162015-1076364b1950
k8s.io/client-go v0.0.0-20230309033544-64e2c7ff167c
k8s.io/component-base v0.0.0-20230308075123-cfc68dcaff73
k8s.io/component-helpers v0.0.0-20230304001010-94a6fd9a7905
k8s.io/klog/v2 v2.90.1
k8s.io/kube-openapi v0.0.0-20230303024457-afdc3dddf62d
@ -91,12 +91,12 @@ require (
)
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/cli-runtime => k8s.io/cli-runtime v0.0.0-20230307041416-a9210da26a77
k8s.io/client-go => k8s.io/client-go v0.0.0-20230307074101-60e53732bb78
k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20230307162015-1076364b1950
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/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/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-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=
k8s.io/api v0.0.0-20230307055637-a9e2fb5f094f h1:kf7PdQ7o4kYpR8wNMDP5BBDX6zN+f7sNbrzz25aL7oE=
k8s.io/api v0.0.0-20230307055637-a9e2fb5f094f/go.mod h1:esKbT+6XB9TZUHyxlJVQ3zUM0abhQZ81Ic68eirO+xM=
k8s.io/api v0.0.0-20230308234233-a4afee70a903 h1:TmxUf1tDcGUHE8qZKLRWmn2nr2FypkwvqC2qviOUmQc=
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/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-20230307041416-a9210da26a77/go.mod h1:19C5E5NH5eRJBAD9wkz/TV0JUtO1/tGG1z+uTXwW58k=
k8s.io/client-go v0.0.0-20230307074101-60e53732bb78 h1:sC/AUHGA0wfCxQQCzQLYZkyTCtWZf7cZN0nTFSbLlzw=
k8s.io/client-go v0.0.0-20230307074101-60e53732bb78/go.mod h1:p2Qa5FMYfRFhtNUgj4FM9vYyVvKg8PfBqOpUwg4f4zY=
k8s.io/component-base v0.0.0-20230304000900-3bf8af940f29 h1:26PuXs5/GsCX5EPltZN9KjZqkW/EwYHaebfNmIK3Wxc=
k8s.io/component-base v0.0.0-20230304000900-3bf8af940f29/go.mod h1:1aFiUfjXiy45a88xJmd/nSkReVCoyShEHbGmWJQbp2Q=
k8s.io/cli-runtime v0.0.0-20230307162015-1076364b1950 h1:e8AHvyKm96kzxrktgL7dPMY65BA5C2S30X3CPKHKO+E=
k8s.io/cli-runtime v0.0.0-20230307162015-1076364b1950/go.mod h1:OBJKwPnOnWPdaZpa2ULBQaUf0b2p01bWWddPOpIxtyE=
k8s.io/client-go v0.0.0-20230309033544-64e2c7ff167c h1:1IXuG9QQvPMR3GbYgBhOKre47MAIq+U41cWOGoAHpd8=
k8s.io/client-go v0.0.0-20230309033544-64e2c7ff167c/go.mod h1:hjEB5iFHr17qVb6wnh6w2LQvO5DfoP6rzLN8NAE8K6U=
k8s.io/component-base v0.0.0-20230308075123-cfc68dcaff73 h1:MEKvhkstqrRFmA9+qQlnkA/jPbZUH/VnMKiEfBeLbf8=
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/go.mod h1:IrR1jWfgEwsQ2rBPvbuwL8bmskaLy5lDQ8mIPnPXcbA=
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
approvers:
- sig-auth-authenticators-approvers
- sig-auth-authorizers-approvers
reviewers:
- sig-auth-authenticators-reviewers
- sig-auth-authorizers-reviewers
labels:
- sig/auth

View File

@ -22,7 +22,9 @@ import (
"io"
"github.com/spf13/cobra"
authenticationv1 "k8s.io/api/authentication/v1"
authenticationv1alpha1 "k8s.io/api/authentication/v1alpha1"
authenticationv1beta1 "k8s.io/api/authentication/v1beta1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@ -30,6 +32,7 @@ import (
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/printers"
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"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/templates"
@ -71,7 +74,12 @@ func (flags *WhoAmIFlags) ToOptions(ctx context.Context, args []string) (*WhoAmI
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 {
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,
// add them here instead of referencing the cmd.Flags()
type WhoAmIOptions struct {
authClient authenticationv1alpha1client.AuthenticationV1alpha1Interface
ctx context.Context
authV1alpha1Client authenticationv1alpha1client.AuthenticationV1alpha1Interface
authV1beta1Client authenticationv1beta1client.AuthenticationV1beta1Interface
ctx context.Context
resourcePrinterFunc printers.ResourcePrinterFunc
@ -142,7 +152,7 @@ func NewCmdWhoAmI(restClientGetter genericclioptions.RESTClientGetter, streams g
var (
notEnabledErr = fmt.Errorf(
"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(
"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.
func (o WhoAmIOptions) Run() error {
sar := &authenticationv1alpha1.SelfSubjectReview{}
response, err := o.authClient.SelfSubjectReviews().Create(context.TODO(), sar, metav1.CreateOptions{})
var (
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 {
switch {
case errors.IsForbidden(err):
@ -162,25 +184,34 @@ func (o WhoAmIOptions) Run() error {
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 {
ssr, ok := obj.(*authenticationv1alpha1.SelfSubjectReview)
if !ok {
return fmt.Errorf("object is not SelfSubjectReview")
ui, err := getUserInfo(obj)
if err != nil {
return err
}
w := printers.GetNewTabWriter(out)
defer w.Flush()
_, err := fmt.Fprintf(w, "ATTRIBUTE\tVALUE\n")
_, err = fmt.Fprintf(w, "ATTRIBUTE\tVALUE\n")
if err != nil {
return fmt.Errorf("cannot write a header: %w", err)
}
ui := ssr.Status.UserInfo
if ui.Username != "" {
_, err := fmt.Fprintf(w, "Username\t%s\n", ui.Username)
if err != nil {

View File

@ -25,6 +25,7 @@ import (
authenticationv1 "k8s.io/api/authentication/v1"
authenticationv1alpha1 "k8s.io/api/authentication/v1alpha1"
authenticationv1beta1 "k8s.io/api/authentication/v1beta1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
@ -37,10 +38,12 @@ import (
func TestWhoAmIRun(t *testing.T) {
tests := []struct {
name string
o *WhoAmIOptions
args []string
serverErr error
name string
o *WhoAmIOptions
args []string
serverErr error
alphaDisabled bool
betaDisabled bool
expectedError error
expectedBodyStrings []string
@ -70,6 +73,116 @@ func TestWhoAmIRun(t *testing.T) {
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 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",
"metadata": {
"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",
o: &WhoAmIOptions{
@ -150,22 +273,43 @@ func TestWhoAmIRun(t *testing.T) {
return true, nil, test.serverErr
}
res := &authenticationv1alpha1.SelfSubjectReview{
Status: authenticationv1alpha1.SelfSubjectReviewStatus{
UserInfo: authenticationv1.UserInfo{
Username: "jane.doe",
UID: "uniq-id",
Groups: []string{"students", "teachers"},
Extra: map[string]authenticationv1.ExtraValue{
"subjects": {"math", "sports"},
"skills": {"reading", "learning"},
},
},
ui := authenticationv1.UserInfo{
Username: "jane.doe",
UID: "uniq-id",
Groups: []string{"students", "teachers"},
Extra: map[string]authenticationv1.ExtraValue{
"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()
switch {