...

Source file src/k8s.io/kubectl/pkg/cmd/config/set_credentials.go

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

     1  /*
     2  Copyright 2014 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 config
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"io"
    23  	"os"
    24  	"path/filepath"
    25  	"strings"
    26  
    27  	"github.com/spf13/cobra"
    28  
    29  	"k8s.io/client-go/tools/clientcmd"
    30  	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
    31  	cliflag "k8s.io/component-base/cli/flag"
    32  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    33  	"k8s.io/kubectl/pkg/util/i18n"
    34  	"k8s.io/kubectl/pkg/util/templates"
    35  )
    36  
    37  type setCredentialsOptions struct {
    38  	configAccess      clientcmd.ConfigAccess
    39  	name              string
    40  	clientCertificate cliflag.StringFlag
    41  	clientKey         cliflag.StringFlag
    42  	token             cliflag.StringFlag `datapolicy:"token"`
    43  	username          cliflag.StringFlag
    44  	password          cliflag.StringFlag `datapolicy:"password"`
    45  	embedCertData     cliflag.Tristate
    46  	authProvider      cliflag.StringFlag
    47  
    48  	authProviderArgs         map[string]string
    49  	authProviderArgsToRemove []string
    50  
    51  	execCommand            cliflag.StringFlag
    52  	execAPIVersion         cliflag.StringFlag
    53  	execInteractiveMode    cliflag.StringFlag
    54  	execProvideClusterInfo cliflag.Tristate
    55  	execArgs               []string
    56  	execEnv                map[string]string
    57  	execEnvToRemove        []string
    58  }
    59  
    60  const (
    61  	flagAuthProvider    = "auth-provider"
    62  	flagAuthProviderArg = "auth-provider-arg"
    63  
    64  	flagExecCommand            = "exec-command"
    65  	flagExecAPIVersion         = "exec-api-version"
    66  	flagExecArg                = "exec-arg"
    67  	flagExecEnv                = "exec-env"
    68  	flagExecInteractiveMode    = "exec-interactive-mode"
    69  	flagExecProvideClusterInfo = "exec-provide-cluster-info"
    70  )
    71  
    72  var (
    73  	setCredentialsLong = fmt.Sprintf(templates.LongDesc(i18n.T(`
    74  		Set a user entry in kubeconfig.
    75  
    76  		Specifying a name that already exists will merge new fields on top of existing values.
    77  
    78  		    Client-certificate flags:
    79  		    --%v=certfile --%v=keyfile
    80  
    81  		    Bearer token flags:
    82  			  --%v=bearer_token
    83  
    84  		    Basic auth flags:
    85  			  --%v=basic_user --%v=basic_password
    86  
    87  		Bearer token and basic auth are mutually exclusive.`)), clientcmd.FlagCertFile, clientcmd.FlagKeyFile, clientcmd.FlagBearerToken, clientcmd.FlagUsername, clientcmd.FlagPassword)
    88  
    89  	setCredentialsExample = templates.Examples(`
    90  		# Set only the "client-key" field on the "cluster-admin"
    91  		# entry, without touching other values
    92  		kubectl config set-credentials cluster-admin --client-key=~/.kube/admin.key
    93  
    94  		# Set basic auth for the "cluster-admin" entry
    95  		kubectl config set-credentials cluster-admin --username=admin --password=uXFGweU9l35qcif
    96  
    97  		# Embed client certificate data in the "cluster-admin" entry
    98  		kubectl config set-credentials cluster-admin --client-certificate=~/.kube/admin.crt --embed-certs=true
    99  
   100  		# Enable the Google Compute Platform auth provider for the "cluster-admin" entry
   101  		kubectl config set-credentials cluster-admin --auth-provider=gcp
   102  
   103  		# Enable the OpenID Connect auth provider for the "cluster-admin" entry with additional arguments
   104  		kubectl config set-credentials cluster-admin --auth-provider=oidc --auth-provider-arg=client-id=foo --auth-provider-arg=client-secret=bar
   105  
   106  		# Remove the "client-secret" config value for the OpenID Connect auth provider for the "cluster-admin" entry
   107  		kubectl config set-credentials cluster-admin --auth-provider=oidc --auth-provider-arg=client-secret-
   108  
   109  		# Enable new exec auth plugin for the "cluster-admin" entry
   110  		kubectl config set-credentials cluster-admin --exec-command=/path/to/the/executable --exec-api-version=client.authentication.k8s.io/v1beta1
   111  
   112  		# Enable new exec auth plugin for the "cluster-admin" entry with interactive mode
   113  		kubectl config set-credentials cluster-admin --exec-command=/path/to/the/executable --exec-api-version=client.authentication.k8s.io/v1beta1 --exec-interactive-mode=Never
   114  
   115  		# Define new exec auth plugin arguments for the "cluster-admin" entry
   116  		kubectl config set-credentials cluster-admin --exec-arg=arg1 --exec-arg=arg2
   117  
   118  		# Create or update exec auth plugin environment variables for the "cluster-admin" entry
   119  		kubectl config set-credentials cluster-admin --exec-env=key1=val1 --exec-env=key2=val2
   120  
   121  		# Remove exec auth plugin environment variables for the "cluster-admin" entry
   122  		kubectl config set-credentials cluster-admin --exec-env=var-to-remove-`)
   123  )
   124  
   125  // NewCmdConfigSetCredentials returns a Command instance for 'config set-credentials' sub command
   126  func NewCmdConfigSetCredentials(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
   127  	options := &setCredentialsOptions{configAccess: configAccess}
   128  	return newCmdConfigSetCredentials(out, options)
   129  }
   130  
   131  // NewCmdConfigSetAuthInfo returns a Command instance for 'config set-credentials' sub command
   132  // DEPRECATED: Use NewCmdConfigSetCredentials instead
   133  func NewCmdConfigSetAuthInfo(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
   134  	return NewCmdConfigSetCredentials(out, configAccess)
   135  }
   136  
   137  func newCmdConfigSetCredentials(out io.Writer, options *setCredentialsOptions) *cobra.Command {
   138  	cmd := &cobra.Command{
   139  		Use: fmt.Sprintf(
   140  			"set-credentials NAME [--%v=path/to/certfile] "+
   141  				"[--%v=path/to/keyfile] "+
   142  				"[--%v=bearer_token] "+
   143  				"[--%v=basic_user] "+
   144  				"[--%v=basic_password] "+
   145  				"[--%v=provider_name] "+
   146  				"[--%v=key=value] "+
   147  				"[--%v=exec_command] "+
   148  				"[--%v=exec_api_version] "+
   149  				"[--%v=arg] "+
   150  				"[--%v=key=value]",
   151  			clientcmd.FlagCertFile,
   152  			clientcmd.FlagKeyFile,
   153  			clientcmd.FlagBearerToken,
   154  			clientcmd.FlagUsername,
   155  			clientcmd.FlagPassword,
   156  			flagAuthProvider,
   157  			flagAuthProviderArg,
   158  			flagExecCommand,
   159  			flagExecAPIVersion,
   160  			flagExecArg,
   161  			flagExecEnv,
   162  		),
   163  		DisableFlagsInUseLine: true,
   164  		Short:                 i18n.T("Set a user entry in kubeconfig"),
   165  		Long:                  setCredentialsLong,
   166  		Example:               setCredentialsExample,
   167  		Run: func(cmd *cobra.Command, args []string) {
   168  			err := options.complete(cmd)
   169  			if err != nil {
   170  				cmd.Help()
   171  				cmdutil.CheckErr(err)
   172  			}
   173  			cmdutil.CheckErr(options.run())
   174  			fmt.Fprintf(out, "User %q set.\n", options.name)
   175  		},
   176  	}
   177  
   178  	cmd.Flags().Var(&options.clientCertificate, clientcmd.FlagCertFile, "Path to "+clientcmd.FlagCertFile+" file for the user entry in kubeconfig")
   179  	cmd.MarkFlagFilename(clientcmd.FlagCertFile)
   180  	cmd.Flags().Var(&options.clientKey, clientcmd.FlagKeyFile, "Path to "+clientcmd.FlagKeyFile+" file for the user entry in kubeconfig")
   181  	cmd.MarkFlagFilename(clientcmd.FlagKeyFile)
   182  	cmd.Flags().Var(&options.token, clientcmd.FlagBearerToken, clientcmd.FlagBearerToken+" for the user entry in kubeconfig")
   183  	cmd.Flags().Var(&options.username, clientcmd.FlagUsername, clientcmd.FlagUsername+" for the user entry in kubeconfig")
   184  	cmd.Flags().Var(&options.password, clientcmd.FlagPassword, clientcmd.FlagPassword+" for the user entry in kubeconfig")
   185  	cmd.Flags().Var(&options.authProvider, flagAuthProvider, "Auth provider for the user entry in kubeconfig")
   186  	cmd.Flags().StringSlice(flagAuthProviderArg, nil, "'key=value' arguments for the auth provider")
   187  	cmd.Flags().Var(&options.execCommand, flagExecCommand, "Command for the exec credential plugin for the user entry in kubeconfig")
   188  	cmd.Flags().Var(&options.execAPIVersion, flagExecAPIVersion, "API version of the exec credential plugin for the user entry in kubeconfig")
   189  	cmd.Flags().Var(&options.execInteractiveMode, flagExecInteractiveMode, "InteractiveMode of the exec credentials plugin for the user entry in kubeconfig")
   190  	flagClusterInfo := cmd.Flags().VarPF(&options.execProvideClusterInfo, flagExecProvideClusterInfo, "", "ProvideClusterInfo of the exec credentials plugin for the user entry in kubeconfig")
   191  	flagClusterInfo.NoOptDefVal = "true"
   192  	cmd.Flags().StringSlice(flagExecArg, nil, "New arguments for the exec credential plugin command for the user entry in kubeconfig")
   193  	cmd.Flags().StringArray(flagExecEnv, nil, "'key=value' environment values for the exec credential plugin")
   194  	f := cmd.Flags().VarPF(&options.embedCertData, clientcmd.FlagEmbedCerts, "", "Embed client cert/key for the user entry in kubeconfig")
   195  	f.NoOptDefVal = "true"
   196  
   197  	return cmd
   198  }
   199  
   200  func (o setCredentialsOptions) run() error {
   201  	err := o.validate()
   202  	if err != nil {
   203  		return err
   204  	}
   205  
   206  	config, err := o.configAccess.GetStartingConfig()
   207  	if err != nil {
   208  		return err
   209  	}
   210  
   211  	startingStanza, exists := config.AuthInfos[o.name]
   212  	if !exists {
   213  		startingStanza = clientcmdapi.NewAuthInfo()
   214  	}
   215  	authInfo := o.modifyAuthInfo(*startingStanza)
   216  	config.AuthInfos[o.name] = &authInfo
   217  
   218  	if err := clientcmd.ModifyConfig(o.configAccess, *config, true); err != nil {
   219  		return err
   220  	}
   221  
   222  	return nil
   223  }
   224  
   225  func (o *setCredentialsOptions) modifyAuthInfo(existingAuthInfo clientcmdapi.AuthInfo) clientcmdapi.AuthInfo {
   226  	modifiedAuthInfo := existingAuthInfo
   227  
   228  	var setToken, setBasic bool
   229  
   230  	if o.clientCertificate.Provided() {
   231  		certPath := o.clientCertificate.Value()
   232  		if o.embedCertData.Value() {
   233  			modifiedAuthInfo.ClientCertificateData, _ = os.ReadFile(certPath)
   234  			modifiedAuthInfo.ClientCertificate = ""
   235  		} else {
   236  			certPath, _ = filepath.Abs(certPath)
   237  			modifiedAuthInfo.ClientCertificate = certPath
   238  			if len(modifiedAuthInfo.ClientCertificate) > 0 {
   239  				modifiedAuthInfo.ClientCertificateData = nil
   240  			}
   241  		}
   242  	}
   243  	if o.clientKey.Provided() {
   244  		keyPath := o.clientKey.Value()
   245  		if o.embedCertData.Value() {
   246  			modifiedAuthInfo.ClientKeyData, _ = os.ReadFile(keyPath)
   247  			modifiedAuthInfo.ClientKey = ""
   248  		} else {
   249  			keyPath, _ = filepath.Abs(keyPath)
   250  			modifiedAuthInfo.ClientKey = keyPath
   251  			if len(modifiedAuthInfo.ClientKey) > 0 {
   252  				modifiedAuthInfo.ClientKeyData = nil
   253  			}
   254  		}
   255  	}
   256  
   257  	if o.token.Provided() {
   258  		modifiedAuthInfo.Token = o.token.Value()
   259  		setToken = len(modifiedAuthInfo.Token) > 0
   260  	}
   261  
   262  	if o.username.Provided() {
   263  		modifiedAuthInfo.Username = o.username.Value()
   264  		setBasic = setBasic || len(modifiedAuthInfo.Username) > 0
   265  	}
   266  	if o.password.Provided() {
   267  		modifiedAuthInfo.Password = o.password.Value()
   268  		setBasic = setBasic || len(modifiedAuthInfo.Password) > 0
   269  	}
   270  	if o.authProvider.Provided() {
   271  		newName := o.authProvider.Value()
   272  
   273  		// Only overwrite if the existing auth-provider is nil, or different than the newly specified one.
   274  		if modifiedAuthInfo.AuthProvider == nil || modifiedAuthInfo.AuthProvider.Name != newName {
   275  			modifiedAuthInfo.AuthProvider = &clientcmdapi.AuthProviderConfig{
   276  				Name: newName,
   277  			}
   278  		}
   279  	}
   280  
   281  	if modifiedAuthInfo.AuthProvider != nil {
   282  		if modifiedAuthInfo.AuthProvider.Config == nil {
   283  			modifiedAuthInfo.AuthProvider.Config = make(map[string]string)
   284  		}
   285  		for _, toRemove := range o.authProviderArgsToRemove {
   286  			delete(modifiedAuthInfo.AuthProvider.Config, toRemove)
   287  		}
   288  		for key, value := range o.authProviderArgs {
   289  			modifiedAuthInfo.AuthProvider.Config[key] = value
   290  		}
   291  	}
   292  
   293  	if o.execCommand.Provided() {
   294  		newExecCommand := o.execCommand.Value()
   295  
   296  		// create new Exec if doesn't exist, otherwise just modify the command
   297  		if modifiedAuthInfo.Exec == nil {
   298  			modifiedAuthInfo.Exec = &clientcmdapi.ExecConfig{
   299  				Command: newExecCommand,
   300  			}
   301  		} else {
   302  			modifiedAuthInfo.Exec.Command = newExecCommand
   303  			// explicitly reset exec arguments
   304  			modifiedAuthInfo.Exec.Args = nil
   305  		}
   306  	}
   307  
   308  	// modify next values only if Exec exists, ignore these changes otherwise
   309  	if modifiedAuthInfo.Exec != nil {
   310  		if o.execAPIVersion.Provided() {
   311  			modifiedAuthInfo.Exec.APIVersion = o.execAPIVersion.Value()
   312  		}
   313  
   314  		// rewrite exec arguments list with new values
   315  		if o.execArgs != nil {
   316  			modifiedAuthInfo.Exec.Args = o.execArgs
   317  		}
   318  
   319  		if o.execInteractiveMode.Provided() {
   320  			modifiedAuthInfo.Exec.InteractiveMode = clientcmdapi.ExecInteractiveMode(o.execInteractiveMode.Value())
   321  		}
   322  
   323  		if o.execProvideClusterInfo.Provided() {
   324  			modifiedAuthInfo.Exec.ProvideClusterInfo = o.execProvideClusterInfo.Value()
   325  		}
   326  
   327  		// iterate over the existing exec env values and remove the specified
   328  		if o.execEnvToRemove != nil {
   329  			newExecEnv := []clientcmdapi.ExecEnvVar{}
   330  			for _, value := range modifiedAuthInfo.Exec.Env {
   331  				needToRemove := false
   332  				for _, elemToRemove := range o.execEnvToRemove {
   333  					if value.Name == elemToRemove {
   334  						needToRemove = true
   335  						break
   336  					}
   337  				}
   338  				if !needToRemove {
   339  					newExecEnv = append(newExecEnv, value)
   340  				}
   341  			}
   342  			modifiedAuthInfo.Exec.Env = newExecEnv
   343  		}
   344  
   345  		// update or create specified environment variables for the exec plugin
   346  		if o.execEnv != nil {
   347  			newEnv := []clientcmdapi.ExecEnvVar{}
   348  			for newEnvName, newEnvValue := range o.execEnv {
   349  				needToCreate := true
   350  				for i := 0; i < len(modifiedAuthInfo.Exec.Env); i++ {
   351  					if modifiedAuthInfo.Exec.Env[i].Name == newEnvName {
   352  						// update the existing value
   353  						needToCreate = false
   354  						modifiedAuthInfo.Exec.Env[i].Value = newEnvValue
   355  						break
   356  					}
   357  				}
   358  				if needToCreate {
   359  					// create a new env value
   360  					newEnv = append(newEnv, clientcmdapi.ExecEnvVar{Name: newEnvName, Value: newEnvValue})
   361  				}
   362  			}
   363  			modifiedAuthInfo.Exec.Env = append(modifiedAuthInfo.Exec.Env, newEnv...)
   364  		}
   365  	}
   366  
   367  	// If any auth info was set, make sure any other existing auth types are cleared
   368  	if setToken || setBasic {
   369  		if !setToken {
   370  			modifiedAuthInfo.Token = ""
   371  		}
   372  		if !setBasic {
   373  			modifiedAuthInfo.Username = ""
   374  			modifiedAuthInfo.Password = ""
   375  		}
   376  	}
   377  
   378  	return modifiedAuthInfo
   379  }
   380  
   381  func (o *setCredentialsOptions) complete(cmd *cobra.Command) error {
   382  	args := cmd.Flags().Args()
   383  	if len(args) != 1 {
   384  		return fmt.Errorf("unexpected args: %v", args)
   385  	}
   386  
   387  	authProviderArgs, err := cmd.Flags().GetStringSlice(flagAuthProviderArg)
   388  	if err != nil {
   389  		return err
   390  	}
   391  
   392  	if len(authProviderArgs) > 0 {
   393  		newPairs, removePairs, err := cmdutil.ParsePairs(authProviderArgs, flagAuthProviderArg, true)
   394  		if err != nil {
   395  			return err
   396  		}
   397  		o.authProviderArgs = newPairs
   398  		o.authProviderArgsToRemove = removePairs
   399  	}
   400  
   401  	execArgs, err := cmd.Flags().GetStringSlice(flagExecArg)
   402  	if err != nil {
   403  		return err
   404  	}
   405  	if len(execArgs) > 0 {
   406  		o.execArgs = execArgs
   407  	}
   408  
   409  	execEnv, err := cmd.Flags().GetStringArray(flagExecEnv)
   410  	if err != nil {
   411  		return err
   412  	}
   413  	if len(execEnv) > 0 {
   414  		newPairs, removePairs, err := cmdutil.ParsePairs(execEnv, flagExecEnv, true)
   415  		if err != nil {
   416  			return err
   417  		}
   418  		o.execEnv = newPairs
   419  		o.execEnvToRemove = removePairs
   420  	}
   421  
   422  	o.name = args[0]
   423  	return nil
   424  }
   425  
   426  func (o setCredentialsOptions) validate() error {
   427  	if len(o.name) == 0 {
   428  		return errors.New("you must specify a non-empty user name")
   429  	}
   430  	methods := []string{}
   431  	if len(o.token.Value()) > 0 {
   432  		methods = append(methods, fmt.Sprintf("--%v", clientcmd.FlagBearerToken))
   433  	}
   434  	if len(o.username.Value()) > 0 || len(o.password.Value()) > 0 {
   435  		methods = append(methods, fmt.Sprintf("--%v/--%v", clientcmd.FlagUsername, clientcmd.FlagPassword))
   436  	}
   437  	if len(methods) > 1 {
   438  		return fmt.Errorf("you cannot specify more than one authentication method at the same time: %v", strings.Join(methods, ", "))
   439  	}
   440  	if o.embedCertData.Value() {
   441  		certPath := o.clientCertificate.Value()
   442  		keyPath := o.clientKey.Value()
   443  		if certPath == "" && keyPath == "" {
   444  			return fmt.Errorf("you must specify a --%s or --%s to embed", clientcmd.FlagCertFile, clientcmd.FlagKeyFile)
   445  		}
   446  		if certPath != "" {
   447  			if _, err := os.Stat(certPath); err != nil {
   448  				return fmt.Errorf("could not stat %s file %s: %v", clientcmd.FlagCertFile, certPath, err)
   449  			}
   450  		}
   451  		if keyPath != "" {
   452  			if _, err := os.Stat(keyPath); err != nil {
   453  				return fmt.Errorf("could not stat %s file %s: %v", clientcmd.FlagKeyFile, keyPath, err)
   454  			}
   455  		}
   456  	}
   457  
   458  	if o.execInteractiveMode.Provided() {
   459  		interactiveMode := o.execInteractiveMode.Value()
   460  		if interactiveMode != string(clientcmdapi.IfAvailableExecInteractiveMode) &&
   461  			interactiveMode != string(clientcmdapi.AlwaysExecInteractiveMode) &&
   462  			interactiveMode != string(clientcmdapi.NeverExecInteractiveMode) {
   463  			return fmt.Errorf("invalid interactive mode type, can be only IfAvailable, Never, Always")
   464  		}
   465  	}
   466  
   467  	return nil
   468  }
   469  

View as plain text