Browse Source

*: support granting key permission to role in v3 auth

Hitoshi Mitake 9 years ago
parent
commit
02033b4c47

+ 235 - 3
auth/authpb/auth.pb.go

@@ -10,6 +10,7 @@
 
 	It has these top-level messages:
 		User
+		Permission
 		Role
 */
 package authpb
@@ -29,6 +30,29 @@ var _ = proto.Marshal
 var _ = fmt.Errorf
 var _ = math.Inf
 
+type Permission_Type int32
+
+const (
+	READ      Permission_Type = 0
+	WRITE     Permission_Type = 1
+	READWRITE Permission_Type = 2
+)
+
+var Permission_Type_name = map[int32]string{
+	0: "READ",
+	1: "WRITE",
+	2: "READWRITE",
+}
+var Permission_Type_value = map[string]int32{
+	"READ":      0,
+	"WRITE":     1,
+	"READWRITE": 2,
+}
+
+func (x Permission_Type) String() string {
+	return proto.EnumName(Permission_Type_name, int32(x))
+}
+
 // User is a single entry in the bucket authUsers
 type User struct {
 	Name      []byte `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
@@ -40,9 +64,20 @@ func (m *User) Reset()         { *m = User{} }
 func (m *User) String() string { return proto.CompactTextString(m) }
 func (*User) ProtoMessage()    {}
 
+// Permission is a single entity
+type Permission struct {
+	Key      []byte          `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
+	PermType Permission_Type `protobuf:"varint,2,opt,name=permType,proto3,enum=authpb.Permission_Type" json:"permType,omitempty"`
+}
+
+func (m *Permission) Reset()         { *m = Permission{} }
+func (m *Permission) String() string { return proto.CompactTextString(m) }
+func (*Permission) ProtoMessage()    {}
+
 // Role is a single entry in the bucket authRoles
 type Role struct {
-	Name []byte `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
+	Name          []byte        `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	KeyPermission []*Permission `protobuf:"bytes,2,rep,name=keyPermission" json:"keyPermission,omitempty"`
 }
 
 func (m *Role) Reset()         { *m = Role{} }
@@ -51,7 +86,9 @@ func (*Role) ProtoMessage()    {}
 
 func init() {
 	proto.RegisterType((*User)(nil), "authpb.User")
+	proto.RegisterType((*Permission)(nil), "authpb.Permission")
 	proto.RegisterType((*Role)(nil), "authpb.Role")
+	proto.RegisterEnum("authpb.Permission_Type", Permission_Type_name, Permission_Type_value)
 }
 func (m *User) Marshal() (data []byte, err error) {
 	size := m.Size()
@@ -92,6 +129,37 @@ func (m *User) MarshalTo(data []byte) (int, error) {
 	return i, nil
 }
 
+func (m *Permission) Marshal() (data []byte, err error) {
+	size := m.Size()
+	data = make([]byte, size)
+	n, err := m.MarshalTo(data)
+	if err != nil {
+		return nil, err
+	}
+	return data[:n], nil
+}
+
+func (m *Permission) MarshalTo(data []byte) (int, error) {
+	var i int
+	_ = i
+	var l int
+	_ = l
+	if m.Key != nil {
+		if len(m.Key) > 0 {
+			data[i] = 0xa
+			i++
+			i = encodeVarintAuth(data, i, uint64(len(m.Key)))
+			i += copy(data[i:], m.Key)
+		}
+	}
+	if m.PermType != 0 {
+		data[i] = 0x10
+		i++
+		i = encodeVarintAuth(data, i, uint64(m.PermType))
+	}
+	return i, nil
+}
+
 func (m *Role) Marshal() (data []byte, err error) {
 	size := m.Size()
 	data = make([]byte, size)
@@ -109,12 +177,24 @@ func (m *Role) MarshalTo(data []byte) (int, error) {
 	_ = l
 	if m.Name != nil {
 		if len(m.Name) > 0 {
-			data[i] = 0x12
+			data[i] = 0xa
 			i++
 			i = encodeVarintAuth(data, i, uint64(len(m.Name)))
 			i += copy(data[i:], m.Name)
 		}
 	}
+	if len(m.KeyPermission) > 0 {
+		for _, msg := range m.KeyPermission {
+			data[i] = 0x12
+			i++
+			i = encodeVarintAuth(data, i, uint64(msg.Size()))
+			n, err := msg.MarshalTo(data[i:])
+			if err != nil {
+				return 0, err
+			}
+			i += n
+		}
+	}
 	return i, nil
 }
 
@@ -166,6 +246,21 @@ func (m *User) Size() (n int) {
 	return n
 }
 
+func (m *Permission) Size() (n int) {
+	var l int
+	_ = l
+	if m.Key != nil {
+		l = len(m.Key)
+		if l > 0 {
+			n += 1 + l + sovAuth(uint64(l))
+		}
+	}
+	if m.PermType != 0 {
+		n += 1 + sovAuth(uint64(m.PermType))
+	}
+	return n
+}
+
 func (m *Role) Size() (n int) {
 	var l int
 	_ = l
@@ -175,6 +270,12 @@ func (m *Role) Size() (n int) {
 			n += 1 + l + sovAuth(uint64(l))
 		}
 	}
+	if len(m.KeyPermission) > 0 {
+		for _, e := range m.KeyPermission {
+			l = e.Size()
+			n += 1 + l + sovAuth(uint64(l))
+		}
+	}
 	return n
 }
 
@@ -322,6 +423,106 @@ func (m *User) Unmarshal(data []byte) error {
 	}
 	return nil
 }
