// Copyright 2015 The etcd Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package command import ( "fmt" "os" "strings" "github.com/bgentry/speakeasy" "github.com/urfave/cli" "go.etcd.io/etcd/client" ) func NewUserCommands() cli.Command { return cli.Command{ Name: "user", Usage: "user add, grant and revoke subcommands", Subcommands: []cli.Command{ { Name: "add", Usage: "add a new user for the etcd cluster", ArgsUsage: "", Action: actionUserAdd, }, { Name: "get", Usage: "get details for a user", ArgsUsage: "", Action: actionUserGet, }, { Name: "list", Usage: "list all current users", ArgsUsage: "", Action: actionUserList, }, { Name: "remove", Usage: "remove a user for the etcd cluster", ArgsUsage: "", Action: actionUserRemove, }, { Name: "grant", Usage: "grant roles to an etcd user", ArgsUsage: "", Flags: []cli.Flag{cli.StringSliceFlag{Name: "roles", Value: new(cli.StringSlice), Usage: "List of roles to grant or revoke"}}, Action: actionUserGrant, }, { Name: "revoke", Usage: "revoke roles for an etcd user", ArgsUsage: "", Flags: []cli.Flag{cli.StringSliceFlag{Name: "roles", Value: new(cli.StringSlice), Usage: "List of roles to grant or revoke"}}, Action: actionUserRevoke, }, { Name: "passwd", Usage: "change password for a user", ArgsUsage: "", Action: actionUserPasswd, }, }, } } func mustNewAuthUserAPI(c *cli.Context) client.AuthUserAPI { hc := mustNewClient(c) if c.GlobalBool("debug") { fmt.Fprintf(os.Stderr, "Cluster-Endpoints: %s\n", strings.Join(hc.Endpoints(), ", ")) } return client.NewAuthUserAPI(hc) } func actionUserList(c *cli.Context) error { if len(c.Args()) != 0 { fmt.Fprintln(os.Stderr, "No arguments accepted") os.Exit(1) } u := mustNewAuthUserAPI(c) ctx, cancel := contextWithTotalTimeout(c) users, err := u.ListUsers(ctx) cancel() if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } for _, user := range users { fmt.Printf("%s\n", user) } return nil } func actionUserAdd(c *cli.Context) error { api, userarg := mustUserAPIAndName(c) ctx, cancel := contextWithTotalTimeout(c) defer cancel() user, _, _ := getUsernamePassword("", userarg+":") _, pass, err := getUsernamePassword("New password: ", userarg) if err != nil { fmt.Fprintln(os.Stderr, "Error reading password:", err) os.Exit(1) } err = api.AddUser(ctx, user, pass) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } fmt.Printf("User %s created\n", user) return nil } func actionUserRemove(c *cli.Context) error { api, user := mustUserAPIAndName(c) ctx, cancel := contextWithTotalTimeout(c) err := api.RemoveUser(ctx, user) cancel() if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } fmt.Printf("User %s removed\n", user) return nil } func actionUserPasswd(c *cli.Context) error { api, user := mustUserAPIAndName(c) ctx, cancel := contextWithTotalTimeout(c) defer cancel() pass, err := speakeasy.Ask("New password: ") if err != nil { fmt.Fprintln(os.Stderr, "Error reading password:", err) os.Exit(1) } _, err = api.ChangePassword(ctx, user, pass) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } fmt.Printf("Password updated\n") return nil } func actionUserGrant(c *cli.Context) error { userGrantRevoke(c, true) return nil } func actionUserRevoke(c *cli.Context) error { userGrantRevoke(c, false) return nil } func userGrantRevoke(c *cli.Context, grant bool) { roles := c.StringSlice("roles") if len(roles) == 0 { fmt.Fprintln(os.Stderr, "No roles specified; please use `--roles`") os.Exit(1) } ctx, cancel := contextWithTotalTimeout(c) defer cancel() api, user := mustUserAPIAndName(c) var err error if grant { _, err = api.GrantUser(ctx, user, roles) } else { _, err = api.RevokeUser(ctx, user, roles) } if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } fmt.Printf("User %s updated\n", user) } func actionUserGet(c *cli.Context) error { api, username := mustUserAPIAndName(c) ctx, cancel := contextWithTotalTimeout(c) user, err := api.GetUser(ctx, username) cancel() if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } fmt.Printf("User: %s\n", user.User) fmt.Printf("Roles: %s\n", strings.Join(user.Roles, " ")) return nil } func mustUserAPIAndName(c *cli.Context) (client.AuthUserAPI, string) { args := c.Args() if len(args) != 1 { fmt.Fprintln(os.Stderr, "Please provide a username") os.Exit(1) } api := mustNewAuthUserAPI(c) username := args[0] return api, username }