mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 12:15:52 +00:00
kubeadm: Implement the kubeadm token command fully and move it out of the experimental subsection
This commit is contained in:
parent
70a268528e
commit
796c3f9773
@ -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",
|
||||
],
|
||||
)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 := "<forever>"
|
||||
expires := "<never>"
|
||||
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 = "<none>"
|
||||
}
|
||||
|
||||
description := getSecretString(&secret, bootstrapapi.BootstrapTokenDescriptionKey)
|
||||
if len(description) == 0 {
|
||||
description = "<none>"
|
||||
}
|
||||
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 ""
|
||||
}
|
||||
|
@ -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"}
|
||||
)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user