...

Source file src/k8s.io/kubectl/pkg/cmd/create/create_token.go

Documentation: k8s.io/kubectl/pkg/cmd/create

     1  /*
     2  Copyright 2022 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package create
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"os"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/spf13/cobra"
    27  	"github.com/spf13/pflag"
    28  
    29  	authenticationv1 "k8s.io/api/authentication/v1"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/apimachinery/pkg/types"
    33  	"k8s.io/apimachinery/pkg/util/sets"
    34  	"k8s.io/cli-runtime/pkg/genericclioptions"
    35  	"k8s.io/cli-runtime/pkg/genericiooptions"
    36  	corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
    37  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    38  	"k8s.io/kubectl/pkg/scheme"
    39  	"k8s.io/kubectl/pkg/util/completion"
    40  	"k8s.io/kubectl/pkg/util/templates"
    41  	"k8s.io/kubectl/pkg/util/term"
    42  	"k8s.io/utils/pointer"
    43  )
    44  
    45  // TokenOptions is the data required to perform a token request operation.
    46  type TokenOptions struct {
    47  	// PrintFlags holds options necessary for obtaining a printer
    48  	PrintFlags *genericclioptions.PrintFlags
    49  	PrintObj   func(obj runtime.Object) error
    50  
    51  	// Flags hold the parsed CLI flags.
    52  	Flags *pflag.FlagSet
    53  
    54  	// Name and namespace of service account to create a token for
    55  	Name      string
    56  	Namespace string
    57  
    58  	// BoundObjectKind is the kind of object to bind the token to. Optional. Can be Pod or Secret.
    59  	BoundObjectKind string
    60  	// BoundObjectName is the name of the object to bind the token to. Required if BoundObjectKind is set.
    61  	BoundObjectName string
    62  	// BoundObjectUID is the uid of the object to bind the token to. If unset, defaults to the current uid of the bound object.
    63  	BoundObjectUID string
    64  
    65  	// Audiences indicate the valid audiences for the requested token. If unset, defaults to the Kubernetes API server audiences.
    66  	Audiences []string
    67  
    68  	// Duration is the requested token lifetime. Optional.
    69  	Duration time.Duration
    70  
    71  	// CoreClient is the API client used to request the token. Required.
    72  	CoreClient corev1client.CoreV1Interface
    73  
    74  	// IOStreams are the output streams for the operation. Required.
    75  	genericiooptions.IOStreams
    76  }
    77  
    78  var (
    79  	tokenLong = templates.LongDesc(`Request a service account token.`)
    80  
    81  	tokenExample = templates.Examples(`
    82  		# Request a token to authenticate to the kube-apiserver as the service account "myapp" in the current namespace
    83  		kubectl create token myapp
    84  
    85  		# Request a token for a service account in a custom namespace
    86  		kubectl create token myapp --namespace myns
    87  
    88  		# Request a token with a custom expiration
    89  		kubectl create token myapp --duration 10m
    90  
    91  		# Request a token with a custom audience
    92  		kubectl create token myapp --audience https://example.com
    93  
    94  		# Request a token bound to an instance of a Secret object
    95  		kubectl create token myapp --bound-object-kind Secret --bound-object-name mysecret
    96  
    97  		# Request a token bound to an instance of a Secret object with a specific UID
    98  		kubectl create token myapp --bound-object-kind Secret --bound-object-name mysecret --bound-object-uid 0d4691ed-659b-4935-a832-355f77ee47cc
    99  `)
   100  )
   101  
   102  func boundObjectKindToAPIVersions() map[string]string {
   103  	kinds := map[string]string{
   104  		"Pod":    "v1",
   105  		"Secret": "v1",
   106  	}
   107  	if os.Getenv("KUBECTL_NODE_BOUND_TOKENS") == "true" {
   108  		kinds["Node"] = "v1"
   109  	}
   110  	return kinds
   111  }
   112  
   113  func NewTokenOpts(ioStreams genericiooptions.IOStreams) *TokenOptions {
   114  	return &TokenOptions{
   115  		PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
   116  		IOStreams:  ioStreams,
   117  	}
   118  }
   119  
   120  // NewCmdCreateToken returns an initialized Command for 'create token' sub command
   121  func NewCmdCreateToken(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
   122  	o := NewTokenOpts(ioStreams)
   123  
   124  	cmd := &cobra.Command{
   125  		Use:                   "token SERVICE_ACCOUNT_NAME",
   126  		DisableFlagsInUseLine: true,
   127  		Short:                 "Request a service account token",
   128  		Long:                  tokenLong,
   129  		Example:               tokenExample,
   130  		ValidArgsFunction:     completion.ResourceNameCompletionFunc(f, "serviceaccount"),
   131  		Run: func(cmd *cobra.Command, args []string) {
   132  			if err := o.Complete(f, cmd, args); err != nil {
   133  				cmdutil.CheckErr(err)
   134  				return
   135  			}
   136  			if err := o.Validate(); err != nil {
   137  				cmdutil.CheckErr(err)
   138  				return
   139  			}
   140  			if err := o.Run(); err != nil {
   141  				cmdutil.CheckErr(err)
   142  				return
   143  			}
   144  		},
   145  	}
   146  
   147  	o.PrintFlags.AddFlags(cmd)
   148  
   149  	cmd.Flags().StringArrayVar(&o.Audiences, "audience", o.Audiences, "Audience of the requested token. If unset, defaults to requesting a token for use with the Kubernetes API server. May be repeated to request a token valid for multiple audiences.")
   150  
   151  	cmd.Flags().DurationVar(&o.Duration, "duration", o.Duration, "Requested lifetime of the issued token. If not set or if set to 0, the lifetime will be determined by the server automatically. The server may return a token with a longer or shorter lifetime.")
   152  
   153  	cmd.Flags().StringVar(&o.BoundObjectKind, "bound-object-kind", o.BoundObjectKind, "Kind of an object to bind the token to. "+
   154  		"Supported kinds are "+strings.Join(sets.StringKeySet(boundObjectKindToAPIVersions()).List(), ", ")+". "+
   155  		"If set, --bound-object-name must be provided.")
   156  	cmd.Flags().StringVar(&o.BoundObjectName, "bound-object-name", o.BoundObjectName, "Name of an object to bind the token to. "+
   157  		"The token will expire when the object is deleted. "+
   158  		"Requires --bound-object-kind.")
   159  	cmd.Flags().StringVar(&o.BoundObjectUID, "bound-object-uid", o.BoundObjectUID, "UID of an object to bind the token to. "+
   160  		"Requires --bound-object-kind and --bound-object-name. "+
   161  		"If unset, the UID of the existing object is used.")
   162  
   163  	o.Flags = cmd.Flags()
   164  
   165  	return cmd
   166  }
   167  
   168  // Complete completes all the required options
   169  func (o *TokenOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
   170  	var err error
   171  
   172  	o.Name, err = NameFromCommandArgs(cmd, args)
   173  	if err != nil {
   174  		return err
   175  	}
   176  
   177  	o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
   178  	if err != nil {
   179  		return err
   180  	}
   181  
   182  	client, err := f.KubernetesClientSet()
   183  	if err != nil {
   184  		return err
   185  	}
   186  	o.CoreClient = client.CoreV1()
   187  
   188  	printer, err := o.PrintFlags.ToPrinter()
   189  	if err != nil {
   190  		return err
   191  	}
   192  
   193  	o.PrintObj = func(obj runtime.Object) error {
   194  		return printer.PrintObj(obj, o.Out)
   195  	}
   196  
   197  	return nil
   198  }
   199  
   200  // Validate makes sure provided values for TokenOptions are valid
   201  func (o *TokenOptions) Validate() error {
   202  	if o.CoreClient == nil {
   203  		return fmt.Errorf("no client provided")
   204  	}
   205  	if len(o.Name) == 0 {
   206  		return fmt.Errorf("service account name is required")
   207  	}
   208  	if len(o.Namespace) == 0 {
   209  		return fmt.Errorf("--namespace is required")
   210  	}
   211  	if o.Duration < 0 {
   212  		return fmt.Errorf("--duration must be greater than or equal to 0")
   213  	}
   214  	if o.Duration%time.Second != 0 {
   215  		return fmt.Errorf("--duration cannot be expressed in units less than seconds")
   216  	}
   217  	for _, aud := range o.Audiences {
   218  		if len(aud) == 0 {
   219  			return fmt.Errorf("--audience must not be an empty string")
   220  		}
   221  	}
   222  
   223  	if len(o.BoundObjectKind) == 0 {
   224  		if len(o.BoundObjectName) > 0 {
   225  			return fmt.Errorf("--bound-object-name can only be set if --bound-object-kind is provided")
   226  		}
   227  		if len(o.BoundObjectUID) > 0 {
   228  			return fmt.Errorf("--bound-object-uid can only be set if --bound-object-kind is provided")
   229  		}
   230  	} else {
   231  		if _, ok := boundObjectKindToAPIVersions()[o.BoundObjectKind]; !ok {
   232  			return fmt.Errorf("supported --bound-object-kind values are %s", strings.Join(sets.StringKeySet(boundObjectKindToAPIVersions()).List(), ", "))
   233  		}
   234  		if len(o.BoundObjectName) == 0 {
   235  			return fmt.Errorf("--bound-object-name is required if --bound-object-kind is provided")
   236  		}
   237  	}
   238  
   239  	return nil
   240  }
   241  
   242  // Run requests a token
   243  func (o *TokenOptions) Run() error {
   244  	request := &authenticationv1.TokenRequest{
   245  		Spec: authenticationv1.TokenRequestSpec{
   246  			Audiences: o.Audiences,
   247  		},
   248  	}
   249  	if o.Duration > 0 {
   250  		request.Spec.ExpirationSeconds = pointer.Int64(int64(o.Duration / time.Second))
   251  	}
   252  	if len(o.BoundObjectKind) > 0 {
   253  		request.Spec.BoundObjectRef = &authenticationv1.BoundObjectReference{
   254  			Kind:       o.BoundObjectKind,
   255  			APIVersion: boundObjectKindToAPIVersions()[o.BoundObjectKind],
   256  			Name:       o.BoundObjectName,
   257  			UID:        types.UID(o.BoundObjectUID),
   258  		}
   259  	}
   260  
   261  	response, err := o.CoreClient.ServiceAccounts(o.Namespace).CreateToken(context.TODO(), o.Name, request, metav1.CreateOptions{})
   262  	if err != nil {
   263  		return fmt.Errorf("failed to create token: %v", err)
   264  	}
   265  	if len(response.Status.Token) == 0 {
   266  		return fmt.Errorf("failed to create token: no token in server response")
   267  	}
   268  
   269  	if o.PrintFlags.OutputFlagSpecified() {
   270  		return o.PrintObj(response)
   271  	}
   272  
   273  	if term.IsTerminal(o.Out) {
   274  		// include a newline when printing interactively
   275  		fmt.Fprintf(o.Out, "%s\n", response.Status.Token)
   276  	} else {
   277  		// otherwise just print the token
   278  		fmt.Fprintf(o.Out, "%s", response.Status.Token)
   279  	}
   280  
   281  	return nil
   282  }
   283  

View as plain text