Delete bootstrap token

pull/58/head
Darren Shepherd 2018-10-08 00:01:37 -07:00
parent 9cbf136137
commit a260e501ef
6 changed files with 1 additions and 673 deletions

View File

@ -32,7 +32,6 @@ import (
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/apis/meta/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/util/sets"
@ -68,12 +67,12 @@ import (
"k8s.io/kubernetes/pkg/registry/cachesize"
rbacrest "k8s.io/kubernetes/pkg/registry/rbac/rest"
"k8s.io/kubernetes/pkg/serviceaccount"
utilflag "k8s.io/kubernetes/pkg/util/flag"
_ "k8s.io/kubernetes/pkg/util/reflector/prometheus" // for reflector metric registration
_ "k8s.io/kubernetes/pkg/util/workqueue/prometheus" // for workqueue metric registration
"k8s.io/kubernetes/pkg/version"
"k8s.io/kubernetes/pkg/version/verflag"
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/bootstrap"
)
const etcdRetryLimit = 60
@ -450,9 +449,6 @@ func BuildAuthenticator(s *options.ServerRunOptions, extclient clientgoclientset
if s.Authentication.ServiceAccounts.Lookup {
authenticatorConfig.ServiceAccountTokenGetter = serviceaccountcontroller.NewGetterFromClient(extclient)
}
authenticatorConfig.BootstrapTokenAuthenticator = bootstrap.NewTokenAuthenticator(
versionedInformer.Core().V1().Secrets().Lister().Secrets(v1.NamespaceSystem),
)
return authenticatorConfig.New()
}

View File

@ -47,7 +47,6 @@ import (
type Config struct {
Anonymous bool
BasicAuthFile string
BootstrapToken bool
ClientCAFile string
TokenAuthFile string
OIDCIssuerURL string
@ -73,7 +72,6 @@ type Config struct {
// TODO, this is the only non-serializable part of the entire config. Factor it out into a clientconfig
ServiceAccountTokenGetter serviceaccount.ServiceAccountTokenGetter
BootstrapTokenAuthenticator authenticator.Token
}
// New returns an authenticator.Request or an error that supports the standard
@ -138,12 +136,6 @@ func (config Config) New() (authenticator.Request, error) {
}
tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
}
if config.BootstrapToken {
if config.BootstrapTokenAuthenticator != nil {
// TODO: This can sometimes be nil because of
tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, config.BootstrapTokenAuthenticator))
}
}
// NOTE(ericchiang): Keep the OpenID Connect after Service Accounts.
//
// Because both plugins verify JWTs whichever comes first in the union experiences

View File

