Merge pull request #120780 from munnerz/bound-token-improvements

Including JTI & node reference in issued service account tokens (kep 4193)

Kubernetes-commit: ab13d0b47c790cfcf8b623a10ea08336a458a5b6
This commit is contained in:
Kubernetes Publisher 2023-10-31 01:22:56 +01:00
commit 539f801f01
2 changed files with 50 additions and 6 deletions

View File

@ -19,6 +19,7 @@ package create
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"strings" "strings"
"time" "time"
@ -96,12 +97,18 @@ var (
# Request a token bound to an instance of a Secret object with a specific UID # 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 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", "Pod": "v1",
"Secret": "v1", "Secret": "v1",
} }
) if os.Getenv("KUBECTL_NODE_BOUND_TOKENS") == "true" {
kinds["Node"] = "v1"
}
return kinds
}
func NewTokenOpts(ioStreams genericiooptions.IOStreams) *TokenOptions { func NewTokenOpts(ioStreams genericiooptions.IOStreams) *TokenOptions {
return &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().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. "+ 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.") "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. "+ 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. "+ "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") return fmt.Errorf("--bound-object-uid can only be set if --bound-object-kind is provided")
} }
} else { } else {
if _, ok := boundObjectKindToAPIVersion[o.BoundObjectKind]; !ok { if _, ok := boundObjectKindToAPIVersions()[o.BoundObjectKind]; !ok {
return fmt.Errorf("supported --bound-object-kind values are %s", strings.Join(sets.StringKeySet(boundObjectKindToAPIVersion).List(), ", ")) return fmt.Errorf("supported --bound-object-kind values are %s", strings.Join(sets.StringKeySet(boundObjectKindToAPIVersions()).List(), ", "))
} }
if len(o.BoundObjectName) == 0 { if len(o.BoundObjectName) == 0 {
return fmt.Errorf("--bound-object-name is required if --bound-object-kind is provided") 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 { if len(o.BoundObjectKind) > 0 {
request.Spec.BoundObjectRef = &authenticationv1.BoundObjectReference{ request.Spec.BoundObjectRef = &authenticationv1.BoundObjectReference{
Kind: o.BoundObjectKind, Kind: o.BoundObjectKind,
APIVersion: boundObjectKindToAPIVersion[o.BoundObjectKind], APIVersion: boundObjectKindToAPIVersions()[o.BoundObjectKind],
Name: o.BoundObjectName, Name: o.BoundObjectName,
UID: types.UID(o.BoundObjectUID), UID: types.UID(o.BoundObjectUID),
} }

View File

@ -21,6 +21,7 @@ import (
"encoding/json" "encoding/json"
"io" "io"
"net/http" "net/http"
"os"
"reflect" "reflect"
"testing" "testing"
"time" "time"
@ -53,6 +54,8 @@ func TestCreateToken(t *testing.T) {
audiences []string audiences []string
duration time.Duration duration time.Duration
enableNodeBindingFeature bool
serverResponseToken string serverResponseToken string
serverResponseError string serverResponseError string
@ -117,6 +120,13 @@ status:
boundObjectKind: "Foo", boundObjectKind: "Foo",
expectStderr: `error: supported --bound-object-kind values are Pod, Secret`, 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", test: "missing bound object name",
name: "mysa", name: "mysa",
@ -158,7 +168,30 @@ status:
serverResponseToken: "abc", serverResponseToken: "abc",
expectStdout: "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", test: "invalid audience",
name: "mysa", name: "mysa",
@ -319,6 +352,10 @@ status:
if test.duration != 0 { if test.duration != 0 {
cmd.Flags().Set("duration", test.duration.String()) 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}) cmd.Run(cmd, []string{test.name})
if !reflect.DeepEqual(tokenRequest, test.expectTokenRequest) { if !reflect.DeepEqual(tokenRequest, test.expectTokenRequest) {