From 33e02aff604fa4795b3d02be254155cd9829eed8 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Tue, 22 Aug 2017 15:43:47 -0500 Subject: [PATCH 1/4] Add extra group constants and validation to `pkg/bootstrap/api`. This adds constants and validation for a new `auth-extra-groups` key on `bootstrap.kubernetes.io/token` secrets. This key allows a bootstrap token to authenticate to extra groups in addition to the `system:bootstrappers` group. Extra groups are always applied in addition to the `system:bootstrappers` group, must begin with a `system:bootstrappers:` prefix, are limited in length, and are limited to a restricted set of characters (alphanumeric, colons, and dashes without a trailing colon/dash). --- pkg/bootstrap/api/BUILD | 8 +++ pkg/bootstrap/api/helpers.go | 34 ++++++++++++ pkg/bootstrap/api/helpers_test.go | 52 +++++++++++++++++++ pkg/bootstrap/api/types.go | 18 +++++-- .../token/bootstrap/bootstrap.go | 2 +- 5 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 pkg/bootstrap/api/helpers.go create mode 100644 pkg/bootstrap/api/helpers_test.go diff --git a/pkg/bootstrap/api/BUILD b/pkg/bootstrap/api/BUILD index dfb7d697638..db7be057bf6 100644 --- a/pkg/bootstrap/api/BUILD +++ b/pkg/bootstrap/api/BUILD @@ -3,12 +3,14 @@ package(default_visibility = ["//visibility:public"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", + "go_test", ) go_library( name = "go_default_library", srcs = [ "doc.go", + "helpers.go", "types.go", ], deps = ["//vendor/k8s.io/api/core/v1:go_default_library"], @@ -26,3 +28,9 @@ filegroup( srcs = [":package-srcs"], tags = ["automanaged"], ) + +go_test( + name = "go_default_test", + srcs = ["helpers_test.go"], + library = ":go_default_library", +) diff --git a/pkg/bootstrap/api/helpers.go b/pkg/bootstrap/api/helpers.go new file mode 100644 index 00000000000..639d61a33c3 --- /dev/null +++ b/pkg/bootstrap/api/helpers.go @@ -0,0 +1,34 @@ +/* +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 api + +import ( + "fmt" + "regexp" +) + +var bootstrapGroupRegexp = regexp.MustCompile(`\A` + BootstrapGroupPattern + `\z`) + +// ValidateBootstrapGroupName checks if the provided group name is a valid +// bootstrap group name. Returns nil if valid or a validation error if invalid. +// TODO(mattmoyer): this validation should migrate out to client-go (see https://github.com/kubernetes/client-go/issues/114) +func ValidateBootstrapGroupName(name string) error { + if bootstrapGroupRegexp.Match([]byte(name)) { + return nil + } + return fmt.Errorf("bootstrap group %q is invalid (must match %s)", name, BootstrapGroupPattern) +} diff --git a/pkg/bootstrap/api/helpers_test.go b/pkg/bootstrap/api/helpers_test.go new file mode 100644 index 00000000000..177687150c5 --- /dev/null +++ b/pkg/bootstrap/api/helpers_test.go @@ -0,0 +1,52 @@ +/* +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 api + +import ( + "strings" + "testing" +) + +func TestValidateBootstrapGroupName(t *testing.T) { + tests := []struct { + name string + input string + valid bool + }{ + {"valid", "system:bootstrappers:foo", true}, + {"valid nested", "system:bootstrappers:foo:bar:baz", true}, + {"valid with dashes and number", "system:bootstrappers:foo-bar-42", true}, + {"invalid uppercase", "system:bootstrappers:Foo", false}, + {"missing prefix", "foo", false}, + {"prefix with no body", "system:bootstrappers:", false}, + {"invalid spaces", "system:bootstrappers: ", false}, + {"invalid asterisk", "system:bootstrappers:*", false}, + {"trailing colon", "system:bootstrappers:foo:", false}, + {"trailing dash", "system:bootstrappers:foo-", false}, + {"script tags", "system:bootstrappers:", false}, + {"too long", "system:bootstrappers:" + strings.Repeat("x", 300), false}, + } + for _, test := range tests { + err := ValidateBootstrapGroupName(test.input) + if err != nil && test.valid { + t.Errorf("test %q: ValidateBootstrapGroupName(%q) returned unexpected error: %v", test.name, test.input, err) + } + if err == nil && !test.valid { + t.Errorf("test %q: ValidateBootstrapGroupName(%q) was supposed to return an error but didn't", test.name, test.input) + } + } +} diff --git a/pkg/bootstrap/api/types.go b/pkg/bootstrap/api/types.go index a7cca0c9bd8..a4e67a1c249 100644 --- a/pkg/bootstrap/api/types.go +++ b/pkg/bootstrap/api/types.go @@ -51,6 +51,11 @@ const ( // describes what the bootstrap token is used for. Optional. BootstrapTokenDescriptionKey = "description" + // BootstrapTokenExtraGroupsKey is a comma-separated list of group names. + // The bootstrap token will authenticate as these groups in addition to the + // "system:bootstrappers" group. + BootstrapTokenExtraGroupsKey = "auth-extra-groups" + // BootstrapTokenUsagePrefix is the prefix for the other usage constants that specifies different // functions of a bootstrap token BootstrapTokenUsagePrefix = "usage-bootstrap-" @@ -63,7 +68,8 @@ const ( // BootstrapTokenUsageAuthentication signals that this token should be used // as a bearer token to authenticate against the Kubernetes API. The bearer // token takes the form "." and authenticates as the - // user "system:bootstrap:" in the group "system:bootstrappers". + // user "system:bootstrap:" in the "system:bootstrappers" group + // as well as any groups specified using BootstrapTokenExtraGroupsKey. // Value must be "true". Any other value is assumed to be false. Optional. BootstrapTokenUsageAuthentication = "usage-bootstrap-authentication" @@ -80,6 +86,12 @@ const ( // authenticate as. The full username given is "system:bootstrap:". BootstrapUserPrefix = "system:bootstrap:" - // BootstrapGroup is the group bootstrapping bearer tokens authenticate in. - BootstrapGroup = "system:bootstrappers" + // BootstrapGroupPattern is the valid regex pattern that all groups + // assigned to a bootstrap token by BootstrapTokenExtraGroupsKey must match. + // See also ValidateBootstrapGroupName(). + BootstrapGroupPattern = "system:bootstrappers:[a-z0-9:-]{0,255}[a-z0-9]" + + // BootstrapDefaultGroup is the default group for bootstrapping bearer + // tokens (in addition to any groups from BootstrapTokenExtraGroupsKey). + BootstrapDefaultGroup = "system:bootstrappers" ) diff --git a/plugin/pkg/auth/authenticator/token/bootstrap/bootstrap.go b/plugin/pkg/auth/authenticator/token/bootstrap/bootstrap.go index 62b3ad50d8c..abd5eb196f2 100644 --- a/plugin/pkg/auth/authenticator/token/bootstrap/bootstrap.go +++ b/plugin/pkg/auth/authenticator/token/bootstrap/bootstrap.go @@ -136,7 +136,7 @@ func (t *TokenAuthenticator) AuthenticateToken(token string) (user.Info, bool, e return &user.DefaultInfo{ Name: bootstrapapi.BootstrapUserPrefix + string(id), - Groups: []string{bootstrapapi.BootstrapGroup}, + Groups: []string{bootstrapapi.BootstrapDefaultGroup}, }, true, nil } From fd5c00b38dd7fb74eacecef47799972f59baa4dc Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Tue, 22 Aug 2017 15:51:57 -0500 Subject: [PATCH 2/4] Implement `auth-extra-groups` in bootstrap token authenticator. This implements support for the new `auth-extra-groups` key in `bootstrap.kubernetes.io/token` secrets by adding extra groups to the user info returned for valid bootstrap tokens. --- .../auth/authenticator/token/bootstrap/BUILD | 1 + .../token/bootstrap/bootstrap.go | 36 +++++- .../token/bootstrap/bootstrap_test.go | 110 ++++++++++++++++++ 3 files changed, 146 insertions(+), 1 deletion(-) 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) + } + } +} From 77f1b72a404b90326263fc55bef2f951b77db5f0 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Tue, 22 Aug 2017 16:13:24 -0500 Subject: [PATCH 3/4] kubeadm: add `--groups` flag for `kubeadm token create`. This adds support for creating a bootstrap token that authenticates with extra `system:bootstrappers:*` groups in addition to `system:bootstrappers`. --- cmd/kubeadm/app/cmd/BUILD | 1 + cmd/kubeadm/app/cmd/init.go | 2 +- cmd/kubeadm/app/cmd/token.go | 25 ++++++++++++++++--- .../app/phases/bootstraptoken/node/token.go | 17 ++++++++----- .../phases/bootstraptoken/node/token_test.go | 2 +- 5 files changed, 36 insertions(+), 11 deletions(-) diff --git a/cmd/kubeadm/app/cmd/BUILD b/cmd/kubeadm/app/cmd/BUILD index 4249096ded3..6c261ac2c68 100644 --- a/cmd/kubeadm/app/cmd/BUILD +++ b/cmd/kubeadm/app/cmd/BUILD @@ -66,6 +66,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/version:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index 1fdbd1cf82b..d6f08cf471c 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -339,7 +339,7 @@ func (i *Init) Run(out io.Writer) error { // Create the default node bootstrap token tokenDescription := "The default bootstrap token generated by 'kubeadm init'." - if err := nodebootstraptokenphase.UpdateOrCreateToken(client, i.cfg.Token, false, i.cfg.TokenTTL, kubeadmconstants.DefaultTokenUsages, tokenDescription); err != nil { + if err := nodebootstraptokenphase.UpdateOrCreateToken(client, i.cfg.Token, false, i.cfg.TokenTTL, kubeadmconstants.DefaultTokenUsages, []string{}, tokenDescription); err != nil { return err } // Create RBAC rules that makes the bootstrap tokens able to post CSRs diff --git a/cmd/kubeadm/app/cmd/token.go b/cmd/kubeadm/app/cmd/token.go index d3bbda1041d..1aab3f196e0 100644 --- a/cmd/kubeadm/app/cmd/token.go +++ b/cmd/kubeadm/app/cmd/token.go @@ -31,6 +31,7 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/util/sets" clientset "k8s.io/client-go/kubernetes" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" @@ -87,6 +88,7 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command { "dry-run", dryRun, "Whether to enable dry-run mode or not") var usages []string + var extraGroups []string var tokenDuration time.Duration var description string createCmd := &cobra.Command{ @@ -114,7 +116,7 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command { fmt.Fprintln(errW, "[kubeadm] WARNING: starting in 1.8, tokens expire after 24 hours by default (if you require a non-expiring token use --ttl 0)") } - err = RunCreateToken(out, client, token, tokenDuration, usages, description) + err = RunCreateToken(out, client, token, tokenDuration, usages, extraGroups, description) kubeadmutil.CheckErr(err) }, } @@ -122,6 +124,9 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command { "ttl", kubeadmconstants.DefaultTokenDuration, "The duration before the token is automatically deleted (e.g. 1s, 2m, 3h). 0 means 'never expires'.") createCmd.Flags().StringSliceVar(&usages, "usages", kubeadmconstants.DefaultTokenUsages, "The ways in which this token can be used. Valid options: [signing,authentication].") + createCmd.Flags().StringSliceVar(&extraGroups, + "groups", []string{}, + fmt.Sprintf("Extra groups that this token will authenticate as when used for authentication. Must match %q.", bootstrapapi.BootstrapGroupPattern)) createCmd.Flags().StringVar(&description, "description", "", "A human friendly description of how this token is used.") tokenCmd.AddCommand(createCmd) @@ -192,7 +197,7 @@ func NewCmdTokenGenerate(out io.Writer) *cobra.Command { } // RunCreateToken generates a new bootstrap token and stores it as a secret on the server. -func RunCreateToken(out io.Writer, client clientset.Interface, token string, tokenDuration time.Duration, usages []string, description string) error { +func RunCreateToken(out io.Writer, client clientset.Interface, token string, tokenDuration time.Duration, usages []string, extraGroups []string, description string) error { if len(token) == 0 { var err error @@ -207,8 +212,22 @@ func RunCreateToken(out io.Writer, client clientset.Interface, token string, tok } } + // adding groups only makes sense for authentication + var usagesSet sets.String + usagesSet.Insert(usages...) + if len(extraGroups) > 0 && !usagesSet.Has("authentication") { + return fmt.Errorf("--groups cannot be specified unless --usages includes \"authentication\"") + } + + // validate any extra group names + for _, group := range extraGroups { + if err := bootstrapapi.ValidateBootstrapGroupName(group); err != nil { + return err + } + } + // TODO: Validate usages here so we don't allow something unsupported - err := tokenphase.CreateNewToken(client, token, tokenDuration, usages, description) + err := tokenphase.CreateNewToken(client, token, tokenDuration, usages, extraGroups, description) if err != nil { return err } diff --git a/cmd/kubeadm/app/phases/bootstraptoken/node/token.go b/cmd/kubeadm/app/phases/bootstraptoken/node/token.go index e4c29912e4a..4b59d81fcc0 100644 --- a/cmd/kubeadm/app/phases/bootstraptoken/node/token.go +++ b/cmd/kubeadm/app/phases/bootstraptoken/node/token.go @@ -18,6 +18,7 @@ package node import ( "fmt" + "strings" "time" "k8s.io/api/core/v1" @@ -33,12 +34,12 @@ const tokenCreateRetries = 5 // TODO(mattmoyer): Move CreateNewToken, UpdateOrCreateToken and encodeTokenSecretData out of this package to client-go for a generic abstraction and client for a Bootstrap Token // CreateNewToken tries to create a token and fails if one with the same ID already exists -func CreateNewToken(client clientset.Interface, token string, tokenDuration time.Duration, usages []string, description string) error { - return UpdateOrCreateToken(client, token, true, tokenDuration, usages, description) +func CreateNewToken(client clientset.Interface, token string, tokenDuration time.Duration, usages []string, extraGroups []string, description string) error { + return UpdateOrCreateToken(client, token, true, tokenDuration, usages, extraGroups, description) } // UpdateOrCreateToken attempts to update a token with the given ID, or create if it does not already exist. -func UpdateOrCreateToken(client clientset.Interface, token string, failIfExists bool, tokenDuration time.Duration, usages []string, description string) error { +func UpdateOrCreateToken(client clientset.Interface, token string, failIfExists bool, tokenDuration time.Duration, usages []string, extraGroups []string, description string) error { tokenID, tokenSecret, err := tokenutil.ParseToken(token) if err != nil { return err @@ -52,7 +53,7 @@ func UpdateOrCreateToken(client clientset.Interface, token string, failIfExists return fmt.Errorf("a token with id %q already exists", tokenID) } // Secret with this ID already exists, update it: - secret.Data = encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, description) + secret.Data = encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, extraGroups, description) if _, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Update(secret); err == nil { return nil } @@ -67,7 +68,7 @@ func UpdateOrCreateToken(client clientset.Interface, token string, failIfExists Name: secretName, }, Type: v1.SecretType(bootstrapapi.SecretTypeBootstrapToken), - Data: encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, description), + Data: encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, extraGroups, description), } if _, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Create(secret); err == nil { return nil @@ -85,12 +86,16 @@ func UpdateOrCreateToken(client clientset.Interface, token string, failIfExists } // encodeTokenSecretData takes the token discovery object and an optional duration and returns the .Data for the Secret -func encodeTokenSecretData(tokenID, tokenSecret string, duration time.Duration, usages []string, description string) map[string][]byte { +func encodeTokenSecretData(tokenID, tokenSecret string, duration time.Duration, usages []string, extraGroups []string, description string) map[string][]byte { data := map[string][]byte{ bootstrapapi.BootstrapTokenIDKey: []byte(tokenID), bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret), } + if len(extraGroups) > 0 { + data[bootstrapapi.BootstrapTokenExtraGroupsKey] = []byte(strings.Join(extraGroups, ",")) + } + if duration > 0 { // Get the current time, add the specified duration, and format it accordingly durationString := time.Now().Add(duration).Format(time.RFC3339) diff --git a/cmd/kubeadm/app/phases/bootstraptoken/node/token_test.go b/cmd/kubeadm/app/phases/bootstraptoken/node/token_test.go index 48a6f80e982..7af575e01d1 100644 --- a/cmd/kubeadm/app/phases/bootstraptoken/node/token_test.go +++ b/cmd/kubeadm/app/phases/bootstraptoken/node/token_test.go @@ -33,7 +33,7 @@ func TestEncodeTokenSecretData(t *testing.T) { {token: &kubeadmapi.TokenDiscovery{ID: "foo", Secret: "bar"}, t: time.Second}, // should use default } for _, rt := range tests { - actual := encodeTokenSecretData(rt.token.ID, rt.token.Secret, rt.t, []string{}, "") + actual := encodeTokenSecretData(rt.token.ID, rt.token.Secret, rt.t, []string{}, []string{}, "") if !bytes.Equal(actual["token-id"], []byte(rt.token.ID)) { t.Errorf( "failed EncodeTokenSecretData:\n\texpected: %s\n\t actual: %s", From c7996a7236ee67c01babcfca3f4a91f88cca699e Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Wed, 23 Aug 2017 16:37:56 -0500 Subject: [PATCH 4/4] kubeadm: add extra group info to `token list`. This adds an `EXTRA GROUPS` column to the output of `kubeadm token list`. This displays any extra `system:bootstrappers:*` groups that are specified in the token's `auth-extra-groups` key. --- cmd/kubeadm/app/cmd/token.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/kubeadm/app/cmd/token.go b/cmd/kubeadm/app/cmd/token.go index 1aab3f196e0..f21b22934b2 100644 --- a/cmd/kubeadm/app/cmd/token.go +++ b/cmd/kubeadm/app/cmd/token.go @@ -265,7 +265,7 @@ func RunListTokens(out io.Writer, errW io.Writer, client clientset.Interface) er } w := tabwriter.NewWriter(out, 10, 4, 3, ' ', 0) - fmt.Fprintln(w, "TOKEN\tTTL\tEXPIRES\tUSAGES\tDESCRIPTION") + fmt.Fprintln(w, "TOKEN\tTTL\tEXPIRES\tUSAGES\tDESCRIPTION\tEXTRA GROUPS") for _, secret := range secrets.Items { tokenId := getSecretString(&secret, bootstrapapi.BootstrapTokenIDKey) if len(tokenId) == 0 { @@ -323,7 +323,12 @@ func RunListTokens(out io.Writer, errW io.Writer, client clientset.Interface) er if len(description) == 0 { description = "" } - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", tokenutil.BearerToken(td), ttl, expires, usageString, description) + + groups := getSecretString(&secret, bootstrapapi.BootstrapTokenExtraGroupsKey) + if len(groups) == 0 { + groups = "" + } + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", tokenutil.BearerToken(td), ttl, expires, usageString, description, groups) } w.Flush() return nil