mirror of https://github.com/k3s-io/k3s
Signed-off-by: Brad Davidson <brad.davidson@rancher.com>pull/6922/head
parent
7d49202721
commit
373df1c8b0
@ -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