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 }