go.etcd.io/etcd@v3.3.27+incompatible/etcdctl/ctlv2/command/user_commands.go (about) 1 // Copyright 2015 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 "fmt" 19 "os" 20 "strings" 21 22 "github.com/bgentry/speakeasy" 23 "github.com/coreos/etcd/client" 24 "github.com/urfave/cli" 25 ) 26 27 func NewUserCommands() cli.Command { 28 return cli.Command{ 29 Name: "user", 30 Usage: "user add, grant and revoke subcommands", 31 Subcommands: []cli.Command{ 32 { 33 Name: "add", 34 Usage: "add a new user for the etcd cluster", 35 ArgsUsage: "<user>", 36 Action: actionUserAdd, 37 }, 38 { 39 Name: "get", 40 Usage: "get details for a user", 41 ArgsUsage: "<user>", 42 Action: actionUserGet, 43 }, 44 { 45 Name: "list", 46 Usage: "list all current users", 47 ArgsUsage: "<user>", 48 Action: actionUserList, 49 }, 50 { 51 Name: "remove", 52 Usage: "remove a user for the etcd cluster", 53 ArgsUsage: "<user>", 54 Action: actionUserRemove, 55 }, 56 { 57 Name: "grant", 58 Usage: "grant roles to an etcd user", 59 ArgsUsage: "<user>", 60 Flags: []cli.Flag{cli.StringSliceFlag{Name: "roles", Value: new(cli.StringSlice), Usage: "List of roles to grant or revoke"}}, 61 Action: actionUserGrant, 62 }, 63 { 64 Name: "revoke", 65 Usage: "revoke roles for an etcd user", 66 ArgsUsage: "<user>", 67 Flags: []cli.Flag{cli.StringSliceFlag{Name: "roles", Value: new(cli.StringSlice), Usage: "List of roles to grant or revoke"}}, 68 Action: actionUserRevoke, 69 }, 70 { 71 Name: "passwd", 72 Usage: "change password for a user", 73 ArgsUsage: "<user>", 74 Action: actionUserPasswd, 75 }, 76 }, 77 } 78 } 79 80 func mustNewAuthUserAPI(c *cli.Context) client.AuthUserAPI { 81 hc := mustNewClient(c) 82 83 if c.GlobalBool("debug") { 84 fmt.Fprintf(os.Stderr, "Cluster-Endpoints: %s\n", strings.Join(hc.Endpoints(), ", ")) 85 } 86 87 return client.NewAuthUserAPI(hc) 88 } 89 90 func actionUserList(c *cli.Context) error { 91 if len(c.Args()) != 0 { 92 fmt.Fprintln(os.Stderr, "No arguments accepted") 93 os.Exit(1) 94 } 95 u := mustNewAuthUserAPI(c) 96 ctx, cancel := contextWithTotalTimeout(c) 97 users, err := u.ListUsers(ctx) 98 cancel() 99 if err != nil { 100 fmt.Fprintln(os.Stderr, err.Error()) 101 os.Exit(1) 102 } 103 104 for _, user := range users { 105 fmt.Printf("%s\n", user) 106 } 107 return nil 108 } 109 110 func actionUserAdd(c *cli.Context) error { 111 api, userarg := mustUserAPIAndName(c) 112 ctx, cancel := contextWithTotalTimeout(c) 113 defer cancel() 114 user, _, _ := getUsernamePassword("", userarg+":") 115 116 _, pass, err := getUsernamePassword("New password: ", userarg) 117 if err != nil { 118 fmt.Fprintln(os.Stderr, "Error reading password:", err) 119 os.Exit(1) 120 } 121 err = api.AddUser(ctx, user, pass) 122 if err != nil { 123 fmt.Fprintln(os.Stderr, err.Error()) 124 os.Exit(1) 125 } 126 127 fmt.Printf("User %s created\n", user) 128 return nil 129 } 130 131 func actionUserRemove(c *cli.Context) error { 132 api, user := mustUserAPIAndName(c) 133 ctx, cancel := contextWithTotalTimeout(c) 134 err := api.RemoveUser(ctx, user) 135 cancel() 136 if err != nil { 137 fmt.Fprintln(os.Stderr, err.Error()) 138 os.Exit(1) 139 } 140 141 fmt.Printf("User %s removed\n", user) 142 return nil 143 } 144 145 func actionUserPasswd(c *cli.Context) error { 146 api, user := mustUserAPIAndName(c) 147 ctx, cancel := contextWithTotalTimeout(c) 148 defer cancel() 149 pass, err := speakeasy.Ask("New password: ") 150 if err != nil { 151 fmt.Fprintln(os.Stderr, "Error reading password:", err) 152 os.Exit(1) 153 } 154 155 _, err = api.ChangePassword(ctx, user, pass) 156 if err != nil { 157 fmt.Fprintln(os.Stderr, err.Error()) 158 os.Exit(1) 159 } 160 161 fmt.Printf("Password updated\n") 162 return nil 163 } 164 165 func actionUserGrant(c *cli.Context) error { 166 userGrantRevoke(c, true) 167 return nil 168 } 169 170 func actionUserRevoke(c *cli.Context) error { 171 userGrantRevoke(c, false) 172 return nil 173 } 174 175 func userGrantRevoke(c *cli.Context, grant bool) { 176 roles := c.StringSlice("roles") 177 if len(roles) == 0 { 178 fmt.Fprintln(os.Stderr, "No roles specified; please use `--roles`") 179 os.Exit(1) 180 } 181 182 ctx, cancel := contextWithTotalTimeout(c) 183 defer cancel() 184 185 api, user := mustUserAPIAndName(c) 186 var err error 187 if grant { 188 _, err = api.GrantUser(ctx, user, roles) 189 } else { 190 _, err = api.RevokeUser(ctx, user, roles) 191 } 192 193 if err != nil { 194 fmt.Fprintln(os.Stderr, err.Error()) 195 os.Exit(1) 196 } 197 198 fmt.Printf("User %s updated\n", user) 199 } 200 201 func actionUserGet(c *cli.Context) error { 202 api, username := mustUserAPIAndName(c) 203 ctx, cancel := contextWithTotalTimeout(c) 204 user, err := api.GetUser(ctx, username) 205 cancel() 206 if err != nil { 207 fmt.Fprintln(os.Stderr, err.Error()) 208 os.Exit(1) 209 } 210 fmt.Printf("User: %s\n", user.User) 211 fmt.Printf("Roles: %s\n", strings.Join(user.Roles, " ")) 212 return nil 213 } 214 215 func mustUserAPIAndName(c *cli.Context) (client.AuthUserAPI, string) { 216 args := c.Args() 217 if len(args) != 1 { 218 fmt.Fprintln(os.Stderr, "Please provide a username") 219 os.Exit(1) 220 } 221 222 api := mustNewAuthUserAPI(c) 223 username := args[0] 224 return api, username 225 }