diff --git a/auth/authpb/auth.pb.go b/auth/authpb/auth.pb.go index 5149e07b0..00a5cfe64 100644 --- a/auth/authpb/auth.pb.go +++ b/auth/authpb/auth.pb.go @@ -55,8 +55,9 @@ func (x Permission_Type) String() string { // User is a single entry in the bucket authUsers 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{} } @@ -120,6 +121,21 @@ func (m *User) MarshalTo(data []byte) (int, error) { 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 } @@ -234,6 +250,12 @@ func (m *User) Size() (n int) { 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 } @@ -374,6 +396,35 @@ func (m *User) Unmarshal(data []byte) error { m.Password = []byte{} } 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: iNdEx = preIndex skippy, err := skipAuth(data[iNdEx:]) diff --git a/auth/authpb/auth.proto b/auth/authpb/auth.proto index a6e7c27f3..9308a179a 100644 --- a/auth/authpb/auth.proto +++ b/auth/authpb/auth.proto @@ -13,6 +13,7 @@ option (gogoproto.goproto_enum_prefix_all) = false; message User { bytes name = 1; bytes password = 2; + repeated string roles = 3; } // Permission is a single entity diff --git a/auth/store.go b/auth/store.go index 793d9ea18..2fe4eadd1 100644 --- a/auth/store.go +++ b/auth/store.go @@ -16,6 +16,7 @@ package auth import ( "errors" + "sort" "strings" "github.com/coreos/etcd/auth/authpb" @@ -56,6 +57,9 @@ type AuthStore interface { // UserChangePassword changes a password of a user 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(r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) @@ -174,6 +178,47 @@ func (as *authStore) UserChangePassword(r *pb.AuthUserChangePasswordRequest) (*p 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) { tx := as.be.BatchTx() tx.Lock() diff --git a/clientv3/auth.go b/clientv3/auth.go index b5334583c..e44ac0c67 100644 --- a/clientv3/auth.go +++ b/clientv3/auth.go @@ -29,6 +29,7 @@ type ( AuthUserAddResponse pb.AuthUserAddResponse AuthUserDeleteResponse pb.AuthUserDeleteResponse AuthUserChangePasswordResponse pb.AuthUserChangePasswordResponse + AuthUserGrantResponse pb.AuthUserGrantResponse AuthRoleAddResponse pb.AuthRoleAddResponse AuthRoleGrantResponse pb.AuthRoleGrantResponse @@ -54,6 +55,9 @@ type Auth interface { // UserChangePassword changes a password of a user. 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(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 } +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) { resp, err := auth.remote.RoleAdd(ctx, &pb.AuthRoleAddRequest{Name: name}) return (*AuthRoleAddResponse)(resp), err diff --git a/etcdctl/ctlv3/command/user_command.go b/etcdctl/ctlv3/command/user_command.go index 364562403..81c43b540 100644 --- a/etcdctl/ctlv3/command/user_command.go +++ b/etcdctl/ctlv3/command/user_command.go @@ -33,6 +33,7 @@ func NewUserCommand() *cobra.Command { ac.AddCommand(newUserAddCommand()) ac.AddCommand(newUserDeleteCommand()) ac.AddCommand(newUserChangePasswordCommand()) + ac.AddCommand(newUserGrantCommand()) return ac } @@ -73,6 +74,14 @@ func newUserChangePasswordCommand() *cobra.Command { return &cmd } +func newUserGrantCommand() *cobra.Command { + return &cobra.Command{ + Use: "grant ", + Short: "grant a role to a user", + Run: userGrantCommandFunc, + } +} + // userAddCommandFunc executes the "user add" command. func userAddCommandFunc(cmd *cobra.Command, args []string) { if len(args) != 1 { @@ -131,6 +140,20 @@ func userChangePasswordCommandFunc(cmd *cobra.Command, args []string) { 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 { prompt1 := fmt.Sprintf("Password of %s: ", name) password1, err1 := speakeasy.Ask(prompt1) diff --git a/etcdserver/api/v3rpc/auth.go b/etcdserver/api/v3rpc/auth.go index 9e8e30cd6..05a04532a 100644 --- a/etcdserver/api/v3rpc/auth.go +++ b/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) { - 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) { diff --git a/etcdserver/apply.go b/etcdserver/apply.go index 34ec27908..22344d0c0 100644 --- a/etcdserver/apply.go +++ b/etcdserver/apply.go @@ -57,6 +57,7 @@ type applierV3 interface { UserAdd(ua *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) UserDelete(ua *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error) UserChangePassword(ua *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) + UserGrant(ua *pb.AuthUserGrantRequest) (*pb.AuthUserGrantResponse, error) RoleAdd(ua *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, 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) case r.AuthUserChangePassword != nil: 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: ar.resp, ar.err = s.applyV3.RoleAdd(r.AuthRoleAdd) case r.AuthRoleGrant != nil: @@ -495,6 +498,10 @@ func (a *applierV3backend) UserChangePassword(r *pb.AuthUserChangePasswordReques 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) { return a.s.AuthStore().RoleAdd(r) } diff --git a/etcdserver/etcdserverpb/raft_internal.pb.go b/etcdserver/etcdserverpb/raft_internal.pb.go index 88e265aa0..0b3a5fed6 100644 --- a/etcdserver/etcdserverpb/raft_internal.pb.go +++ b/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"` 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"` - 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{} } @@ -195,38 +196,50 @@ func (m *InternalRaftRequest) MarshalTo(data []byte) (int, error) { } i += n12 } - if m.AuthRoleAdd != nil { + if m.AuthUserGrant != nil { data[i] = 0x72 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 { return 0, err } i += n13 } - if m.AuthRoleGrant != nil { + if m.AuthRoleAdd != nil { data[i] = 0x7a 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 { return 0, err } i += n14 } - if m.Alarm != nil { + if m.AuthRoleGrant != nil { data[i] = 0x82 i++ data[i] = 0x1 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 { return 0, err } 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 } @@ -329,13 +342,17 @@ func (m *InternalRaftRequest) Size() (n int) { l = m.AuthUserChangePassword.Size() n += 1 + l + sovRaftInternal(uint64(l)) } + if m.AuthUserGrant != nil { + l = m.AuthUserGrant.Size() + n += 1 + l + sovRaftInternal(uint64(l)) + } if m.AuthRoleAdd != nil { l = m.AuthRoleAdd.Size() n += 1 + l + sovRaftInternal(uint64(l)) } if m.AuthRoleGrant != nil { l = m.AuthRoleGrant.Size() - n += 1 + l + sovRaftInternal(uint64(l)) + n += 2 + l + sovRaftInternal(uint64(l)) } if m.Alarm != nil { l = m.Alarm.Size() @@ -808,6 +825,39 @@ func (m *InternalRaftRequest) Unmarshal(data []byte) error { } iNdEx = postIndex 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 { return fmt.Errorf("proto: wrong wireType = %d for field AuthRoleAdd", wireType) } @@ -840,7 +890,7 @@ func (m *InternalRaftRequest) Unmarshal(data []byte) error { return err } iNdEx = postIndex - case 15: + case 16: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field AuthRoleGrant", wireType) } @@ -873,7 +923,7 @@ func (m *InternalRaftRequest) Unmarshal(data []byte) error { return err } iNdEx = postIndex - case 16: + case 17: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Alarm", wireType) } diff --git a/etcdserver/etcdserverpb/raft_internal.proto b/etcdserver/etcdserverpb/raft_internal.proto index 2a6134bd2..2a6c9295d 100644 --- a/etcdserver/etcdserverpb/raft_internal.proto +++ b/etcdserver/etcdserverpb/raft_internal.proto @@ -29,10 +29,11 @@ message InternalRaftRequest { AuthUserAddRequest auth_user_add = 11; AuthUserDeleteRequest auth_user_delete = 12; 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 { diff --git a/etcdserver/etcdserverpb/rpc.pb.go b/etcdserver/etcdserverpb/rpc.pb.go index 7b574020a..c07034146 100644 --- a/etcdserver/etcdserverpb/rpc.pb.go +++ b/etcdserver/etcdserverpb/rpc.pb.go @@ -1329,6 +1329,8 @@ func (m *AuthUserChangePasswordRequest) String() string { return proto.CompactTe func (*AuthUserChangePasswordRequest) ProtoMessage() {} 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{} } @@ -4640,6 +4642,18 @@ func (m *AuthUserGrantRequest) MarshalTo(data []byte) (int, error) { _ = i var l int _ = 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 } @@ -5956,6 +5970,14 @@ func (m *AuthUserChangePasswordRequest) Size() (n int) { func (m *AuthUserGrantRequest) Size() (n int) { var l int _ = 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 } @@ -11260,6 +11282,64 @@ func (m *AuthUserGrantRequest) Unmarshal(data []byte) error { return fmt.Errorf("proto: AuthUserGrantRequest: illegal tag %d (wire type %d)", fieldNum, wire) } 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: iNdEx = preIndex skippy, err := skipRpc(data[iNdEx:]) diff --git a/etcdserver/etcdserverpb/rpc.proto b/etcdserver/etcdserverpb/rpc.proto index afd578480..a36cd19b7 100644 --- a/etcdserver/etcdserverpb/rpc.proto +++ b/etcdserver/etcdserverpb/rpc.proto @@ -510,6 +510,8 @@ message AuthUserChangePasswordRequest { } message AuthUserGrantRequest { + string user = 1; + string role = 2; } message AuthUserRevokeRequest { diff --git a/etcdserver/v3demo_server.go b/etcdserver/v3demo_server.go index f5f4d69c4..3c16f9f20 100644 --- a/etcdserver/v3demo_server.go +++ b/etcdserver/v3demo_server.go @@ -56,6 +56,7 @@ type Authenticator interface { UserAdd(ctx context.Context, r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) UserDelete(ctx context.Context, r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, 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) 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 } +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) { result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthRoleAdd: r}) if err != nil {