Browse Source

*: support granting a role to a user in v3 auth

Hitoshi Mitake 9 năm trước cách đây
mục cha
commit
7ba2646d37

+ 53 - 2
auth/authpb/auth.pb.go

@@ -55,8 +55,9 @@ func (x Permission_Type) String() string {
 
 
 // User is a single entry in the bucket authUsers
 // User is a single entry in the bucket authUsers
 type User struct {
 type User struct {
-	Name     []byte `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
-	Password []byte `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
+	Name     []byte   `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	Password []byte   `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
+	Roles    []string `protobuf:"bytes,3,rep,name=roles" json:"roles,omitempty"`
 }
 }
 
 
 func (m *User) Reset()         { *m = User{} }
 func (m *User) Reset()         { *m = User{} }
@@ -120,6 +121,21 @@ func (m *User) MarshalTo(data []byte) (int, error) {
 			i += copy(data[i:], m.Password)
 			i += copy(data[i:], m.Password)
 		}
 		}
 	}
 	}
+	if len(m.Roles) > 0 {
+		for _, s := range m.Roles {
+			data[i] = 0x1a
+			i++
+			l = len(s)
+			for l >= 1<<7 {
+				data[i] = uint8(uint64(l)&0x7f | 0x80)
+				l >>= 7
+				i++
+			}
+			data[i] = uint8(l)
+			i++
+			i += copy(data[i:], s)
+		}
+	}
 	return i, nil
 	return i, nil
 }
 }
 
 
@@ -234,6 +250,12 @@ func (m *User) Size() (n int) {
 			n += 1 + l + sovAuth(uint64(l))
 			n += 1 + l + sovAuth(uint64(l))
 		}
 		}
 	}
 	}
+	if len(m.Roles) > 0 {
+		for _, s := range m.Roles {
+			l = len(s)
+			n += 1 + l + sovAuth(uint64(l))
+		}
+	}
 	return n
 	return n
 }
 }
 
 
