mirror of https://github.com/k3s-io/k3s
Brad Davidson
2 years ago
committed by
Brad Davidson
14 changed files with 653 additions and 5 deletions
@ -0,0 +1,29 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"os" |
||||
|
||||
"github.com/k3s-io/k3s/pkg/cli/cmds" |
||||
"github.com/k3s-io/k3s/pkg/cli/token" |
||||
"github.com/k3s-io/k3s/pkg/configfilearg" |
||||
"github.com/sirupsen/logrus" |
||||
"github.com/urfave/cli" |
||||
) |
||||
|
||||
func main() { |
||||
app := cmds.NewApp() |
||||
app.Commands = []cli.Command{ |
||||
cmds.NewTokenCommands( |
||||
token.Create, |
||||
token.Delete, |
||||
token.Generate, |
||||
token.List, |
||||
), |
||||
} |
||||
|
||||
if err := app.Run(configfilearg.MustParse(os.Args)); err != nil && !errors.Is(err, context.Canceled) { |
||||
logrus.Fatal(err) |
||||
} |
||||
} |
@ -0,0 +1,97 @@
|
||||
package cmds |
||||
|
||||
import ( |
||||
"time" |
||||
|
||||
"github.com/urfave/cli" |
||||
) |
||||
|
||||
const TokenCommand = "token" |
||||
|
||||
// Config holds CLI values for the token subcommands
|
||||
type Token struct { |
||||
Description string |
||||
Kubeconfig string |
||||
Token string |
||||
Output string |
||||
Groups cli.StringSlice |
||||
Usages cli.StringSlice |
||||
TTL time.Duration |
||||
} |
||||
|
||||
var ( |
||||
TokenConfig = Token{} |
||||
TokenFlags = []cli.Flag{ |
||||
DataDirFlag, |
||||
cli.StringFlag{ |
||||
Name: "kubeconfig", |
||||
Usage: "(cluster) Server to connect to", |
||||
EnvVar: "KUBECONFIG", |
||||
Destination: &TokenConfig.Kubeconfig, |
||||
}, |
||||
} |
||||
) |
||||
|
||||
func NewTokenCommands(create, delete, generate, list func(ctx *cli.Context) error) cli.Command { |
||||
return cli.Command{ |
||||
Name: TokenCommand, |
||||
Usage: "Manage bootstrap tokens", |
||||
SkipFlagParsing: false, |
||||
SkipArgReorder: true, |
||||
Subcommands: []cli.Command{ |
||||
{ |
||||
Name: "create", |
||||
Usage: "Create bootstrap tokens on the server", |
||||
Flags: append(TokenFlags, &cli.StringFlag{ |
||||
Name: "description", |
||||
Usage: "A human friendly description of how this token is used", |
||||
Destination: &TokenConfig.Description, |
||||
}, &cli.StringSliceFlag{ |
||||
Name: "groups", |
||||
Usage: "Extra groups that this token will authenticate as when used for authentication", |
||||
Value: &TokenConfig.Groups, |
||||
}, &cli.DurationFlag{ |
||||
Name: "ttl", |
||||
Usage: "The duration before the token is automatically deleted (e.g. 1s, 2m, 3h). If set to '0', the token will never expire", |
||||
Value: time.Hour * 24, |
||||
Destination: &TokenConfig.TTL, |
||||
}, &cli.StringSliceFlag{ |
||||
Name: "usages", |
||||
Usage: "Describes the ways in which this token can be used.", |
||||
Value: &TokenConfig.Usages, |
||||
}), |
||||
SkipFlagParsing: false, |
||||
SkipArgReorder: true, |
||||
Action: create, |
||||
}, |
||||
{ |
||||
Name: "delete", |
||||
Usage: "Delete bootstrap tokens on the server", |
||||
Flags: TokenFlags, |
||||
SkipFlagParsing: false, |
||||
SkipArgReorder: true, |
||||
Action: delete, |
||||
}, |
||||
{ |
||||
Name: "generate", |
||||
Usage: "Generate and print a bootstrap token, but do not create it on the server", |
||||
Flags: TokenFlags, |
||||
SkipFlagParsing: false, |
||||
SkipArgReorder: true, |
||||
Action: generate, |
||||
}, |
||||
{ |
||||
Name: "list", |
||||
Usage: "List bootstrap tokens on the server", |
||||
Flags: append(TokenFlags, &cli.StringFlag{ |
||||
Name: "output,o", |
||||
Value: "text", |
||||
Destination: &TokenConfig.Output, |
||||
}), |
||||
SkipFlagParsing: false, |
||||
SkipArgReorder: true, |
||||
Action: list, |
||||
}, |
||||
}, |
||||
} |
||||
} |
@ -0,0 +1,224 @@
|
||||
package token |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"fmt" |
||||
"os" |
||||
"strings" |
||||
"text/tabwriter" |
||||
"time" |
||||
|
||||
"github.com/k3s-io/k3s/pkg/cli/cmds" |
||||
"github.com/k3s-io/k3s/pkg/clientaccess" |
||||
"github.com/k3s-io/k3s/pkg/kubeadm" |
||||
"github.com/k3s-io/k3s/pkg/util" |
||||
"github.com/pkg/errors" |
||||
"github.com/urfave/cli" |
||||
"gopkg.in/yaml.v2" |
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
||||
"k8s.io/apimachinery/pkg/fields" |
||||
"k8s.io/apimachinery/pkg/util/duration" |
||||
"k8s.io/client-go/tools/clientcmd" |
||||
bootstrapapi "k8s.io/cluster-bootstrap/token/api" |
||||
bootstraputil "k8s.io/cluster-bootstrap/token/util" |
||||
) |
||||
|
||||
func Create(app *cli.Context) error { |
||||
if err := cmds.InitLogging(); err != nil { |
||||
return err |
||||
} |
||||
return create(app, &cmds.TokenConfig) |
||||
} |
||||
|
||||
func create(app *cli.Context, cfg *cmds.Token) error { |
||||
if err := kubeadm.SetDefaults(app, cfg); err != nil { |
||||
return err |
||||
} |
||||
|
||||
cfg.Kubeconfig = util.GetKubeConfigPath(cfg.Kubeconfig) |
||||
client, err := util.GetClientSet(cfg.Kubeconfig) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
restConfig, err := clientcmd.BuildConfigFromFlags("", cfg.Kubeconfig) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if len(restConfig.TLSClientConfig.CAData) == 0 && restConfig.TLSClientConfig.CAFile != "" { |
||||
restConfig.TLSClientConfig.CAData, err = os.ReadFile(restConfig.TLSClientConfig.CAFile) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
bts, err := kubeadm.NewBootstrapTokenString(cfg.Token) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
bt := kubeadm.BootstrapToken{ |
||||
Token: bts, |
||||
Description: cfg.Description, |
||||
TTL: &metav1.Duration{Duration: cfg.TTL}, |
||||
Usages: cfg.Usages, |
||||
Groups: cfg.Groups, |
||||
} |
||||
|
||||
secretName := bootstraputil.BootstrapTokenSecretName(bt.Token.ID) |
||||
if secret, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Get(context.TODO(), secretName, metav1.GetOptions{}); secret != nil && err == nil { |
||||
return fmt.Errorf("a token with id %q already exists", bt.Token.ID) |
||||
} |
||||
|
||||
secret := kubeadm.BootstrapTokenToSecret(&bt) |
||||
if _, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Create(context.TODO(), secret, metav1.CreateOptions{}); err != nil { |
||||
return err |
||||
} |
||||
|
||||
token, err := clientaccess.FormatTokenBytes(bt.Token.String(), restConfig.TLSClientConfig.CAData) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
fmt.Println(token) |
||||
return nil |
||||
} |
||||
|
||||
func Delete(app *cli.Context) error { |
||||
if err := cmds.InitLogging(); err != nil { |
||||
return err |
||||
} |
||||
return delete(app, &cmds.TokenConfig) |
||||
} |
||||
|
||||
func delete(app *cli.Context, cfg *cmds.Token) error { |
||||
args := app.Args() |
||||
if len(args) < 1 { |
||||
return errors.New("missing argument; 'token delete' is missing token") |
||||
} |
||||
|
||||
cfg.Kubeconfig = util.GetKubeConfigPath(cfg.Kubeconfig) |
||||
client, err := util.GetClientSet(cfg.Kubeconfig) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
for _, token := range args { |
||||
if !bootstraputil.IsValidBootstrapTokenID(token) { |
||||
bts, err := kubeadm.NewBootstrapTokenString(cfg.Token) |
||||
if err != nil { |
||||
return fmt.Errorf("given token didn't match pattern %q or %q", bootstrapapi.BootstrapTokenIDPattern, bootstrapapi.BootstrapTokenIDPattern) |
||||
} |
||||
token = bts.ID |
||||
} |
||||
secretName := bootstraputil.BootstrapTokenSecretName(token) |
||||
if err := client.CoreV1().Secrets(metav1.NamespaceSystem).Delete(context.TODO(), secretName, metav1.DeleteOptions{}); err != nil { |
||||
return errors.Wrapf(err, "failed to delete bootstrap token %q", err) |
||||
} |
||||
|
||||
fmt.Printf("bootstrap token %q deleted\n", token) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func Generate(app *cli.Context) error { |
||||
if err := cmds.InitLogging(); err != nil { |
||||
return err |
||||
} |
||||
return generate(app, &cmds.TokenConfig) |
||||
} |
||||
|
||||
func generate(app *cli.Context, cfg *cmds.Token) error { |
||||
token, err := bootstraputil.GenerateBootstrapToken() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
fmt.Println(token) |
||||
return nil |
||||
} |
||||
|
||||
func List(app *cli.Context) error { |
||||
if err := cmds.InitLogging(); err != nil { |
||||
return err |
||||
} |
||||
return list(app, &cmds.TokenConfig) |
||||
} |
||||
|
||||
func list(app *cli.Context, cfg *cmds.Token) error { |
||||
if err := kubeadm.SetDefaults(app, cfg); err != nil { |
||||
return err |
||||
} |
||||
|
||||
cfg.Kubeconfig = util.GetKubeConfigPath(cfg.Kubeconfig) |
||||
client, err := util.GetClientSet(cfg.Kubeconfig) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
tokenSelector := fields.SelectorFromSet( |
||||
map[string]string{ |
||||
"type": string(bootstrapapi.SecretTypeBootstrapToken), |
||||
}, |
||||
) |
||||
listOptions := metav1.ListOptions{ |
||||
FieldSelector: tokenSelector.String(), |
||||
} |
||||
|
||||
secrets, err := client.CoreV1().Secrets(metav1.NamespaceSystem).List(context.TODO(), listOptions) |
||||
if err != nil { |
||||
return errors.Wrapf(err, "failed to list bootstrap tokens") |
||||
} |
||||
|
||||
tokens := make([]*kubeadm.BootstrapToken, len(secrets.Items)) |
||||
for i, secret := range secrets.Items { |
||||
token, err := kubeadm.BootstrapTokenFromSecret(&secret) |
||||
if err != nil { |
||||
fmt.Printf("%v", err) |
||||
continue |
||||
} |
||||
tokens[i] = token |
||||
} |
||||
|
||||
switch cfg.Output { |
||||
case "json": |
||||
if err := json.NewEncoder(os.Stdout).Encode(tokens); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
case "yaml": |
||||
if err := yaml.NewEncoder(os.Stdout).Encode(tokens); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
default: |
||||
format := "%s\t%s\t%s\t%s\t%s\t%s\n" |
||||
w := tabwriter.NewWriter(os.Stdout, 10, 4, 3, ' ', 0) |
||||
defer w.Flush() |
||||
|
||||
fmt.Fprintf(w, format, "TOKEN", "TTL", "EXPIRES", "USAGES", "DESCRIPTION", "EXTRA GROUPS") |
||||
for _, token := range tokens { |
||||
ttl := "<forever>" |
||||
expires := "<never>" |
||||
if token.Expires != nil { |
||||
ttl = duration.ShortHumanDuration(token.Expires.Sub(time.Now())) |
||||
expires = token.Expires.Format(time.RFC3339) |
||||
} |
||||
|
||||
fmt.Fprintf(w, format, token.Token.ID, ttl, expires, joinOrNone(token.Usages...), joinOrNone(token.Description), joinOrNone(token.Groups...)) |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// joinOrNone joins strings with a comma. If the resulting output is an empty string,
|
||||
// it instead returns the replacement string "<none>"
|
||||
func joinOrNone(s ...string) string { |
||||
j := strings.Join(s, ",") |
||||
if j == "" { |
||||
return "<none>" |
||||
} |
||||
return j |
||||
} |
@ -0,0 +1,52 @@
|
||||
package kubeadm |
||||
|
||||
import ( |
||||
"github.com/k3s-io/k3s/pkg/cli/cmds" |
||||
"github.com/k3s-io/k3s/pkg/version" |
||||
"github.com/pkg/errors" |
||||
"github.com/urfave/cli" |
||||
bootstrapapi "k8s.io/cluster-bootstrap/token/api" |
||||
bootstraputil "k8s.io/cluster-bootstrap/token/util" |
||||
) |
||||
|
||||
var ( |
||||
NodeBootstrapTokenAuthGroup = "system:bootstrappers:" + version.Program + ":default-node-token" |
||||
) |
||||
|
||||
// SetDefaults ensures that the default values are set on the token configuration.
|
||||
// These are set here, rather than in the default Token struct, to avoid
|
||||
// importing the cluster-bootstrap packages into the CLI.
|
||||
func SetDefaults(clx *cli.Context, cfg *cmds.Token) error { |
||||
if !clx.IsSet("groups") { |
||||
cfg.Groups = []string{NodeBootstrapTokenAuthGroup} |
||||
} |
||||
|
||||
if !clx.IsSet("usages") { |
||||
cfg.Usages = bootstrapapi.KnownTokenUsages |
||||
} |
||||
|
||||
if cfg.Output == "" { |
||||
cfg.Output = "text" |
||||
} else { |
||||
switch cfg.Output { |
||||
case "text", "json", "yaml": |
||||
default: |
||||
return errors.New("invalid output format: " + cfg.Output) |
||||
} |
||||
} |
||||
|
||||
args := clx.Args() |
||||
if len(args) > 0 { |
||||
cfg.Token = args[0] |
||||
} |
||||
|
||||
if cfg.Token == "" { |
||||
var err error |
||||
cfg.Token, err = bootstraputil.GenerateBootstrapToken() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -0,0 +1,45 @@
|
||||
package kubeadm |
||||
|
||||
import ( |
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
||||
) |
||||
|
||||
// kubeadm bootstrap token types cribbed from:
|
||||
// https://github.com/kubernetes/kubernetes/blob/v1.25.4/cmd/kubeadm/app/apis/bootstraptoken/v1/types.go
|
||||
// Copying these instead of importing from kubeadm saves about 4mb of binary size.
|
||||
|
||||
// BootstrapToken describes one bootstrap token, stored as a Secret in the cluster
|
||||
type BootstrapToken struct { |
||||
// Token is used for establishing bidirectional trust between nodes and control-planes.
|
||||
// Used for joining nodes in the cluster.
|
||||
Token *BootstrapTokenString `json:"token" datapolicy:"token"` |
||||
// Description sets a human-friendly message why this token exists and what it's used
|
||||
// for, so other administrators can know its purpose.
|
||||
// +optional
|
||||
Description string `json:"description,omitempty"` |
||||
// TTL defines the time to live for this token. Defaults to 24h.
|
||||
// Expires and TTL are mutually exclusive.
|
||||
// +optional
|
||||
TTL *metav1.Duration `json:"ttl,omitempty"` |
||||
// Expires specifies the timestamp when this token expires. Defaults to being set
|
||||
// dynamically at runtime based on the TTL. Expires and TTL are mutually exclusive.
|
||||
// +optional
|
||||
Expires *metav1.Time `json:"expires,omitempty"` |
||||
// Usages describes the ways in which this token can be used. Can by default be used
|
||||
// for establishing bidirectional trust, but that can be changed here.
|
||||
// +optional
|
||||
Usages []string `json:"usages,omitempty"` |
||||
// Groups specifies the extra groups that this token will authenticate as when/if
|
||||
// used for authentication
|
||||
// +optional
|
||||
Groups []string `json:"groups,omitempty"` |
||||
} |
||||
|
||||
// BootstrapTokenString is a token of the format abcdef.abcdef0123456789 that is used
|
||||
// for both validation of the identity of the API server from a joining node's point
|
||||
// of view and as an authentication method for the node. This token is and should be
|
||||
// short-lived.
|
||||
type BootstrapTokenString struct { |
||||
ID string `json:"-"` |
||||
Secret string `json:"-" datapolicy:"token"` |
||||
} |
@ -0,0 +1,173 @@
|
||||
package kubeadm |
||||
|
||||
import ( |
||||
"sort" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/pkg/errors" |
||||
v1 "k8s.io/api/core/v1" |
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
||||
bootstrapapi "k8s.io/cluster-bootstrap/token/api" |
||||
bootstraputil "k8s.io/cluster-bootstrap/token/util" |
||||
bootstrapsecretutil "k8s.io/cluster-bootstrap/util/secrets" |
||||
) |
||||
|
||||
// kubeadm bootstrap token utilities cribbed from:
|
||||
// https://github.com/kubernetes/kubernetes/blob/v1.25.4/cmd/kubeadm/app/apis/bootstraptoken/v1/utils.go
|
||||
// Copying these instead of importing from kubeadm saves about 4mb of binary size.
|
||||
|
||||
// String returns the string representation of the BootstrapTokenString
|
||||
func (bts BootstrapTokenString) String() string { |
||||
if len(bts.ID) > 0 && len(bts.Secret) > 0 { |
||||
return bootstraputil.TokenFromIDAndSecret(bts.ID, bts.Secret) |
||||
} |
||||
return "" |
||||
} |
||||
|
||||
// NewBootstrapTokenString converts the given Bootstrap Token as a string
|
||||
// to the BootstrapTokenString object used for serialization/deserialization
|
||||
// and internal usage. It also automatically validates that the given token
|
||||
// is of the right format
|
||||
func NewBootstrapTokenString(token string) (*BootstrapTokenString, error) { |
||||
substrs := bootstraputil.BootstrapTokenRegexp.FindStringSubmatch(token) |
||||
if len(substrs) != 3 { |
||||
return nil, errors.Errorf("the bootstrap token %q was not of the form %q", token, bootstrapapi.BootstrapTokenPattern) |
||||
} |
||||
|
||||
return &BootstrapTokenString{ID: substrs[1], Secret: substrs[2]}, nil |
||||
} |
||||
|
||||
// NewBootstrapTokenStringFromIDAndSecret is a wrapper around NewBootstrapTokenString
|
||||
// that allows the caller to specify the ID and Secret separately
|
||||
func NewBootstrapTokenStringFromIDAndSecret(id, secret string) (*BootstrapTokenString, error) { |
||||
return NewBootstrapTokenString(bootstraputil.TokenFromIDAndSecret(id, secret)) |
||||
} |
||||
|
||||
// BootstrapTokenToSecret converts the given BootstrapToken object to its Secret representation that
|
||||
// may be submitted to the API Server in order to be stored.
|
||||
func BootstrapTokenToSecret(bt *BootstrapToken) *v1.Secret { |
||||
return &v1.Secret{ |
||||
ObjectMeta: metav1.ObjectMeta{ |
||||
Name: bootstraputil.BootstrapTokenSecretName(bt.Token.ID), |
||||
Namespace: metav1.NamespaceSystem, |
||||
}, |
||||
Type: v1.SecretType(bootstrapapi.SecretTypeBootstrapToken), |
||||
Data: encodeTokenSecretData(bt, time.Now()), |
||||
} |
||||
} |
||||
|
||||
// encodeTokenSecretData takes the token discovery object and an optional duration and returns the .Data for the Secret
|
||||
// now is passed in order to be able to used in unit testing
|
||||
func encodeTokenSecretData(token *BootstrapToken, now time.Time) map[string][]byte { |
||||
data := map[string][]byte{ |
||||
bootstrapapi.BootstrapTokenIDKey: []byte(token.Token.ID), |
||||
bootstrapapi.BootstrapTokenSecretKey: []byte(token.Token.Secret), |
||||
} |
||||
|
||||
if len(token.Description) > 0 { |
||||
data[bootstrapapi.BootstrapTokenDescriptionKey] = []byte(token.Description) |
||||
} |
||||
|
||||
// If for some strange reason both token.TTL and token.Expires would be set
|
||||
// (they are mutually exclusive in validation so this shouldn't be the case),
|
||||
// token.Expires has higher priority, as can be seen in the logic here.
|
||||
if token.Expires != nil { |
||||
// Format the expiration date accordingly
|
||||
// TODO: This maybe should be a helper function in bootstraputil?
|
||||
expirationString := token.Expires.Time.UTC().Format(time.RFC3339) |
||||
data[bootstrapapi.BootstrapTokenExpirationKey] = []byte(expirationString) |
||||
|
||||
} else if token.TTL != nil && token.TTL.Duration > 0 { |
||||
// Only if .Expires is unset, TTL might have an effect
|
||||
// Get the current time, add the specified duration, and format it accordingly
|
||||
expirationString := now.Add(token.TTL.Duration).UTC().Format(time.RFC3339) |
||||
data[bootstrapapi.BootstrapTokenExpirationKey] = []byte(expirationString) |
||||
} |
||||
|
||||
for _, usage := range token.Usages { |
||||
data[bootstrapapi.BootstrapTokenUsagePrefix+usage] = []byte("true") |
||||
} |
||||
|
||||
if len(token.Groups) > 0 { |
||||
data[bootstrapapi.BootstrapTokenExtraGroupsKey] = []byte(strings.Join(token.Groups, ",")) |
||||
} |
||||
return data |
||||
} |
||||
|
||||
// BootstrapTokenFromSecret returns a BootstrapToken object from the given Secret
|
||||
func BootstrapTokenFromSecret(secret *v1.Secret) (*BootstrapToken, error) { |
||||
// Get the Token ID field from the Secret data
|
||||
tokenID := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenIDKey) |
||||
if len(tokenID) == 0 { |
||||
return nil, errors.Errorf("bootstrap Token Secret has no token-id data: %s", secret.Name) |
||||
} |
||||
|
||||
// Enforce the right naming convention
|
||||
if secret.Name != bootstraputil.BootstrapTokenSecretName(tokenID) { |
||||
return nil, errors.Errorf("bootstrap token name is not of the form '%s(token-id)'. Actual: %q. Expected: %q", |
||||
bootstrapapi.BootstrapTokenSecretPrefix, secret.Name, bootstraputil.BootstrapTokenSecretName(tokenID)) |
||||
} |
||||
|
||||
tokenSecret := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenSecretKey) |
||||
if len(tokenSecret) == 0 { |
||||
return nil, errors.Errorf("bootstrap Token Secret has no token-secret data: %s", secret.Name) |
||||
} |
||||
|
||||
// Create the BootstrapTokenString object based on the ID and Secret
|
||||
bts, err := NewBootstrapTokenStringFromIDAndSecret(tokenID, tokenSecret) |
||||
if err != nil { |
||||
return nil, errors.Wrap(err, "bootstrap Token Secret is invalid and couldn't be parsed") |
||||
} |
||||
|
||||
// Get the description (if any) from the Secret
|
||||
description := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenDescriptionKey) |
||||
|
||||
// Expiration time is optional, if not specified this implies the token
|
||||
// never expires.
|
||||
secretExpiration := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenExpirationKey) |
||||
var expires *metav1.Time |
||||
if len(secretExpiration) > 0 { |
||||
expTime, err := time.Parse(time.RFC3339, secretExpiration) |
||||
if err != nil { |
||||
return nil, errors.Wrapf(err, "can't parse expiration time of bootstrap token %q", secret.Name) |
||||
} |
||||
expires = &metav1.Time{Time: expTime} |
||||
} |
||||
|
||||
// Build an usages string slice from the Secret data
|
||||
var usages []string |
||||
for k, v := range secret.Data { |
||||
// Skip all fields that don't include this prefix
|
||||
if !strings.HasPrefix(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)) |
||||
} |
||||
// Only sort the slice if defined
|
||||
if usages != nil { |
||||
sort.Strings(usages) |
||||
} |
||||
|
||||
// Get the extra groups information from the Secret
|
||||
// It's done this way to make .Groups be nil in case there is no items, rather than an
|
||||
// empty slice or an empty slice with a "" string only
|
||||
var groups []string |
||||
groupsString := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenExtraGroupsKey) |
||||
g := strings.Split(groupsString, ",") |
||||
if len(g) > 0 && len(g[0]) > 0 { |
||||
groups = g |
||||
} |
||||
|
||||
return &BootstrapToken{ |
||||
Token: bts, |
||||
Description: description, |
||||
Expires: expires, |
||||
Usages: usages, |
||||
Groups: groups, |
||||
}, nil |
||||
} |
Loading…
Reference in new issue