Jelajahi Sumber

*: support deleting user in v3 auth

This commit adds a functionality of user deletion. It can be invoked
with the new user delete command.

Example usage:
$ ETCDCTL_API=3 etcdctl user delete usr1
Hitoshi Mitake 9 tahun lalu
induk
melakukan
d8888ded12

+ 21 - 0
auth/store.go

@@ -32,6 +32,7 @@ var (
 	plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "auth")
 
 	ErrUserAlreadyExist = errors.New("auth: user already exists")
+	ErrUserNotFound     = errors.New("auth: user not found")
 )
 
 type AuthStore interface {
@@ -43,6 +44,9 @@ type AuthStore interface {
 
 	// UserAdd adds a new user
 	UserAdd(r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error)
+
+	// UserDelete deletes a user
+	UserDelete(r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error)
 }
 
 type authStore struct {
@@ -103,6 +107,23 @@ func (as *authStore) UserAdd(r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse,
 	return &pb.AuthUserAddResponse{}, nil
 }
 
+func (as *authStore) UserDelete(r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error) {
+	tx := as.be.BatchTx()
+	tx.Lock()
+	defer tx.Unlock()
+
+	_, vs := tx.UnsafeRange(authUsersBucketName, []byte(r.Name), nil, 0)
+	if len(vs) != 1 {
+		return &pb.AuthUserDeleteResponse{}, ErrUserNotFound
+	}
+
+	tx.UnsafeDelete(authUsersBucketName, []byte(r.Name))
+
+	plog.Noticef("deleted a user: %s", r.Name)
+
+	return &pb.AuthUserDeleteResponse{}, nil
+}
+
 func NewAuthStore(be backend.Backend) *authStore {
 	tx := be.BatchTx()
 	tx.Lock()

+ 11 - 2
clientv3/auth.go

@@ -21,8 +21,9 @@ import (
 )
 
 type (
-	AuthEnableResponse  pb.AuthEnableResponse
-	AuthUserAddResponse pb.AuthUserAddResponse
+	AuthEnableResponse     pb.AuthEnableResponse
+	AuthUserAddResponse    pb.AuthUserAddResponse
+	AuthUserDeleteResponse pb.AuthUserDeleteResponse
 )
 
 type Auth interface {
@@ -31,6 +32,9 @@ type Auth interface {
 
 	// UserAdd adds a new user to an etcd cluster.
 	UserAdd(ctx context.Context, name string, password string) (*AuthUserAddResponse, error)
+
+	// UserDelete deletes a user from an etcd cluster.
+	UserDelete(ctx context.Context, name string) (*AuthUserDeleteResponse, error)
 }
 
 type auth struct {
@@ -58,3 +62,8 @@ func (auth *auth) UserAdd(ctx context.Context, name string, password string) (*A
 	resp, err := auth.remote.UserAdd(ctx, &pb.AuthUserAddRequest{Name: name, Password: password})
 	return (*AuthUserAddResponse)(resp), err
 }
+
+func (auth *auth) UserDelete(ctx context.Context, name string) (*AuthUserDeleteResponse, error) {
+	resp, err := auth.remote.UserDelete(ctx, &pb.AuthUserDeleteRequest{Name: name})
+	return (*AuthUserDeleteResponse)(resp), err
+}

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

@@ -31,6 +31,7 @@ func NewUserCommand() *cobra.Command {
 	}
 
 	ac.AddCommand(NewUserAddCommand())
+	ac.AddCommand(NewUserDeleteCommand())
 
 	return ac
 }
@@ -51,6 +52,14 @@ func NewUserAddCommand() *cobra.Command {
 	return &cmd
 }
 
+func NewUserDeleteCommand() *cobra.Command {
+	return &cobra.Command{
+		Use:   "delete <user name>",
+		Short: "delete a user",
+		Run:   userDeleteCommandFunc,
+	}
+}
+
 // userAddCommandFunc executes the "user add" command.
 func userAddCommandFunc(cmd *cobra.Command, args []string) {
 	if len(args) != 1 {
@@ -89,3 +98,15 @@ func userAddCommandFunc(cmd *cobra.Command, args []string) {
 		ExitWithError(ExitError, err)
 	}
 }
+
+// userDeleteCommandFunc executes the "user delete" command.
+func userDeleteCommandFunc(cmd *cobra.Command, args []string) {
+	if len(args) != 1 {
+		ExitWithError(ExitBadArgs, fmt.Errorf("user delete command requires user name as its argument."))
+	}
+
+	_, err := mustClientFromCmd(cmd).Auth.UserDelete(context.TODO(), args[0])
+	if err != nil {
+		ExitWithError(ExitError, err)
+	}
+}

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

@@ -80,8 +80,11 @@ func (as *AuthServer) UserAdd(ctx context.Context, r *pb.AuthUserAddRequest) (*p
 }
 
 func (as *AuthServer) UserDelete(ctx context.Context, r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error) {
-	plog.Info("not implemented yet")
-	return nil, nil
+	resp, err := as.authenticator.UserDelete(ctx, r)
+	if err != nil {
+		return nil, togRPCError(err)
+	}
+	return resp, nil
 }
 
 func (as *AuthServer) UserGet(ctx context.Context, r *pb.AuthUserGetRequest) (*pb.AuthUserGetResponse, error) {

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

@@ -38,4 +38,5 @@ var (
 	ErrRequestTooLarge = grpc.Errorf(codes.InvalidArgument, "etcdserver: request is too large")
 
 	ErrUserAlreadyExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: user name already exists")
+	ErrUserNotFound     = grpc.Errorf(codes.FailedPrecondition, "etcdserver: user name not found")
 )

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

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

+ 7 - 0
etcdserver/apply.go

@@ -55,6 +55,7 @@ type applierV3 interface {
 	Alarm(*pb.AlarmRequest) (*pb.AlarmResponse, error)
 	AuthEnable() (*pb.AuthEnableResponse, error)
 	UserAdd(ua *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error)
+	UserDelete(ua *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error)
 }
 
 type applierV3backend struct {
@@ -84,6 +85,8 @@ func (s *EtcdServer) applyV3Request(r *pb.InternalRaftRequest) *applyResult {
 		ar.resp, ar.err = s.applyV3.AuthEnable()
 	case r.AuthUserAdd != nil:
 		ar.resp, ar.err = s.applyV3.UserAdd(r.AuthUserAdd)
+	case r.AuthUserDelete != nil:
+		ar.resp, ar.err = s.applyV3.UserDelete(r.AuthUserDelete)
 	default:
 		panic("not implemented")
 	}
@@ -475,6 +478,10 @@ func (a *applierV3backend) UserAdd(r *pb.AuthUserAddRequest) (*pb.AuthUserAddRes
 	return a.s.AuthStore().UserAdd(r)
 }
 
+func (a *applierV3backend) UserDelete(r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error) {
+	return a.s.AuthStore().UserDelete(r)
+}
+
 type quotaApplierV3 struct {
 	applierV3
 	q Quota

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

@@ -22,18 +22,19 @@ var _ = math.Inf
 // An InternalRaftRequest is the union of all requests which can be
 // sent via raft.
 type InternalRaftRequest struct {
-	ID          uint64              `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
-	V2          *Request            `protobuf:"bytes,2,opt,name=v2" json:"v2,omitempty"`
-	Range       *RangeRequest       `protobuf:"bytes,3,opt,name=range" json:"range,omitempty"`
-	Put         *PutRequest         `protobuf:"bytes,4,opt,name=put" json:"put,omitempty"`
-	DeleteRange *DeleteRangeRequest `protobuf:"bytes,5,opt,name=delete_range" json:"delete_range,omitempty"`
-	Txn         *TxnRequest         `protobuf:"bytes,6,opt,name=txn" json:"txn,omitempty"`
-	Compaction  *CompactionRequest  `protobuf:"bytes,7,opt,name=compaction" json:"compaction,omitempty"`
-	LeaseCreate *LeaseCreateRequest `protobuf:"bytes,8,opt,name=lease_create" json:"lease_create,omitempty"`
-	LeaseRevoke *LeaseRevokeRequest `protobuf:"bytes,9,opt,name=lease_revoke" json:"lease_revoke,omitempty"`
-	AuthEnable  *AuthEnableRequest  `protobuf:"bytes,10,opt,name=auth_enable" json:"auth_enable,omitempty"`
-	AuthUserAdd *AuthUserAddRequest `protobuf:"bytes,11,opt,name=auth_user_add" json:"auth_user_add,omitempty"`
-	Alarm       *AlarmRequest       `protobuf:"bytes,12,opt,name=alarm" json:"alarm,omitempty"`
+	ID             uint64                 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
+	V2             *Request               `protobuf:"bytes,2,opt,name=v2" json:"v2,omitempty"`
+	Range          *RangeRequest          `protobuf:"bytes,3,opt,name=range" json:"range,omitempty"`
+	Put            *PutRequest            `protobuf:"bytes,4,opt,name=put" json:"put,omitempty"`
+	DeleteRange    *DeleteRangeRequest    `protobuf:"bytes,5,opt,name=delete_range" json:"delete_range,omitempty"`
+	Txn            *TxnRequest            `protobuf:"bytes,6,opt,name=txn" json:"txn,omitempty"`
+	Compaction     *CompactionRequest     `protobuf:"bytes,7,opt,name=compaction" json:"compaction,omitempty"`
+	LeaseCreate    *LeaseCreateRequest    `protobuf:"bytes,8,opt,name=lease_create" json:"lease_create,omitempty"`
+	LeaseRevoke    *LeaseRevokeRequest    `protobuf:"bytes,9,opt,name=lease_revoke" json:"lease_revoke,omitempty"`
+	AuthEnable     *AuthEnableRequest     `protobuf:"bytes,10,opt,name=auth_enable" json:"auth_enable,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"`
+	Alarm          *AlarmRequest          `protobuf:"bytes,13,opt,name=alarm" json:"alarm,omitempty"`
 }
 
 func (m *InternalRaftRequest) Reset()         { *m = InternalRaftRequest{} }
@@ -171,16 +172,26 @@ func (m *InternalRaftRequest) MarshalTo(data []byte) (int, error) {
 		}
 		i += n10
 	}
-	if m.Alarm != nil {
+	if m.AuthUserDelete != nil {
 		data[i] = 0x62
 		i++
-		i = encodeVarintRaftInternal(data, i, uint64(m.Alarm.Size()))
-		n11, err := m.Alarm.MarshalTo(data[i:])
+		i = encodeVarintRaftInternal(data, i, uint64(m.AuthUserDelete.Size()))
+		n11, err := m.AuthUserDelete.MarshalTo(data[i:])
 		if err != nil {
 			return 0, err
 		}
 		i += n11
 	}
+	if m.Alarm != nil {
+		data[i] = 0x6a
+		i++
+		i = encodeVarintRaftInternal(data, i, uint64(m.Alarm.Size()))
+		n12, err := m.Alarm.MarshalTo(data[i:])
+		if err != nil {
+			return 0, err
+		}
+		i += n12
+	}
 	return i, nil
 }
 
@@ -275,6 +286,10 @@ func (m *InternalRaftRequest) Size() (n int) {
 		l = m.AuthUserAdd.Size()
 		n += 1 + l + sovRaftInternal(uint64(l))
 	}
+	if m.AuthUserDelete != nil {
+		l = m.AuthUserDelete.Size()
+		n += 1 + l + sovRaftInternal(uint64(l))
+	}
 	if m.Alarm != nil {
 		l = m.Alarm.Size()
 		n += 1 + l + sovRaftInternal(uint64(l))
@@ -680,6 +695,39 @@ func (m *InternalRaftRequest) Unmarshal(data []byte) error {
 			}
 			iNdEx = postIndex
 		case 12:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field AuthUserDelete", 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.AuthUserDelete == nil {
+				m.AuthUserDelete = &AuthUserDeleteRequest{}
+			}
+			if err := m.AuthUserDelete.Unmarshal(data[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 13:
 			if wireType != 2 {
 				return fmt.Errorf("proto: wrong wireType = %d for field Alarm", wireType)
 			}

+ 2 - 1
etcdserver/etcdserverpb/raft_internal.proto

@@ -27,8 +27,9 @@ message InternalRaftRequest {
 
   AuthEnableRequest auth_enable = 10;
   AuthUserAddRequest auth_user_add = 11;
+  AuthUserDeleteRequest auth_user_delete = 12;
 
-  AlarmRequest alarm = 12;
+  AlarmRequest alarm = 13;
 }
 
 message EmptyResponse {

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

@@ -1287,6 +1287,7 @@ func (m *AuthUserGetRequest) String() string { return proto.CompactTextString(m)
 func (*AuthUserGetRequest) ProtoMessage()    {}
 
 type AuthUserDeleteRequest struct {
+	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
 }
 
 func (m *AuthUserDeleteRequest) Reset()         { *m = AuthUserDeleteRequest{} }
@@ -4465,6 +4466,12 @@ func (m *AuthUserDeleteRequest) 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)
+	}
 	return i, nil
 }
 
@@ -5751,6 +5758,10 @@ func (m *AuthUserGetRequest) Size() (n int) {
 func (m *AuthUserDeleteRequest) Size() (n int) {
 	var l int
 	_ = l
+	l = len(m.Name)
+	if l > 0 {
+		n += 1 + l + sovRpc(uint64(l))
+	}
 	return n
 }
 
@@ -10706,6 +10717,35 @@ func (m *AuthUserDeleteRequest) Unmarshal(data []byte) error {
 			return fmt.Errorf("proto: AuthUserDeleteRequest: 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
 		default:
 			iNdEx = preIndex
 			skippy, err := skipRpc(data[iNdEx:])

+ 1 - 0
etcdserver/etcdserverpb/rpc.proto

@@ -489,6 +489,7 @@ message AuthUserGetRequest {
 }
 
 message AuthUserDeleteRequest {
+  string name = 1;
 }
 
 message AuthUserChangePasswordRequest {

+ 9 - 0
etcdserver/v3demo_server.go

@@ -54,6 +54,7 @@ type Lessor interface {
 type Authenticator interface {
 	AuthEnable(ctx context.Context, r *pb.AuthEnableRequest) (*pb.AuthEnableResponse, error)
 	UserAdd(ctx context.Context, r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error)
+	UserDelete(ctx context.Context, r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error)
 }
 
 func (s *EtcdServer) Range(ctx context.Context, r *pb.RangeRequest) (*pb.RangeResponse, error) {
@@ -191,6 +192,14 @@ func (s *EtcdServer) UserAdd(ctx context.Context, r *pb.AuthUserAddRequest) (*pb
 	return result.resp.(*pb.AuthUserAddResponse), result.err
 }
 
+func (s *EtcdServer) UserDelete(ctx context.Context, r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error) {
+	result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthUserDelete: r})
+	if err != nil {
+		return nil, err
+	}
+	return result.resp.(*pb.AuthUserDeleteResponse), result.err
+}
+
 func (s *EtcdServer) processInternalRaftRequest(ctx context.Context, r pb.InternalRaftRequest) (*applyResult, error) {
 	r.ID = s.reqIDGen.Next()