From 8fcab98bf2baf91a5441869bea9edcdba2169946 Mon Sep 17 00:00:00 2001 From: Hitoshi Mitake Date: Thu, 17 May 2018 13:21:27 +0900 Subject: [PATCH] etcdctl: add flags for specifying password This commit adds two flags to etcdctl: 1. `--password` flag to etcdctl as a global option. It can be used for specifying password for authentication required for the command execution. 2. `--new-user-password` flag to `etcdctl user add`. It can be used for specifying password of newly created user by the command. The main motivation of the flags is allowing user to have : in its name. Fix https://github.com/coreos/etcd/issues/9691 --- Documentation/op-guide/authentication.md | 9 +++++++- etcdctl/ctlv3/command/global.go | 28 +++++++++++++++-------- etcdctl/ctlv3/command/user_command.go | 29 +++++++++++++++--------- etcdctl/ctlv3/ctl.go | 1 + 4 files changed, 46 insertions(+), 21 deletions(-) diff --git a/Documentation/op-guide/authentication.md b/Documentation/op-guide/authentication.md index a528a8d04c4..4261ed9eab0 100644 --- a/Documentation/op-guide/authentication.md +++ b/Documentation/op-guide/authentication.md @@ -32,7 +32,7 @@ Creating a user is as easy as $ etcdctl user add myusername ``` -Creating a new user will prompt for a new password. The password can be supplied from standard input when an option `--interactive=false` is given. +Creating a new user will prompt for a new password. The password can be supplied from standard input when an option `--interactive=false` is given. `--new-user-password` can also be used for supplying the password. Roles can be granted and revoked for a user with: @@ -157,6 +157,13 @@ The password can be taken from a prompt: $ etcdctl --user user get foo ``` +The password can also be taken from a command line flag `--password`: + +``` +$ etcdctl --user user --password password get foo +``` + + Otherwise, all `etcdctl` commands remain the same. Users and roles can still be created and modified, but require authentication by a user with the root role. ## Using TLS Common Name diff --git a/etcdctl/ctlv3/command/global.go b/etcdctl/ctlv3/command/global.go index 07fdee1af53..62a01299bea 100644 --- a/etcdctl/ctlv3/command/global.go +++ b/etcdctl/ctlv3/command/global.go @@ -53,7 +53,8 @@ type GlobalFlags struct { OutputFormat string IsHex bool - User string + User string + Password string Debug bool } @@ -341,6 +342,10 @@ func authCfgFromCmd(cmd *cobra.Command) *authCfg { if err != nil { ExitWithError(ExitBadArgs, err) } + passwordFlag, err := cmd.Flags().GetString("password") + if err != nil { + ExitWithError(ExitBadArgs, err) + } if userFlag == "" { return nil @@ -348,16 +353,21 @@ func authCfgFromCmd(cmd *cobra.Command) *authCfg { var cfg authCfg - splitted := strings.SplitN(userFlag, ":", 2) - if len(splitted) < 2 { - cfg.username = userFlag - cfg.password, err = speakeasy.Ask("Password: ") - if err != nil { - ExitWithError(ExitError, err) + if passwordFlag == "" { + splitted := strings.SplitN(userFlag, ":", 2) + if len(splitted) < 2 { + cfg.username = userFlag + cfg.password, err = speakeasy.Ask("Password: ") + if err != nil { + ExitWithError(ExitError, err) + } + } else { + cfg.username = splitted[0] + cfg.password = splitted[1] } } else { - cfg.username = splitted[0] - cfg.password = splitted[1] + cfg.username = userFlag + cfg.password = passwordFlag } return &cfg diff --git a/etcdctl/ctlv3/command/user_command.go b/etcdctl/ctlv3/command/user_command.go index c243561de0f..447a6c57cf2 100644 --- a/etcdctl/ctlv3/command/user_command.go +++ b/etcdctl/ctlv3/command/user_command.go @@ -47,6 +47,7 @@ func NewUserCommand() *cobra.Command { var ( passwordInteractive bool + passwordFromFlag string ) func newUserAddCommand() *cobra.Command { @@ -57,6 +58,7 @@ func newUserAddCommand() *cobra.Command { } cmd.Flags().BoolVar(&passwordInteractive, "interactive", true, "Read password from stdin instead of interactive terminal") + cmd.Flags().StringVar(&passwordFromFlag, "new-user-password", "", "Supply password from the command line flag") return &cmd } @@ -126,19 +128,24 @@ func userAddCommandFunc(cmd *cobra.Command, args []string) { var password string var user string - splitted := strings.SplitN(args[0], ":", 2) - if len(splitted) < 2 { + if passwordFromFlag != "" { user = args[0] - if !passwordInteractive { - fmt.Scanf("%s", &password) - } else { - password = readPasswordInteractive(args[0]) - } + password = passwordFromFlag } else { - user = splitted[0] - password = splitted[1] - if len(user) == 0 { - ExitWithError(ExitBadArgs, fmt.Errorf("empty user name is not allowed.")) + splitted := strings.SplitN(args[0], ":", 2) + if len(splitted) < 2 { + user = args[0] + if !passwordInteractive { + fmt.Scanf("%s", &password) + } else { + password = readPasswordInteractive(args[0]) + } + } else { + user = splitted[0] + password = splitted[1] + if len(user) == 0 { + ExitWithError(ExitBadArgs, fmt.Errorf("empty user name is not allowed.")) + } } } diff --git a/etcdctl/ctlv3/ctl.go b/etcdctl/ctlv3/ctl.go index 43bbf73338e..4a9233b79df 100644 --- a/etcdctl/ctlv3/ctl.go +++ b/etcdctl/ctlv3/ctl.go @@ -65,6 +65,7 @@ func init() { rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.KeyFile, "key", "", "identify secure client using this TLS key file") rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.TrustedCAFile, "cacert", "", "verify certificates of TLS-enabled secure servers using this CA bundle") rootCmd.PersistentFlags().StringVar(&globalFlags.User, "user", "", "username[:password] for authentication (prompt if password is not supplied)") + rootCmd.PersistentFlags().StringVar(&globalFlags.Password, "password", "", "password for authentication (if this option is used, --user option shouldn't include password)") rootCmd.PersistentFlags().StringVarP(&globalFlags.TLS.ServerName, "discovery-srv", "d", "", "domain name to query for SRV records describing cluster endpoints") rootCmd.AddCommand(