go.etcd.io/etcd@v3.3.27+incompatible/etcdctl/ctlv2/command/role_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  	"reflect"
    21  	"strings"
    22  
    23  	"github.com/coreos/etcd/client"
    24  	"github.com/coreos/etcd/pkg/pathutil"
    25  	"github.com/urfave/cli"
    26  )
    27  
    28  func NewRoleCommands() cli.Command {
    29  	return cli.Command{
    30  		Name:  "role",
    31  		Usage: "role add, grant and revoke subcommands",
    32  		Subcommands: []cli.Command{
    33  			{
    34  				Name:      "add",
    35  				Usage:     "add a new role for the etcd cluster",
    36  				ArgsUsage: "<role> ",
    37  				Action:    actionRoleAdd,
    38  			},
    39  			{
    40  				Name:      "get",
    41  				Usage:     "get details for a role",
    42  				ArgsUsage: "<role>",
    43  				Action:    actionRoleGet,
    44  			},
    45  			{
    46  				Name:      "list",
    47  				Usage:     "list all roles",
    48  				ArgsUsage: " ",
    49  				Action:    actionRoleList,
    50  			},
    51  			{
    52  				Name:      "remove",
    53  				Usage:     "remove a role from the etcd cluster",
    54  				ArgsUsage: "<role>",
    55  				Action:    actionRoleRemove,
    56  			},
    57  			{
    58  				Name:      "grant",
    59  				Usage:     "grant path matches to an etcd role",
    60  				ArgsUsage: "<role>",
    61  				Flags: []cli.Flag{
    62  					cli.StringFlag{Name: "path", Value: "", Usage: "Path granted for the role to access"},
    63  					cli.BoolFlag{Name: "read", Usage: "Grant read-only access"},
    64  					cli.BoolFlag{Name: "write", Usage: "Grant write-only access"},
    65  					cli.BoolFlag{Name: "readwrite, rw", Usage: "Grant read-write access"},
    66  				},
    67  				Action: actionRoleGrant,
    68  			},
    69  			{
    70  				Name:      "revoke",
    71  				Usage:     "revoke path matches for an etcd role",
    72  				ArgsUsage: "<role>",
    73  				Flags: []cli.Flag{
    74  					cli.StringFlag{Name: "path", Value: "", Usage: "Path revoked for the role to access"},
    75  					cli.BoolFlag{Name: "read", Usage: "Revoke read access"},
    76  					cli.BoolFlag{Name: "write", Usage: "Revoke write access"},
    77  					cli.BoolFlag{Name: "readwrite, rw", Usage: "Revoke read-write access"},
    78  				},
    79  				Action: actionRoleRevoke,
    80  			},
    81  		},
    82  	}
    83  }
    84  
    85  func mustNewAuthRoleAPI(c *cli.Context) client.AuthRoleAPI {
    86  	hc := mustNewClient(c)
    87  
    88  	if c.GlobalBool("debug") {
    89  		fmt.Fprintf(os.Stderr, "Cluster-Endpoints: %s\n", strings.Join(hc.Endpoints(), ", "))
    90  	}
    91  
    92  	return client.NewAuthRoleAPI(hc)
    93  }
    94  
    95  func actionRoleList(c *cli.Context) error {
    96  	if len(c.Args()) != 0 {
    97  		fmt.Fprintln(os.Stderr, "No arguments accepted")
    98  		os.Exit(1)
    99  	}
   100  	r := mustNewAuthRoleAPI(c)
   101  	ctx, cancel := contextWithTotalTimeout(c)
   102  	roles, err := r.ListRoles(ctx)
   103  	cancel()
   104  	if err != nil {
   105  		fmt.Fprintln(os.Stderr, err.Error())
   106  		os.Exit(1)
   107  	}
   108  
   109  	for _, role := range roles {
   110  		fmt.Printf("%s\n", role)
   111  	}
   112  
   113  	return nil
   114  }
   115  
   116  func actionRoleAdd(c *cli.Context) error {
   117  	api, role := mustRoleAPIAndName(c)
   118  	ctx, cancel := contextWithTotalTimeout(c)
   119  	defer cancel()
   120  	currentRole, _ := api.GetRole(ctx, role)
   121  	if currentRole != nil {
   122  		fmt.Fprintf(os.Stderr, "Role %s already exists\n", role)
   123  		os.Exit(1)
   124  	}
   125  
   126  	err := api.AddRole(ctx, role)
   127  	if err != nil {
   128  		fmt.Fprintln(os.Stderr, err.Error())
   129  		os.Exit(1)
   130  	}
   131  
   132  	fmt.Printf("Role %s created\n", role)
   133  	return nil
   134  }
   135  
   136  func actionRoleRemove(c *cli.Context) error {
   137  	api, role := mustRoleAPIAndName(c)
   138  	ctx, cancel := contextWithTotalTimeout(c)
   139  	err := api.RemoveRole(ctx, role)
   140  	cancel()
   141  	if err != nil {
   142  		fmt.Fprintln(os.Stderr, err.Error())
   143  		os.Exit(1)
   144  	}
   145  
   146  	fmt.Printf("Role %s removed\n", role)
   147  	return nil
   148  }
   149  
   150  func actionRoleGrant(c *cli.Context) error {
   151  	roleGrantRevoke(c, true)
   152  	return nil
   153  }
   154  
   155  func actionRoleRevoke(c *cli.Context) error {
   156  	roleGrantRevoke(c, false)
   157  	return nil
   158  }
   159  
   160  func roleGrantRevoke(c *cli.Context, grant bool) {
   161  	path := c.String("path")
   162  	if path == "" {
   163  		fmt.Fprintln(os.Stderr, "No path specified; please use `--path`")
   164  		os.Exit(1)
   165  	}
   166  	if pathutil.CanonicalURLPath(path) != path {
   167  		fmt.Fprintf(os.Stderr, "Not canonical path; please use `--path=%s`\n", pathutil.CanonicalURLPath(path))
   168  		os.Exit(1)
   169  	}
   170  
   171  	read := c.Bool("read")
   172  	write := c.Bool("write")
   173  	rw := c.Bool("readwrite")
   174  	permcount := 0
   175  	for _, v := range []bool{read, write, rw} {
   176  		if v {
   177  			permcount++
   178  		}
   179  	}
   180  	if permcount != 1 {
   181  		fmt.Fprintln(os.Stderr, "Please specify exactly one of --read, --write or --readwrite")
   182  		os.Exit(1)
   183  	}
   184  	var permType client.PermissionType
   185  	switch {
   186  	case read:
   187  		permType = client.ReadPermission
   188  	case write:
   189  		permType = client.WritePermission
   190  	case rw:
   191  		permType = client.ReadWritePermission
   192  	}
   193  
   194  	api, role := mustRoleAPIAndName(c)
   195  	ctx, cancel := contextWithTotalTimeout(c)
   196  	defer cancel()
   197  	currentRole, err := api.GetRole(ctx, role)
   198  	if err != nil {
   199  		fmt.Fprintln(os.Stderr, err.Error())
   200  		os.Exit(1)
   201  	}
   202  	var newRole *client.Role
   203  	if grant {
   204  		newRole, err = api.GrantRoleKV(ctx, role, []string{path}, permType)
   205  	} else {
   206  		newRole, err = api.RevokeRoleKV(ctx, role, []string{path}, permType)
   207  	}
   208  	if err != nil {
   209  		fmt.Fprintln(os.Stderr, err.Error())
   210  		os.Exit(1)
   211  	}
   212  	if reflect.DeepEqual(newRole, currentRole) {
   213  		if grant {
   214  			fmt.Printf("Role unchanged; already granted")
   215  		} else {
   216  			fmt.Printf("Role unchanged; already revoked")
   217  		}
   218  	}
   219  
   220  	fmt.Printf("Role %s updated\n", role)
   221  }
   222  
   223  func actionRoleGet(c *cli.Context) error {
   224  	api, rolename := mustRoleAPIAndName(c)
   225  
   226  	ctx, cancel := contextWithTotalTimeout(c)
   227  	role, err := api.GetRole(ctx, rolename)
   228  	cancel()
   229  	if err != nil {
   230  		fmt.Fprintln(os.Stderr, err.Error())
   231  		os.Exit(1)
   232  	}
   233  	fmt.Printf("Role: %s\n", role.Role)
   234  	fmt.Printf("KV Read:\n")
   235  	for _, v := range role.Permissions.KV.Read {
   236  		fmt.Printf("\t%s\n", v)
   237  	}
   238  	fmt.Printf("KV Write:\n")
   239  	for _, v := range role.Permissions.KV.Write {
   240  		fmt.Printf("\t%s\n", v)
   241  	}
   242  	return nil
   243  }
   244  
   245  func mustRoleAPIAndName(c *cli.Context) (client.AuthRoleAPI, string) {
   246  	args := c.Args()
   247  	if len(args) != 1 {
   248  		fmt.Fprintln(os.Stderr, "Please provide a role name")
   249  		os.Exit(1)
   250  	}
   251  
   252  	name := args[0]
   253  	api := mustNewAuthRoleAPI(c)
   254  	return api, name
   255  }