mirror of https://github.com/k3s-io/k3s
225 lines
5.7 KiB
Go
225 lines
5.7 KiB
Go
|
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
|
||
|
}
|