diff --git a/plugin/pkg/auth/authenticator/token/bootstrap/BUILD b/plugin/pkg/auth/authenticator/token/bootstrap/BUILD index abba54f6eee..ff951252f08 100644 --- a/plugin/pkg/auth/authenticator/token/bootstrap/BUILD +++ b/plugin/pkg/auth/authenticator/token/bootstrap/BUILD @@ -30,6 +30,7 @@ go_library( "//pkg/client/listers/core/internalversion:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", ], ) diff --git a/plugin/pkg/auth/authenticator/token/bootstrap/bootstrap.go b/plugin/pkg/auth/authenticator/token/bootstrap/bootstrap.go index abd5eb196f2..fecea0f6855 100644 --- a/plugin/pkg/auth/authenticator/token/bootstrap/bootstrap.go +++ b/plugin/pkg/auth/authenticator/token/bootstrap/bootstrap.go @@ -23,11 +23,13 @@ import ( "crypto/subtle" "fmt" "regexp" + "strings" "time" "github.com/golang/glog" "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/kubernetes/pkg/api" bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api" @@ -79,6 +81,7 @@ func tokenErrorf(s *api.Secret, format string, i ...interface{}) { // 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: @@ -134,9 +137,15 @@ func (t *TokenAuthenticator) AuthenticateToken(token string) (user.Info, bool, e 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 &user.DefaultInfo{ Name: bootstrapapi.BootstrapUserPrefix + string(id), - Groups: []string{bootstrapapi.BootstrapDefaultGroup}, + Groups: groups, }, true, nil } @@ -184,3 +193,28 @@ func parseToken(s string) (string, string, error) { } 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 *api.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 := bootstrapapi.ValidateBootstrapGroupName(group); err != nil { + return nil, err + } + groups.Insert(group) + } + + // return the result as a deduplicated, sorted list + return groups.List(), nil +} diff --git a/plugin/pkg/auth/authenticator/token/bootstrap/bootstrap_test.go b/plugin/pkg/auth/authenticator/token/bootstrap/bootstrap_test.go index e8586e9f1a4..7e76faaf63a 100644 --- a/plugin/pkg/auth/authenticator/token/bootstrap/bootstrap_test.go +++ b/plugin/pkg/auth/authenticator/token/bootstrap/bootstrap_test.go @@ -84,6 +84,47 @@ func TestTokenAuthenticator(t *testing.T) { Groups: []string{"system:bootstrappers"}, }, }, + { + name: "valid token with extra group", + 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.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: []*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.BootstrapTokenExtraGroupsKey: []byte("foo"), + }, + Type: "bootstrap.kubernetes.io/token", + }, + }, + token: tokenID + "." + tokenSecret, + wantNotFound: true, + }, { name: "invalid secret name", secrets: []*api.Secret{ @@ -247,3 +288,72 @@ func TestTokenAuthenticator(t *testing.T) { }() } } + +func TestGetGroups(t *testing.T) { + tests := []struct { + name string + secret *api.Secret + expectResult []string + expectError bool + }{ + { + name: "not set", + secret: &api.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Data: map[string][]byte{}, + }, + expectResult: []string{"system:bootstrappers"}, + }, + { + name: "set to empty value", + secret: &api.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Data: map[string][]byte{ + bootstrapapi.BootstrapTokenExtraGroupsKey: []byte(""), + }, + }, + expectResult: []string{"system:bootstrappers"}, + }, + { + name: "invalid prefix", + secret: &api.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Data: map[string][]byte{ + bootstrapapi.BootstrapTokenExtraGroupsKey: []byte("foo"), + }, + }, + expectError: true, + }, + { + name: "valid", + secret: &api.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) + } + } +}