1
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
126 func NewCmdConfigSetCredentials(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
127 options := &setCredentialsOptions{configAccess: configAccess}
128 return newCmdConfigSetCredentials(out, options)
129 }
130
131
132
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
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
297 if modifiedAuthInfo.Exec == nil {
298 modifiedAuthInfo.Exec = &clientcmdapi.ExecConfig{
299 Command: newExecCommand,
300 }
301 } else {
302 modifiedAuthInfo.Exec.Command = newExecCommand
303
304 modifiedAuthInfo.Exec.Args = nil
305 }
306 }
307
308
309 if modifiedAuthInfo.Exec != nil {
310 if o.execAPIVersion.Provided() {
311 modifiedAuthInfo.Exec.APIVersion = o.execAPIVersion.Value()
312 }
313
314
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
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
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
353 needToCreate = false
354 modifiedAuthInfo.Exec.Env[i].Value = newEnvValue
355 break
356 }
357 }
358 if needToCreate {
359
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
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