瀏覽代碼

*: support changing password in v3 auth

This commit adds a functionality for updating password of existing
users.
Hitoshi Mitake 9 年之前
父節點
當前提交
73166b41e9

+ 39 - 0
auth/store.go

@@ -47,6 +47,9 @@ type AuthStore interface {
 
 	// UserDelete deletes a user
 	UserDelete(r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error)
+
+	// UserChangePassword changes a password of a user
+	UserChangePassword(r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error)
 }
 
 type authStore struct {
@@ -124,6 +127,42 @@ func (as *authStore) UserDelete(r *pb.AuthUserDeleteRequest) (*pb.AuthUserDelete
 	return &pb.AuthUserDeleteResponse{}, nil
 }
 
+func (as *authStore) UserChangePassword(r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) {
+	// TODO(mitake): measure the cost of bcrypt.GenerateFromPassword()
+	// If the cost is too high, we should move the encryption to outside of the raft
+	hashed, err := bcrypt.GenerateFromPassword([]byte(r.Password), bcrypt.DefaultCost)
+	if err != nil {
+		plog.Errorf("failed to hash password: %s", err)
+		return nil, err
+	}
+
+	tx := as.be.BatchTx()
+	tx.Lock()
+	defer tx.Unlock()
+
+	_, vs := tx.UnsafeRange(authUsersBucketName, []byte(r.Name), nil, 0)
+	if len(vs) != 1 {
+		return &pb.AuthUserChangePasswordResponse{}, ErrUserNotFound
+	}
+
+	updatedUser := authpb.User{
+		Name:     []byte(r.Name),
+		Password: hashed,
+	}
+
+	marshaledUser, merr := updatedUser.Marshal()
+	if merr != nil {
+		plog.Errorf("failed to marshal a new user data: %s", merr)
+		return nil, merr
+	}
+
+	tx.UnsafePut(authUsersBucketName, []byte(r.Name), marshaledUser)
+
+	plog.Noticef("changed a password of a user: %s", r.Name)
+
+	return &pb.AuthUserChangePasswordResponse{}, nil
+}
+
 func NewAuthStore(be backend.Backend) *authStore {
 	tx := be.BatchTx()
 	tx.Lock()

+ 12 - 3
clientv3/auth.go

@@ -21,9 +21,10 @@ import (
 )
 
 type (
-	AuthEnableResponse     pb.AuthEnableResponse
-	AuthUserAddResponse    pb.AuthUserAddResponse
-	AuthUserDeleteResponse pb.AuthUserDeleteResponse
+	AuthEnableResponse             pb.AuthEnableResponse
+	AuthUserAddResponse            pb.AuthUserAddResponse
+	AuthUserDeleteResponse         pb.AuthUserDeleteResponse
+	AuthUserChangePasswordResponse pb.AuthUserChangePasswordResponse
 )
 
 type Auth interface {
@@ -35,6 +36,9 @@ type Auth interface {
 
 	// UserDelete deletes a user from an etcd cluster.
 	UserDelete(ctx context.Context, name string) (*AuthUserDeleteResponse, error)
+
+	// UserChangePassword changes a password of a user.
+	UserChangePassword(ctx context.Context, name string, password string) (*AuthUserChangePasswordResponse, error)
 }
 
 type auth struct {
@@ -67,3 +71,8 @@ func (auth *auth) UserDelete(ctx context.Context, name string) (*AuthUserDeleteR
 	resp, err := auth.remote.UserDelete(ctx, &pb.AuthUserDeleteRequest{Name: name})
 	return (*AuthUserDeleteResponse)(resp), err
 }
+
+func (auth *auth) UserChangePassword(ctx context.Context, name string, password string) (*AuthUserChangePasswordResponse, error) {
+	resp, err := auth.remote.UserChangePassword(ctx, &pb.AuthUserChangePasswordRequest{Name: name, Password: password})
+	return (*AuthUserChangePasswordResponse)(resp), err
+}

+ 60 - 20
etcdctl/ctlv3/command/user_command.go

@@ -32,6 +32,7 @@ func NewUserCommand() *cobra.Command {
 
 	ac.AddCommand(NewUserAddCommand())
 	ac.AddCommand(NewUserDeleteCommand())
+	ac.AddCommand(NewUserChangePasswordCommand())
 
 	return ac
 }
@@ -60,6 +61,18 @@ func NewUserDeleteCommand() *cobra.Command {
 	}
 }
 
+func NewUserChangePasswordCommand() *cobra.Command {
+	cmd := cobra.Command{
+		Use:   "passwd <user name>",
+		Short: "change password of user",
+		Run:   userChangePasswordCommandFunc,
+	}
+
+	cmd.Flags().BoolVar(&passwordInteractive, "interactive", true, "read password from stdin instead of interactive terminal")
+
+	return &cmd
+}
+
 // userAddCommandFunc executes the "user add" command.
 func userAddCommandFunc(cmd *cobra.Command, args []string) {
 	if len(args) != 1 {
@@ -71,26 +84,7 @@ func userAddCommandFunc(cmd *cobra.Command, args []string) {
 	if !passwordInteractive {
 		fmt.Scanf("%s", &password)
 	} else {
-		prompt1 := fmt.Sprintf("Password of %s: ", args[0])
-		password1, err1 := speakeasy.Ask(prompt1)
-		if err1 != nil {
-			ExitWithError(ExitBadArgs, fmt.Errorf("failed to ask password: %s.", err1))
-		}
-
-		if len(password1) == 0 {
-			ExitWithError(ExitBadArgs, fmt.Errorf("empty password"))
-		}
-
-		prompt2 := fmt.Sprintf("Type password of %s again for confirmation: ", args[0])
-		password2, err2 := speakeasy.Ask(prompt2)
-		if err2 != nil {
-			ExitWithError(ExitBadArgs, fmt.Errorf("failed to ask password: %s.", err2))
-		}
-
-		if strings.Compare(password1, password2) != 0 {
-			ExitWithError(ExitBadArgs, fmt.Errorf("given passwords are different."))
-		}
-		password = password1
+		password = readPasswordInteractive(args[0])
 	}
 
 	_, err := mustClientFromCmd(cmd).Auth.UserAdd(context.TODO(), args[0], password)
@@ -114,3 +108,49 @@ func userDeleteCommandFunc(cmd *cobra.Command, args []string) {
 
 	fmt.Printf("User %s deleted\n", args[0])
 }
+
+// userChangePasswordCommandFunc executes the "user passwd" command.
+func userChangePasswordCommandFunc(cmd *cobra.Command, args []string) {
+	if len(args) != 1 {
+		ExitWithError(ExitBadArgs, fmt.Errorf("user passwd command requires user name as its argument."))
+	}
+
+	var password string
+
+	if !passwordInteractive {
+		fmt.Scanf("%s", &password)
+	} else {
+		password = readPasswordInteractive(args[0])
+	}
+
+	_, err := mustClientFromCmd(cmd).Auth.UserChangePassword(context.TODO(), args[0], password)
+	if err != nil {
+		ExitWithError(ExitError, err)
+	}
+
+	fmt.Println("Password updated")
+}
+
+func readPasswordInteractive(name string) string {
+	prompt1 := fmt.Sprintf("Password of %s: ", name)
+	password1, err1 := speakeasy.Ask(prompt1)
+	if err1 != nil {
+		ExitWithError(ExitBadArgs, fmt.Errorf("failed to ask password: %s.", err1))
+	}
+
+	if len(password1) == 0 {
+		ExitWithError(ExitBadArgs, fmt.Errorf("empty password"))
+	}
+
+	prompt2 := fmt.Sprintf("Type password of %s again for confirmation: ", name)
+	password2, err2 := speakeasy.Ask(prompt2)
+	if err2 != nil {
+		ExitWithError(ExitBadArgs, fmt.Errorf("failed to ask password: %s.", err2))
+	}
+
+	if strings.Compare(password1, password2) != 0 {
+		ExitWithError(ExitBadArgs, fmt.Errorf("given passwords are different."))
+	}
+
+	return password1
+}

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

@@ -103,6 +103,9 @@ func (as *AuthServer) UserRevoke(ctx context.Context, r *pb.AuthUserRevokeReques
 }
 
 func (as *AuthServer) UserChangePassword(ctx context.Context, r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) {
-	plog.Info("not implemented yet")
-	return nil, nil
+	resp, err := as.authenticator.UserChangePassword(ctx, r)
+	if err != nil {
+		return nil, togRPCError(err)
+	}
+	return resp, nil
 }

+ 7 - 0
etcdserver/apply.go

@@ -56,6 +56,7 @@ type applierV3 interface {
 	AuthEnable() (*pb.AuthEnableResponse, error)
 	UserAdd(ua *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error)
 	UserDelete(ua *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error)
+	UserChangePassword(ua *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error)
 }
 
 type applierV3backend struct {
@@ -87,6 +88,8 @@ func (s *EtcdServer) applyV3Request(r *pb.InternalRaftRequest) *applyResult {
 		ar.resp, ar.err = s.applyV3.UserAdd(r.AuthUserAdd)
 	case r.AuthUserDelete != nil:
 		ar.resp, ar.err = s.applyV3.UserDelete(r.AuthUserDelete)
+	case r.AuthUserChangePassword != nil:
+		ar.resp, ar.err = s.applyV3.UserChangePassword(r.AuthUserChangePassword)
 	default:
 		panic("not implemented")
 	}
@@ -482,6 +485,10 @@ func (a *applierV3backend) UserDelete(r *pb.AuthUserDeleteRequest) (*pb.AuthUser
 	return a.s.AuthStore().UserDelete(r)
 }
 
+func (a *applierV3backend) UserChangePassword(r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) {
+	return a.s.AuthStore().UserChangePassword(r)
+}
+
 type quotaApplierV3 struct {
 	applierV3
 	q Quota

+ 64 - 16
etcdserver/etcdserverpb/raft_internal.pb.go

@@ -22,19 +22,20 @@ 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"`
-	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"`
+	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"`
+	AuthUserChangePassword *AuthUserChangePasswordRequest `protobuf:"bytes,13,opt,name=auth_user_change_password" json:"auth_user_change_password,omitempty"`
+	Alarm                  *AlarmRequest                  `protobuf:"bytes,14,opt,name=alarm" json:"alarm,omitempty"`
 }
 
 func (m *InternalRaftRequest) Reset()         { *m = InternalRaftRequest{} }
@@ -182,16 +183,26 @@ func (m *InternalRaftRequest) MarshalTo(data []byte) (int, error) {
 		}
 		i += n11
 	}
-	if m.Alarm != nil {
+	if m.AuthUserChangePassword != nil {
 		data[i] = 0x6a
 		i++
-		i = encodeVarintRaftInternal(data, i, uint64(m.Alarm.Size()))
-		n12, err := m.Alarm.MarshalTo(data[i:])
+		i = encodeVarintRaftInternal(data, i, uint64(m.AuthUserChangePassword.Size()))
+		n12, err := m.AuthUserChangePassword.MarshalTo(data[i:])
 		if err != nil {
 			return 0, err
 		}
 		i += n12
 	}
+	if m.Alarm != nil {
+		data[i] = 0x72
+		i++
+		i = encodeVarintRaftInternal(data, i, uint64(m.Alarm.Size()))
+		n13, err := m.Alarm.MarshalTo(data[i:])
+		if err != nil {
+			return 0, err
+		}
+		i += n13
+	}
 	return i, nil
 }
 
@@ -290,6 +301,10 @@ func (m *InternalRaftRequest) Size() (n int) {
 		l = m.AuthUserDelete.Size()
 		n += 1 + l + sovRaftInternal(uint64(l))
 	}
+	if m.AuthUserChangePassword != nil {
+		l = m.AuthUserChangePassword.Size()
+		n += 1 + l + sovRaftInternal(uint64(l))
+	}
 	if m.Alarm != nil {
 		l = m.Alarm.Size()
 		n += 1 + l + sovRaftInternal(uint64(l))
@@ -728,6 +743,39 @@ func (m *InternalRaftRequest) Unmarshal(data []byte) error {
 			}
 			iNdEx = postIndex
 		case 13:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field AuthUserChangePassword", 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.AuthUserChangePassword == nil {
+				m.AuthUserChangePassword = &AuthUserChangePasswordRequest{}
+			}
+			if err := m.AuthUserChangePassword.Unmarshal(data[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 14:
 			if wireType != 2 {
 				return fmt.Errorf("proto: wrong wireType = %d for field Alarm", wireType)
 			}

+ 2 - 1
etcdserver/etcdserverpb/raft_internal.proto

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

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

@@ -1295,6 +1295,8 @@ func (m *AuthUserDeleteRequest) String() string { return proto.CompactTextString
 func (*AuthUserDeleteRequest) ProtoMessage()    {}
 
 type AuthUserChangePasswordRequest struct {
+	Name     string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
 }
 
 func (m *AuthUserChangePasswordRequest) Reset()         { *m = AuthUserChangePasswordRequest{} }
@@ -4490,6 +4492,18 @@ func (m *AuthUserChangePasswordRequest) 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 len(m.Password) > 0 {
+		data[i] = 0x12
+		i++
+		i = encodeVarintRpc(data, i, uint64(len(m.Password)))
+		i += copy(data[i:], m.Password)
+	}
 	return i, nil
 }
 
@@ -5768,6 +5782,14 @@ func (m *AuthUserDeleteRequest) Size() (n int) {
 func (m *AuthUserChangePasswordRequest) Size() (n int) {
 	var l int
 	_ = l
+	l = len(m.Name)
+	if l > 0 {
+		n += 1 + l + sovRpc(uint64(l))
+	}
+	l = len(m.Password)
+	if l > 0 {
+		n += 1 + l + sovRpc(uint64(l))
+	}
 	return n
 }
 
@@ -10796,6 +10818,64 @@ func (m *AuthUserChangePasswordRequest) Unmarshal(data []byte) error {
 			return fmt.Errorf("proto: AuthUserChangePasswordRequest: 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 Password", 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.Password = string(data[iNdEx:postIndex])
+			iNdEx = postIndex
 		default:
 			iNdEx = preIndex
 			skippy, err := skipRpc(data[iNdEx:])

+ 2 - 0
etcdserver/etcdserverpb/rpc.proto

@@ -493,6 +493,8 @@ message AuthUserDeleteRequest {
 }
 
 message AuthUserChangePasswordRequest {
+  string name = 1;
+  string password = 2;
 }
 
 message AuthUserGrantRequest {

+ 9 - 0
etcdserver/v3demo_server.go

@@ -55,6 +55,7 @@ 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)
+	UserChangePassword(ctx context.Context, r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error)
 }
 
 func (s *EtcdServer) Range(ctx context.Context, r *pb.RangeRequest) (*pb.RangeResponse, error) {
@@ -200,6 +201,14 @@ func (s *EtcdServer) UserDelete(ctx context.Context, r *pb.AuthUserDeleteRequest
 	return result.resp.(*pb.AuthUserDeleteResponse), result.err
 }
 
+func (s *EtcdServer) UserChangePassword(ctx context.Context, r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) {
+	result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthUserChangePassword: r})
+	if err != nil {
+		return nil, err
+	}
+	return result.resp.(*pb.AuthUserChangePasswordResponse), result.err
+}
+
 func (s *EtcdServer) processInternalRaftRequest(ctx context.Context, r pb.InternalRaftRequest) (*applyResult, error) {
 	r.ID = s.reqIDGen.Next()