mirror of https://github.com/k3s-io/k3s
Delete bootstrap token
parent
9cbf136137
commit
a260e501ef
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"],
|
||||
)
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue