diff --git a/cmd/kubeadm/app/cmd/BUILD b/cmd/kubeadm/app/cmd/BUILD index 9c9d15929e3..353483db2fb 100644 --- a/cmd/kubeadm/app/cmd/BUILD +++ b/cmd/kubeadm/app/cmd/BUILD @@ -53,7 +53,9 @@ go_library( "//vendor:k8s.io/apimachinery/pkg/runtime", "//vendor:k8s.io/apimachinery/pkg/util/net", "//vendor:k8s.io/apiserver/pkg/util/flag", + "//vendor:k8s.io/client-go/kubernetes", "//vendor:k8s.io/client-go/pkg/api", + "//vendor:k8s.io/client-go/pkg/api/v1", "//vendor:k8s.io/client-go/util/cert", ], ) diff --git a/cmd/kubeadm/app/cmd/cmd.go b/cmd/kubeadm/app/cmd/cmd.go index d4f5e532e26..71aaa119069 100644 --- a/cmd/kubeadm/app/cmd/cmd.go +++ b/cmd/kubeadm/app/cmd/cmd.go @@ -83,13 +83,13 @@ func NewKubeadmCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob cmds.AddCommand(NewCmdJoin(out)) cmds.AddCommand(NewCmdReset(out)) cmds.AddCommand(NewCmdVersion(out)) + cmds.AddCommand(NewCmdToken(out, err)) - // Wrap not yet usable/supported commands in experimental sub-command: + // Wrap not yet fully supported commands in an alpha subcommand experimentalCmd := &cobra.Command{ - Use: "ex", + Use: "alpha", Short: "Experimental sub-commands not yet fully functional.", } - experimentalCmd.AddCommand(NewCmdToken(out, err)) experimentalCmd.AddCommand(phases.NewCmdPhase(out)) cmds.AddCommand(experimentalCmd) diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index 78d26d74ec0..e2ab81c0b0c 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -233,7 +233,8 @@ func (i *Init) Run(out io.Writer) error { if err := kubemaster.CreateDiscoveryDeploymentAndSecret(i.cfg, client); err != nil { return err } - if err := tokenphase.UpdateOrCreateToken(client, i.cfg.Discovery.Token, kubeadmconstants.DefaultTokenDuration); err != nil { + tokenDescription := "The default bootstrap token generated by 'kubeadm init'." + if err := tokenphase.UpdateOrCreateToken(client, i.cfg.Discovery.Token, false, kubeadmconstants.DefaultTokenDuration, kubeadmconstants.DefaultTokenUsages, tokenDescription); err != nil { return err } } diff --git a/cmd/kubeadm/app/cmd/token.go b/cmd/kubeadm/app/cmd/token.go index b4360dfb651..9fc28a2ac76 100644 --- a/cmd/kubeadm/app/cmd/token.go +++ b/cmd/kubeadm/app/cmd/token.go @@ -20,7 +20,7 @@ import ( "errors" "fmt" "io" - "path" + "strings" "text/tabwriter" "time" @@ -29,7 +29,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" + clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/pkg/api" + "k8s.io/client-go/pkg/api/v1" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" tokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/token" @@ -42,9 +44,30 @@ import ( func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command { + var kubeConfigFile string tokenCmd := &cobra.Command{ Use: "token", Short: "Manage bootstrap tokens.", + Long: dedent.Dedent(` + This command will manage Bootstrap Token for you. + Please note this usage of this command is optional, and mostly for advanced users. + + In short, Bootstrap Tokens are used for establishing bidirectional trust between a client and a server. + A Bootstrap Token can be used when a client (for example a node that's about to join the cluster) needs + to trust the server it is talking to. Then a Bootstrap Token with the "signing" usage can be used. + Bootstrap Tokens can also function as a way to allow short-lived authentication to the API Server + (the token serves as a way for the API Server to trust the client), for example for doing the TLS Bootstrap. + + What is a Bootstrap Token more exactly? + - It is a Secret in the kube-system namespace of type "bootstrap.kubernetes.io/token". + - A Bootstrap Token must be of the form "[a-z0-9]{6}.[a-z0-9]{16}"; the former part is the public Token ID, + and the latter is the Token Secret, which must be kept private at all circumstances. + - The name of the Secret must be named "bootstrap-token-(token-id)". + + You can read more about Bootstrap Tokens in this proposal: + + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/bootstrap-discovery.md + `), // Without this callback, if a user runs just the "token" // command without a subcommand, or with an invalid subcommand, @@ -60,22 +83,41 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command { }, } - var token string + tokenCmd.PersistentFlags().StringVar(&kubeConfigFile, + "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use for talking to the cluster") + + var usages []string var tokenDuration time.Duration + var description string createCmd := &cobra.Command{ - Use: "create", + Use: "create [token]", Short: "Create bootstrap tokens on the server.", + Long: dedent.Dedent(` + This command will create a Bootstrap Token for you. + You can specify the usages for this token, the time to live and an optional human friendly description. + + The [token] is the actual token to write. + This should be a securely generated random token of the form "[a-z0-9]{6}.[a-z0-9]{16}". + If no [token] is given, kubeadm will generate a random token instead. + `), Run: func(tokenCmd *cobra.Command, args []string) { - err := RunCreateToken(out, tokenCmd, tokenDuration, token) + token := "" + if len(args) != 0 { + token = args[0] + } + client, err := kubeconfigutil.ClientSetFromFile(kubeConfigFile) + kubeadmutil.CheckErr(err) + + err = RunCreateToken(out, client, token, tokenDuration, usages, description) kubeadmutil.CheckErr(err) }, } - createCmd.PersistentFlags().DurationVar(&tokenDuration, + createCmd.Flags().DurationVar(&tokenDuration, "ttl", kubeadmconstants.DefaultTokenDuration, "The duration before the token is automatically deleted. 0 means 'never expires'.") - createCmd.PersistentFlags().StringVar( - &token, "token", "", - "Shared secret used to secure cluster bootstrap. If none is provided, one will be generated for you.", - ) + createCmd.Flags().StringSliceVar(&usages, + "usages", kubeadmconstants.DefaultTokenUsages, "The ways in which this token can be used. Valid options: [signing,authentication].") + createCmd.Flags().StringVar(&description, + "description", "", "A human friendly description of how this token is used.") tokenCmd.AddCommand(createCmd) tokenCmd.AddCommand(NewCmdTokenGenerate(out)) @@ -83,21 +125,36 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command { listCmd := &cobra.Command{ Use: "list", Short: "List bootstrap tokens on the server.", + Long: dedent.Dedent(` + This command will list all Bootstrap Tokens for you. + `), Run: func(tokenCmd *cobra.Command, args []string) { - err := RunListTokens(out, errW, tokenCmd) + client, err := kubeconfigutil.ClientSetFromFile(kubeConfigFile) + kubeadmutil.CheckErr(err) + + err = RunListTokens(out, errW, client) kubeadmutil.CheckErr(err) }, } tokenCmd.AddCommand(listCmd) deleteCmd := &cobra.Command{ - Use: "delete", + Use: "delete [token-value]", Short: "Delete bootstrap tokens on the server.", + Long: dedent.Dedent(` + This command will delete a given Bootstrap Token for you. + + The [token-value] is the full Token of the form "[a-z0-9]{6}.[a-z0-9]{16}" or the + Token ID of the form "[a-z0-9]{6}" to delete. + `), Run: func(tokenCmd *cobra.Command, args []string) { if len(args) < 1 { - kubeadmutil.CheckErr(fmt.Errorf("missing subcommand; 'token delete' is missing token of form [\"^([a-z0-9]{6})$\"]")) + kubeadmutil.CheckErr(fmt.Errorf("missing subcommand; 'token delete' is missing token of form [%q]", tokenutil.TokenIDRegexpString)) } - err := RunDeleteToken(out, tokenCmd, args[0]) + client, err := kubeconfigutil.ClientSetFromFile(kubeConfigFile) + kubeadmutil.CheckErr(err) + + err = RunDeleteToken(out, client, args[0]) kubeadmutil.CheckErr(err) }, } @@ -115,7 +172,7 @@ func NewCmdTokenGenerate(out io.Writer) *cobra.Command { the "init" and "join" commands. You don't have to use this command in order to generate a token, you can do so - yourself as long as it's in the format "<6 characters>:<16 characters>". This + yourself as long as it's in the format "[a-z0-9]{6}.[a-z0-9]{16}". This command is provided for convenience to generate tokens in that format. You can also use "kubeadm init" without specifying a token, and it will @@ -129,27 +186,30 @@ 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, cmd *cobra.Command, tokenDuration time.Duration, token string) error { - client, err := kubeconfigutil.ClientSetFromFile(path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmconstants.AdminKubeConfigFileName)) +func RunCreateToken(out io.Writer, client *clientset.Clientset, token string, tokenDuration time.Duration, usages []string, description string) error { + + td := &kubeadmapi.TokenDiscovery{} + var err error + if len(token) == 0 { + err = tokenutil.GenerateToken(td) + } else { + td.ID, td.Secret, err = tokenutil.ParseToken(token) + } if err != nil { return err } - parsedID, parsedSecret, err := tokenutil.ParseToken(token) + // TODO: Validate usages here so we don't allow something unsupported + err = tokenphase.CreateNewToken(client, td, tokenDuration, usages, description) if err != nil { return err } - td := &kubeadmapi.TokenDiscovery{ID: parsedID, Secret: parsedSecret} - err = tokenphase.UpdateOrCreateToken(client, td, tokenDuration) - if err != nil { - return err - } fmt.Fprintln(out, tokenutil.BearerToken(td)) - return nil } +// RunGenerateToken just generates a random token for the user func RunGenerateToken(out io.Writer) error { td := &kubeadmapi.TokenDiscovery{} err := tokenutil.GenerateToken(td) @@ -162,12 +222,8 @@ func RunGenerateToken(out io.Writer) error { } // RunListTokens lists details on all existing bootstrap tokens on the server. -func RunListTokens(out io.Writer, errW io.Writer, cmd *cobra.Command) error { - client, err := kubeconfigutil.ClientSetFromFile(path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmconstants.AdminKubeConfigFileName)) - if err != nil { - return err - } - +func RunListTokens(out io.Writer, errW io.Writer, client *clientset.Clientset) error { + // First, build our selector for bootstrap tokens only tokenSelector := fields.SelectorFromSet( map[string]string{ api.SecretTypeField: string(bootstrapapi.SecretTypeBootstrapToken), @@ -177,61 +233,99 @@ func RunListTokens(out io.Writer, errW io.Writer, cmd *cobra.Command) error { FieldSelector: tokenSelector.String(), } - results, err := client.Secrets(metav1.NamespaceSystem).List(listOptions) + secrets, err := client.CoreV1().Secrets(metav1.NamespaceSystem).List(listOptions) if err != nil { return fmt.Errorf("failed to list bootstrap tokens [%v]", err) } w := tabwriter.NewWriter(out, 10, 4, 3, ' ', 0) - fmt.Fprintln(w, "ID\tTOKEN\tTTL") - for _, secret := range results.Items { - tokenId, ok := secret.Data[bootstrapapi.BootstrapTokenIDKey] - if !ok { - fmt.Fprintf(errW, "[token] bootstrap token has no token-id data: %s\n", secret.Name) + fmt.Fprintln(w, "TOKEN\tTTL\tEXPIRES\tUSAGES\tDESCRIPTION") + for _, secret := range secrets.Items { + tokenId := getSecretString(&secret, bootstrapapi.BootstrapTokenIDKey) + if len(tokenId) == 0 { + fmt.Fprintf(errW, "bootstrap token has no token-id data: %s\n", secret.Name) continue } - tokenSecret, ok := secret.Data[bootstrapapi.BootstrapTokenSecretKey] - if !ok { - fmt.Fprintf(errW, "[token] bootstrap token has no token-secret data: %s\n", secret.Name) + // enforce the right naming convention + if secret.Name != fmt.Sprintf("%s%s", bootstrapapi.BootstrapTokenSecretPrefix, tokenId) { + fmt.Fprintf(errW, "bootstrap token name is not of the form '%s(token-id)': %s\n", bootstrapapi.BootstrapTokenSecretPrefix, secret.Name) continue } - td := &kubeadmapi.TokenDiscovery{ID: string(tokenId), Secret: string(tokenSecret)} + + tokenSecret := getSecretString(&secret, bootstrapapi.BootstrapTokenSecretKey) + if len(tokenSecret) == 0 { + fmt.Fprintf(errW, "bootstrap token has no token-secret data: %s\n", secret.Name) + continue + } + td := &kubeadmapi.TokenDiscovery{ID: tokenId, Secret: tokenSecret} // Expiration time is optional, if not specified this implies the token // never expires. + ttl := "" expires := "" - secretExpiration, ok := secret.Data[bootstrapapi.BootstrapTokenExpirationKey] - if ok { - expireTime, err := time.Parse(time.RFC3339, string(secretExpiration)) + secretExpiration := getSecretString(&secret, bootstrapapi.BootstrapTokenExpirationKey) + if len(secretExpiration) > 0 { + expireTime, err := time.Parse(time.RFC3339, secretExpiration) if err != nil { - return fmt.Errorf("error parsing expiration time [%v]", err) + fmt.Fprintf(errW, "can't parse expiration time of bootstrap token %s\n", secret.Name) + continue } - expires = printers.ShortHumanDuration(expireTime.Sub(time.Now())) + ttl = printers.ShortHumanDuration(expireTime.Sub(time.Now())) + expires = expireTime.Format(time.RFC3339) } - fmt.Fprintf(w, "%s\t%s\t%s\n", tokenId, tokenutil.BearerToken(td), expires) + + usages := []string{} + for k, v := range secret.Data { + // Skip all fields that don't include this prefix + if !strings.Contains(k, bootstrapapi.BootstrapTokenUsagePrefix) { + continue + } + // Skip those that don't have this usage set to true + if string(v) != "true" { + continue + } + usages = append(usages, strings.TrimPrefix(k, bootstrapapi.BootstrapTokenUsagePrefix)) + } + usageString := strings.Join(usages, ",") + if len(usageString) == 0 { + usageString = "" + } + + description := getSecretString(&secret, bootstrapapi.BootstrapTokenDescriptionKey) + 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) } w.Flush() - return nil } // RunDeleteToken removes a bootstrap token from the server. -func RunDeleteToken(out io.Writer, cmd *cobra.Command, tokenId string) error { - if err := tokenutil.ParseTokenID(tokenId); err != nil { - return err - } - - client, err := kubeconfigutil.ClientSetFromFile(path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmconstants.AdminKubeConfigFileName)) - if err != nil { - return err +func RunDeleteToken(out io.Writer, client *clientset.Clientset, tokenIdOrToken string) error { + // Assume the given first argument is a token id and try to parse it + tokenId := tokenIdOrToken + if err := tokenutil.ParseTokenID(tokenIdOrToken); err != nil { + if tokenId, _, err = tokenutil.ParseToken(tokenIdOrToken); err != nil { + return fmt.Errorf("given token or token id %q didn't match pattern [%q] or [%q]", tokenIdOrToken, tokenutil.TokenIDRegexpString, tokenutil.TokenRegexpString) + } } tokenSecretName := fmt.Sprintf("%s%s", bootstrapapi.BootstrapTokenSecretPrefix, tokenId) - if err := client.Secrets(metav1.NamespaceSystem).Delete(tokenSecretName, nil); err != nil { + if err := client.CoreV1().Secrets(metav1.NamespaceSystem).Delete(tokenSecretName, nil); err != nil { return fmt.Errorf("failed to delete bootstrap token [%v]", err) } - fmt.Fprintf(out, "[token] bootstrap token deleted: %s\n", tokenId) - + fmt.Fprintf(out, "bootstrap token with id %q deleted\n", tokenId) return nil } + +func getSecretString(secret *v1.Secret, key string) string { + if secret.Data == nil { + return "" + } + if val, ok := secret.Data[key]; ok { + return string(val) + } + return "" +} diff --git a/cmd/kubeadm/app/constants/constants.go b/cmd/kubeadm/app/constants/constants.go index 36e8fa9de52..93841658da5 100644 --- a/cmd/kubeadm/app/constants/constants.go +++ b/cmd/kubeadm/app/constants/constants.go @@ -80,7 +80,8 @@ const ( MinimumAddressesInServiceSubnet = 10 // DefaultTokenDuration specifies the default amount of time that a bootstrap token will be valid - DefaultTokenDuration = time.Duration(8) * time.Hour + // Default behaviour is "never expire" == 0 + DefaultTokenDuration = 0 // LabelNodeRoleMaster specifies that a node is a master // It's copied over to kubeadm until it's merged in core: https://github.com/kubernetes/kubernetes/pull/39112 @@ -109,4 +110,7 @@ var ( AuthorizationPolicyPath = path.Join(KubernetesDir, "abac_policy.json") AuthorizationWebhookConfigPath = path.Join(KubernetesDir, "webhook_authz.conf") + + // DefaultTokenUsages specifies the default functions a token will get + DefaultTokenUsages = []string{"signing", "authentication"} ) diff --git a/cmd/kubeadm/app/phases/token/bootstrap.go b/cmd/kubeadm/app/phases/token/bootstrap.go index fc9d438f5b8..6369a7616c1 100644 --- a/cmd/kubeadm/app/phases/token/bootstrap.go +++ b/cmd/kubeadm/app/phases/token/bootstrap.go @@ -29,13 +29,15 @@ import ( bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api" ) -const ( - tokenCreateRetries = 5 -) +const tokenCreateRetries = 5 -// UpdateOrCreateToken attempts to update a token with the given ID, or create if it does -// not already exist. -func UpdateOrCreateToken(client *clientset.Clientset, d *kubeadmapi.TokenDiscovery, tokenDuration time.Duration) error { +// CreateNewToken tries to create a token and fails if one with the same ID already exists +func CreateNewToken(client *clientset.Clientset, d *kubeadmapi.TokenDiscovery, tokenDuration time.Duration, usages []string, description string) error { + return UpdateOrCreateToken(client, d, true, tokenDuration, usages, description) +} + +// UpdateOrCreateToken attempts to update a token with the given ID, or create if it does not already exist. +func UpdateOrCreateToken(client *clientset.Clientset, d *kubeadmapi.TokenDiscovery, failIfExists bool, tokenDuration time.Duration, usages []string, description string) error { // Let's make sure the token is valid if valid, err := tokenutil.ValidateToken(d); !valid { return err @@ -45,8 +47,11 @@ func UpdateOrCreateToken(client *clientset.Clientset, d *kubeadmapi.TokenDiscove for i := 0; i < tokenCreateRetries; i++ { secret, err := client.Secrets(metav1.NamespaceSystem).Get(secretName, metav1.GetOptions{}) if err == nil { + if failIfExists { + return fmt.Errorf("a token with id %q already exists", d.ID) + } // Secret with this ID already exists, update it: - secret.Data = encodeTokenSecretData(d, tokenDuration) + secret.Data = encodeTokenSecretData(d, tokenDuration, usages, description) if _, err := client.Secrets(metav1.NamespaceSystem).Update(secret); err == nil { return nil } else { @@ -62,14 +67,13 @@ func UpdateOrCreateToken(client *clientset.Clientset, d *kubeadmapi.TokenDiscove Name: secretName, }, Type: v1.SecretType(bootstrapapi.SecretTypeBootstrapToken), - Data: encodeTokenSecretData(d, tokenDuration), + Data: encodeTokenSecretData(d, tokenDuration, usages, description), } if _, err := client.Secrets(metav1.NamespaceSystem).Create(secret); err == nil { return nil } else { lastErr = err } - continue } @@ -82,11 +86,10 @@ func UpdateOrCreateToken(client *clientset.Clientset, d *kubeadmapi.TokenDiscove } // encodeTokenSecretData takes the token discovery object and an optional duration and returns the .Data for the Secret -func encodeTokenSecretData(d *kubeadmapi.TokenDiscovery, duration time.Duration) map[string][]byte { +func encodeTokenSecretData(d *kubeadmapi.TokenDiscovery, duration time.Duration, usages []string, description string) map[string][]byte { data := map[string][]byte{ - bootstrapapi.BootstrapTokenIDKey: []byte(d.ID), - bootstrapapi.BootstrapTokenSecretKey: []byte(d.Secret), - bootstrapapi.BootstrapTokenUsageSigningKey: []byte("true"), + bootstrapapi.BootstrapTokenIDKey: []byte(d.ID), + bootstrapapi.BootstrapTokenSecretKey: []byte(d.Secret), } if duration > 0 { @@ -94,5 +97,12 @@ func encodeTokenSecretData(d *kubeadmapi.TokenDiscovery, duration time.Duration) durationString := time.Now().Add(duration).Format(time.RFC3339) data[bootstrapapi.BootstrapTokenExpirationKey] = []byte(durationString) } + if len(description) > 0 { + data[bootstrapapi.BootstrapTokenDescriptionKey] = []byte(description) + } + for _, usage := range usages { + // TODO: Validate the usage string here before + data[bootstrapapi.BootstrapTokenUsagePrefix+usage] = []byte("true") + } return data } diff --git a/cmd/kubeadm/app/phases/token/bootstrap_test.go b/cmd/kubeadm/app/phases/token/bootstrap_test.go index 4975af69271..052c171c4ee 100644 --- a/cmd/kubeadm/app/phases/token/bootstrap_test.go +++ b/cmd/kubeadm/app/phases/token/bootstrap_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, rt.t) + actual := encodeTokenSecretData(rt.token, rt.t, []string{}, "") if !bytes.Equal(actual["token-id"], []byte(rt.token.ID)) { t.Errorf( "failed EncodeTokenSecretData:\n\texpected: %s\n\t actual: %s", diff --git a/cmd/kubeadm/app/util/token/tokens.go b/cmd/kubeadm/app/util/token/tokens.go index c4940cdfd23..9240680ee84 100644 --- a/cmd/kubeadm/app/util/token/tokens.go +++ b/cmd/kubeadm/app/util/token/tokens.go @@ -32,10 +32,10 @@ const ( ) var ( - tokenIDRegexpString = "^([a-z0-9]{6})$" - tokenIDRegexp = regexp.MustCompile(tokenIDRegexpString) - tokenRegexpString = "^([a-z0-9]{6})\\.([a-z0-9]{16})$" - tokenRegexp = regexp.MustCompile(tokenRegexpString) + TokenIDRegexpString = "^([a-z0-9]{6})$" + TokenIDRegexp = regexp.MustCompile(TokenIDRegexpString) + TokenRegexpString = "^([a-z0-9]{6})\\.([a-z0-9]{16})$" + TokenRegexp = regexp.MustCompile(TokenRegexpString) ) func randBytes(length int) (string, error) { @@ -69,8 +69,8 @@ func GenerateToken(d *kubeadmapi.TokenDiscovery) error { // ParseTokenID tries and parse a valid token ID from a string. // An error is returned in case of failure. func ParseTokenID(s string) error { - if !tokenIDRegexp.MatchString(s) { - return fmt.Errorf("token ID [%q] was not of form [%q]", s, tokenIDRegexpString) + if !TokenIDRegexp.MatchString(s) { + return fmt.Errorf("token ID [%q] was not of form [%q]", s, TokenIDRegexpString) } return nil } @@ -78,9 +78,9 @@ func ParseTokenID(s string) error { // 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) + split := TokenRegexp.FindStringSubmatch(s) if len(split) != 3 { - return "", "", fmt.Errorf("token [%q] was not of form [%q]", s, tokenRegexpString) + return "", "", fmt.Errorf("token [%q] was not of form [%q]", s, TokenRegexpString) } return split[1], split[2], nil } diff --git a/cmd/kubeadm/test/cmd/token_test.go b/cmd/kubeadm/test/cmd/token_test.go index eee659e4889..580bedcff7f 100644 --- a/cmd/kubeadm/test/cmd/token_test.go +++ b/cmd/kubeadm/test/cmd/token_test.go @@ -36,17 +36,17 @@ func TestCmdTokenGenerate(t *testing.T) { t.Log("kubeadm cmd tests being skipped") t.Skip() } - stdout, _, err := RunCmd(*kubeadmPath, "ex", "token", "generate") + stdout, _, err := RunCmd(*kubeadmPath, "token", "generate") if err != nil { - t.Fatalf("'kubeadm ex token generate' exited uncleanly: %v", err) + t.Fatalf("'kubeadm token generate' exited uncleanly: %v", err) } matched, err := regexp.MatchString(TokenExpectedRegex, stdout) if err != nil { - t.Fatalf("encountered an error while trying to match 'kubeadm ex token generate' stdout: %v", err) + t.Fatalf("encountered an error while trying to match 'kubeadm token generate' stdout: %v", err) } if !matched { - t.Errorf("'kubeadm ex token generate' stdout did not match expected regex; wanted: [%q], got: [%s]", TokenExpectedRegex, stdout) + t.Errorf("'kubeadm token generate' stdout did not match expected regex; wanted: [%q], got: [%s]", TokenExpectedRegex, stdout) } } @@ -54,7 +54,7 @@ func TestCmdTokenGenerateTypoError(t *testing.T) { /* Since we expect users to do things like this: - $ TOKEN=$(kubeadm ex token generate) + $ TOKEN=$(kubeadm token generate) we want to make sure that if they have a typo in their command, we exit with a non-zero status code after showing the command's usage, so that @@ -65,9 +65,9 @@ func TestCmdTokenGenerateTypoError(t *testing.T) { t.Skip() } - _, _, err := RunCmd(*kubeadmPath, "ex", "token", "genorate") // subtle typo + _, _, err := RunCmd(*kubeadmPath, "token", "genorate") // subtle typo if err == nil { - t.Error("'kubeadm ex token genorate' (a deliberate typo) exited without an error when we expected non-zero exit status") + t.Error("'kubeadm token genorate' (a deliberate typo) exited without an error when we expected non-zero exit status") } } func TestCmdTokenDelete(t *testing.T) { @@ -85,10 +85,10 @@ func TestCmdTokenDelete(t *testing.T) { } for _, rt := range tests { - _, _, actual := RunCmd(*kubeadmPath, "ex", "token", "delete", rt.args) + _, _, actual := RunCmd(*kubeadmPath, "token", "delete", rt.args) if (actual == nil) != rt.expected { t.Errorf( - "failed CmdTokenDelete running 'kubeadm ex token %s' with an error: %v\n\texpected: %t\n\t actual: %t", + "failed CmdTokenDelete running 'kubeadm token %s' with an error: %v\n\texpected: %t\n\t actual: %t", rt.args, actual, rt.expected, diff --git a/pkg/bootstrap/api/types.go b/pkg/bootstrap/api/types.go index f25d197844d..547ca6a675e 100644 --- a/pkg/bootstrap/api/types.go +++ b/pkg/bootstrap/api/types.go @@ -47,6 +47,14 @@ const ( // should be considered invalid. Optional. BootstrapTokenExpirationKey = "expiration" + // BootstrapTokenDescriptionKey is a description in human-readable format that + // describes what the bootstrap token is used for. Optional. + BootstrapTokenDescriptionKey = "description" + + // BootstrapTokenUsagePrefix is the prefix for the other usage constants that specifies different + // functions of a bootstrap token + BootstrapTokenUsagePrefix = "usage-bootstrap-" + // BootstrapTokenUsageSigningKey signals that this token should be used to // sign configs as part of the bootstrap process. Value must be "true". Any // other value is assumed to be false. Optional.