瀏覽代碼

*: support authenticate in v3 auth

This commit implements Authenticate() API of the auth package. It does
authentication based on its authUsers bucket and generate a token for
succeeding RPCs.
Hitoshi Mitake 9 年之前
父節點
當前提交
131e3806bb

+ 71 - 0
auth/simple_token.go

@@ -0,0 +1,71 @@
+// Copyright 2016 Nippon Telegraph and Telephone Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package auth
+
+// CAUTION: This randum number based token mechanism is only for testing purpose.
+// JWT based mechanism will be added in the near future.
+
+import (
+	"crypto/rand"
+	"math/big"
+)
+
+const (
+	letters                  = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+	defaultSimpleTokenLength = 16
+)
+
+var (
+	simpleTokens map[string]string // token -> user ID
+)
+
+func init() {
+	simpleTokens = make(map[string]string)
+}
+
+func genSimpleToken() (string, error) {
+	ret := make([]byte, defaultSimpleTokenLength)
+
+	for i := 0; i < defaultSimpleTokenLength; i++ {
+		bInt, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
+		if err != nil {
+			return "", err
+		}
+
+		ret[i] = letters[bInt.Int64()]
+	}
+
+	return string(ret), nil
+}
+
+func genSimpleTokenForUser(userID string) (string, error) {
+	var token string
+	var err error
+
+	for {
+		// generating random numbers in RSM would't a good idea
+		token, err = genSimpleToken()
+		if err != nil {
+			return "", err
+		}
+
+		if _, ok := simpleTokens[token]; !ok {
+			break
+		}
+	}
+
+	simpleTokens[token] = userID
+	return token, nil
+}

+ 39 - 0
auth/store.go