+func (m *Permission) Unmarshal(data []byte) error {
+	l := len(data)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowAuth
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := data[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: Permission: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: Permission: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType)
+			}
+			var byteLen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowAuth
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := data[iNdEx]
+				iNdEx++
+				byteLen |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if byteLen < 0 {
+				return ErrInvalidLengthAuth
+			}
+			postIndex := iNdEx + byteLen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Key = append(m.Key[:0], data[iNdEx:postIndex]...)
+			if m.Key == nil {
+				m.Key = []byte{}
+			}
+			iNdEx = postIndex
+		case 2:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field PermType", wireType)
+			}
+			m.PermType = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowAuth
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := data[iNdEx]
+				iNdEx++
+				m.PermType |= (Permission_Type(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		default:
+			iNdEx = preIndex
+			skippy, err := skipAuth(data[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if skippy < 0 {
+				return ErrInvalidLengthAuth
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
 func (m *Role) Unmarshal(data []byte) error {
 	l := len(data)
 	iNdEx := 0
@@ -351,7 +552,7 @@ func (m *Role) Unmarshal(data []byte) error {
 			return fmt.Errorf("proto: Role: illegal tag %d (wire type %d)", fieldNum, wire)
 		}
 		switch fieldNum {
-		case 2:
+		case 1:
 			if wireType != 2 {
 				return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
 			}
@@ -382,6 +583,37 @@ func (m *Role) Unmarshal(data []byte) error {
 				m.Name = []byte{}
 			}
 			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field KeyPermission", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowAuth
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := data[iNdEx]
+				iNdEx++
+				msglen |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthAuth
+			}
+			postIndex := iNdEx + msglen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.KeyPermission = append(m.KeyPermission, &Permission{})
+			if err := m.KeyPermission[len(m.KeyPermission)-1].Unmarshal(data[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
 		default:
 			iNdEx = preIndex
 			skippy, err := skipAuth(data[iNdEx:])

+ 15 - 1
auth/authpb/auth.proto

@@ -16,7 +16,21 @@ message User {
   int64 tombstone = 3;
 }
 
+// Permission is a single entity
+message Permission {
+  bytes key = 1;
+
+  enum Type {
+    READ = 0;
+    WRITE = 1;
+    READWRITE = 2;
+  }
+  Type permType = 2;
+}
+
 // Role is a single entry in the bucket authRoles
 message Role {
-  bytes name = 2;
+  bytes name = 1;
+
+  repeated Permission keyPermission = 2;
 }

+ 55 - 0
auth/store.go

@@ -16,6 +16,7 @@ package auth
 
 import (
 	"errors"
+	"strings"
 
 	"github.com/coreos/etcd/auth/authpb"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
@@ -36,6 +37,7 @@ var (
 	ErrUserAlreadyExist = errors.New("auth: user already exists")
 	ErrUserNotFound     = errors.New("auth: user not found")
 	ErrRoleAlreadyExist = errors.New("auth: role already exists")
+	ErrRoleNotFound     = errors.New("auth: role not found")
 )
 
 type AuthStore interface {
@@ -56,6 +58,9 @@ type AuthStore interface {
 
 	// RoleAdd adds a new role
 	RoleAdd(r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error)
+
+	// RoleGrant grants a permission to a role
+	RoleGrant(r *pb.AuthRoleGrantRequest) (*pb.AuthRoleGrantResponse, error)
 }
 
 type authStore struct {
@@ -195,6 +200,56 @@ func (as *authStore) RoleAdd(r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse,
 	return &pb.AuthRoleAddResponse{}, nil
 }
 
+func (as *authStore) RoleGrant(r *pb.AuthRoleGrantRequest) (*pb.AuthRoleGrantResponse, error) {
+	tx := as.be.BatchTx()
+	tx.Lock()
+	defer tx.Unlock()
+
+	_, vs := tx.UnsafeRange(authRolesBucketName, []byte(r.Name), nil, 0)
+	if len(vs) != 1 {
+		return nil, ErrRoleNotFound
+	}
+
+	role := &authpb.Role{}
+	err := role.Unmarshal(vs[0])
+	if err != nil {
+		plog.Errorf("failed to unmarshal a role %s: %s", r.Name, err)
+		return nil, err
+	}
+
+	if !updateExistingPermission(role.KeyPermission, string(r.Perm.Key), r.Perm.PermType) {
+		newPerm := &authpb.Permission{
+			Key:      []byte(r.Perm.Key),
+			PermType: r.Perm.PermType,
+		}
+
+		role.KeyPermission = append(role.KeyPermission, newPerm)
+	}
+
+	marshaledRole, merr := role.Marshal()
+	if merr != nil {
+		plog.Errorf("failed to marshal updated role %s: %s", r.Name, merr)
+		return nil, merr
+	}
+
+	tx.UnsafePut(authRolesBucketName, []byte(r.Name), marshaledRole)
+
+	plog.Noticef("role %s's permission of key %s is updated as %s", r.Name, r.Perm.Key, authpb.Permission_Type_name[int32(r.Perm.PermType)])
+
+	return &pb.AuthRoleGrantResponse{}, nil
+}
+
+func updateExistingPermission(perms []*authpb.Permission, key string, newPerm authpb.Permission_Type) bool {
+	for _, perm := range perms {
+		if strings.Compare(string(perm.Key), key) == 0 {
+			perm.PermType = newPerm
+			return true
+		}
+	}
+
+	return false
+}
+
 func NewAuthStore(be backend.Backend) *authStore {
 	tx := be.BatchTx()
 	tx.Lock()

+ 34 - 1
clientv3/auth.go

@@ -15,6 +15,10 @@
 package clientv3
 
 import (
+	"fmt"
+	"strings"
+
+	"github.com/coreos/etcd/auth/authpb"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	"golang.org/x/net/context"
 	"google.golang.org/grpc"
@@ -26,6 +30,15 @@ type (
 	AuthUserDeleteResponse         pb.AuthUserDeleteResponse
 	AuthUserChangePasswordResponse pb.AuthUserChangePasswordResponse
 	AuthRoleAddResponse            pb.AuthRoleAddResponse
+	AuthRoleGrantResponse          pb.AuthRoleGrantResponse
+
+	PermissionType authpb.Permission_Type
+)
+
+const (
+	PermRead      = authpb.READ
+	PermWrite     = authpb.WRITE
+	PermReadWrite = authpb.READWRITE
 )
 
 type Auth interface {
@@ -41,8 +54,11 @@ type Auth interface {
 	// UserChangePassword changes a password of a user.
 	UserChangePassword(ctx context.Context, name string, password string) (*AuthUserChangePasswordResponse, error)
 
-	// RoleAdd adds a new user to an etcd cluster.
+	// RoleAdd adds a new role to an etcd cluster.
 	RoleAdd(ctx context.Context, name string) (*AuthRoleAddResponse, error)
+
+	// RoleGrant grants a permission to a role.
+	RoleGrant(ctx context.Context, name string, key string, permType PermissionType) (*AuthRoleGrantResponse, error)
 }
 
 type auth struct {
@@ -85,3 +101,20 @@ func (auth *auth) RoleAdd(ctx context.Context, name string) (*AuthRoleAddRespons
 	resp, err := auth.remote.RoleAdd(ctx, &pb.AuthRoleAddRequest{Name: name})
 	return (*AuthRoleAddResponse)(resp), err
 }
+
+func (auth *auth) RoleGrant(ctx context.Context, name string, key string, permType PermissionType) (*AuthRoleGrantResponse, error) {
+	perm := &authpb.Permission{
+		Key:      []byte(key),
+		PermType: authpb.Permission_Type(permType),
+	}
+	resp, err := auth.remote.RoleGrant(ctx, &pb.AuthRoleGrantRequest{Name: name, Perm: perm})
+	return (*AuthRoleGrantResponse)(resp), err
+}
+
+func StrToPermissionType(s string) (PermissionType, error) {
+	val, ok := authpb.Permission_Type_value[strings.ToUpper(s)]
+	if ok {
+		return PermissionType(val), nil
+	}
+	return PermissionType(-1), fmt.Errorf("invalid permission type: %s", s)
+}

+ 29 - 0
etcdctl/ctlv3/command/role_command.go

@@ -17,6 +17,7 @@ package command
 import (
 	"fmt"
 
+	"github.com/coreos/etcd/clientv3"
 	"github.com/spf13/cobra"
 	"golang.org/x/net/context"
 )
@@ -29,6 +30,7 @@ func NewRoleCommand() *cobra.Command {
 	}
 
 	ac.AddCommand(newRoleAddCommand())
+	ac.AddCommand(newRoleGrantCommand())
 
 	return ac
 }
@@ -41,6 +43,14 @@ func newRoleAddCommand() *cobra.Command {
 	}
 }
 
+func newRoleGrantCommand() *cobra.Command {
+	return &cobra.Command{
+		Use:   "grant <role name> <permission type> <key>",
+		Short: "grant a key to a role",
+		Run:   roleGrantCommandFunc,
+	}
+}
+
 // roleAddCommandFunc executes the "role add" command.
 func roleAddCommandFunc(cmd *cobra.Command, args []string) {
 	if len(args) != 1 {
@@ -54,3 +64,22 @@ func roleAddCommandFunc(cmd *cobra.Command, args []string) {
 
 	fmt.Printf("Role %s created\n", args[0])
 }
+
+// roleGrantCommandFunc executes the "role grant" command.
+func roleGrantCommandFunc(cmd *cobra.Command, args []string) {
+	if len(args) != 3 {
+		ExitWithError(ExitBadArgs, fmt.Errorf("role grant command requires role name, permission type, and key as its argument."))
+	}
+
+	perm, err := clientv3.StrToPermissionType(args[1])
+	if err != nil {
+		ExitWithError(ExitBadArgs, err)
+	}
+
+	_, err = mustClientFromCmd(cmd).Auth.RoleGrant(context.TODO(), args[0], args[2], perm)
+	if err != nil {
+		ExitWithError(ExitError, err)
+	}
+
+	fmt.Printf("Role %s updated\n", args[0])
+}

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

@@ -70,8 +70,11 @@ func (as *AuthServer) RoleRevoke(ctx context.Context, r *pb.AuthRoleRevokeReques
 }
 
 func (as *AuthServer) RoleGrant(ctx context.Context, r *pb.AuthRoleGrantRequest) (*pb.AuthRoleGrantResponse, error) {
-	plog.Info("not implemented yet")
-	return nil, nil
+	resp, err := as.authenticator.RoleGrant(ctx, r)
+	if err != nil {
+		return nil, togRPCError(err)
+	}
+	return resp, nil
 }
 
 func (as *AuthServer) UserAdd(ctx context.Context, r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) {

+ 1 - 0
etcdserver/api/v3rpc/rpctypes/error.go

@@ -40,4 +40,5 @@ var (
 	ErrUserAlreadyExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: user name already exists")
 	ErrUserNotFound     = grpc.Errorf(codes.FailedPrecondition, "etcdserver: user name not found")
 	ErrRoleAlreadyExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: role name already exists")
+	ErrRoleNotFound     = grpc.Errorf(codes.FailedPrecondition, "etcdserver: role name not found")
 )

+ 2 - 0
etcdserver/api/v3rpc/util.go

@@ -43,6 +43,8 @@ func togRPCError(err error) error {
 		return rpctypes.ErrUserNotFound
 	case auth.ErrRoleAlreadyExist:
 		return rpctypes.ErrRoleAlreadyExist
+	case auth.ErrRoleNotFound:
+		return rpctypes.ErrRoleNotFound
 	default:
 		return grpc.Errorf(codes.Internal, err.Error())
 	}

+ 7 - 0
etcdserver/apply.go

@@ -58,6 +58,7 @@ type applierV3 interface {
 	UserDelete(ua *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error)
 	UserChangePassword(ua *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error)
 	RoleAdd(ua *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error)
+	RoleGrant(ua *pb.AuthRoleGrantRequest) (*pb.AuthRoleGrantResponse, error)
 }
 
 type applierV3backend struct {
@@ -93,6 +94,8 @@ func (s *EtcdServer) applyV3Request(r *pb.InternalRaftRequest) *applyResult {
 		ar.resp, ar.err = s.applyV3.UserChangePassword(r.AuthUserChangePassword)
 	case r.AuthRoleAdd != nil:
 		ar.resp, ar.err = s.applyV3.RoleAdd(r.AuthRoleAdd)
+	case r.AuthRoleGrant != nil:
+		ar.resp, ar.err = s.applyV3.RoleGrant(r.AuthRoleGrant)
 	default:
 		panic("not implemented")
 	}
@@ -496,6 +499,10 @@ func (a *applierV3backend) RoleAdd(r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddRes
 	return a.s.AuthStore().RoleAdd(r)
 }
 
+func (a *applierV3backend) RoleGrant(r *pb.AuthRoleGrantRequest) (*pb.AuthRoleGrantResponse, error) {
+	return a.s.AuthStore().RoleGrant(r)
+}
+
 type quotaApplierV3 struct {
 	applierV3
 	q Quota

+ 55 - 5
etcdserver/etcdserverpb/raft_internal.pb.go

@@ -36,7 +36,8 @@ type InternalRaftRequest struct {
 	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"`
 	AuthRoleAdd            *AuthRoleAddRequest            `protobuf:"bytes,14,opt,name=auth_role_add" json:"auth_role_add,omitempty"`
-	Alarm                  *AlarmRequest                  `protobuf:"bytes,15,opt,name=alarm" json:"alarm,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"`
 }
 
 func (m *InternalRaftRequest) Reset()         { *m = InternalRaftRequest{} }
@@ -204,16 +205,28 @@ func (m *InternalRaftRequest) MarshalTo(data []byte) (int, error) {
 		}
 		i += n13
 	}
-	if m.Alarm != nil {
+	if m.AuthRoleGrant != nil {
 		data[i] = 0x7a
 		i++
-		i = encodeVarintRaftInternal(data, i, uint64(m.Alarm.Size()))
-		n14, err := m.Alarm.MarshalTo(data[i:])
+		i = encodeVarintRaftInternal(data, i, uint64(m.AuthRoleGrant.Size()))
+		n14, err := m.AuthRoleGrant.MarshalTo(data[i:])
 		if err != nil {
 			return 0, err
 		}
 		i += n14
 	}
+	if m.Alarm != nil {
+		data[i] = 0x82
+		i++
+		data[i] = 0x1
+		i++
+		i = encodeVarintRaftInternal(data, i, uint64(m.Alarm.Size()))
+		n15, err := m.Alarm.MarshalTo(data[i:])
+		if err != nil {
+			return 0, err
+		}
+		i += n15
+	}
 	return i, nil
 }
 
@@ -320,9 +333,13 @@ func (m *InternalRaftRequest) Size() (n int) {
 		l = m.AuthRoleAdd.Size()
 		n += 1 + l + sovRaftInternal(uint64(l))
 	}
+	if m.AuthRoleGrant != nil {
+		l = m.AuthRoleGrant.Size()
+		n += 1 + l + sovRaftInternal(uint64(l))
+	}
 	if m.Alarm != nil {
 		l = m.Alarm.Size()
-		n += 1 + l + sovRaftInternal(uint64(l))
+		n += 2 + l + sovRaftInternal(uint64(l))
 	}
 	return n
 }
@@ -824,6 +841,39 @@ func (m *InternalRaftRequest) Unmarshal(data []byte) error {
 			}
 			iNdEx = postIndex
 		case 15:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field AuthRoleGrant", 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.AuthRoleGrant == nil {
+				m.AuthRoleGrant = &AuthRoleGrantRequest{}
+			}
+			if err := m.AuthRoleGrant.Unmarshal(data[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 16:
 			if wireType != 2 {
 				return fmt.Errorf("proto: wrong wireType = %d for field Alarm", wireType)
 			}

+ 2 - 1
etcdserver/etcdserverpb/raft_internal.proto

@@ -30,8 +30,9 @@ message InternalRaftRequest {
   AuthUserDeleteRequest auth_user_delete = 12;
   AuthUserChangePasswordRequest auth_user_change_password = 13;
   AuthRoleAddRequest auth_role_add = 14;
+  AuthRoleGrantRequest auth_role_grant = 15;
 
-  AlarmRequest alarm = 15;
+  AlarmRequest alarm = 16;
 }
 
 message EmptyResponse {

+ 129 - 31
etcdserver/etcdserverpb/rpc.pb.go

@@ -11,14 +11,17 @@ import (
 
 	math "math"
 
-	context "golang.org/x/net/context"
+	authpb "github.com/coreos/etcd/auth/authpb"
 
-	grpc "google.golang.org/grpc"
+	io "io"
 )
 
 import storagepb "github.com/coreos/etcd/storage/storagepb"
 
-import io "io"
+import (
+	context "golang.org/x/net/context"
+	grpc "google.golang.org/grpc"
+)
 
 // Reference imports to suppress errors if they are not otherwise used.
 var _ = proto.Marshal
@@ -1362,12 +1365,21 @@ func (m *AuthRoleDeleteRequest) String() string { return proto.CompactTextString
 func (*AuthRoleDeleteRequest) ProtoMessage()    {}
 
 type AuthRoleGrantRequest struct {
+	Name string             `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	Perm *authpb.Permission `protobuf:"bytes,2,opt,name=perm" json:"perm,omitempty"`
 }
 
 func (m *AuthRoleGrantRequest) Reset()         { *m = AuthRoleGrantRequest{} }
 func (m *AuthRoleGrantRequest) String() string { return proto.CompactTextString(m) }
 func (*AuthRoleGrantRequest) ProtoMessage()    {}
 
+func (m *AuthRoleGrantRequest) GetPerm() *authpb.Permission {
+	if m != nil {
+		return m.Perm
+	}
+	return nil
+}
+
 type AuthRoleRevokeRequest struct {
 }
 
@@ -4724,6 +4736,22 @@ func (m *AuthRoleGrantRequest) MarshalTo(data []byte) (int, error) {
 	_ = i
 	var l int
 	_ = l
+	if len(m.Name) > 0 {
+		data[i] = 0xa
+		i++
+		i = encodeVarintRpc(data, i, uint64(len(m.Name)))
+		i += copy(data[i:], m.Name)
+	}
+	if m.Perm != nil {
+		data[i] = 0x12
+		i++
+		i = encodeVarintRpc(data, i, uint64(m.Perm.Size()))
+		n31, err := m.Perm.MarshalTo(data[i:])
+		if err != nil {
+			return 0, err
+		}
+		i += n31
+	}
 	return i, nil
 }
 
@@ -4764,11 +4792,11 @@ func (m *AuthEnableResponse) MarshalTo(data []byte) (int, error) {
 		data[i] = 0xa
 		i++
 		i = encodeVarintRpc(data, i, uint64(m.Header.Size()))
-		n31, err := m.Header.MarshalTo(data[i:])
+		n32, err := m.Header.MarshalTo(data[i:])
 		if err != nil {
 			return 0, err
 		}
-		i += n31
+		i += n32
 	}
 	return i, nil
 }
@@ -4792,11 +4820,11 @@ func (m *AuthDisableResponse) MarshalTo(data []byte) (int, error) {
 		data[i] = 0xa
 		i++
 		i = encodeVarintRpc(data, i, uint64(m.Header.Size()))
-		n32, err := m.Header.MarshalTo(data[i:])
+		n33, err := m.Header.MarshalTo(data[i:])
 		if err != nil {
 			return 0, err
 		}
-		i += n32
+		i += n33
 	}
 	return i, nil
 }
@@ -4820,11 +4848,11 @@ func (m *AuthenticateResponse) MarshalTo(data []byte) (int, error) {
 		data[i] = 0xa
 		i++
 		i = encodeVarintRpc(data, i, uint64(m.Header.Size()))
-		n33, err := m.Header.MarshalTo(data[i:])
+		n34, err := m.Header.MarshalTo(data[i:])
 		if err != nil {
 			return 0, err
 		}
-		i += n33
+		i += n34
 	}
 	return i, nil
 }
@@ -4848,11 +4876,11 @@ func (m *AuthUserAddResponse) MarshalTo(data []byte) (int, error) {
 		data[i] = 0xa
 		i++
 		i = encodeVarintRpc(data, i, uint64(m.Header.Size()))
-		n34, err := m.Header.MarshalTo(data[i:])
+		n35, err := m.Header.MarshalTo(data[i:])
 		if err != nil {
 			return 0, err
 		}
-		i += n34
+		i += n35
 	}
 	return i, nil
 }
@@ -4876,11 +4904,11 @@ func (m *AuthUserGetResponse) MarshalTo(data []byte) (int, error) {
 		data[i] = 0xa
 		i++
 		i = encodeVarintRpc(data, i, uint64(m.Header.Size()))
-		n35, err := m.Header.MarshalTo(data[i:])
+		n36, err := m.Header.MarshalTo(data[i:])
 		if err != nil {
 			return 0, err
 		}
-		i += n35
+		i += n36
 	}
 	return i, nil
 }
@@ -4904,11 +4932,11 @@ func (m *AuthUserDeleteResponse) MarshalTo(data []byte) (int, error) {
 		data[i] = 0xa
 		i++
 		i = encodeVarintRpc(data, i, uint64(m.Header.Size()))
-		n36, err := m.Header.MarshalTo(data[i:])
+		n37, err := m.Header.MarshalTo(data[i:])
 		if err != nil {
 			return 0, err
 		}
-		i += n36
+		i += n37
 	}
 	return i, nil
 }
@@ -4932,11 +4960,11 @@ func (m *AuthUserChangePasswordResponse) MarshalTo(data []byte) (int, error) {
 		data[i] = 0xa
 		i++
 		i = encodeVarintRpc(data, i, uint64(m.Header.Size()))
-		n37, err := m.Header.MarshalTo(data[i:])
+		n38, err := m.Header.MarshalTo(data[i:])
 		if err != nil {
 			return 0, err
 		}
-		i += n37
+		i += n38
 	}
 	return i, nil
 }
@@ -4960,11 +4988,11 @@ func (m *AuthUserGrantResponse) MarshalTo(data []byte) (int, error) {
 		data[i] = 0xa
 		i++
 		i = encodeVarintRpc(data, i, uint64(m.Header.Size()))
-		n38, err := m.Header.MarshalTo(data[i:])
+		n39, err := m.Header.MarshalTo(data[i:])
 		if err != nil {
 			return 0, err
 		}
-		i += n38
+		i += n39
 	}
 	return i, nil
 }
@@ -4988,11 +5016,11 @@ func (m *AuthUserRevokeResponse) MarshalTo(data []byte) (int, error) {
 		data[i] = 0xa
 		i++
 		i = encodeVarintRpc(data, i, uint64(m.Header.Size()))
-		n39, err := m.Header.MarshalTo(data[i:])
+		n40, err := m.Header.MarshalTo(data[i:])
 		if err != nil {
 			return 0, err
 		}
-		i += n39
+		i += n40
 	}
 	return i, nil
 }
@@ -5016,11 +5044,11 @@ func (m *AuthRoleAddResponse) MarshalTo(data []byte) (int, error) {
 		data[i] = 0xa
 		i++
 		i = encodeVarintRpc(data, i, uint64(m.Header.Size()))
-		n40, err := m.Header.MarshalTo(data[i:])
+		n41, err := m.Header.MarshalTo(data[i:])
 		if err != nil {
 			return 0, err
 		}
-		i += n40
+		i += n41
 	}
 	return i, nil
 }
@@ -5044,11 +5072,11 @@ func (m *AuthRoleGetResponse) MarshalTo(data []byte) (int, error) {
 		data[i] = 0xa
 		i++
 		i = encodeVarintRpc(data, i, uint64(m.Header.Size()))
-		n41, err := m.Header.MarshalTo(data[i:])
+		n42, err := m.Header.MarshalTo(data[i:])
 		if err != nil {
 			return 0, err
 		}
-		i += n41
+		i += n42
 	}
 	return i, nil
 }
@@ -5072,11 +5100,11 @@ func (m *AuthRoleDeleteResponse) MarshalTo(data []byte) (int, error) {
 		data[i] = 0xa
 		i++
 		i = encodeVarintRpc(data, i, uint64(m.Header.Size()))
-		n42, err := m.Header.MarshalTo(data[i:])
+		n43, err := m.Header.MarshalTo(data[i:])
 		if err != nil {
 			return 0, err
 		}
-		i += n42
+		i += n43
 	}
 	return i, nil
 }
@@ -5100,11 +5128,11 @@ func (m *AuthRoleGrantResponse) MarshalTo(data []byte) (int, error) {
 		data[i] = 0xa
 		i++
 		i = encodeVarintRpc(data, i, uint64(m.Header.Size()))
-		n43, err := m.Header.MarshalTo(data[i:])
+		n44, err := m.Header.MarshalTo(data[i:])
 		if err != nil {
 			return 0, err
 		}
-		i += n43
+		i += n44
 	}
 	return i, nil
 }
@@ -5128,11 +5156,11 @@ func (m *AuthRoleRevokeResponse) MarshalTo(data []byte) (int, error) {
 		data[i] = 0xa
 		i++
 		i = encodeVarintRpc(data, i, uint64(m.Header.Size()))
-		n44, err := m.Header.MarshalTo(data[i:])
+		n45, err := m.Header.MarshalTo(data[i:])
 		if err != nil {
 			return 0, err
 		}
-		i += n44
+		i += n45
 	}
 	return i, nil
 }
@@ -5962,6 +5990,14 @@ func (m *AuthRoleDeleteRequest) Size() (n int) {
 func (m *AuthRoleGrantRequest) Size() (n int) {
 	var l int
 	_ = l
+	l = len(m.Name)
+	if l > 0 {
+		n += 1 + l + sovRpc(uint64(l))
+	}
+	if m.Perm != nil {
+		l = m.Perm.Size()
+		n += 1 + l + sovRpc(uint64(l))
+	}
 	return n
 }
 
@@ -11503,6 +11539,68 @@ func (m *AuthRoleGrantRequest) Unmarshal(data []byte) error {
 			return fmt.Errorf("proto: AuthRoleGrantRequest: illegal tag %d (wire type %d)", fieldNum, wire)
 		}
 		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Name", 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.Name = string(data[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Perm", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowRpc
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := data[iNdEx]
+				iNdEx++
+				msglen |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthRpc
+			}
+			postIndex := iNdEx + msglen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.Perm == nil {
+				m.Perm = &authpb.Permission{}
+			}
+			if err := m.Perm.Unmarshal(data[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
 		default:
 			iNdEx = preIndex
 			skippy, err := skipRpc(data[iNdEx:])

+ 3 - 0
etcdserver/etcdserverpb/rpc.proto

@@ -3,6 +3,7 @@ package etcdserverpb;
 
 import "gogoproto/gogo.proto";
 import "etcd/storage/storagepb/kv.proto";
+import "etcd/auth/authpb/auth.proto";
 
 option (gogoproto.marshaler_all) = true;
 option (gogoproto.unmarshaler_all) = true;
@@ -525,6 +526,8 @@ message AuthRoleDeleteRequest {
 }
 
 message AuthRoleGrantRequest {
+  string name = 1;
+  authpb.Permission perm = 2;
 }
 
 message AuthRoleRevokeRequest {

+ 9 - 0
etcdserver/v3demo_server.go

@@ -57,6 +57,7 @@ type Authenticator interface {
 	UserDelete(ctx context.Context, r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error)
 	UserChangePassword(ctx context.Context, r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error)
 	RoleAdd(ctx context.Context, r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error)
+	RoleGrant(ctx context.Context, r *pb.AuthRoleGrantRequest) (*pb.AuthRoleGrantResponse, error)
 }
 
 func (s *EtcdServer) Range(ctx context.Context, r *pb.RangeRequest) (*pb.RangeResponse, error) {
@@ -242,6 +243,14 @@ func (s *EtcdServer) RoleAdd(ctx context.Context, r *pb.AuthRoleAddRequest) (*pb
 	return result.resp.(*pb.AuthRoleAddResponse), result.err
 }
 
+func (s *EtcdServer) RoleGrant(ctx context.Context, r *pb.AuthRoleGrantRequest) (*pb.AuthRoleGrantResponse, error) {
+	result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthRoleGrant: r})
+	if err != nil {
+		return nil, err
+	}
+	return result.resp.(*pb.AuthRoleGrantResponse), result.err
+}
+
 func (s *EtcdServer) processInternalRaftRequest(ctx context.Context, r pb.InternalRaftRequest) (*applyResult, error) {
 	r.ID = s.reqIDGen.Next()