mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
*: Add security/auth support to etcdctl and etcd/client
add godep for speakeasy and auth entry parsing add security_user to client add role to client add role commands add auth support to etcdclient and etcdctl(member/user) add enable/disable to etcdctl better error messages, read/write/readwrite Bump go-etcd to include codec changes, add new dependency verify the error for revoke/add if nothing changed, remove security-merging prefix
This commit is contained in:
@@ -20,6 +20,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/bgentry/speakeasy"
|
||||
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli"
|
||||
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd"
|
||||
)
|
||||
@@ -36,6 +37,31 @@ func dumpCURL(client *etcd.Client) {
|
||||
}
|
||||
}
|
||||
|
||||
func getUsernamePasswordFromFlag(usernameFlag string) (username string, password string, err error) {
|
||||
colon := strings.Index(usernameFlag, ":")
|
||||
if colon == -1 {
|
||||
username = usernameFlag
|
||||
// Prompt for the password.
|
||||
password, err = speakeasy.Ask("Password: ")
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
} else {
|
||||
username = usernameFlag[:colon]
|
||||
password = usernameFlag[colon+1:]
|
||||
}
|
||||
return username, password, nil
|
||||
}
|
||||
|
||||
func prepAuth(client *etcd.Client, usernameFlag string) error {
|
||||
username, password, err := getUsernamePasswordFromFlag(usernameFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client.SetCredentials(username, password)
|
||||
return nil
|
||||
}
|
||||
|
||||
// rawhandle wraps the command function handlers and sets up the
|
||||
// environment but performs no output formatting.
|
||||
func rawhandle(c *cli.Context, fn handlerFunc) (*etcd.Response, error) {
|
||||
@@ -52,6 +78,14 @@ func rawhandle(c *cli.Context, fn handlerFunc) (*etcd.Response, error) {
|
||||
client := etcd.NewClient(endpoints)
|
||||
client.SetTransport(tr)
|
||||
|
||||
username := c.GlobalString("username")
|
||||
if username != "" {
|
||||
err := prepAuth(client, username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if c.GlobalBool("debug") {
|
||||
go dumpCURL(client)
|
||||
}
|
||||
|
||||
@@ -49,28 +49,8 @@ func NewMemberCommand() cli.Command {
|
||||
}
|
||||
|
||||
func mustNewMembersAPI(c *cli.Context) client.MembersAPI {
|
||||
eps, err := getEndpoints(c)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
tr, err := getTransport(c)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg := client.Config{
|
||||
Transport: tr,
|
||||
Endpoints: eps,
|
||||
}
|
||||
|
||||
hc, err := client.New(cfg)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
hc := mustNewClient(c)
|
||||
|
||||
if !c.GlobalBool("no-sync") {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
|
||||
|
||||
241
etcdctl/command/role_commands.go
Normal file
241
etcdctl/command/role_commands.go
Normal file
@@ -0,0 +1,241 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
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 mustNewSecurityRoleAPI(c *cli.Context) client.SecurityRoleAPI {
|
||||
hc := mustNewClient(c)
|
||||
|
||||
if c.GlobalBool("debug") {
|
||||
fmt.Fprintf(os.Stderr, "Cluster-Endpoints: %s\n", strings.Join(hc.Endpoints(), ", "))
|
||||
}
|
||||
|
||||
return client.NewSecurityRoleAPI(hc)
|
||||
}
|
||||
|
||||
func actionRoleList(c *cli.Context) {
|
||||
if len(c.Args()) != 0 {
|
||||
fmt.Fprintln(os.Stderr, "No arguments accepted")
|
||||
os.Exit(1)
|
||||
}
|
||||
r := mustNewSecurityRoleAPI(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)
|
||||
}
|
||||
|
||||
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.SecurityRoleAPI, string) {
|
||||
args := c.Args()
|
||||
if len(args) != 1 {
|
||||
fmt.Fprintln(os.Stderr, "Please provide a role name")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
api := mustNewSecurityRoleAPI(c)
|
||||
return api, name
|
||||
}
|
||||
87
etcdctl/command/security_commands.go
Normal file
87
etcdctl/command/security_commands.go
Normal file
@@ -0,0 +1,87 @@
|
||||
// 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"
|
||||
"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"
|
||||
)
|
||||
|
||||
func NewSecurityCommands() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "security",
|
||||
Usage: "overall security controls",
|
||||
Subcommands: []cli.Command{
|
||||
cli.Command{
|
||||
Name: "enable",
|
||||
Usage: "enable security access controls",
|
||||
Action: actionSecurityEnable,
|
||||
},
|
||||
cli.Command{
|
||||
Name: "disable",
|
||||
Usage: "disable security access controls",
|
||||
Action: actionSecurityDisable,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func actionSecurityEnable(c *cli.Context) {
|
||||
securityEnableDisable(c, true)
|
||||
}
|
||||
|
||||
func actionSecurityDisable(c *cli.Context) {
|
||||
securityEnableDisable(c, false)
|
||||
}
|
||||
|
||||
func mustNewSecurityAPI(c *cli.Context) client.SecurityAPI {
|
||||
hc := mustNewClient(c)
|
||||
|
||||
if c.GlobalBool("debug") {
|
||||
fmt.Fprintf(os.Stderr, "Cluster-Endpoints: %s\n", strings.Join(hc.Endpoints(), ", "))
|
||||
}
|
||||
|
||||
return client.NewSecurityAPI(hc)
|
||||
}
|
||||
|
||||
func securityEnableDisable(c *cli.Context, enable bool) {
|
||||
if len(c.Args()) != 0 {
|
||||
fmt.Fprintln(os.Stderr, "No arguments accepted")
|
||||
os.Exit(1)
|
||||
}
|
||||
s := mustNewSecurityAPI(c)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
|
||||
var err error
|
||||
if enable {
|
||||
err = s.Enable(ctx)
|
||||
} else {
|
||||
err = s.Disable(ctx)
|
||||
}
|
||||
cancel()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
if enable {
|
||||
fmt.Println("Security Enabled")
|
||||
} else {
|
||||
fmt.Println("Security Disabled")
|
||||
}
|
||||
}
|
||||
242
etcdctl/command/user_commands.go
Normal file
242
etcdctl/command/user_commands.go
Normal file
@@ -0,0 +1,242 @@
|
||||
// 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"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/bgentry/speakeasy"
|
||||
"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"
|
||||
)
|
||||
|
||||
func NewUserCommands() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "user",
|
||||
Usage: "user add, grant and revoke subcommands",
|
||||
Subcommands: []cli.Command{
|
||||
cli.Command{
|
||||
Name: "add",
|
||||
Usage: "add a new user for the etcd cluster",
|
||||
Action: actionUserAdd,
|
||||
},
|
||||
cli.Command{
|
||||
Name: "get",
|
||||
Usage: "get details for a user",
|
||||
Action: actionUserGet,
|
||||
},
|
||||
cli.Command{
|
||||
Name: "list",
|
||||
Usage: "list all current users",
|
||||
Action: actionUserList,
|
||||
},
|
||||
cli.Command{
|
||||
Name: "remove",
|
||||
Usage: "remove a user for the etcd cluster",
|
||||
Action: actionUserRemove,
|
||||
},
|
||||
cli.Command{
|
||||
Name: "grant",
|
||||
Usage: "grant roles to an etcd user",
|
||||
Flags: []cli.Flag{cli.StringSliceFlag{Name: "roles", Value: new(cli.StringSlice), Usage: "List of roles to grant or revoke"}},
|
||||
Action: actionUserGrant,
|
||||
},
|
||||
cli.Command{
|
||||
Name: "revoke",
|
||||
Usage: "revoke roles for an etcd user",
|
||||
Flags: []cli.Flag{cli.StringSliceFlag{Name: "roles", Value: new(cli.StringSlice), Usage: "List of roles to grant or revoke"}},
|
||||
Action: actionUserRevoke,
|
||||
},
|
||||
cli.Command{
|
||||
Name: "passwd",
|
||||
Usage: "change password for a user",
|
||||
Action: actionUserPasswd,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func mustNewSecurityUserAPI(c *cli.Context) client.SecurityUserAPI {
|
||||
hc := mustNewClient(c)
|
||||
|
||||
if c.GlobalBool("debug") {
|
||||
fmt.Fprintf(os.Stderr, "Cluster-Endpoints: %s\n", strings.Join(hc.Endpoints(), ", "))
|
||||
}
|
||||
|
||||
return client.NewSecurityUserAPI(hc)
|
||||
}
|
||||
|
||||
func actionUserList(c *cli.Context) {
|
||||
if len(c.Args()) != 0 {
|
||||
fmt.Fprintln(os.Stderr, "No arguments accepted")
|
||||
os.Exit(1)
|
||||
}
|
||||
u := mustNewSecurityUserAPI(c)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func actionUserAdd(c *cli.Context) {
|
||||
api, user := mustUserAPIAndName(c)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
|
||||
currentUser, err := api.GetUser(ctx, user)
|
||||
cancel()
|
||||
if currentUser != nil {
|
||||
fmt.Fprintf(os.Stderr, "User %s already exists\n", user)
|
||||
os.Exit(1)
|
||||
}
|
||||
pass, err := speakeasy.Ask("New password: ")
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error reading password:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
ctx, cancel = context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
|
||||
err = api.AddUser(ctx, user, pass)
|
||||
cancel()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("User %s created\n", user)
|
||||
}
|
||||
|
||||
func actionUserRemove(c *cli.Context) {
|
||||
api, user := mustUserAPIAndName(c)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
|
||||
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)
|
||||
}
|
||||
|
||||
func actionUserPasswd(c *cli.Context) {
|
||||
api, user := mustUserAPIAndName(c)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
|
||||
currentUser, err := api.GetUser(ctx, user)
|
||||
cancel()
|
||||
if currentUser == nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
pass, err := speakeasy.Ask("New password: ")
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error reading password:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
|
||||
_, err = api.ChangePassword(ctx, user, pass)
|
||||
cancel()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Password updated\n")
|
||||
}
|
||||
|
||||
func actionUserGrant(c *cli.Context) {
|
||||
userGrantRevoke(c, true)
|
||||
}
|
||||
|
||||
func actionUserRevoke(c *cli.Context) {
|
||||
userGrantRevoke(c, false)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
api, user := mustUserAPIAndName(c)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
|
||||
currentUser, err := api.GetUser(ctx, user)
|
||||
cancel()
|
||||
if currentUser == nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
|
||||
var newUser *client.User
|
||||
if grant {
|
||||
newUser, err = api.GrantUser(ctx, user, roles)
|
||||
} else {
|
||||
newUser, err = api.RevokeUser(ctx, user, roles)
|
||||
}
|
||||
cancel()
|
||||
sort.Strings(newUser.Roles)
|
||||
sort.Strings(currentUser.Roles)
|
||||
if reflect.DeepEqual(newUser.Roles, currentUser.Roles) {
|
||||
if grant {
|
||||
fmt.Printf("User unchanged; roles already granted")
|
||||
} else {
|
||||
fmt.Printf("User unchanged; roles already revoked")
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("User %s updated\n", user)
|
||||
}
|
||||
|
||||
func actionUserGet(c *cli.Context) {
|
||||
api, username := mustUserAPIAndName(c)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
|
||||
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, " "))
|
||||
|
||||
}
|
||||
|
||||
func mustUserAPIAndName(c *cli.Context) (client.SecurityUserAPI, string) {
|
||||
args := c.Args()
|
||||
if len(args) != 1 {
|
||||
fmt.Fprintln(os.Stderr, "Please provide a username")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
api := mustNewSecurityUserAPI(c)
|
||||
username := args[0]
|
||||
return api, username
|
||||
}
|
||||
@@ -16,6 +16,7 @@ package command
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
@@ -24,6 +25,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli"
|
||||
"github.com/coreos/etcd/client"
|
||||
"github.com/coreos/etcd/pkg/transport"
|
||||
)
|
||||
|
||||
@@ -112,3 +114,40 @@ func getTransport(c *cli.Context) (*http.Transport, error) {
|
||||
}
|
||||
return transport.NewTransport(tls)
|
||||
}
|
||||
|
||||
func mustNewClient(c *cli.Context) client.Client {
|
||||
eps, err := getEndpoints(c)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
tr, err := getTransport(c)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg := client.Config{
|
||||
Transport: tr,
|
||||
Endpoints: eps,
|
||||
}
|
||||
|
||||
uFlag := c.GlobalString("username")
|
||||
if uFlag != "" {
|
||||
username, password, err := getUsernamePasswordFromFlag(uFlag)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
cfg.Username = username
|
||||
cfg.Password = password
|
||||
}
|
||||
|
||||
hc, err := client.New(cfg)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
return hc
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ func main() {
|
||||
cli.StringFlag{Name: "cert-file", Value: "", Usage: "identify HTTPS client using this SSL certificate file"},
|
||||
cli.StringFlag{Name: "key-file", Value: "", Usage: "identify HTTPS client using this SSL key file"},
|
||||
cli.StringFlag{Name: "ca-file", Value: "", Usage: "verify certificates of HTTPS-enabled servers using this CA bundle"},
|
||||
cli.StringFlag{Name: "username, u", Value: "", Usage: "provide username[:password] and prompt if password is not supplied."},
|
||||
}
|
||||
app.Commands = []cli.Command{
|
||||
command.NewBackupCommand(),
|
||||
@@ -53,6 +54,9 @@ func main() {
|
||||
command.NewExecWatchCommand(),
|
||||
command.NewMemberCommand(),
|
||||
command.NewImportSnapCommand(),
|
||||
command.NewUserCommands(),
|
||||
command.NewRoleCommands(),
|
||||
command.NewSecurityCommands(),
|
||||
}
|
||||
|
||||
app.Run(os.Args)
|
||||
|
||||
Reference in New Issue
Block a user