go.etcd.io/etcd@v3.3.27+incompatible/etcdctl/ctlv3/command/user_command.go (about) 1 // Copyright 2016 The etcd Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package command 16 17 import ( 18 "context" 19 "fmt" 20 "strings" 21 22 "github.com/bgentry/speakeasy" 23 "github.com/spf13/cobra" 24 ) 25 26 var ( 27 userShowDetail bool 28 ) 29 30 // NewUserCommand returns the cobra command for "user". 31 func NewUserCommand() *cobra.Command { 32 ac := &cobra.Command{ 33 Use: "user <subcommand>", 34 Short: "User related commands", 35 } 36 37 ac.AddCommand(newUserAddCommand()) 38 ac.AddCommand(newUserDeleteCommand()) 39 ac.AddCommand(newUserGetCommand()) 40 ac.AddCommand(newUserListCommand()) 41 ac.AddCommand(newUserChangePasswordCommand()) 42 ac.AddCommand(newUserGrantRoleCommand()) 43 ac.AddCommand(newUserRevokeRoleCommand()) 44 45 return ac 46 } 47 48 var ( 49 passwordInteractive bool 50 ) 51 52 func newUserAddCommand() *cobra.Command { 53 cmd := cobra.Command{ 54 Use: "add <user name or user:password> [options]", 55 Short: "Adds a new user", 56 Run: userAddCommandFunc, 57 } 58 59 cmd.Flags().BoolVar(&passwordInteractive, "interactive", true, "Read password from stdin instead of interactive terminal") 60 61 return &cmd 62 } 63 64 func newUserDeleteCommand() *cobra.Command { 65 return &cobra.Command{ 66 Use: "delete <user name>", 67 Short: "Deletes a user", 68 Run: userDeleteCommandFunc, 69 } 70 } 71 72 func newUserGetCommand() *cobra.Command { 73 cmd := cobra.Command{ 74 Use: "get <user name> [options]", 75 Short: "Gets detailed information of a user", 76 Run: userGetCommandFunc, 77 } 78 79 cmd.Flags().BoolVar(&userShowDetail, "detail", false, "Show permissions of roles granted to the user") 80 81 return &cmd 82 } 83 84 func newUserListCommand() *cobra.Command { 85 return &cobra.Command{ 86 Use: "list", 87 Short: "Lists all users", 88 Run: userListCommandFunc, 89 } 90 } 91 92 func newUserChangePasswordCommand() *cobra.Command { 93 cmd := cobra.Command{ 94 Use: "passwd <user name> [options]", 95 Short: "Changes password of user", 96 Run: userChangePasswordCommandFunc, 97 } 98 99 cmd.Flags().BoolVar(&passwordInteractive, "interactive", true, "If true, read password from stdin instead of interactive terminal") 100 101 return &cmd 102 } 103 104 func newUserGrantRoleCommand() *cobra.Command { 105 return &cobra.Command{ 106 Use: "grant-role <user name> <role name>", 107 Short: "Grants a role to a user", 108 Run: userGrantRoleCommandFunc, 109 } 110 } 111 112 func newUserRevokeRoleCommand() *cobra.Command { 113 return &cobra.Command{ 114 Use: "revoke-role <user name> <role name>", 115 Short: "Revokes a role from a user", 116 Run: userRevokeRoleCommandFunc, 117 } 118 } 119 120 // userAddCommandFunc executes the "user add" command. 121 func userAddCommandFunc(cmd *cobra.Command, args []string) { 122 if len(args) != 1 { 123 ExitWithError(ExitBadArgs, fmt.Errorf("user add command requires user name as its argument.")) 124 } 125 126 var password string 127 var user string 128 129 splitted := strings.SplitN(args[0], ":", 2) 130 if len(splitted) < 2 { 131 user = args[0] 132 if !passwordInteractive { 133 fmt.Scanf("%s", &password) 134 } else { 135 password = readPasswordInteractive(args[0]) 136 } 137 } else { 138 user = splitted[0] 139 password = splitted[1] 140 if len(user) == 0 { 141 ExitWithError(ExitBadArgs, fmt.Errorf("empty user name is not allowed.")) 142 } 143 } 144 145 resp, err := mustClientFromCmd(cmd).Auth.UserAdd(context.TODO(), user, password) 146 if err != nil { 147 ExitWithError(ExitError, err) 148 } 149 150 display.UserAdd(user, *resp) 151 } 152 153 // userDeleteCommandFunc executes the "user delete" command. 154 func userDeleteCommandFunc(cmd *cobra.Command, args []string) { 155 if len(args) != 1 { 156 ExitWithError(ExitBadArgs, fmt.Errorf("user delete command requires user name as its argument.")) 157 } 158 159 resp, err := mustClientFromCmd(cmd).Auth.UserDelete(context.TODO(), args[0]) 160 if err != nil { 161 ExitWithError(ExitError, err) 162 } 163 display.UserDelete(args[0], *resp) 164 } 165 166 // userGetCommandFunc executes the "user get" command. 167 func userGetCommandFunc(cmd *cobra.Command, args []string) { 168 if len(args) != 1 { 169 ExitWithError(ExitBadArgs, fmt.Errorf("user get command requires user name as its argument.")) 170 } 171 172 name := args[0] 173 client := mustClientFromCmd(cmd) 174 resp, err := client.Auth.UserGet(context.TODO(), name) 175 if err != nil { 176 ExitWithError(ExitError, err) 177 } 178 179 if userShowDetail { 180 fmt.Printf("User: %s\n", name) 181 for _, role := range resp.Roles { 182 fmt.Printf("\n") 183 roleResp, err := client.Auth.RoleGet(context.TODO(), role) 184 if err != nil { 185 ExitWithError(ExitError, err) 186 } 187 display.RoleGet(role, *roleResp) 188 } 189 } else { 190 display.UserGet(name, *resp) 191 } 192 } 193 194 // userListCommandFunc executes the "user list" command. 195 func userListCommandFunc(cmd *cobra.Command, args []string) { 196 if len(args) != 0 { 197 ExitWithError(ExitBadArgs, fmt.Errorf("user list command requires no arguments.")) 198 } 199 200 resp, err := mustClientFromCmd(cmd).Auth.UserList(context.TODO()) 201 if err != nil { 202 ExitWithError(ExitError, err) 203 } 204 205 display.UserList(*resp) 206 } 207 208 // userChangePasswordCommandFunc executes the "user passwd" command. 209 func userChangePasswordCommandFunc(cmd *cobra.Command, args []string) { 210 if len(args) != 1 { 211 ExitWithError(ExitBadArgs, fmt.Errorf("user passwd command requires user name as its argument.")) 212 } 213 214 var password string 215 216 if !passwordInteractive { 217 fmt.Scanf("%s", &password) 218 } else { 219 password = readPasswordInteractive(args[0]) 220 } 221 222 resp, err := mustClientFromCmd(cmd).Auth.UserChangePassword(context.TODO(), args[0], password) 223 if err != nil { 224 ExitWithError(ExitError, err) 225 } 226 227 display.UserChangePassword(*resp) 228 } 229 230 // userGrantRoleCommandFunc executes the "user grant-role" command. 231 func userGrantRoleCommandFunc(cmd *cobra.Command, args []string) { 232 if len(args) != 2 { 233 ExitWithError(ExitBadArgs, fmt.Errorf("user grant command requires user name and role name as its argument.")) 234 } 235 236 resp, err := mustClientFromCmd(cmd).Auth.UserGrantRole(context.TODO(), args[0], args[1]) 237 if err != nil { 238 ExitWithError(ExitError, err) 239 } 240 241 display.UserGrantRole(args[0], args[1], *resp) 242 } 243 244 // userRevokeRoleCommandFunc executes the "user revoke-role" command. 245 func userRevokeRoleCommandFunc(cmd *cobra.Command, args []string) { 246 if len(args) != 2 { 247 ExitWithError(ExitBadArgs, fmt.Errorf("user revoke-role requires user name and role name as its argument.")) 248 } 249 250 resp, err := mustClientFromCmd(cmd).Auth.UserRevokeRole(context.TODO(), args[0], args[1]) 251 if err != nil { 252 ExitWithError(ExitError, err) 253 } 254 255 display.UserRevokeRole(args[0], args[1], *resp) 256 } 257 258 func readPasswordInteractive(name string) string { 259 prompt1 := fmt.Sprintf("Password of %s: ", name) 260 password1, err1 := speakeasy.Ask(prompt1) 261 if err1 != nil { 262 ExitWithError(ExitBadArgs, fmt.Errorf("failed to ask password: %s.", err1)) 263 } 264 265 if len(password1) == 0 { 266 ExitWithError(ExitBadArgs, fmt.Errorf("empty password")) 267 } 268 269 prompt2 := fmt.Sprintf("Type password of %s again for confirmation: ", name) 270 password2, err2 := speakeasy.Ask(prompt2) 271 if err2 != nil { 272 ExitWithError(ExitBadArgs, fmt.Errorf("failed to ask password: %s.", err2)) 273 } 274 275 if strings.Compare(password1, password2) != 0 { 276 ExitWithError(ExitBadArgs, fmt.Errorf("given passwords are different.")) 277 } 278 279 return password1 280 }