From 468fb1fd0cc2ba0be31f1695c5eefa5e36727e13 Mon Sep 17 00:00:00 2001 From: James Munnelly Date: Tue, 19 Sep 2023 15:23:28 +0100 Subject: [PATCH] KEP-4193: bound service account token improvements Kubernetes-commit: 76463e21d4dec90b4d49975b182a13e1fdb6b20a --- pkg/cmd/create/create_token.go | 19 ++++++++++----- pkg/cmd/create/create_token_test.go | 37 +++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/pkg/cmd/create/create_token.go b/pkg/cmd/create/create_token.go index e2c7a7270..63f52169d 100644 --- a/pkg/cmd/create/create_token.go +++ b/pkg/cmd/create/create_token.go @@ -19,6 +19,7 @@ package create import ( "context" "fmt" + "os" "strings" "time" @@ -96,12 +97,18 @@ var ( # Request a token bound to an instance of a Secret object with a specific UID kubectl create token myapp --bound-object-kind Secret --bound-object-name mysecret --bound-object-uid 0d4691ed-659b-4935-a832-355f77ee47cc `) +) - boundObjectKindToAPIVersion = map[string]string{ +func boundObjectKindToAPIVersions() map[string]string { + kinds := map[string]string{ "Pod": "v1", "Secret": "v1", } -) + if os.Getenv("KUBECTL_NODE_BOUND_TOKENS") == "true" { + kinds["Node"] = "v1" + } + return kinds +} func NewTokenOpts(ioStreams genericiooptions.IOStreams) *TokenOptions { return &TokenOptions{ @@ -144,7 +151,7 @@ func NewCmdCreateToken(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) cmd.Flags().DurationVar(&o.Duration, "duration", o.Duration, "Requested lifetime of the issued token. If not set, the lifetime will be determined by the server automatically. The server may return a token with a longer or shorter lifetime.") cmd.Flags().StringVar(&o.BoundObjectKind, "bound-object-kind", o.BoundObjectKind, "Kind of an object to bind the token to. "+ - "Supported kinds are "+strings.Join(sets.StringKeySet(boundObjectKindToAPIVersion).List(), ", ")+". "+ + "Supported kinds are "+strings.Join(sets.StringKeySet(boundObjectKindToAPIVersions()).List(), ", ")+". "+ "If set, --bound-object-name must be provided.") cmd.Flags().StringVar(&o.BoundObjectName, "bound-object-name", o.BoundObjectName, "Name of an object to bind the token to. "+ "The token will expire when the object is deleted. "+ @@ -221,8 +228,8 @@ func (o *TokenOptions) Validate() error { return fmt.Errorf("--bound-object-uid can only be set if --bound-object-kind is provided") } } else { - if _, ok := boundObjectKindToAPIVersion[o.BoundObjectKind]; !ok { - return fmt.Errorf("supported --bound-object-kind values are %s", strings.Join(sets.StringKeySet(boundObjectKindToAPIVersion).List(), ", ")) + if _, ok := boundObjectKindToAPIVersions()[o.BoundObjectKind]; !ok { + return fmt.Errorf("supported --bound-object-kind values are %s", strings.Join(sets.StringKeySet(boundObjectKindToAPIVersions()).List(), ", ")) } if len(o.BoundObjectName) == 0 { return fmt.Errorf("--bound-object-name is required if --bound-object-kind is provided") @@ -245,7 +252,7 @@ func (o *TokenOptions) Run() error { if len(o.BoundObjectKind) > 0 { request.Spec.BoundObjectRef = &authenticationv1.BoundObjectReference{ Kind: o.BoundObjectKind, - APIVersion: boundObjectKindToAPIVersion[o.BoundObjectKind], + APIVersion: boundObjectKindToAPIVersions()[o.BoundObjectKind], Name: o.BoundObjectName, UID: types.UID(o.BoundObjectUID), } diff --git a/pkg/cmd/create/create_token_test.go b/pkg/cmd/create/create_token_test.go index 8e3a36e3b..7df27dec1 100644 --- a/pkg/cmd/create/create_token_test.go +++ b/pkg/cmd/create/create_token_test.go @@ -21,6 +21,7 @@ import ( "encoding/json" "io" "net/http" + "os" "reflect" "testing" "time" @@ -53,6 +54,8 @@ func TestCreateToken(t *testing.T) { audiences []string duration time.Duration + enableNodeBindingFeature bool + serverResponseToken string serverResponseError string @@ -117,6 +120,13 @@ status: boundObjectKind: "Foo", expectStderr: `error: supported --bound-object-kind values are Pod, Secret`, }, + { + test: "bad bound object kind (node feature enabled)", + name: "mysa", + enableNodeBindingFeature: true, + boundObjectKind: "Foo", + expectStderr: `error: supported --bound-object-kind values are Node, Pod, Secret`, + }, { test: "missing bound object name", name: "mysa", @@ -158,7 +168,30 @@ status: serverResponseToken: "abc", expectStdout: "abc", }, + { + test: "valid bound object (Node)", + name: "mysa", + enableNodeBindingFeature: true, + boundObjectKind: "Node", + boundObjectName: "mynode", + boundObjectUID: "myuid", + + expectRequestPath: "/api/v1/namespaces/test/serviceaccounts/mysa/token", + expectTokenRequest: &authenticationv1.TokenRequest{ + TypeMeta: metav1.TypeMeta{APIVersion: "authentication.k8s.io/v1", Kind: "TokenRequest"}, + Spec: authenticationv1.TokenRequestSpec{ + BoundObjectRef: &authenticationv1.BoundObjectReference{ + Kind: "Node", + APIVersion: "v1", + Name: "mynode", + UID: "myuid", + }, + }, + }, + serverResponseToken: "abc", + expectStdout: "abc", + }, { test: "invalid audience", name: "mysa", @@ -319,6 +352,10 @@ status: if test.duration != 0 { cmd.Flags().Set("duration", test.duration.String()) } + if test.enableNodeBindingFeature { + os.Setenv("KUBECTL_NODE_BOUND_TOKENS", "true") + defer os.Unsetenv("KUBECTL_NODE_BOUND_TOKENS") + } cmd.Run(cmd, []string{test.name}) if !reflect.DeepEqual(tokenRequest, test.expectTokenRequest) {