@@ -40,12 +40,19 @@ var (
 	ErrUserNotFound     = errors.New("auth: user not found")
 	ErrRoleAlreadyExist = errors.New("auth: role already exists")
 	ErrRoleNotFound     = errors.New("auth: role not found")
+	ErrAuthFailed       = errors.New("auth: authentication failed, invalid user ID or password")
 )
 
 type AuthStore interface {
 	// AuthEnable() turns on the authentication feature
 	AuthEnable()
 
+	// Authenticate() does authentication based on given user name and password,
+	// and returns a token for successful case.
+	// Note that the generated token is valid only for the member the client
+	// connected to within fixed time duration. Reauth is required after the duration.
+	Authenticate(name string, password string) (*pb.AuthenticateResponse, error)
+
 	// Recover recovers the state of auth store from the given backend
 	Recover(b backend.Backend)
 
@@ -85,6 +92,38 @@ func (as *authStore) AuthEnable() {
 	plog.Noticef("Authentication enabled")
 }
 
+func (as *authStore) Authenticate(name string, password string) (*pb.AuthenticateResponse, error) {
+	tx := as.be.BatchTx()
+	tx.Lock()
+	defer tx.Unlock()
+
+	_, vs := tx.UnsafeRange(authUsersBucketName, []byte(name), nil, 0)
+	if len(vs) != 1 {
+		plog.Noticef("authentication failed, user %s doesn't exist", name)
+		return &pb.AuthenticateResponse{}, ErrAuthFailed
+	}
+
+	user := &authpb.User{}
+	err := user.Unmarshal(vs[0])
+	if err != nil {
+		return nil, err
+	}
+
+	if bcrypt.CompareHashAndPassword(user.Password, []byte(password)) != nil {
+		plog.Noticef("authentication failed, invalid password for user %s", name)
+		return &pb.AuthenticateResponse{}, ErrAuthFailed
+	}
+
+	token, err := genSimpleTokenForUser(name)
+	if err != nil {
+		plog.Errorf("failed to generate simple token: %s", err)
+		return nil, err
+	}
+
+	plog.Infof("authorized %s, token is %s", name, token)
+	return &pb.AuthenticateResponse{Token: token}, nil
+}
+
 func (as *authStore) Recover(be backend.Backend) {
 	as.be = be
 	// TODO(mitake): recovery process

+ 9 - 0
clientv3/auth.go

@@ -26,6 +26,7 @@ import (
 
 type (
 	AuthEnableResponse             pb.AuthEnableResponse
+	AuthenticateResponse           pb.AuthenticateResponse
 	AuthUserAddResponse            pb.AuthUserAddResponse
 	AuthUserDeleteResponse         pb.AuthUserDeleteResponse
 	AuthUserChangePasswordResponse pb.AuthUserChangePasswordResponse
@@ -46,6 +47,9 @@ type Auth interface {
 	// AuthEnable enables auth of an etcd cluster.
 	AuthEnable(ctx context.Context) (*AuthEnableResponse, error)
 
+	// Authenticate does authenticate with given user name and password.
+	Authenticate(ctx context.Context, name string, password string) (*AuthenticateResponse, error)
+
 	// UserAdd adds a new user to an etcd cluster.
 	UserAdd(ctx context.Context, name string, password string) (*AuthUserAddResponse, error)
 
@@ -86,6 +90,11 @@ func (auth *auth) AuthEnable(ctx context.Context) (*AuthEnableResponse, error) {
 	return (*AuthEnableResponse)(resp), err
 }
 
+func (auth *auth) Authenticate(ctx context.Context, name string, password string) (*AuthenticateResponse, error) {
+	resp, err := auth.remote.Authenticate(ctx, &pb.AuthenticateRequest{Name: name, Password: password})
+	return (*AuthenticateResponse)(resp), err
+}
+
 func (auth *auth) UserAdd(ctx context.Context, name string, password string) (*AuthUserAddResponse, error) {
 	resp, err := auth.remote.UserAdd(ctx, &pb.AuthUserAddRequest{Name: name, Password: password})
 	return (*AuthUserAddResponse)(resp), err

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

@@ -42,8 +42,11 @@ func (as *AuthServer) AuthDisable(ctx context.Context, r *pb.AuthDisableRequest)
 }
 
 func (as *AuthServer) Authenticate(ctx context.Context, r *pb.AuthenticateRequest) (*pb.AuthenticateResponse, error) {
-	plog.Info("not implemented yet")
-	return nil, nil
+	resp, err := as.authenticator.Authenticate(ctx, r)
+	if err != nil {
+		return nil, togRPCError(err)
+	}
+	return resp, nil
 }
 
 func (as *AuthServer) RoleAdd(ctx context.Context, r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) {

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

@@ -41,4 +41,5 @@ var (
 	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")
+	ErrAuthFailed       = grpc.Errorf(codes.InvalidArgument, "etcdserver: authentication failed, invalid user ID or password")
 )

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

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

+ 7 - 0
etcdserver/apply.go

@@ -54,6 +54,7 @@ type applierV3 interface {
 	LeaseRevoke(lc *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error)
 	Alarm(*pb.AlarmRequest) (*pb.AlarmResponse, error)
 	AuthEnable() (*pb.AuthEnableResponse, error)
+	Authenticate(r *pb.AuthenticateRequest) (*pb.AuthenticateResponse, error)
 	UserAdd(ua *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error)
 	UserDelete(ua *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error)
 	UserChangePassword(ua *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error)
@@ -87,6 +88,8 @@ func (s *EtcdServer) applyV3Request(r *pb.InternalRaftRequest) *applyResult {
 		ar.resp, ar.err = s.applyV3.Alarm(r.Alarm)
 	case r.AuthEnable != nil:
 		ar.resp, ar.err = s.applyV3.AuthEnable()
+	case r.Authenticate != nil:
+		ar.resp, ar.err = s.applyV3.Authenticate(r.Authenticate)
 	case r.AuthUserAdd != nil:
 		ar.resp, ar.err = s.applyV3.UserAdd(r.AuthUserAdd)
 	case r.AuthUserDelete != nil:
@@ -490,6 +493,10 @@ func (a *applierV3backend) AuthEnable() (*pb.AuthEnableResponse, error) {
 	return &pb.AuthEnableResponse{}, nil
 }
 
+func (a *applierV3backend) Authenticate(r *pb.AuthenticateRequest) (*pb.AuthenticateResponse, error) {
+	return a.s.AuthStore().Authenticate(r.Name, r.Password)
+}
+
 func (a *applierV3backend) UserAdd(r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) {
 	return a.s.AuthStore().UserAdd(r)
 }

+ 54 - 4
etcdserver/etcdserverpb/raft_internal.pb.go

@@ -38,7 +38,8 @@ type InternalRaftRequest struct {
 	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"`
+	Authenticate           *AuthenticateRequest           `protobuf:"bytes,17,opt,name=authenticate" json:"authenticate,omitempty"`
+	Alarm                  *AlarmRequest                  `protobuf:"bytes,18,opt,name=alarm" json:"alarm,omitempty"`
 }
 
 func (m *InternalRaftRequest) Reset()         { *m = InternalRaftRequest{} }
@@ -228,18 +229,30 @@ func (m *InternalRaftRequest) MarshalTo(data []byte) (int, error) {
 		}
 		i += n15
 	}
-	if m.Alarm != nil {
+	if m.Authenticate != nil {
 		data[i] = 0x8a
 		i++
 		data[i] = 0x1
 		i++
-		i = encodeVarintRaftInternal(data, i, uint64(m.Alarm.Size()))
-		n16, err := m.Alarm.MarshalTo(data[i:])
+		i = encodeVarintRaftInternal(data, i, uint64(m.Authenticate.Size()))
+		n16, err := m.Authenticate.MarshalTo(data[i:])
 		if err != nil {
 			return 0, err
 		}
 		i += n16
 	}
+	if m.Alarm != nil {
+		data[i] = 0x92
+		i++
+		data[i] = 0x1
+		i++
+		i = encodeVarintRaftInternal(data, i, uint64(m.Alarm.Size()))
+		n17, err := m.Alarm.MarshalTo(data[i:])
+		if err != nil {
+			return 0, err
+		}
+		i += n17
+	}
 	return i, nil
 }
 
@@ -354,6 +367,10 @@ func (m *InternalRaftRequest) Size() (n int) {
 		l = m.AuthRoleGrant.Size()
 		n += 2 + l + sovRaftInternal(uint64(l))
 	}
+	if m.Authenticate != nil {
+		l = m.Authenticate.Size()
+		n += 2 + l + sovRaftInternal(uint64(l))
+	}
 	if m.Alarm != nil {
 		l = m.Alarm.Size()
 		n += 2 + l + sovRaftInternal(uint64(l))
@@ -924,6 +941,39 @@ func (m *InternalRaftRequest) Unmarshal(data []byte) error {
 			}
 			iNdEx = postIndex
 		case 17:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Authenticate", 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.Authenticate == nil {
+				m.Authenticate = &AuthenticateRequest{}
+			}
+			if err := m.Authenticate.Unmarshal(data[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 18:
 			if wireType != 2 {
 				return fmt.Errorf("proto: wrong wireType = %d for field Alarm", wireType)
 			}

+ 2 - 1
etcdserver/etcdserverpb/raft_internal.proto

@@ -32,8 +32,9 @@ message InternalRaftRequest {
   AuthUserGrantRequest auth_user_grant = 14;
   AuthRoleAddRequest auth_role_add = 15;
   AuthRoleGrantRequest auth_role_grant = 16;
+  AuthenticateRequest authenticate = 17;
 
-  AlarmRequest alarm = 17;
+  AlarmRequest alarm = 18;
 }
 
 message EmptyResponse {

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

@@ -1374,6 +1374,8 @@ func (m *AuthDisableRequest) String() string { return proto.CompactTextString(m)
 func (*AuthDisableRequest) ProtoMessage()    {}
 
 type AuthenticateRequest 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 *AuthenticateRequest) Reset()         { *m = AuthenticateRequest{} }
@@ -1514,6 +1516,8 @@ func (m *AuthDisableResponse) GetHeader() *ResponseHeader {
 
 type AuthenticateResponse struct {
 	Header *ResponseHeader `protobuf:"bytes,1,opt,name=header" json:"header,omitempty"`
+	// token is an authorized token that can be used in succeeding RPCs
+	Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"`
 }
 
 func (m *AuthenticateResponse) Reset()         { *m = AuthenticateResponse{} }
@@ -4755,6 +4759,18 @@ func (m *AuthenticateRequest) 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
 }
 
@@ -5101,6 +5117,12 @@ func (m *AuthenticateResponse) MarshalTo(data []byte) (int, error) {
 		}
 		i += n35
 	}
+	if len(m.Token) > 0 {
+		data[i] = 0x12
+		i++
+		i = encodeVarintRpc(data, i, uint64(len(m.Token)))
+		i += copy(data[i:], m.Token)
+	}
 	return i, nil
 }
 
@@ -6187,6 +6209,14 @@ func (m *AuthDisableRequest) Size() (n int) {
 func (m *AuthenticateRequest) 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
 }
 
@@ -6323,6 +6353,10 @@ func (m *AuthenticateResponse) Size() (n int) {
 		l = m.Header.Size()
 		n += 1 + l + sovRpc(uint64(l))
 	}
+	l = len(m.Token)
+	if l > 0 {
+		n += 1 + l + sovRpc(uint64(l))
+	}
 	return n
 }
 
@@ -11393,6 +11427,64 @@ func (m *AuthenticateRequest) Unmarshal(data []byte) error {
 			return fmt.Errorf("proto: AuthenticateRequest: 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:])
@@ -12486,6 +12578,35 @@ func (m *AuthenticateResponse) Unmarshal(data []byte) error {
 				return err
 			}
 			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Token", 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.Token = string(data[iNdEx:postIndex])
+			iNdEx = postIndex
 		default:
 			iNdEx = preIndex
 			skippy, err := skipRpc(data[iNdEx:])

+ 4 - 0
etcdserver/etcdserverpb/rpc.proto

@@ -559,6 +559,8 @@ message AuthDisableRequest {
 }
 
 message AuthenticateRequest {
+  string name = 1;
+  string password = 2;
 }
 
 message AuthUserAddRequest {
@@ -622,6 +624,8 @@ message AuthDisableResponse {
 
 message AuthenticateResponse {
   ResponseHeader header = 1;
+  // token is an authorized token that can be used in succeeding RPCs
+  string token = 2;
 }
 
 message AuthUserAddResponse {

+ 9 - 0
etcdserver/v3_server.go

@@ -53,6 +53,7 @@ type Lessor interface {
 
 type Authenticator interface {
 	AuthEnable(ctx context.Context, r *pb.AuthEnableRequest) (*pb.AuthEnableResponse, error)
+	Authenticate(ctx context.Context, r *pb.AuthenticateRequest) (*pb.AuthenticateResponse, 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)
@@ -212,6 +213,14 @@ func (s *EtcdServer) AuthEnable(ctx context.Context, r *pb.AuthEnableRequest) (*
 	return result.resp.(*pb.AuthEnableResponse), result.err
 }
 
+func (s *EtcdServer) Authenticate(ctx context.Context, r *pb.AuthenticateRequest) (*pb.AuthenticateResponse, error) {
+	result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{Authenticate: r})
+	if err != nil {
+		return nil, err
+	}
+	return result.resp.(*pb.AuthenticateResponse), result.err
+}
+
 func (s *EtcdServer) UserAdd(ctx context.Context, r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) {
 	result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthUserAdd: r})
 	if err != nil {