kube-apiserver: add a bootstrap token authenticator for TLS bootstrapping

This commit is contained in:
Eric Chiang 2017-02-16 14:40:55 -08:00
parent 7a06e41f93
commit a0df658b20
11 changed files with 526 additions and 10 deletions

View File

@ -20,6 +20,7 @@ go_library(
"//pkg/apis/batch:go_default_library",
"//pkg/capabilities:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/cloudprovider:go_default_library",
"//pkg/cloudprovider/providers:go_default_library",
"//pkg/controller/informers:go_default_library",
@ -51,10 +52,12 @@ go_library(
"//plugin/pkg/admission/securitycontext/scdeny:go_default_library",
"//plugin/pkg/admission/serviceaccount:go_default_library",
"//plugin/pkg/admission/storageclass/default:go_default_library",
"//plugin/pkg/auth/authenticator/token/bootstrap:go_default_library",
"//vendor:github.com/go-openapi/spec",
"//vendor:github.com/golang/glog",
"//vendor:github.com/spf13/cobra",
"//vendor:github.com/spf13/pflag",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/openapi",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apimachinery/pkg/util/errors",

View File

@ -35,6 +35,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/openapi"
"k8s.io/apimachinery/pkg/runtime/schema"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
@ -50,6 +51,7 @@ import (
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/capabilities"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
"k8s.io/kubernetes/pkg/cloudprovider"
"k8s.io/kubernetes/pkg/controller/informers"
serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
@ -61,6 +63,7 @@ import (
"k8s.io/kubernetes/pkg/master/tunneler"
"k8s.io/kubernetes/pkg/registry/cachesize"
"k8s.io/kubernetes/pkg/version"
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/bootstrap"
)
// NewAPIServerCommand creates a *cobra.Command object with default parameters
@ -253,11 +256,6 @@ func Run(s *options.ServerRunOptions) error {
authenticatorConfig.ServiceAccountTokenGetter = serviceaccountcontroller.NewGetterFromStorageInterface(storageConfig, storageFactory.ResourcePrefix(api.Resource("serviceaccounts")), storageFactory.ResourcePrefix(api.Resource("secrets")))
}
apiAuthenticator, securityDefinitions, err := authenticatorConfig.New()
if err != nil {
return fmt.Errorf("invalid Authentication Config: %v", err)
}
client, err := internalclientset.NewForConfig(genericConfig.LoopbackClientConfig)
if err != nil {
kubeAPIVersions := os.Getenv("KUBE_API_VERSIONS")
@ -267,10 +265,29 @@ func Run(s *options.ServerRunOptions) error {
// KUBE_API_VERSIONS is used in test-update-storage-objects.sh, disabling a number of API
// groups. This leads to a nil client above and undefined behaviour further down.
//
// TODO: get rid of KUBE_API_VERSIONS or define sane behaviour if set
glog.Errorf("Failed to create clientset with KUBE_API_VERSIONS=%q. KUBE_API_VERSIONS is only for testing. Things will break.", kubeAPIVersions)
}
// TODO: Internal informers should switch to using 'pkg/client/informers/informers_generated',
// the second informer created here. Refactor clients which take the former to accept the latter.
sharedInformers := informers.NewSharedInformerFactory(nil, client, 10*time.Minute)
internalSharedInformers := internalversion.NewSharedInformerFactory(client, 10*time.Minute)
if client == nil {
// TODO: Remove check once client can never be nil.
glog.Errorf("Failed to setup bootstrap token authenticator because the loopback clientset was not setup properly.")
} else {
authenticatorConfig.BootstrapTokenAuthenticator = bootstrap.NewTokenAuthenticator(
internalSharedInformers.Core().InternalVersion().Secrets().Lister().Secrets(v1.NamespaceSystem),
)
}
apiAuthenticator, securityDefinitions, err := authenticatorConfig.New()
if err != nil {
return fmt.Errorf("invalid authentication config: %v", err)
}
authorizationConfig := s.Authorization.ToAuthorizationConfig(sharedInformers)
apiAuthorizer, err := authorizationConfig.New()
@ -350,6 +367,7 @@ func Run(s *options.ServerRunOptions) error {
}
sharedInformers.Start(wait.NeverStop)
internalSharedInformers.Start(wait.NeverStop)
m.GenericAPIServer.PrepareRun().Run(wait.NeverStop)
return nil
}

View File

@ -267,6 +267,7 @@ plugin/pkg/admission/resourcequota/apis/resourcequota/install
plugin/pkg/admission/resourcequota/apis/resourcequota/validation
plugin/pkg/admission/securitycontext/scdeny
plugin/pkg/auth
plugin/pkg/auth/authenticator/token/bootstrap
plugin/pkg/auth/authorizer
plugin/pkg/auth/authorizer/rbac/bootstrappolicy
staging/src/k8s.io/apimachinery/pkg/api/equality

View File

@ -204,6 +204,7 @@ executor-suicide-timeout
exit-on-lock-contention
experimental-allowed-unsafe-sysctls
experimental-bootstrap-kubeconfig
experimental-bootstrap-token-auth
experimental-keystone-url
experimental-keystone-ca-file
experimental-mounter-path

View File

@ -33,12 +33,12 @@ const (
SecretTypeBootstrapToken v1.SecretType = "bootstrap.kubernetes.io/token"
// BootstrapTokenIDKey is the id of this token. This can be transmitted in the
// clear and encoded in the name of the secret. It should be a random 6
// character string. Required
// clear and encoded in the name of the secret. It must be a random 6 character
// string that matches the regexp `^([a-z0-9]{6})$`. Required.
BootstrapTokenIDKey = "token-id"
// BootstrapTokenSecretKey is the actual secret. Typically this is a random 16
// character string. Required.
// BootstrapTokenSecretKey is the actual secret. It must be a random 16 character
// string that matches the regexp `^([a-z0-9]{16})$`. Required.
BootstrapTokenSecretKey = "token-secret"
// BootstrapTokenExpirationKey is when this token should be expired and no
@ -52,6 +52,13 @@ const (
// other value is assumed to be false. Optional.
BootstrapTokenUsageSigningKey = "usage-bootstrap-signing"
// BootstrapTokenUsageAuthentication signals that this token should be used
// as a bearer token to authenticate against the Kubernetes API. The bearer
// token takes the form "<token-id>.<token-secret>" and authenticates as the
// user "system:bootstrap:<token-id>" in the group "system:bootstrappers".
// Value must be "true". Any other value is assumed to be false. Optional.
BootstrapTokenUsageAuthentication = "usage-bootstrap-authentication"
// ConfigMapClusterInfo defines the name for the ConfigMap where the information how to connect and trust the cluster exist
ConfigMapClusterInfo = "cluster-info"
@ -60,4 +67,11 @@ const (
// JWSSignatureKeyPrefix defines what key prefix the JWS-signed tokens have
JWSSignatureKeyPrefix = "jws-kubeconfig-"
// BootstrapUserPrefix is the username prefix bootstrapping bearer tokens
// authenticate as. The full username given is "system:bootstrap:<token-id>".
BootstrapUserPrefix = "system:bootstrap:"
// BootstrapGroup is the group bootstrapping bearer tokens authenticate in.
BootstrapGroup = "system:bootstrappers"
)

View File

@ -49,6 +49,7 @@ type AuthenticatorConfig struct {
Anonymous bool
AnyToken bool
BasicAuthFile string
BootstrapToken bool
ClientCAFile string
TokenAuthFile string
OIDCIssuerURL string
@ -66,7 +67,8 @@ type AuthenticatorConfig struct {
RequestHeaderConfig *authenticatorfactory.RequestHeaderConfig
// TODO, this is the only non-serializable part of the entire config. Factor it out into a clientconfig
ServiceAccountTokenGetter serviceaccount.ServiceAccountTokenGetter
ServiceAccountTokenGetter serviceaccount.ServiceAccountTokenGetter
BootstrapTokenAuthenticator authenticator.Token
}
// New returns an authenticator.Request or an error that supports the standard
@ -136,6 +138,13 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe
authenticators = append(authenticators, serviceAccountAuth)
hasTokenAuth = true
}
if config.BootstrapToken {
if config.BootstrapTokenAuthenticator != nil {
// TODO: This can sometimes be nil because of
authenticators = append(authenticators, bearertoken.New(config.BootstrapTokenAuthenticator))
hasTokenAuth = true
}
}
// NOTE(ericchiang): Keep the OpenID Connect after Service Accounts.
//
// Because both plugins verify JWTs whichever comes first in the union experiences

View File

@ -33,6 +33,7 @@ import (
type BuiltInAuthenticationOptions struct {
Anonymous *AnonymousAuthenticationOptions
AnyToken *AnyTokenAuthenticationOptions
BootstrapToken *BootstrapTokenAuthenticationOptions
ClientCert *genericoptions.ClientCertAuthenticationOptions
Keystone *KeystoneAuthenticationOptions
OIDC *OIDCAuthenticationOptions
@ -51,6 +52,10 @@ type AnonymousAuthenticationOptions struct {
Allow bool
}
type BootstrapTokenAuthenticationOptions struct {
Allow bool
}
type KeystoneAuthenticationOptions struct {
URL string
CAFile string
@ -90,6 +95,7 @@ func (s *BuiltInAuthenticationOptions) WithAll() *BuiltInAuthenticationOptions {
return s.
WithAnyonymous().
WithAnyToken().
WithBootstrapToken().
WithClientCert().
WithKeystone().
WithOIDC().
@ -110,6 +116,11 @@ func (s *BuiltInAuthenticationOptions) WithAnyToken() *BuiltInAuthenticationOpti
return s
}
func (s *BuiltInAuthenticationOptions) WithBootstrapToken() *BuiltInAuthenticationOptions {
s.BootstrapToken = &BootstrapTokenAuthenticationOptions{}
return s
}
func (s *BuiltInAuthenticationOptions) WithClientCert() *BuiltInAuthenticationOptions {
s.ClientCert = &genericoptions.ClientCertAuthenticationOptions{}
return s
@ -172,6 +183,12 @@ func (s *BuiltInAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
}
if s.BootstrapToken != nil {
fs.BoolVar(&s.BootstrapToken.Allow, "experimental-bootstrap-token-auth", s.BootstrapToken.Allow, ""+
"Enable to allow secrets of type 'bootstrap.kubernetes.io/token' in the 'kube-system' "+
"namespace to be used for TLS bootstrapping authentication.")
}
if s.ClientCert != nil {
s.ClientCert.AddFlags(fs)
}
@ -255,6 +272,10 @@ func (s *BuiltInAuthenticationOptions) ToAuthenticationConfig() authenticator.Au
ret.AnyToken = s.AnyToken.Allow
}
if s.BootstrapToken != nil {
ret.BootstrapToken = s.BootstrapToken.Allow
}
if s.ClientCert != nil {
ret.ClientCAFile = s.ClientCert.ClientCA
}

View File

@ -24,6 +24,7 @@ filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//plugin/pkg/auth/authenticator/token/bootstrap:all-srcs",
"//plugin/pkg/auth/authorizer:all-srcs",
],
tags = ["automanaged"],

View File

@ -0,0 +1,52 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["bootstrap_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/bootstrap/api:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/api/errors",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/labels",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apiserver/pkg/authentication/user",
],
)
go_library(
name = "go_default_library",
srcs = ["bootstrap.go"],
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/bootstrap/api:go_default_library",
"//pkg/client/listers/core/internalversion:go_default_library",
"//vendor:github.com/golang/glog",
"//vendor:k8s.io/apimachinery/pkg/api/errors",
"//vendor:k8s.io/apiserver/pkg/authentication/user",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -0,0 +1,168 @@
/*
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 bootstrap provides a token authenticator for TLS bootstrap secrets.
*/
package bootstrap
import (
"fmt"
"regexp"
"time"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/kubernetes/pkg/api"
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
"k8s.io/kubernetes/pkg/client/listers/core/internalversion"
)
// TODO: A few methods in this package is copied from other sources. Either
// because the existing functionality isn't exported or because it is in a
// package that shouldn't be directly imported by this packages.
// NewTokenAuthenticator initializes a bootstrap token authenticator.
//
// Lister is expected to be for the "kube-system" namespace.
func NewTokenAuthenticator(lister internalversion.SecretNamespaceLister) *TokenAuthenticator {
return &TokenAuthenticator{lister}
}
// TokenAuthenticator authenticates bootstrap tokens from secrets in the API server.
type TokenAuthenticator struct {
lister internalversion.SecretNamespaceLister
}
// AuthenticateToken tries to match the provided token to a bootstrap token secret
// in a given namespace. If found, it authenticates the token in the
// "system:bootstrappers" group and with the "system:bootstrap:(token-id)" username.
//
// All secrets must be of type "bootstrap.kubernetes.io/token". An example secret:
//
// apiVersion: v1
// kind: Secret
// metadata:
// # Name MUST be of form "bootstrap-token-( token id )".
// name: bootstrap-token-( token id )
// namespace: kube-system
// # Only secrets of this type will be evaluated.
// type: bootstrap.kubernetes.io/token
// data:
// token-secret: ( private part of token )
// token-id: ( token id )
// # Required key usage.
// usage-bootstrap-authentication: true
// # May also contain an expiry.
//
// Tokens are expected to be of the form:
//
// ( token-id ).( token-secret )
//
func (t *TokenAuthenticator) AuthenticateToken(token string) (user.Info, bool, error) {
tokenID, tokenSecret, err := parseToken(token)
if err != nil {
// Token isn't of the correct form, ignore it.
return nil, false, nil
}
secretName := bootstrapapi.BootstrapTokenSecretPrefix + tokenID
secret, err := t.lister.Get(secretName)
if err != nil {
if errors.IsNotFound(err) {
return nil, false, nil
}
return nil, false, err
}
if string(secret.Type) != string(bootstrapapi.SecretTypeBootstrapToken) || secret.Data == nil {
return nil, false, nil
}
ts := getSecretString(secret, bootstrapapi.BootstrapTokenSecretKey)
if ts != tokenSecret {
return nil, false, nil
}
id := getSecretString(secret, bootstrapapi.BootstrapTokenIDKey)
if id != tokenID {
return nil, false, nil
}
if isSecretExpired(secret) {
return nil, false, nil
}
if getSecretString(secret, bootstrapapi.BootstrapTokenUsageAuthentication) != "true" {
glog.V(3).Infof("Bearer token matching bootstrap Secret %s/%s not marked %s=true.",
secret.Namespace, secret.Name, bootstrapapi.BootstrapTokenUsageAuthentication)
return nil, false, nil
}
return &user.DefaultInfo{
Name: bootstrapapi.BootstrapUserPrefix + string(id),
Groups: []string{bootstrapapi.BootstrapGroup},
}, true, nil
}
// Copied from k8s.io/kubernetes/pkg/bootstrap/api
func getSecretString(secret *api.Secret, key string) string {
if secret.Data == nil {
return ""
}
if val, ok := secret.Data[key]; ok {
return string(val)
}
return ""
}
// Copied from k8s.io/kubernetes/pkg/bootstrap/api
func isSecretExpired(secret *api.Secret) bool {
expiration := getSecretString(secret, bootstrapapi.BootstrapTokenExpirationKey)
if len(expiration) > 0 {
expTime, err2 := time.Parse(time.RFC3339, expiration)
if err2 != nil {
glog.V(3).Infof("Unparseable expiration time (%s) in %s/%s Secret: %v. Treating as expired.",
expiration, secret.Namespace, secret.Name, err2)
return true
}
if time.Now().After(expTime) {
glog.V(3).Infof("Expired bootstrap token in %s/%s Secret: %v",
secret.Namespace, secret.Name, expiration)
return true
}
}
return false
}
// Copied from kubernetes/cmd/kubeadm/app/util/token
var (
tokenRegexpString = "^([a-z0-9]{6})\\.([a-z0-9]{16})$"
tokenRegexp = regexp.MustCompile(tokenRegexpString)
)
// parseToken tries and parse a valid token from a string.
// A token ID and token secret are returned in case of success, an error otherwise.
func parseToken(s string) (string, string, error) {
split := tokenRegexp.FindStringSubmatch(s)
if len(split) != 3 {
return "", "", fmt.Errorf("token [%q] was not of form [%q]", s, tokenRegexpString)
}
return split[1], split[2], nil
}

View File

@ -0,0 +1,228 @@
/*
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 bootstrap
import (
"reflect"
"testing"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/kubernetes/pkg/api"
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
)
type lister struct {
secrets []*api.Secret
}
func (l *lister) List(selector labels.Selector) (ret []*api.Secret, err error) {
return l.secrets, nil
}
func (l *lister) Get(name string) (*api.Secret, error) {
for _, s := range l.secrets {
if s.Name == name {
return s, nil
}
}
return nil, errors.NewNotFound(schema.GroupResource{}, name)
}
const (
tokenID = "foobar" // 6 letters
tokenSecret = "circumnavigation" // 16 letters
)
func TestTokenAuthenticator(t *testing.T) {
tests := []struct {
name string
secrets []*api.Secret
token string
wantNotFound bool
wantUser *user.DefaultInfo
}{
{
name: "valid token",
secrets: []*api.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: bootstrapapi.BootstrapTokenSecretPrefix + tokenID,
},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte(tokenID),
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
},
Type: "bootstrap.kubernetes.io/token",
},
},
token: tokenID + "." + tokenSecret,
wantUser: &user.DefaultInfo{
Name: "system:bootstrap:" + tokenID,
Groups: []string{"system:bootstrappers"},
},
},
{
name: "invalid secret name",
secrets: []*api.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: "bad-name",
},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte(tokenID),
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
},
Type: "bootstrap.kubernetes.io/token",
},
},
token: tokenID + "." + tokenSecret,
wantNotFound: true,
},
{
name: "no usage",
secrets: []*api.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: bootstrapapi.BootstrapTokenSecretPrefix + tokenID,
},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte(tokenID),
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
},
Type: "bootstrap.kubernetes.io/token",
},
},
token: tokenID + "." + tokenSecret,
wantNotFound: true,
},
{
name: "wrong token",
secrets: []*api.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: bootstrapapi.BootstrapTokenSecretPrefix + tokenID,
},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte(tokenID),
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
},
Type: "bootstrap.kubernetes.io/token",
},
},
token: "barfoo" + "." + tokenSecret,
wantNotFound: true,
},
{
name: "expired token",
secrets: []*api.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: bootstrapapi.BootstrapTokenSecretPrefix + tokenID,
},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte(tokenID),
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
bootstrapapi.BootstrapTokenExpirationKey: []byte("2009-11-10T23:00:00Z"),
},
Type: "bootstrap.kubernetes.io/token",
},
},
token: tokenID + "." + tokenSecret,
wantNotFound: true,
},
{
name: "not expired token",
secrets: []*api.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: bootstrapapi.BootstrapTokenSecretPrefix + tokenID,
},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte(tokenID),
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
bootstrapapi.BootstrapTokenExpirationKey: []byte("2109-11-10T23:00:00Z"),
},
Type: "bootstrap.kubernetes.io/token",
},
},
token: tokenID + "." + tokenSecret,
wantUser: &user.DefaultInfo{
Name: "system:bootstrap:" + tokenID,
Groups: []string{"system:bootstrappers"},
},
},
{
name: "token id wrong length",
secrets: []*api.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: bootstrapapi.BootstrapTokenSecretPrefix + "foo",
},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte("foo"),
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
},
Type: "bootstrap.kubernetes.io/token",
},
},
// Token ID must be 6 characters.
token: "foo" + "." + tokenSecret,
wantNotFound: true,
},
}
for _, test := range tests {
func() {
a := NewTokenAuthenticator(&lister{test.secrets})
u, found, err := a.AuthenticateToken(test.token)
if err != nil {
t.Errorf("test %q returned an error: %v", test.name, err)
return
}
if !found {
if !test.wantNotFound {
t.Errorf("test %q expected to get user", test.name)
}
return
}
if test.wantNotFound {
t.Errorf("test %q expected to not get a user", test.name)
return
}
gotUser := u.(*user.DefaultInfo)
if !reflect.DeepEqual(gotUser, test.wantUser) {
t.Errorf("test %q want user=%#v, got=%#v", test.name, test.wantUser, gotUser)
}
}()
}
}