mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00

Non-canonical permission path is useless because the path received by auth is always canonical, which is due to our ServeMux always redirects request to canonical path(). This helps users to detect path permission setting error early. Ref: http://godoc.org/net/http#ServeMux
247 lines
6.4 KiB
Go
247 lines
6.4 KiB
Go
// Copyright 2015 CoreOS, Inc.
|
|
//
|
|
// 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"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli"
|
|
"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
|
|
"github.com/coreos/etcd/client"
|
|
"github.com/coreos/etcd/pkg/pathutil"
|
|
)
|
|
|
|
func NewRoleCommands() cli.Command {
|
|
return cli.Command{
|
|
Name: "role",
|
|
Usage: "role add, grant and revoke subcommands",
|
|
Subcommands: []cli.Command{
|
|
cli.Command{
|
|
Name: "add",
|
|
Usage: "add a new role for the etcd cluster",
|
|
Action: actionRoleAdd,
|
|
},
|
|
cli.Command{
|
|
Name: "get",
|
|
Usage: "get details for a role",
|
|
Action: actionRoleGet,
|
|
},
|
|
cli.Command{
|
|
Name: "list",
|
|
Usage: "list all roles",
|
|
Action: actionRoleList,
|
|
},
|
|
cli.Command{
|
|
Name: "remove",
|
|
Usage: "remove a role from the etcd cluster",
|
|
Action: actionRoleRemove,
|
|
},
|
|
cli.Command{
|
|
Name: "grant",
|
|
Usage: "grant path matches to an etcd role",
|
|
Flags: []cli.Flag{
|
|
cli.StringFlag{Name: "path", Value: "", Usage: "Path granted for the role to access"},
|
|
cli.BoolFlag{Name: "read", Usage: "Grant read-only access"},
|
|
cli.BoolFlag{Name: "write", Usage: "Grant write-only access"},
|
|
cli.BoolFlag{Name: "readwrite", Usage: "Grant read-write access"},
|
|
},
|
|
Action: actionRoleGrant,
|
|
},
|
|
cli.Command{
|
|
Name: "revoke",
|
|
Usage: "revoke path matches for an etcd role",
|
|
Flags: []cli.Flag{
|
|
cli.StringFlag{Name: "path", Value: "", Usage: "Path revoked for the role to access"},
|
|
cli.BoolFlag{Name: "read", Usage: "Revoke read access"},
|
|
cli.BoolFlag{Name: "write", Usage: "Revoke write access"},
|
|
cli.BoolFlag{Name: "readwrite", Usage: "Revoke read-write access"},
|
|
},
|
|
Action: actionRoleRevoke,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func mustNewAuthRoleAPI(c *cli.Context) client.AuthRoleAPI {
|
|
hc := mustNewClient(c)
|
|
|
|
if c.GlobalBool("debug") {
|
|
fmt.Fprintf(os.Stderr, "Cluster-Endpoints: %s\n", strings.Join(hc.Endpoints(), ", "))
|
|
}
|
|
|
|
return client.NewAuthRoleAPI(hc)
|
|
}
|
|
|
|
func actionRoleList(c *cli.Context) {
|
|
if len(c.Args()) != 0 {
|
|
fmt.Fprintln(os.Stderr, "No arguments accepted")
|
|
os.Exit(1)
|
|
}
|
|
r := mustNewAuthRoleAPI(c)
|
|
ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
|
|
roles, err := r.ListRoles(ctx)
|
|
cancel()
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
for _, role := range roles {
|
|
fmt.Printf("%s\n", role)
|
|
}
|
|
}
|
|
|
|
func actionRoleAdd(c *cli.Context) {
|
|
api, role := mustRoleAPIAndName(c)
|
|
ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
|
|
currentRole, err := api.GetRole(ctx, role)
|
|
cancel()
|
|
if currentRole != nil {
|
|
fmt.Fprintf(os.Stderr, "Role %s already exists\n", role)
|
|
os.Exit(1)
|
|
}
|
|
ctx, cancel = context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
|
|
err = api.AddRole(ctx, role)
|
|
cancel()
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Printf("Role %s created\n", role)
|
|
}
|
|
|
|
func actionRoleRemove(c *cli.Context) {
|
|
api, role := mustRoleAPIAndName(c)
|
|
ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
|
|
err := api.RemoveRole(ctx, role)
|
|
cancel()
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Printf("Role %s removed\n", role)
|
|
}
|
|
|
|
func actionRoleGrant(c *cli.Context) {
|
|
roleGrantRevoke(c, true)
|
|
}
|
|
|
|
func actionRoleRevoke(c *cli.Context) {
|
|
roleGrantRevoke(c, false)
|
|
}
|
|
|
|
func roleGrantRevoke(c *cli.Context, grant bool) {
|
|
path := c.String("path")
|
|
if path == "" {
|
|
fmt.Fprintln(os.Stderr, "No path specified; please use `-path`")
|
|
os.Exit(1)
|
|
}
|
|
if pathutil.CanonicalURLPath(path) != path {
|
|
fmt.Fprintf(os.Stderr, "Not canonical path; please use `-path=%s`\n", pathutil.CanonicalURLPath(path))
|
|
os.Exit(1)
|
|
}
|
|
|
|
read := c.Bool("read")
|
|
write := c.Bool("write")
|
|
rw := c.Bool("readwrite")
|
|
permcount := 0
|
|
for _, v := range []bool{read, write, rw} {
|
|
if v {
|
|
permcount++
|
|
}
|
|
}
|
|
if permcount != 1 {
|
|
fmt.Fprintln(os.Stderr, "Please specify exactly one of -read, -write or -readwrite")
|
|
os.Exit(1)
|
|
}
|
|
var permType client.PermissionType
|
|
switch {
|
|
case read:
|
|
permType = client.ReadPermission
|
|
case write:
|
|
permType = client.WritePermission
|
|
case rw:
|
|
permType = client.ReadWritePermission
|
|
}
|
|
|
|
api, role := mustRoleAPIAndName(c)
|
|
ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
|
|
currentRole, err := api.GetRole(ctx, role)
|
|
cancel()
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err.Error())
|
|
os.Exit(1)
|
|
}
|
|
ctx, cancel = context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
|
|
var newRole *client.Role
|
|
if grant {
|
|
newRole, err = api.GrantRoleKV(ctx, role, []string{path}, permType)
|
|
} else {
|
|
newRole, err = api.RevokeRoleKV(ctx, role, []string{path}, permType)
|
|
}
|
|
cancel()
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err.Error())
|
|
os.Exit(1)
|
|
}
|
|
if reflect.DeepEqual(newRole, currentRole) {
|
|
if grant {
|
|
fmt.Printf("Role unchanged; already granted")
|
|
} else {
|
|
fmt.Printf("Role unchanged; already revoked")
|
|
}
|
|
}
|
|
|
|
fmt.Printf("Role %s updated\n", role)
|
|
}
|
|
|
|
func actionRoleGet(c *cli.Context) {
|
|
api, rolename := mustRoleAPIAndName(c)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
|
|
role, err := api.GetRole(ctx, rolename)
|
|
cancel()
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err.Error())
|
|
os.Exit(1)
|
|
}
|
|
fmt.Printf("Role: %s\n", role.Role)
|
|
fmt.Printf("KV Read:\n")
|
|
for _, v := range role.Permissions.KV.Read {
|
|
fmt.Printf("\t%s\n", v)
|
|
}
|
|
fmt.Printf("KV Write:\n")
|
|
for _, v := range role.Permissions.KV.Write {
|
|
fmt.Printf("\t%s\n", v)
|
|
}
|
|
}
|
|
|
|
func mustRoleAPIAndName(c *cli.Context) (client.AuthRoleAPI, string) {
|
|
args := c.Args()
|
|
if len(args) != 1 {
|
|
fmt.Fprintln(os.Stderr, "Please provide a role name")
|
|
os.Exit(1)
|
|
}
|
|
|
|
name := args[0]
|
|
api := mustNewAuthRoleAPI(c)
|
|
return api, name
|
|
}
|