@@ -374,6 +396,35 @@ func (m *User) Unmarshal(data []byte) error {
 				m.Password = []byte{}
 				m.Password = []byte{}
 			}
 			}
 			iNdEx = postIndex
 			iNdEx = postIndex
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Roles", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowAuth
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := data[iNdEx]
+				iNdEx++
+				stringLen |= (uint64(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthAuth
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Roles = append(m.Roles, string(data[iNdEx:postIndex]))
+			iNdEx = postIndex
 		default:
 		default:
 			iNdEx = preIndex
 			iNdEx = preIndex
 			skippy, err := skipAuth(data[iNdEx:])
 			skippy, err := skipAuth(data[iNdEx:])

+ 1 - 0
auth/authpb/auth.proto

@@ -13,6 +13,7 @@ option (gogoproto.goproto_enum_prefix_all) = false;
 message User {
 message User {
   bytes name = 1;
   bytes name = 1;
   bytes password = 2;
   bytes password = 2;
+  repeated string roles = 3;
 }
 }
 
 
 // Permission is a single entity
 // Permission is a single entity

+ 45 - 0
auth/store.go

@@ -16,6 +16,7 @@ package auth
 
 
 import (
 import (
 	"errors"
 	"errors"
+	"sort"
 	"strings"
 	"strings"
 
 
 	"github.com/coreos/etcd/auth/authpb"
 	"github.com/coreos/etcd/auth/authpb"
@@ -56,6 +57,9 @@ type AuthStore interface {
 	// UserChangePassword changes a password of a user
 	// UserChangePassword changes a password of a user
 	UserChangePassword(r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error)
 	UserChangePassword(r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error)
 
 
+	// UserGrant grants a role to the user
+	UserGrant(r *pb.AuthUserGrantRequest) (*pb.AuthUserGrantResponse, error)
+
 	// RoleAdd adds a new role
 	// RoleAdd adds a new role
 	RoleAdd(r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error)
 	RoleAdd(r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error)
 
 
@@ -174,6 +178,47 @@ func (as *authStore) UserChangePassword(r *pb.AuthUserChangePasswordRequest) (*p
 	return &pb.AuthUserChangePasswordResponse{}, nil
 	return &pb.AuthUserChangePasswordResponse{}, nil
 }
 }
 
 
+func (as *authStore) UserGrant(r *pb.AuthUserGrantRequest) (*pb.AuthUserGrantResponse, error) {
+	tx := as.be.BatchTx()
+	tx.Lock()
+	defer tx.Unlock()
+
+	_, vs := tx.UnsafeRange(authUsersBucketName, []byte(r.User), nil, 0)
+	if len(vs) != 1 {
+		return nil, ErrUserNotFound
+	}
+
+	user := &authpb.User{}
+	err := user.Unmarshal(vs[0])
+	if err != nil {
+		return nil, err
+	}
+
+	_, vs = tx.UnsafeRange(authRolesBucketName, []byte(r.Role), nil, 0)
+	if len(vs) != 1 {
+		return nil, ErrRoleNotFound
+	}
+
+	idx := sort.SearchStrings(user.Roles, r.Role)
+	if idx < len(user.Roles) && strings.Compare(user.Roles[idx], r.Role) == 0 {
+		plog.Warningf("user %s is already granted role %s", r.User, r.Role)
+		return &pb.AuthUserGrantResponse{}, nil
+	}
+
+	user.Roles = append(user.Roles, r.Role)
+	sort.Sort(sort.StringSlice(user.Roles))
+
+	marshaledUser, merr := user.Marshal()
+	if merr != nil {
+		return nil, merr
+	}
+
+	tx.UnsafePut(authUsersBucketName, user.Name, marshaledUser)
+
+	plog.Noticef("granted role %s to user %s", r.Role, r.User)
+	return &pb.AuthUserGrantResponse{}, nil
+}
+
 func (as *authStore) RoleAdd(r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) {
 func (as *authStore) RoleAdd(r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) {
 	tx := as.be.BatchTx()
 	tx := as.be.BatchTx()
 	tx.Lock()
 	tx.Lock()

+ 9 - 0
clientv3/auth.go

@@ -29,6 +29,7 @@ type (
 	AuthUserAddResponse            pb.AuthUserAddResponse
 	AuthUserAddResponse            pb.AuthUserAddResponse
 	AuthUserDeleteResponse         pb.AuthUserDeleteResponse
 	AuthUserDeleteResponse         pb.AuthUserDeleteResponse
 	AuthUserChangePasswordResponse pb.AuthUserChangePasswordResponse
 	AuthUserChangePasswordResponse pb.AuthUserChangePasswordResponse
+	AuthUserGrantResponse          pb.AuthUserGrantResponse
 	AuthRoleAddResponse            pb.AuthRoleAddResponse
 	AuthRoleAddResponse            pb.AuthRoleAddResponse
 	AuthRoleGrantResponse          pb.AuthRoleGrantResponse
 	AuthRoleGrantResponse          pb.AuthRoleGrantResponse
 
 
@@ -54,6 +55,9 @@ type Auth interface {
 	// UserChangePassword changes a password of a user.
 	// UserChangePassword changes a password of a user.
 	UserChangePassword(ctx context.Context, name string, password string) (*AuthUserChangePasswordResponse, error)
 	UserChangePassword(ctx context.Context, name string, password string) (*AuthUserChangePasswordResponse, error)
 
 
+	// UserGrant grants a role to a user.
+	UserGrant(ctx context.Context, user string, role string) (*AuthUserGrantResponse, error)
+
 	// RoleAdd adds a new role to an etcd cluster.
 	// RoleAdd adds a new role to an etcd cluster.
 	RoleAdd(ctx context.Context, name string) (*AuthRoleAddResponse, error)
 	RoleAdd(ctx context.Context, name string) (*AuthRoleAddResponse, error)
 
 
@@ -97,6 +101,11 @@ func (auth *auth) UserChangePassword(ctx context.Context, name string, password
 	return (*AuthUserChangePasswordResponse)(resp), err
 	return (*AuthUserChangePasswordResponse)(resp), err
 }
 }
 
 
+func (auth *auth) UserGrant(ctx context.Context, user string, role string) (*AuthUserGrantResponse, error) {
+	resp, err := auth.remote.UserGrant(ctx, &pb.AuthUserGrantRequest{User: user, Role: role})
+	return (*AuthUserGrantResponse)(resp), err
+}
+
 func (auth *auth) RoleAdd(ctx context.Context, name string) (*AuthRoleAddResponse, error) {
 func (auth *auth) RoleAdd(ctx context.Context, name string) (*AuthRoleAddResponse, error) {
 	resp, err := auth.remote.RoleAdd(ctx, &pb.AuthRoleAddRequest{Name: name})
 	resp, err := auth.remote.RoleAdd(ctx, &pb.AuthRoleAddRequest{Name: name})
 	return (*AuthRoleAddResponse)(resp), err
 	return (*AuthRoleAddResponse)(resp), err

+ 23 - 0
etcdctl/ctlv3/command/user_command.go

@@ -33,6 +33,7 @@ func NewUserCommand() *cobra.Command {
 	ac.AddCommand(newUserAddCommand())
 	ac.AddCommand(newUserAddCommand())
 	ac.AddCommand(newUserDeleteCommand())
 	ac.AddCommand(newUserDeleteCommand())
 	ac.AddCommand(newUserChangePasswordCommand())
 	ac.AddCommand(newUserChangePasswordCommand())
+	ac.AddCommand(newUserGrantCommand())
 
 
 	return ac
 	return ac
 }
 }
@@ -73,6 +74,14 @@ func newUserChangePasswordCommand() *cobra.Command {
 	return &cmd
 	return &cmd
 }
 }
 
 
+func newUserGrantCommand() *cobra.Command {
+	return &cobra.Command{
+		Use:   "grant <user name> <role name>",
+		Short: "grant a role to a user",
+		Run:   userGrantCommandFunc,
+	}
+}
+
 // userAddCommandFunc executes the "user add" command.
 // userAddCommandFunc executes the "user add" command.
 func userAddCommandFunc(cmd *cobra.Command, args []string) {
 func userAddCommandFunc(cmd *cobra.Command, args []string) {
 	if len(args) != 1 {
 	if len(args) != 1 {
@@ -131,6 +140,20 @@ func userChangePasswordCommandFunc(cmd *cobra.Command, args []string) {
 	fmt.Println("Password updated")
 	fmt.Println("Password updated")
 }
 }
 
 
+// userGrantCommandFunc executes the "user grant" command.
+func userGrantCommandFunc(cmd *cobra.Command, args []string) {
+	if len(args) != 2 {
+		ExitWithError(ExitBadArgs, fmt.Errorf("user grant command requires user name and role name as its argument."))
+	}
+
+	_, err := mustClientFromCmd(cmd).Auth.UserGrant(context.TODO(), args[0], args[1])
+	if err != nil {
+		ExitWithError(ExitError, err)
+	}
+
+	fmt.Printf("Role %s is granted to user %s\n", args[1], args[0])
+}
+
 func readPasswordInteractive(name string) string {
 func readPasswordInteractive(name string) string {
 	prompt1 := fmt.Sprintf("Password of %s: ", name)
 	prompt1 := fmt.Sprintf("Password of %s: ", name)
 	password1, err1 := speakeasy.Ask(prompt1)
 	password1, err1 := speakeasy.Ask(prompt1)

+ 5 - 2
etcdserver/api/v3rpc/auth.go

@@ -99,8 +99,11 @@ func (as *AuthServer) UserGet(ctx context.Context, r *pb.AuthUserGetRequest) (*p
 }
 }
 
 
 func (as *AuthServer) UserGrant(ctx context.Context, r *pb.AuthUserGrantRequest) (*pb.AuthUserGrantResponse, error) {
 func (as *AuthServer) UserGrant(ctx context.Context, r *pb.AuthUserGrantRequest) (*pb.AuthUserGrantResponse, error) {
-	plog.Info("not implemented yet")
-	return nil, nil
+	resp, err := as.authenticator.UserGrant(ctx, r)
+	if err != nil {
+		return nil, togRPCError(err)
+	}
+	return resp, nil
 }
 }
 
 
 func (as *AuthServer) UserRevoke(ctx context.Context, r *pb.AuthUserRevokeRequest) (*pb.AuthUserRevokeResponse, error) {
 func (as *AuthServer) UserRevoke(ctx context.Context, r *pb.AuthUserRevokeRequest) (*pb.AuthUserRevokeResponse, error) {

+ 7 - 0
etcdserver/apply.go

@@ -57,6 +57,7 @@ type applierV3 interface {
 	UserAdd(ua *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error)
 	UserAdd(ua *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error)
 	UserDelete(ua *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error)
 	UserDelete(ua *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error)
 	UserChangePassword(ua *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error)
 	UserChangePassword(ua *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error)
+	UserGrant(ua *pb.AuthUserGrantRequest) (*pb.AuthUserGrantResponse, error)
 	RoleAdd(ua *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error)
 	RoleAdd(ua *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error)
 	RoleGrant(ua *pb.AuthRoleGrantRequest) (*pb.AuthRoleGrantResponse, error)
 	RoleGrant(ua *pb.AuthRoleGrantRequest) (*pb.AuthRoleGrantResponse, error)
 }
 }
@@ -92,6 +93,8 @@ func (s *EtcdServer) applyV3Request(r *pb.InternalRaftRequest) *applyResult {
 		ar.resp, ar.err = s.applyV3.UserDelete(r.AuthUserDelete)
 		ar.resp, ar.err = s.applyV3.UserDelete(r.AuthUserDelete)
 	case r.AuthUserChangePassword != nil:
 	case r.AuthUserChangePassword != nil:
 		ar.resp, ar.err = s.applyV3.UserChangePassword(r.AuthUserChangePassword)
 		ar.resp, ar.err = s.applyV3.UserChangePassword(r.AuthUserChangePassword)
+	case r.AuthUserGrant != nil:
+		ar.resp, ar.err = s.applyV3.UserGrant(r.AuthUserGrant)
 	case r.AuthRoleAdd != nil:
 	case r.AuthRoleAdd != nil:
 		ar.resp, ar.err = s.applyV3.RoleAdd(r.AuthRoleAdd)
 		ar.resp, ar.err = s.applyV3.RoleAdd(r.AuthRoleAdd)
 	case r.AuthRoleGrant != nil:
 	case r.AuthRoleGrant != nil:
@@ -495,6 +498,10 @@ func (a *applierV3backend) UserChangePassword(r *pb.AuthUserChangePasswordReques
 	return a.s.AuthStore().UserChangePassword(r)
 	return a.s.AuthStore().UserChangePassword(r)
 }
 }
 
 
+func (a *applierV3backend) UserGrant(r *pb.AuthUserGrantRequest) (*pb.AuthUserGrantResponse, error) {
+	return a.s.AuthStore().UserGrant(r)
+}
+
 func (a *applierV3backend) RoleAdd(r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) {
 func (a *applierV3backend) RoleAdd(r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) {
 	return a.s.AuthStore().RoleAdd(r)
 	return a.s.AuthStore().RoleAdd(r)
 }
 }

+ 65 - 15
etcdserver/etcdserverpb/raft_internal.pb.go

@@ -35,9 +35,10 @@ type InternalRaftRequest struct {
 	AuthUserAdd            *AuthUserAddRequest            `protobuf:"bytes,11,opt,name=auth_user_add" json:"auth_user_add,omitempty"`
 	AuthUserAdd            *AuthUserAddRequest            `protobuf:"bytes,11,opt,name=auth_user_add" json:"auth_user_add,omitempty"`
 	AuthUserDelete         *AuthUserDeleteRequest         `protobuf:"bytes,12,opt,name=auth_user_delete" json:"auth_user_delete,omitempty"`
 	AuthUserDelete         *AuthUserDeleteRequest         `protobuf:"bytes,12,opt,name=auth_user_delete" json:"auth_user_delete,omitempty"`
 	AuthUserChangePassword *AuthUserChangePasswordRequest `protobuf:"bytes,13,opt,name=auth_user_change_password" json:"auth_user_change_password,omitempty"`
 	AuthUserChangePassword *AuthUserChangePasswordRequest `protobuf:"bytes,13,opt,name=auth_user_change_password" json:"auth_user_change_password,omitempty"`
-	AuthRoleAdd            *AuthRoleAddRequest            `protobuf:"bytes,14,opt,name=auth_role_add" json:"auth_role_add,omitempty"`
-	AuthRoleGrant          *AuthRoleGrantRequest          `protobuf:"bytes,15,opt,name=auth_role_grant" json:"auth_role_grant,omitempty"`
-	Alarm                  *AlarmRequest                  `protobuf:"bytes,16,opt,name=alarm" json:"alarm,omitempty"`
+	AuthUserGrant          *AuthUserGrantRequest          `protobuf:"bytes,14,opt,name=auth_user_grant" json:"auth_user_grant,omitempty"`
+	AuthRoleAdd            *AuthRoleAddRequest            `protobuf:"bytes,15,opt,name=auth_role_add" json:"auth_role_add,omitempty"`
+	AuthRoleGrant          *AuthRoleGrantRequest          `protobuf:"bytes,16,opt,name=auth_role_grant" json:"auth_role_grant,omitempty"`
+	Alarm                  *AlarmRequest                  `protobuf:"bytes,17,opt,name=alarm" json:"alarm,omitempty"`
 }
 }
 
 
 func (m *InternalRaftRequest) Reset()         { *m = InternalRaftRequest{} }
 func (m *InternalRaftRequest) Reset()         { *m = InternalRaftRequest{} }
@@ -195,38 +196,50 @@ func (m *InternalRaftRequest) MarshalTo(data []byte) (int, error) {
 		}
 		}
 		i += n12
 		i += n12
 	}
 	}
-	if m.AuthRoleAdd != nil {
+	if m.AuthUserGrant != nil {
 		data[i] = 0x72
 		data[i] = 0x72
 		i++
 		i++
-		i = encodeVarintRaftInternal(data, i, uint64(m.AuthRoleAdd.Size()))
-		n13, err := m.AuthRoleAdd.MarshalTo(data[i:])
+		i = encodeVarintRaftInternal(data, i, uint64(m.AuthUserGrant.Size()))
+		n13, err := m.AuthUserGrant.MarshalTo(data[i:])
 		if err != nil {
 		if err != nil {
 			return 0, err
 			return 0, err
 		}
 		}
 		i += n13
 		i += n13
 	}
 	}
-	if m.AuthRoleGrant != nil {
+	if m.AuthRoleAdd != nil {
 		data[i] = 0x7a
 		data[i] = 0x7a
 		i++
 		i++
-		i = encodeVarintRaftInternal(data, i, uint64(m.AuthRoleGrant.Size()))
-		n14, err := m.AuthRoleGrant.MarshalTo(data[i:])
+		i = encodeVarintRaftInternal(data, i, uint64(m.AuthRoleAdd.Size()))
+		n14, err := m.AuthRoleAdd.MarshalTo(data[i:])
 		if err != nil {
 		if err != nil {
 			return 0, err
 			return 0, err
 		}
 		}
 		i += n14
 		i += n14
 	}
 	}
-	if m.Alarm != nil {
+	if m.AuthRoleGrant != nil {
 		data[i] = 0x82
 		data[i] = 0x82
 		i++
 		i++
 		data[i] = 0x1
 		data[i] = 0x1
 		i++
 		i++
-		i = encodeVarintRaftInternal(data, i, uint64(m.Alarm.Size()))
-		n15, err := m.Alarm.MarshalTo(data[i:])
+		i = encodeVarintRaftInternal(data, i, uint64(m.AuthRoleGrant.Size()))
+		n15, err := m.AuthRoleGrant.MarshalTo(data[i:])
 		if err != nil {
 		if err != nil {
 			return 0, err
 			return 0, err
 		}
 		}
 		i += n15
 		i += n15
 	}
 	}
+	if m.Alarm != nil {
+		data[i] = 0x8a
+		i++
+		data[i] = 0x1
+		i++
+		i = encodeVarintRaftInternal(data, i, uint64(m.Alarm.Size()))
+		n16, err := m.Alarm.MarshalTo(data[i:])
+		if err != nil {
+			return 0, err
+		}
+		i += n16
+	}
 	return i, nil
 	return i, nil
 }
 }
 
 
@@ -329,13 +342,17 @@ func (m *InternalRaftRequest) Size() (n int) {
 		l = m.AuthUserChangePassword.Size()
 		l = m.AuthUserChangePassword.Size()
 		n += 1 + l + sovRaftInternal(uint64(l))
 		n += 1 + l + sovRaftInternal(uint64(l))
 	}
 	}
+	if m.AuthUserGrant != nil {
+		l = m.AuthUserGrant.Size()
+		n += 1 + l + sovRaftInternal(uint64(l))
+	}
 	if m.AuthRoleAdd != nil {
 	if m.AuthRoleAdd != nil {
 		l = m.AuthRoleAdd.Size()
 		l = m.AuthRoleAdd.Size()
 		n += 1 + l + sovRaftInternal(uint64(l))
 		n += 1 + l + sovRaftInternal(uint64(l))
 	}
 	}
 	if m.AuthRoleGrant != nil {
 	if m.AuthRoleGrant != nil {
 		l = m.AuthRoleGrant.Size()
 		l = m.AuthRoleGrant.Size()
-		n += 1 + l + sovRaftInternal(uint64(l))
+		n += 2 + l + sovRaftInternal(uint64(l))
 	}
 	}
 	if m.Alarm != nil {
 	if m.Alarm != nil {
 		l = m.Alarm.Size()
 		l = m.Alarm.Size()
@@ -808,6 +825,39 @@ func (m *InternalRaftRequest) Unmarshal(data []byte) error {
 			}
 			}
 			iNdEx = postIndex
 			iNdEx = postIndex
 		case 14:
 		case 14:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field AuthUserGrant", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowRaftInternal
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := data[iNdEx]
+				iNdEx++
+				msglen |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthRaftInternal
+			}
+			postIndex := iNdEx + msglen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.AuthUserGrant == nil {
+				m.AuthUserGrant = &AuthUserGrantRequest{}
+			}
+			if err := m.AuthUserGrant.Unmarshal(data[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 15:
 			if wireType != 2 {
 			if wireType != 2 {
 				return fmt.Errorf("proto: wrong wireType = %d for field AuthRoleAdd", wireType)
 				return fmt.Errorf("proto: wrong wireType = %d for field AuthRoleAdd", wireType)
 			}
 			}
@@ -840,7 +890,7 @@ func (m *InternalRaftRequest) Unmarshal(data []byte) error {
 				return err
 				return err
 			}
 			}
 			iNdEx = postIndex
 			iNdEx = postIndex
-		case 15:
+		case 16:
 			if wireType != 2 {
 			if wireType != 2 {
 				return fmt.Errorf("proto: wrong wireType = %d for field AuthRoleGrant", wireType)
 				return fmt.Errorf("proto: wrong wireType = %d for field AuthRoleGrant", wireType)
 			}
 			}
@@ -873,7 +923,7 @@ func (m *InternalRaftRequest) Unmarshal(data []byte) error {
 				return err
 				return err
 			}
 			}
 			iNdEx = postIndex
 			iNdEx = postIndex
-		case 16:
+		case 17:
 			if wireType != 2 {
 			if wireType != 2 {
 				return fmt.Errorf("proto: wrong wireType = %d for field Alarm", wireType)
 				return fmt.Errorf("proto: wrong wireType = %d for field Alarm", wireType)
 			}
 			}

+ 4 - 3
etcdserver/etcdserverpb/raft_internal.proto

@@ -29,10 +29,11 @@ message InternalRaftRequest {
   AuthUserAddRequest auth_user_add = 11;
   AuthUserAddRequest auth_user_add = 11;
   AuthUserDeleteRequest auth_user_delete = 12;
   AuthUserDeleteRequest auth_user_delete = 12;
   AuthUserChangePasswordRequest auth_user_change_password = 13;
   AuthUserChangePasswordRequest auth_user_change_password = 13;
-  AuthRoleAddRequest auth_role_add = 14;
-  AuthRoleGrantRequest auth_role_grant = 15;
+  AuthUserGrantRequest auth_user_grant = 14;
+  AuthRoleAddRequest auth_role_add = 15;
+  AuthRoleGrantRequest auth_role_grant = 16;
 
 
-  AlarmRequest alarm = 16;
+  AlarmRequest alarm = 17;
 }
 }
 
 
 message EmptyResponse {
 message EmptyResponse {

+ 80 - 0
etcdserver/etcdserverpb/rpc.pb.go

@@ -1329,6 +1329,8 @@ func (m *AuthUserChangePasswordRequest) String() string { return proto.CompactTe
 func (*AuthUserChangePasswordRequest) ProtoMessage()    {}
 func (*AuthUserChangePasswordRequest) ProtoMessage()    {}
 
 
 type AuthUserGrantRequest struct {
 type AuthUserGrantRequest struct {
+	User string `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"`
+	Role string `protobuf:"bytes,2,opt,name=role,proto3" json:"role,omitempty"`
 }
 }
 
 
 func (m *AuthUserGrantRequest) Reset()         { *m = AuthUserGrantRequest{} }
 func (m *AuthUserGrantRequest) Reset()         { *m = AuthUserGrantRequest{} }
@@ -4640,6 +4642,18 @@ func (m *AuthUserGrantRequest) MarshalTo(data []byte) (int, error) {
 	_ = i
 	_ = i
 	var l int
 	var l int
 	_ = l
 	_ = l
+	if len(m.User) > 0 {
+		data[i] = 0xa
+		i++
+		i = encodeVarintRpc(data, i, uint64(len(m.User)))
+		i += copy(data[i:], m.User)
+	}
+	if len(m.Role) > 0 {
+		data[i] = 0x12
+		i++
+		i = encodeVarintRpc(data, i, uint64(len(m.Role)))
+		i += copy(data[i:], m.Role)
+	}
 	return i, nil
 	return i, nil
 }
 }
 
 
@@ -5956,6 +5970,14 @@ func (m *AuthUserChangePasswordRequest) Size() (n int) {
 func (m *AuthUserGrantRequest) Size() (n int) {
 func (m *AuthUserGrantRequest) Size() (n int) {
 	var l int
 	var l int
 	_ = l
 	_ = l
+	l = len(m.User)
+	if l > 0 {
+		n += 1 + l + sovRpc(uint64(l))
+	}
+	l = len(m.Role)
+	if l > 0 {
+		n += 1 + l + sovRpc(uint64(l))
+	}
 	return n
 	return n
 }
 }
 
 
@@ -11260,6 +11282,64 @@ func (m *AuthUserGrantRequest) Unmarshal(data []byte) error {
 			return fmt.Errorf("proto: AuthUserGrantRequest: illegal tag %d (wire type %d)", fieldNum, wire)
 			return fmt.Errorf("proto: AuthUserGrantRequest: illegal tag %d (wire type %d)", fieldNum, wire)
 		}
 		}
 		switch fieldNum {
 		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field User", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowRpc
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := data[iNdEx]
+				iNdEx++
+				stringLen |= (uint64(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthRpc
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.User = string(data[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Role", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowRpc
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := data[iNdEx]
+				iNdEx++
+				stringLen |= (uint64(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthRpc
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Role = string(data[iNdEx:postIndex])
+			iNdEx = postIndex
 		default:
 		default:
 			iNdEx = preIndex
 			iNdEx = preIndex
 			skippy, err := skipRpc(data[iNdEx:])
 			skippy, err := skipRpc(data[iNdEx:])

+ 2 - 0
etcdserver/etcdserverpb/rpc.proto

@@ -510,6 +510,8 @@ message AuthUserChangePasswordRequest {
 }
 }
 
 
 message AuthUserGrantRequest {
 message AuthUserGrantRequest {
+  string user = 1;
+  string role = 2;
 }
 }
 
 
 message AuthUserRevokeRequest {
 message AuthUserRevokeRequest {

+ 9 - 0
etcdserver/v3demo_server.go

@@ -56,6 +56,7 @@ type Authenticator interface {
 	UserAdd(ctx context.Context, r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error)
 	UserAdd(ctx context.Context, r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error)
 	UserDelete(ctx context.Context, r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error)
 	UserDelete(ctx context.Context, r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error)
 	UserChangePassword(ctx context.Context, r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error)
 	UserChangePassword(ctx context.Context, r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error)
+	UserGrant(ctx context.Context, r *pb.AuthUserGrantRequest) (*pb.AuthUserGrantResponse, error)
 	RoleAdd(ctx context.Context, r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error)
 	RoleAdd(ctx context.Context, r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error)
 	RoleGrant(ctx context.Context, r *pb.AuthRoleGrantRequest) (*pb.AuthRoleGrantResponse, error)
 	RoleGrant(ctx context.Context, r *pb.AuthRoleGrantRequest) (*pb.AuthRoleGrantResponse, error)
 }
 }
@@ -235,6 +236,14 @@ func (s *EtcdServer) UserChangePassword(ctx context.Context, r *pb.AuthUserChang
 	return result.resp.(*pb.AuthUserChangePasswordResponse), result.err
 	return result.resp.(*pb.AuthUserChangePasswordResponse), result.err
 }
 }
 
 
+func (s *EtcdServer) UserGrant(ctx context.Context, r *pb.AuthUserGrantRequest) (*pb.AuthUserGrantResponse, error) {
+	result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthUserGrant: r})
+	if err != nil {
+		return nil, err
+	}
+	return result.resp.(*pb.AuthUserGrantResponse), result.err
+}
+
 func (s *EtcdServer) RoleAdd(ctx context.Context, r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) {
 func (s *EtcdServer) RoleAdd(ctx context.Context, r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) {
 	result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthRoleAdd: r})
 	result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthRoleAdd: r})
 	if err != nil {
 	if err != nil {