@ -37,7 +37,6 @@ import (
type BuiltInAuthenticationOptions struct {
APIAudiences []string
Anonymous *AnonymousAuthenticationOptions
BootstrapToken *BootstrapTokenAuthenticationOptions
ClientCert *genericoptions.ClientCertAuthenticationOptions
OIDC *OIDCAuthenticationOptions
PasswordFile *PasswordFileAuthenticationOptions
@ -54,10 +53,6 @@ type AnonymousAuthenticationOptions struct {
Allow bool
}
type BootstrapTokenAuthenticationOptions struct {
Enable bool
}
type OIDCAuthenticationOptions struct {
CAFile string
ClientID string
@ -100,7 +95,6 @@ func NewBuiltInAuthenticationOptions() *BuiltInAuthenticationOptions {
func (s *BuiltInAuthenticationOptions) WithAll() *BuiltInAuthenticationOptions {
return s.
WithAnonymous().
WithBootstrapToken().
WithClientCert().
WithOIDC().
WithPasswordFile().
@ -115,11 +109,6 @@ func (s *BuiltInAuthenticationOptions) WithAnonymous() *BuiltInAuthenticationOpt
return s
}
func (s *BuiltInAuthenticationOptions) WithBootstrapToken() *BuiltInAuthenticationOptions {
s.BootstrapToken = &BootstrapTokenAuthenticationOptions{}
return s
}
func (s *BuiltInAuthenticationOptions) WithClientCert() *BuiltInAuthenticationOptions {
s.ClientCert = &genericoptions.ClientCertAuthenticationOptions{}
return s
@ -188,12 +177,6 @@ func (s *BuiltInAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
"Anonymous requests have a username of system:anonymous, and a group name of system:unauthenticated.")
}
if s.BootstrapToken != nil {
fs.BoolVar(&s.BootstrapToken.Enable, "enable-bootstrap-token-auth", s.BootstrapToken.Enable, ""+
"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)
}
@ -302,10 +285,6 @@ func (s *BuiltInAuthenticationOptions) ToAuthenticationConfig() kubeauthenticato
ret.Anonymous = s.Anonymous.Allow
}
if s.BootstrapToken != nil {
ret.BootstrapToken = s.BootstrapToken.Enable
}
if s.ClientCert != nil {
ret.ClientCAFile = s.ClientCert.ClientCA
}

View File

@ -1,52 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["bootstrap_test.go"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//staging/src/k8s.io/cluster-bootstrap/token/api:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["bootstrap.go"],
importpath = "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/bootstrap",
deps = [
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//staging/src/k8s.io/client-go/listers/core/v1:go_default_library",
"//staging/src/k8s.io/cluster-bootstrap/token/api:go_default_library",
"//staging/src/k8s.io/cluster-bootstrap/token/util:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,228 +0,0 @@
/*
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 (
"context"
"crypto/subtle"
"fmt"
"regexp"
"strings"
"time"
"k8s.io/klog"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user"
corev1listers "k8s.io/client-go/listers/core/v1"
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
bootstraputil "k8s.io/cluster-bootstrap/token/util"
)
// 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 corev1listers.SecretNamespaceLister) *TokenAuthenticator {
return &TokenAuthenticator{lister}
}
// TokenAuthenticator authenticates bootstrap tokens from secrets in the API server.
type TokenAuthenticator struct {
lister corev1listers.SecretNamespaceLister
}
// tokenErrorf prints a error message for a secret that has matched a bearer
// token but fails to meet some other criteria.
//
// tokenErrorf(secret, "has invalid value for key %s", key)
//
func tokenErrorf(s *corev1.Secret, format string, i ...interface{}) {
format = fmt.Sprintf("Bootstrap secret %s/%s matching bearer token ", s.Namespace, s.Name) + format
klog.V(3).Infof(format, i...)
}
// 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
// auth-extra-groups: "system:bootstrappers:custom-group1,system:bootstrappers:custom-group2"
// # May also contain an expiry.
//
// Tokens are expected to be of the form:
//
// ( token-id ).( token-secret )
//
func (t *TokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, 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) {
klog.V(3).Infof("No secret of name %s to match bootstrap bearer token", secretName)
return nil, false, nil
}
return nil, false, err
}
if secret.DeletionTimestamp != nil {
tokenErrorf(secret, "is deleted and awaiting removal")
return nil, false, nil
}
if string(secret.Type) != string(bootstrapapi.SecretTypeBootstrapToken) || secret.Data == nil {
tokenErrorf(secret, "has invalid type, expected %s.", bootstrapapi.SecretTypeBootstrapToken)
return nil, false, nil
}
ts := getSecretString(secret, bootstrapapi.BootstrapTokenSecretKey)
if subtle.ConstantTimeCompare([]byte(ts), []byte(tokenSecret)) != 1 {
tokenErrorf(secret, "has invalid value for key %s, expected %s.", bootstrapapi.BootstrapTokenSecretKey, tokenSecret)
return nil, false, nil
}
id := getSecretString(secret, bootstrapapi.BootstrapTokenIDKey)
if id != tokenID {
tokenErrorf(secret, "has invalid value for key %s, expected %s.", bootstrapapi.BootstrapTokenIDKey, tokenID)
return nil, false, nil
}
if isSecretExpired(secret) {
// logging done in isSecretExpired method.
return nil, false, nil
}
if getSecretString(secret, bootstrapapi.BootstrapTokenUsageAuthentication) != "true" {
tokenErrorf(secret, "not marked %s=true.", bootstrapapi.BootstrapTokenUsageAuthentication)
return nil, false, nil
}
groups, err := getGroups(secret)
if err != nil {
tokenErrorf(secret, "has invalid value for key %s: %v.", bootstrapapi.BootstrapTokenExtraGroupsKey, err)
return nil, false, nil
}
return &authenticator.Response{
User: &user.DefaultInfo{
Name: bootstrapapi.BootstrapUserPrefix + string(id),
Groups: groups,
},
}, true, nil
}
// Copied from k8s.io/cluster-bootstrap/token/api
func getSecretString(secret *corev1.Secret, key string) string {
data, ok := secret.Data[key]
if !ok {
return ""
}
return string(data)
}
// Copied from k8s.io/cluster-bootstrap/token/api
func isSecretExpired(secret *corev1.Secret) bool {
expiration := getSecretString(secret, bootstrapapi.BootstrapTokenExpirationKey)
if len(expiration) > 0 {
expTime, err2 := time.Parse(time.RFC3339, expiration)
if err2 != nil {
klog.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) {
klog.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 defines id.secret regular expression pattern
tokenRegexpString = "^([a-z0-9]{6})\\.([a-z0-9]{16})$"
// tokenRegexp is a compiled regular expression of TokenRegexpString
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
}
// getGroups loads and validates the bootstrapapi.BootstrapTokenExtraGroupsKey
// key from the bootstrap token secret, returning a list of group names or an
// error if any of the group names are invalid.
func getGroups(secret *corev1.Secret) ([]string, error) {
// always include the default group
groups := sets.NewString(bootstrapapi.BootstrapDefaultGroup)
// grab any extra groups and if there are none, return just the default
extraGroupsString := getSecretString(secret, bootstrapapi.BootstrapTokenExtraGroupsKey)
if extraGroupsString == "" {
return groups.List(), nil
}
// validate the names of the extra groups
for _, group := range strings.Split(extraGroupsString, ",") {
if err := bootstraputil.ValidateBootstrapGroupName(group); err != nil {
return nil, err
}
groups.Insert(group)
}
// return the result as a deduplicated, sorted list
return groups.List(), nil
}

View File

@ -1,359 +0,0 @@
/*
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 (
"context"
"reflect"
"testing"
corev1 "k8s.io/api/core/v1"
"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"
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
)
type lister struct {
secrets []*corev1.Secret
}
func (l *lister) List(selector labels.Selector) (ret []*corev1.Secret, err error) {
return l.secrets, nil
}
func (l *lister) Get(name string) (*corev1.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) {
now := metav1.Now()
tests := []struct {
name string
secrets []*corev1.Secret
token string
wantNotFound bool
wantUser *user.DefaultInfo
}{
{
name: "valid token",
secrets: []*corev1.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: "valid token with extra group",
secrets: []*corev1.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.BootstrapTokenExtraGroupsKey: []byte("system:bootstrappers:foo"),
},
Type: "bootstrap.kubernetes.io/token",
},
},
token: tokenID + "." + tokenSecret,
wantUser: &user.DefaultInfo{
Name: "system:bootstrap:" + tokenID,
Groups: []string{"system:bootstrappers", "system:bootstrappers:foo"},
},
},
{
name: "invalid group",
secrets: []*corev1.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.BootstrapTokenExtraGroupsKey: []byte("foo"),
},
Type: "bootstrap.kubernetes.io/token",
},
},
token: tokenID + "." + tokenSecret,
wantNotFound: true,
},
{
name: "invalid secret name",
secrets: []*corev1.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: []*corev1.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: []*corev1.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: "deleted token",
secrets: []*corev1.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: bootstrapapi.BootstrapTokenSecretPrefix + tokenID,
DeletionTimestamp: &now,
},
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: "expired token",
secrets: []*corev1.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: []*corev1.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: []*corev1.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})
resp, found, err := a.AuthenticateToken(context.Background(), 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 := resp.User.(*user.DefaultInfo)
if !reflect.DeepEqual(gotUser, test.wantUser) {
t.Errorf("test %q want user=%#v, got=%#v", test.name, test.wantUser, gotUser)
}
}()
}
}
func TestGetGroups(t *testing.T) {
tests := []struct {
name string
secret *corev1.Secret
expectResult []string
expectError bool
}{
{
name: "not set",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{},
},
expectResult: []string{"system:bootstrappers"},
},
{
name: "set to empty value",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenExtraGroupsKey: []byte(""),
},
},
expectResult: []string{"system:bootstrappers"},
},
{
name: "invalid prefix",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenExtraGroupsKey: []byte("foo"),
},
},
expectError: true,
},
{
name: "valid",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenExtraGroupsKey: []byte("system:bootstrappers:foo,system:bootstrappers:bar,system:bootstrappers:bar"),
},
},
// expect the results in deduplicated, sorted order
expectResult: []string{
"system:bootstrappers",
"system:bootstrappers:bar",
"system:bootstrappers:foo",
},
},
}
for _, test := range tests {
result, err := getGroups(test.secret)
if test.expectError {
if err == nil {
t.Errorf("test %q expected an error, but didn't get one (result: %#v)", test.name, result)
}
continue
}
if err != nil {
t.Errorf("test %q return an unexpected error: %v", test.name, err)
continue
}
if !reflect.DeepEqual(result, test.expectResult) {
t.Errorf("test %q expected %#v, got %#v", test.name, test.expectResult, result)
}
}
}