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  }