Browse Source

auth, etcdserver: make auth tokens consistent for all nodes

Currently auth tokens are generated in the replicated state machine
layer randomly. It means one auth token generated in node A cannot be
used for node B. It is problematic for load balancing and fail
over. This commit moves the token generation logic from the state
machine to API layer (before raft) and let all nodes share a single
token.

Log index of Raft is also added to a token for ensuring uniqueness of
the token and detecting activation of the token in the cluster (some
nodes can receive the token before generating and installing the token
in its state machine).

This commit also lets authStore have simple token related things. It
is required because of unit test. The test requires cleaning of the
state of the simple token things after one test (succeeding test can
create duplicated token and it causes panic).
Hitoshi Mitake 9 years ago
parent
commit
ead5096fa9

+ 3 - 1
Documentation/dev-guide/api_reference_v3.md

@@ -203,6 +203,7 @@ Empty field.
 | ----- | ----------- | ---- |
 | role |  | string |
 | key |  | string |
+| range_end |  | string |
 
 
 
@@ -755,8 +756,9 @@ Permission is a single entity
 
 | Field | Description | Type |
 | ----- | ----------- | ---- |
-| key |  | bytes |
 | permType |  | Type |
+| key |  | bytes |
+| range_end |  | bytes |
 
 
 

+ 8 - 29
auth/simple_token.go

@@ -20,7 +20,6 @@ package auth
 import (
 	"crypto/rand"
 	"math/big"
-	"sync"
 )
 
 const (
@@ -28,16 +27,7 @@ const (
 	defaultSimpleTokenLength = 16
 )
 
-var (
-	simpleTokensMu sync.RWMutex
-	simpleTokens   map[string]string // token -> username
-)
-
-func init() {
-	simpleTokens = make(map[string]string)
-}
-
-func genSimpleToken() (string, error) {
+func (as *authStore) GenSimpleToken() (string, error) {
 	ret := make([]byte, defaultSimpleTokenLength)
 
 	for i := 0; i < defaultSimpleTokenLength; i++ {
@@ -52,25 +42,14 @@ func genSimpleToken() (string, error) {
 	return string(ret), nil
 }
 
-func genSimpleTokenForUser(username 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
-		}
+func (as *authStore) assignSimpleTokenToUser(username, token string) {
+	as.simpleTokensMu.Lock()
 
-		if _, ok := simpleTokens[token]; !ok {
-			break
-		}
+	_, ok := as.simpleTokens[token]
+	if ok {
+		plog.Panicf("token %s is alredy used", token)
 	}
 
-	simpleTokensMu.Lock()
-	simpleTokens[token] = username
-	simpleTokensMu.Unlock()
-
-	return token, nil
+	as.simpleTokens[token] = username
+	as.simpleTokensMu.Unlock()
 }

+ 25 - 18
auth/store.go

@@ -17,6 +17,7 @@ package auth
 import (
 	"bytes"
 	"errors"
+	"fmt"
 	"sort"
 	"strings"
 	"sync"
@@ -26,6 +27,7 @@ import (
 	"github.com/coreos/etcd/mvcc/backend"
 	"github.com/coreos/pkg/capnslog"
 	"golang.org/x/crypto/bcrypt"
+	"golang.org/x/net/context"
 )
 
 var (
@@ -63,11 +65,8 @@ type AuthStore interface {
 	// AuthDisable turns off the authentication feature
 	AuthDisable()
 
-	// 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)
+	// Authenticate does authentication based on given user name and password
+	Authenticate(ctx context.Context, username, password string) (*pb.AuthenticateResponse, error)
 
 	// Recover recovers the state of auth store from the given backend
 	Recover(b backend.Backend)
@@ -116,6 +115,9 @@ type AuthStore interface {
 
 	// IsAdminPermitted checks admin permission of the user
 	IsAdminPermitted(username string) bool
+
+	// GenSimpleToken produces a simple random string
+	GenSimpleToken() (string, error)
 }
 
 type authStore struct {
@@ -124,6 +126,9 @@ type authStore struct {
 	enabledMu sync.RWMutex
 
 	rangePermCache map[string]*unifiedRangePermissions // username -> unifiedRangePermissions
+
+	simpleTokensMu sync.RWMutex
+	simpleTokens   map[string]string // token -> username
 }
 
 func (as *authStore) AuthEnable() error {
@@ -172,28 +177,29 @@ func (as *authStore) AuthDisable() {
 	plog.Noticef("Authentication disabled")
 }
 
-func (as *authStore) Authenticate(name string, password string) (*pb.AuthenticateResponse, error) {
+func (as *authStore) Authenticate(ctx context.Context, username, password string) (*pb.AuthenticateResponse, error) {
+	// TODO(mitake): after adding jwt support, branching based on values of ctx is required
+	index := ctx.Value("index").(uint64)
+	simpleToken := ctx.Value("simpleToken").(string)
+
 	tx := as.be.BatchTx()
 	tx.Lock()
 	defer tx.Unlock()
 
-	user := getUser(tx, name)
+	user := getUser(tx, username)
 	if user == nil {
 		return nil, ErrAuthFailed
 	}
 
 	if bcrypt.CompareHashAndPassword(user.Password, []byte(password)) != nil {
-		plog.Noticef("authentication failed, invalid password for user %s", name)
+		plog.Noticef("authentication failed, invalid password for user %s", username)
 		return &pb.AuthenticateResponse{}, ErrAuthFailed
 	}
 
-	token, err := genSimpleTokenForUser(name)
-	if err != nil {
-		plog.Errorf("failed to generate simple token: %s", err)
-		return nil, err
-	}
+	token := fmt.Sprintf("%s.%d", simpleToken, index)
+	as.assignSimpleTokenToUser(username, token)
 
-	plog.Infof("authorized %s, token is %s", name, token)
+	plog.Infof("authorized %s, token is %s", username, token)
 	return &pb.AuthenticateResponse{Token: token}, nil
 }
 
@@ -482,9 +488,9 @@ func (as *authStore) RoleAdd(r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse,
 }
 
 func (as *authStore) UsernameFromToken(token string) (string, bool) {
-	simpleTokensMu.RLock()
-	defer simpleTokensMu.RUnlock()
-	t, ok := simpleTokens[token]
+	as.simpleTokensMu.RLock()
+	defer as.simpleTokensMu.RUnlock()
+	t, ok := as.simpleTokens[token]
 	return t, ok
 }
 
@@ -680,7 +686,8 @@ func NewAuthStore(be backend.Backend) *authStore {
 	be.ForceCommit()
 
 	return &authStore{
-		be: be,
+		be:           be,
+		simpleTokens: make(map[string]string),
 	}
 }
 

+ 13 - 5
auth/store_test.go

@@ -20,6 +20,7 @@ import (
 
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	"github.com/coreos/etcd/mvcc/backend"
+	"golang.org/x/net/context"
 )
 
 func TestUserAdd(t *testing.T) {
@@ -60,7 +61,8 @@ func TestAuthenticate(t *testing.T) {
 	}
 
 	// auth a non-existing user
-	_, err = as.Authenticate("foo-test", "bar")
+	ctx1 := context.WithValue(context.WithValue(context.TODO(), "index", uint64(1)), "simpleToken", "dummy")
+	_, err = as.Authenticate(ctx1, "foo-test", "bar")
 	if err == nil {
 		t.Fatalf("expected %v, got %v", ErrAuthFailed, err)
 	}
@@ -69,13 +71,15 @@ func TestAuthenticate(t *testing.T) {
 	}
 
 	// auth an existing user with correct password
-	_, err = as.Authenticate("foo", "bar")
+	ctx2 := context.WithValue(context.WithValue(context.TODO(), "index", uint64(2)), "simpleToken", "dummy")
+	_, err = as.Authenticate(ctx2, "foo", "bar")
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	// auth an existing user but with wrong password
-	_, err = as.Authenticate("foo", "")
+	ctx3 := context.WithValue(context.WithValue(context.TODO(), "index", uint64(3)), "simpleToken", "dummy")
+	_, err = as.Authenticate(ctx3, "foo", "")
 	if err == nil {
 		t.Fatalf("expected %v, got %v", ErrAuthFailed, err)
 	}
@@ -129,7 +133,9 @@ func TestUserChangePassword(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	_, err = as.Authenticate("foo", "")
+
+	ctx1 := context.WithValue(context.WithValue(context.TODO(), "index", uint64(1)), "simpleToken", "dummy")
+	_, err = as.Authenticate(ctx1, "foo", "")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -138,7 +144,9 @@ func TestUserChangePassword(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	_, err = as.Authenticate("foo", "bar")
+
+	ctx2 := context.WithValue(context.WithValue(context.TODO(), "index", uint64(2)), "simpleToken", "dummy")
+	_, err = as.Authenticate(ctx2, "foo", "bar")
 	if err != nil {
 		t.Fatal(err)
 	}

+ 6 - 4
etcdserver/apply.go

@@ -26,6 +26,7 @@ import (
 	"github.com/coreos/etcd/mvcc/mvccpb"
 	"github.com/coreos/etcd/pkg/types"
 	"github.com/gogo/protobuf/proto"
+	"golang.org/x/net/context"
 )
 
 const (
@@ -59,7 +60,7 @@ type applierV3 interface {
 
 	AuthEnable() (*pb.AuthEnableResponse, error)
 	AuthDisable() (*pb.AuthDisableResponse, error)
-	Authenticate(r *pb.AuthenticateRequest) (*pb.AuthenticateResponse, error)
+	Authenticate(ctx context.Context, username, password string) (*pb.AuthenticateResponse, error)
 	UserAdd(ua *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error)
 	UserDelete(ua *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error)
 	UserChangePassword(ua *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error)
@@ -117,7 +118,8 @@ func (s *EtcdServer) applyV3Request(r *pb.InternalRaftRequest) *applyResult {
 	case r.AuthDisable != nil:
 		ar.resp, ar.err = s.applyV3.AuthDisable()
 	case r.Authenticate != nil:
-		ar.resp, ar.err = s.applyV3.Authenticate(r.Authenticate)
+		ctx := context.WithValue(context.WithValue(context.TODO(), "index", s.consistIndex.ConsistentIndex()), "simpleToken", r.Authenticate.SimpleToken)
+		ar.resp, ar.err = s.applyV3.Authenticate(ctx, r.Authenticate.Name, r.Authenticate.Password)
 	case r.AuthUserAdd != nil:
 		ar.resp, ar.err = s.applyV3.UserAdd(r.AuthUserAdd)
 	case r.AuthUserDelete != nil:
@@ -541,8 +543,8 @@ func (a *applierV3backend) AuthDisable() (*pb.AuthDisableResponse, error) {
 	return &pb.AuthDisableResponse{}, nil
 }
 
-func (a *applierV3backend) Authenticate(r *pb.AuthenticateRequest) (*pb.AuthenticateResponse, error) {
-	return a.s.AuthStore().Authenticate(r.Name, r.Password)
+func (a *applierV3backend) Authenticate(ctx context.Context, username, password string) (*pb.AuthenticateResponse, error) {
+	return a.s.AuthStore().Authenticate(ctx, username, password)
 }
 
 func (a *applierV3backend) UserAdd(r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) {

+ 27 - 26
etcdserver/etcdserverpb/etcdserver.pb.go

@@ -16,6 +16,7 @@
 		RequestHeader
 		InternalRaftRequest
 		EmptyResponse
+		InternalAuthenticateRequest
 		ResponseHeader
 		RangeRequest
 		RangeResponse
@@ -1006,30 +1007,30 @@ var (
 )
 
 var fileDescriptorEtcdserver = []byte{
-	// 398 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x5c, 0x92, 0xd1, 0x6e, 0xd3, 0x30,
-	0x14, 0x86, 0xe7, 0xd6, 0x4d, 0x1b, 0x33, 0xa0, 0x58, 0x13, 0x3a, 0x9a, 0x50, 0xa8, 0x2a, 0x2e,
-	0x7a, 0x05, 0xef, 0x30, 0xba, 0x8b, 0x4a, 0x0c, 0x8d, 0x0e, 0x8d, 0x6b, 0xd3, 0x1c, 0x56, 0x4b,
-	0x49, 0x9c, 0xd9, 0x27, 0xa1, 0x6f, 0xc0, 0xab, 0xf5, 0x92, 0x27, 0x40, 0xd0, 0x27, 0x41, 0x76,
-	0x96, 0x62, 0x76, 0x67, 0x7d, 0xff, 0xef, 0xdf, 0xbf, 0xed, 0x23, 0xa6, 0x48, 0x9b, 0xdc, 0xa1,
-	0x6d, 0xd1, 0xbe, 0xad, 0xad, 0x21, 0x23, 0x4f, 0xff, 0x91, 0xfa, 0xeb, 0xf9, 0xd9, 0x9d, 0xb9,
-	0x33, 0x41, 0x78, 0xe7, 0x57, 0x9d, 0x67, 0xfe, 0x83, 0x8b, 0xf1, 0x1a, 0xef, 0x1b, 0x74, 0x24,
-	0xcf, 0xc4, 0x60, 0xb5, 0x04, 0x36, 0x63, 0x0b, 0x7e, 0xc1, 0xf7, 0xbf, 0x5e, 0x9f, 0xac, 0x07,
-	0x7a, 0x29, 0x5f, 0x89, 0xe4, 0x0a, 0x69, 0x6b, 0x72, 0x18, 0xcc, 0xd8, 0x22, 0x7d, 0x50, 0x92,
-	0x32, 0x30, 0x09, 0x82, 0x5f, 0x2b, 0xda, 0xc2, 0x30, 0xd2, 0x78, 0xad, 0x68, 0x2b, 0x5f, 0x8a,
-	0xe1, 0xad, 0x2a, 0x80, 0x47, 0xc2, 0xb0, 0x55, 0x85, 0xe7, 0x4b, 0x6d, 0x61, 0x34, 0x63, 0x8b,
-	0x49, 0xcf, 0x73, 0x6d, 0xe5, 0x5c, 0xa4, 0xd7, 0x16, 0xdb, 0x5b, 0x55, 0x34, 0x08, 0x49, 0xb4,
-	0x2b, 0xad, 0x7b, 0xdc, 0x7b, 0x56, 0x55, 0x8e, 0x3b, 0x18, 0x47, 0x45, 0x83, 0x27, 0xe0, 0xde,
-	0x73, 0xb9, 0xd3, 0x8e, 0x60, 0x72, 0x3c, 0x85, 0x75, 0x9e, 0x80, 0xe5, 0x1b, 0x21, 0x2e, 0x77,
-	0xb5, 0xb6, 0x8a, 0xb4, 0xa9, 0x20, 0x9d, 0xb1, 0xc5, 0xf0, 0x21, 0x48, 0xe0, 0x91, 0xfb, 0xbb,
-	0x7d, 0x51, 0x9a, 0x40, 0x44, 0x55, 0xf9, 0x77, 0xa5, 0x49, 0x9e, 0x8b, 0xd1, 0x8d, 0xae, 0x36,
-	0x08, 0x4f, 0xa2, 0x0e, 0x23, 0xe7, 0x91, 0x3f, 0x7f, 0x8d, 0x9b, 0xc6, 0x3a, 0xdd, 0x22, 0x9c,
-	0x46, 0x5b, 0x53, 0xdb, 0x63, 0xff, 0xa6, 0x37, 0xc6, 0x12, 0xe6, 0xf0, 0x34, 0x32, 0x24, 0x2e,
-	0x30, 0xaf, 0x7e, 0x6a, 0x8c, 0x6d, 0x4a, 0x78, 0x16, 0xab, 0xf7, 0x81, 0xf9, 0x56, 0x9f, 0x75,
-	0x89, 0xf0, 0x3c, 0x6a, 0xcd, 0x49, 0x97, 0x5d, 0x2a, 0x59, 0x54, 0x25, 0x4c, 0xff, 0x4b, 0x0d,
-	0x4c, 0x66, 0xfe, 0xa3, 0xbf, 0x59, 0x74, 0x5b, 0x78, 0x11, 0xbd, 0xca, 0xd8, 0x76, 0x70, 0xfe,
-	0x41, 0x4c, 0xae, 0x90, 0x54, 0xae, 0x48, 0xf9, 0xa4, 0x8f, 0x26, 0xc7, 0x47, 0xd3, 0x90, 0x54,
-	0x81, 0xf9, 0x1b, 0xbe, 0x2f, 0x1a, 0x47, 0x68, 0x57, 0xcb, 0x30, 0x14, 0xc7, 0x5f, 0xd8, 0xf4,
-	0xf8, 0x62, 0xba, 0xff, 0x93, 0x9d, 0xec, 0x0f, 0x19, 0xfb, 0x79, 0xc8, 0xd8, 0xef, 0x43, 0xc6,
-	0xfe, 0x06, 0x00, 0x00, 0xff, 0xff, 0xfb, 0x8e, 0x1a, 0x0d, 0xa0, 0x02, 0x00, 0x00,
+	// 388 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x5c, 0x92, 0xd1, 0xee, 0xd2, 0x30,
+	0x14, 0xc6, 0xff, 0xdb, 0xca, 0x60, 0x15, 0x15, 0x1b, 0x62, 0x4e, 0x88, 0x41, 0x43, 0xbc, 0xf0,
+	0x4a, 0xdf, 0x01, 0xe1, 0x82, 0x44, 0x0d, 0x82, 0xd1, 0xeb, 0xba, 0x1d, 0xa1, 0x09, 0x5b, 0x47,
+	0xdb, 0x4d, 0xde, 0xc0, 0x57, 0xe3, 0xd2, 0x27, 0x30, 0xea, 0x93, 0xd8, 0x16, 0x86, 0xd5, 0x8b,
+	0x26, 0xcb, 0xef, 0xfb, 0xce, 0xe9, 0xd7, 0x73, 0x46, 0x47, 0x68, 0xf2, 0x42, 0xa3, 0x6a, 0x51,
+	0xbd, 0xac, 0x95, 0x34, 0x92, 0x0d, 0xff, 0x92, 0xfa, 0xf3, 0x64, 0xbc, 0x93, 0x3b, 0xe9, 0x85,
+	0x57, 0xee, 0xeb, 0xe2, 0x99, 0x7d, 0x23, 0xb4, 0xbf, 0xc1, 0x63, 0x83, 0xda, 0xb0, 0x31, 0x8d,
+	0x57, 0x0b, 0x88, 0x9e, 0x45, 0x2f, 0xc8, 0x9c, 0x9c, 0x7f, 0x3c, 0xbd, 0xdb, 0xc4, 0x62, 0xc1,
+	0x9e, 0xd0, 0xf4, 0x2d, 0x9a, 0xbd, 0x2c, 0x20, 0xb6, 0x4a, 0x76, 0x55, 0xd2, 0xd2, 0x33, 0x06,
+	0x94, 0xac, 0xb9, 0xd9, 0x43, 0x12, 0x68, 0xa4, 0xb6, 0x84, 0x3d, 0xa6, 0xc9, 0x47, 0x7e, 0x00,
+	0x12, 0x08, 0x49, 0xcb, 0x0f, 0x8e, 0x2f, 0x84, 0x82, 0x9e, 0xe5, 0x83, 0x8e, 0x17, 0x42, 0xb1,
+	0x19, 0xcd, 0xd6, 0x0a, 0x5b, 0x5b, 0xd3, 0x20, 0xa4, 0x41, 0x55, 0x56, 0x77, 0xb8, 0xf3, 0xac,
+	0xaa, 0x02, 0x4f, 0xd0, 0x0f, 0x82, 0x7a, 0x8f, 0xc7, 0x9d, 0x67, 0x79, 0x12, 0xda, 0xc0, 0xe0,
+	0x76, 0x4b, 0x74, 0xf1, 0x78, 0xcc, 0x9e, 0x53, 0xba, 0x3c, 0xd5, 0x42, 0x71, 0x23, 0x64, 0x05,
+	0x99, 0x35, 0x25, 0xd7, 0x46, 0x14, 0x6f, 0xdc, 0xbd, 0xed, 0x13, 0x17, 0x06, 0x68, 0x10, 0x95,
+	0x7c, 0xb5, 0x84, 0x4d, 0x68, 0x6f, 0x2b, 0xaa, 0x1c, 0xe1, 0x5e, 0x90, 0xa1, 0xa7, 0x1d, 0x72,
+	0xf7, 0x6f, 0x30, 0x6f, 0x94, 0x16, 0x2d, 0xc2, 0x30, 0x28, 0xcd, 0x54, 0x87, 0xdd, 0x4c, 0xb7,
+	0x52, 0x19, 0x2c, 0xe0, 0x7e, 0x60, 0x48, 0xb5, 0x67, 0x4e, 0x7d, 0xdf, 0x48, 0xd5, 0x94, 0xf0,
+	0x20, 0x54, 0x8f, 0x9e, 0xb9, 0x54, 0x1f, 0x44, 0x89, 0xf0, 0x30, 0x48, 0x4d, 0x8c, 0x25, 0xbe,
+	0xab, 0x51, 0xc8, 0x4b, 0x18, 0xfd, 0xd3, 0xd5, 0x33, 0x36, 0x75, 0x8b, 0xfe, 0xa2, 0x50, 0xef,
+	0xe1, 0x51, 0x30, 0x95, 0xbe, 0xba, 0xc0, 0xd9, 0x1b, 0x3a, 0xb0, 0x7b, 0xe6, 0x05, 0x37, 0xdc,
+	0x75, 0x7a, 0x27, 0x0b, 0xfc, 0xef, 0x6f, 0x48, 0x2b, 0xcf, 0xdc, 0x0b, 0x5f, 0x1f, 0x1a, 0x6d,
+	0x50, 0x59, 0x43, 0x1c, 0x6e, 0x21, 0xef, 0xf0, 0x7c, 0x74, 0xfe, 0x35, 0xbd, 0x3b, 0xff, 0x9e,
+	0x46, 0xdf, 0xed, 0xf9, 0x69, 0xcf, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfb, 0x8e, 0x1a, 0x0d,
+	0xa0, 0x02, 0x00, 0x00,
 }

+ 261 - 49
etcdserver/etcdserverpb/raft_internal.pb.go

@@ -46,7 +46,7 @@ type InternalRaftRequest struct {
 	Alarm                    *AlarmRequest                    `protobuf:"bytes,10,opt,name=alarm" json:"alarm,omitempty"`
 	AuthEnable               *AuthEnableRequest               `protobuf:"bytes,1000,opt,name=auth_enable,json=authEnable" json:"auth_enable,omitempty"`
 	AuthDisable              *AuthDisableRequest              `protobuf:"bytes,1011,opt,name=auth_disable,json=authDisable" json:"auth_disable,omitempty"`
-	Authenticate             *AuthenticateRequest             `protobuf:"bytes,1012,opt,name=authenticate" json:"authenticate,omitempty"`
+	Authenticate             *InternalAuthenticateRequest     `protobuf:"bytes,1012,opt,name=authenticate" json:"authenticate,omitempty"`
 	AuthUserAdd              *AuthUserAddRequest              `protobuf:"bytes,1100,opt,name=auth_user_add,json=authUserAdd" json:"auth_user_add,omitempty"`
 	AuthUserDelete           *AuthUserDeleteRequest           `protobuf:"bytes,1101,opt,name=auth_user_delete,json=authUserDelete" json:"auth_user_delete,omitempty"`
 	AuthUserGet              *AuthUserGetRequest              `protobuf:"bytes,1102,opt,name=auth_user_get,json=authUserGet" json:"auth_user_get,omitempty"`
@@ -73,10 +73,28 @@ func (m *EmptyResponse) String() string            { return proto.CompactTextStr
 func (*EmptyResponse) ProtoMessage()               {}
 func (*EmptyResponse) Descriptor() ([]byte, []int) { return fileDescriptorRaftInternal, []int{2} }
 
+// What is the difference between AuthenticateRequest (defined in rpc.proto) and InternalAuthenticateRequest?
+// InternalAuthenticateRequest has a member that is filled by etcdserver and shouldn't be user-facing.
+// For avoiding misusage the field, we have an internal version of AuthenticateRequest.
+type InternalAuthenticateRequest 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"`
+	// simple_token is generated in API layer (etcdserver/v3_server.go)
+	SimpleToken string `protobuf:"bytes,3,opt,name=simple_token,json=simpleToken,proto3" json:"simple_token,omitempty"`
+}
+
+func (m *InternalAuthenticateRequest) Reset()         { *m = InternalAuthenticateRequest{} }
+func (m *InternalAuthenticateRequest) String() string { return proto.CompactTextString(m) }
+func (*InternalAuthenticateRequest) ProtoMessage()    {}
+func (*InternalAuthenticateRequest) Descriptor() ([]byte, []int) {
+	return fileDescriptorRaftInternal, []int{3}
+}
+
 func init() {
 	proto.RegisterType((*RequestHeader)(nil), "etcdserverpb.RequestHeader")
 	proto.RegisterType((*InternalRaftRequest)(nil), "etcdserverpb.InternalRaftRequest")
 	proto.RegisterType((*EmptyResponse)(nil), "etcdserverpb.EmptyResponse")
+	proto.RegisterType((*InternalAuthenticateRequest)(nil), "etcdserverpb.InternalAuthenticateRequest")
 }
 func (m *RequestHeader) Marshal() (data []byte, err error) {
 	size := m.Size()
@@ -418,6 +436,42 @@ func (m *EmptyResponse) MarshalTo(data []byte) (int, error) {
 	return i, nil
 }
 
+func (m *InternalAuthenticateRequest) 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 *InternalAuthenticateRequest) MarshalTo(data []byte) (int, error) {
+	var i int
+	_ = i
+	var l int
+	_ = l
+	if len(m.Name) > 0 {
+		data[i] = 0xa
+		i++
+		i = encodeVarintRaftInternal(data, i, uint64(len(m.Name)))
+		i += copy(data[i:], m.Name)
+	}
+	if len(m.Password) > 0 {
+		data[i] = 0x12
+		i++
+		i = encodeVarintRaftInternal(data, i, uint64(len(m.Password)))
+		i += copy(data[i:], m.Password)
+	}
+	if len(m.SimpleToken) > 0 {
+		data[i] = 0x1a
+		i++
+		i = encodeVarintRaftInternal(data, i, uint64(len(m.SimpleToken)))
+		i += copy(data[i:], m.SimpleToken)
+	}
+	return i, nil
+}
+
 func encodeFixed64RaftInternal(data []byte, offset int, v uint64) int {
 	data[offset] = uint8(v)
 	data[offset+1] = uint8(v >> 8)
@@ -569,6 +623,24 @@ func (m *EmptyResponse) Size() (n int) {
 	return n
 }
 
+func (m *InternalAuthenticateRequest) Size() (n int) {
+	var l int
+	_ = l
+	l = len(m.Name)
+	if l > 0 {
+		n += 1 + l + sovRaftInternal(uint64(l))
+	}
+	l = len(m.Password)
+	if l > 0 {
+		n += 1 + l + sovRaftInternal(uint64(l))
+	}
+	l = len(m.SimpleToken)
+	if l > 0 {
+		n += 1 + l + sovRaftInternal(uint64(l))
+	}
+	return n
+}
+
 func sovRaftInternal(x uint64) (n int) {
 	for {
 		n++
@@ -1151,7 +1223,7 @@ func (m *InternalRaftRequest) Unmarshal(data []byte) error {
 				return io.ErrUnexpectedEOF
 			}
 			if m.Authenticate == nil {
-				m.Authenticate = &AuthenticateRequest{}
+				m.Authenticate = &InternalAuthenticateRequest{}
 			}
 			if err := m.Authenticate.Unmarshal(data[iNdEx:postIndex]); err != nil {
 				return err
@@ -1591,6 +1663,143 @@ func (m *EmptyResponse) Unmarshal(data []byte) error {
 	}
 	return nil
 }
+func (m *InternalAuthenticateRequest) 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 ErrIntOverflowRaftInternal
+			}
+			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: InternalAuthenticateRequest: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: InternalAuthenticateRequest: 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 ErrIntOverflowRaftInternal
+				}
+				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 ErrInvalidLengthRaftInternal
+			}
+			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 ErrIntOverflowRaftInternal
+				}
+				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 ErrInvalidLengthRaftInternal
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Password = string(data[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field SimpleToken", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowRaftInternal
+				}
+				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 ErrInvalidLengthRaftInternal
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.SimpleToken = string(data[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipRaftInternal(data[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if skippy < 0 {
+				return ErrInvalidLengthRaftInternal
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
 func skipRaftInternal(data []byte) (n int, err error) {
 	l := len(data)
 	iNdEx := 0
@@ -1697,51 +1906,54 @@ var (
 )
 
 var fileDescriptorRaftInternal = []byte{
-	// 725 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x7c, 0x95, 0x5b, 0x4f, 0x13, 0x41,
-	0x14, 0xc7, 0x6d, 0xb9, 0x76, 0x4a, 0x11, 0x07, 0xd0, 0xb1, 0x24, 0x15, 0x31, 0x2a, 0xf1, 0x82,
-	0x06, 0x1e, 0x7d, 0xd0, 0x4a, 0x11, 0x49, 0x8c, 0x21, 0x1b, 0x4d, 0x4c, 0x7c, 0xd8, 0x0c, 0xdd,
-	0x43, 0x5b, 0xdd, 0xee, 0xae, 0xb3, 0xd3, 0x8a, 0xdf, 0xca, 0xdb, 0x87, 0xe0, 0xc1, 0x0b, 0xfa,
-	0x09, 0xb4, 0x4f, 0xbe, 0xeb, 0x07, 0x30, 0x73, 0xd9, 0xd9, 0x6e, 0x3b, 0xcb, 0xdb, 0x72, 0xce,
-	0xff, 0xfc, 0xce, 0x39, 0xf3, 0x9f, 0xa1, 0x68, 0x91, 0xd1, 0x43, 0xee, 0x76, 0x02, 0x0e, 0x2c,
-	0xa0, 0xfe, 0x46, 0xc4, 0x42, 0x1e, 0xe2, 0x39, 0xe0, 0x4d, 0x2f, 0x06, 0xd6, 0x07, 0x16, 0x1d,
-	0x54, 0x97, 0x5a, 0x61, 0x2b, 0x94, 0x89, 0x3b, 0xe2, 0x4b, 0x69, 0xaa, 0x0b, 0xa9, 0x46, 0x47,
-	0x4a, 0x2c, 0x6a, 0xaa, 0xcf, 0xb5, 0x7b, 0xa8, 0xe2, 0xc0, 0x9b, 0x1e, 0xc4, 0xfc, 0x31, 0x50,
-	0x0f, 0x18, 0x9e, 0x47, 0xc5, 0xbd, 0x06, 0x29, 0xac, 0x16, 0xd6, 0x27, 0x9d, 0x62, 0xa7, 0x81,
-	0xab, 0x68, 0xb6, 0x17, 0x8b, 0x96, 0x5d, 0x20, 0xc5, 0xd5, 0xc2, 0x7a, 0xc9, 0x31, 0x7f, 0xaf,
-	0xfd, 0xac, 0xa0, 0xc5, 0x3d, 0x3d, 0x90, 0x43, 0x0f, 0xb9, 0x26, 0x8d, 0x31, 0xae, 0xa2, 0x62,
-	0x7f, 0x53, 0x56, 0x97, 0x37, 0x97, 0x37, 0x86, 0x47, 0xde, 0xd0, 0x25, 0x4e, 0xb1, 0xbf, 0x89,
-	0xef, 0xa2, 0x29, 0x46, 0x83, 0x16, 0x90, 0x09, 0xa9, 0xac, 0x8e, 0x28, 0x45, 0x2a, 0x91, 0x2b,
-	0x21, 0xbe, 0x81, 0x26, 0xa2, 0x1e, 0x27, 0x93, 0x52, 0x4f, 0xb2, 0xfa, 0xfd, 0x5e, 0x32, 0x8f,
-	0x23, 0x44, 0x78, 0x1b, 0xcd, 0x79, 0xe0, 0x03, 0x07, 0x57, 0x35, 0x99, 0x92, 0x45, 0xab, 0xd9,
-	0xa2, 0x86, 0x54, 0x64, 0x5a, 0x95, 0xbd, 0x34, 0x26, 0x1a, 0xf2, 0xa3, 0x80, 0x4c, 0xdb, 0x1a,
-	0x3e, 0x3b, 0x0a, 0x4c, 0x43, 0x7e, 0x14, 0xe0, 0xfb, 0x08, 0x35, 0xc3, 0x6e, 0x44, 0x9b, 0xbc,
-	0x13, 0x06, 0x64, 0x46, 0x96, 0x5c, 0xca, 0x96, 0x6c, 0x9b, 0x7c, 0x52, 0x39, 0x54, 0x82, 0x1f,
-	0xa0, 0xb2, 0x0f, 0x34, 0x06, 0xb7, 0xc5, 0x68, 0xc0, 0xc9, 0xac, 0x8d, 0xf0, 0x44, 0x08, 0x76,
-	0x45, 0xde, 0x10, 0x7c, 0x13, 0x12, 0x3b, 0x2b, 0x02, 0x83, 0x7e, 0xf8, 0x1a, 0x48, 0xc9, 0xb6,
-	0xb3, 0x44, 0x38, 0x52, 0x60, 0x76, 0xf6, 0xd3, 0x98, 0xb0, 0x85, 0xfa, 0x94, 0x75, 0x09, 0xb2,
-	0xd9, 0x52, 0x17, 0x29, 0x63, 0x8b, 0x14, 0xe2, 0x2d, 0x34, 0xdd, 0x96, 0xb7, 0x89, 0x78, 0xb2,
-	0x64, 0xc5, 0xea, 0xb9, 0xba, 0x70, 0x8e, 0x96, 0xe2, 0x3a, 0x2a, 0xd3, 0x1e, 0x6f, 0xbb, 0x10,
-	0xd0, 0x03, 0x1f, 0xc8, 0x1f, 0xeb, 0x81, 0xd5, 0x7b, 0xbc, 0xbd, 0x23, 0x05, 0x66, 0x5d, 0x6a,
-	0x42, 0xb8, 0x81, 0xe6, 0x24, 0xc2, 0xeb, 0xc4, 0x92, 0xf1, 0x77, 0xc6, 0xb6, 0xaf, 0x60, 0x34,
-	0x94, 0xc2, 0xec, 0x4b, 0xd3, 0x18, 0x7e, 0xa4, 0x28, 0x10, 0xf0, 0x4e, 0x93, 0x72, 0x20, 0xff,
-	0x14, 0xe5, 0xf2, 0x38, 0x25, 0x91, 0x24, 0x98, 0x4c, 0x1d, 0xde, 0x41, 0x15, 0x39, 0x8d, 0x78,
-	0x2e, 0x2e, 0xf5, 0x3c, 0xf2, 0x65, 0x36, 0x6f, 0x9c, 0xe7, 0x31, 0xb0, 0xba, 0xe7, 0x65, 0xc6,
-	0xd1, 0x31, 0xfc, 0x14, 0x2d, 0xa4, 0x18, 0x75, 0x17, 0xc9, 0x57, 0x45, 0xba, 0x62, 0x27, 0xe9,
-	0x4b, 0xac, 0x61, 0xf3, 0x34, 0x13, 0xce, 0x8e, 0xd5, 0x02, 0x4e, 0xbe, 0x9d, 0x3a, 0xd6, 0x2e,
-	0xf0, 0xb1, 0xb1, 0x76, 0x81, 0xe3, 0x16, 0xba, 0x98, 0x62, 0x9a, 0x6d, 0xf1, 0x3a, 0xdc, 0x88,
-	0xc6, 0xf1, 0xdb, 0x90, 0x79, 0xe4, 0xbb, 0x42, 0xde, 0xb4, 0x23, 0xb7, 0xa5, 0x7a, 0x5f, 0x8b,
-	0x13, 0xfa, 0x79, 0x6a, 0x4d, 0xe3, 0x17, 0x68, 0x69, 0x68, 0x5e, 0x71, 0xad, 0x5d, 0x16, 0xfa,
-	0x40, 0x4e, 0x54, 0x8f, 0x6b, 0x39, 0x63, 0xcb, 0x27, 0x11, 0xa6, 0x16, 0x9f, 0xa3, 0xa3, 0x19,
-	0xfc, 0x12, 0x2d, 0xa7, 0x64, 0xf5, 0x42, 0x14, 0xfa, 0x87, 0x42, 0x5f, 0xb7, 0xa3, 0xf5, 0x53,
-	0x19, 0x62, 0x63, 0x3a, 0x96, 0x32, 0xc7, 0x2c, 0x80, 0xd2, 0xfd, 0xf7, 0xa5, 0xbc, 0x63, 0x16,
-	0xfa, 0x51, 0xf7, 0x75, 0xcc, 0xb8, 0x2f, 0x31, 0xda, 0xfd, 0x0f, 0xa5, 0x3c, 0xf7, 0x45, 0x95,
-	0xc5, 0xfd, 0x34, 0x9c, 0x1d, 0x4b, 0xb8, 0xff, 0xf1, 0xd4, 0xb1, 0x46, 0xdd, 0xd7, 0x31, 0xfc,
-	0x0a, 0x55, 0x87, 0x30, 0xd2, 0x94, 0x08, 0x58, 0xb7, 0x13, 0xc7, 0xe2, 0x7f, 0xdd, 0x27, 0xc5,
-	0xbc, 0x95, 0xc3, 0x14, 0xf2, 0x7d, 0xa3, 0x4e, 0xf8, 0x17, 0xa8, 0x3d, 0x8f, 0xbb, 0x68, 0x25,
-	0xed, 0xa5, 0x6d, 0x1a, 0x6a, 0xf6, 0x59, 0x35, 0xbb, 0x6d, 0x6f, 0xa6, 0x1c, 0x19, 0xef, 0x46,
-	0x68, 0x8e, 0x60, 0xed, 0x2c, 0xaa, 0xec, 0x74, 0x23, 0xfe, 0xce, 0x81, 0x38, 0x0a, 0x83, 0x18,
-	0x1e, 0x2e, 0x1c, 0xff, 0xae, 0x9d, 0x39, 0x1e, 0xd4, 0x0a, 0x27, 0x83, 0x5a, 0xe1, 0xd7, 0xa0,
-	0x56, 0x38, 0x98, 0x96, 0xbf, 0x9d, 0x5b, 0xff, 0x03, 0x00, 0x00, 0xff, 0xff, 0xac, 0x8d, 0xf6,
-	0xc5, 0x93, 0x07, 0x00, 0x00,
+	// 770 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x7c, 0x95, 0x4b, 0x6f, 0x13, 0x3b,
+	0x14, 0xc7, 0x6f, 0xd2, 0x67, 0x9c, 0xa4, 0xb7, 0xd7, 0x6d, 0x2f, 0x26, 0x95, 0x4a, 0x09, 0xe2,
+	0x0d, 0x05, 0xb5, 0x4b, 0x16, 0x10, 0x9a, 0xaa, 0x54, 0x42, 0xa8, 0x1a, 0x15, 0x09, 0x89, 0xc5,
+	0xc8, 0xcd, 0x98, 0x74, 0x60, 0x5e, 0x78, 0x9c, 0x52, 0xbe, 0x15, 0xaf, 0x0f, 0xd1, 0x05, 0x8f,
+	0xb2, 0x65, 0x05, 0xac, 0xd8, 0xc3, 0x07, 0xc0, 0xaf, 0xf1, 0xcc, 0x24, 0x4e, 0x17, 0x91, 0x66,
+	0xce, 0xf9, 0x9f, 0xdf, 0x39, 0x9e, 0xbf, 0x1d, 0x83, 0x05, 0x8a, 0x9f, 0x31, 0xd7, 0x8f, 0x18,
+	0xa1, 0x11, 0x0e, 0xd6, 0x12, 0x1a, 0xb3, 0x18, 0x36, 0x08, 0xeb, 0x79, 0x29, 0xa1, 0x87, 0x84,
+	0x26, 0xfb, 0xad, 0xc5, 0x7e, 0xdc, 0x8f, 0x65, 0xe2, 0x96, 0x78, 0x52, 0x9a, 0xd6, 0x7c, 0xae,
+	0xd1, 0x91, 0x1a, 0x4d, 0x7a, 0xea, 0xb1, 0x7d, 0x07, 0x34, 0x1d, 0xf2, 0x72, 0x40, 0x52, 0xf6,
+	0x80, 0x60, 0x8f, 0x50, 0x38, 0x07, 0xaa, 0x3b, 0x5d, 0x54, 0x59, 0xad, 0x5c, 0x99, 0x74, 0xaa,
+	0x7e, 0x17, 0xb6, 0xc0, 0xec, 0x20, 0x15, 0x2d, 0x43, 0x82, 0xaa, 0x3c, 0x5a, 0x73, 0xcc, 0x7b,
+	0xfb, 0x5b, 0x13, 0x2c, 0xec, 0xe8, 0x81, 0x1c, 0x3e, 0x9d, 0x26, 0x8d, 0x30, 0x2e, 0x82, 0xea,
+	0xe1, 0xba, 0xac, 0xae, 0xaf, 0x2f, 0xad, 0x15, 0x47, 0x5e, 0xd3, 0x25, 0x0e, 0x17, 0xc0, 0xdb,
+	0x60, 0x8a, 0xe2, 0xa8, 0x4f, 0xd0, 0x84, 0x54, 0xb6, 0x86, 0x94, 0x22, 0x95, 0xc9, 0x95, 0x10,
+	0x5e, 0x03, 0x13, 0xc9, 0x80, 0xa1, 0x49, 0xa9, 0x47, 0x65, 0xfd, 0xee, 0x20, 0x9b, 0xc7, 0x11,
+	0x22, 0xb8, 0x09, 0x1a, 0x1e, 0x09, 0x08, 0x23, 0xae, 0x6a, 0x32, 0x25, 0x8b, 0x56, 0xcb, 0x45,
+	0x5d, 0xa9, 0x28, 0xb5, 0xaa, 0x7b, 0x79, 0x4c, 0x34, 0x64, 0x47, 0x11, 0x9a, 0xb6, 0x35, 0xdc,
+	0x3b, 0x8a, 0x4c, 0x43, 0x2e, 0x82, 0x77, 0x01, 0xe8, 0xc5, 0x61, 0x82, 0x7b, 0xcc, 0x8f, 0x23,
+	0x34, 0x23, 0x4b, 0xce, 0x95, 0x4b, 0x36, 0x4d, 0x3e, 0xab, 0x2c, 0x94, 0xc0, 0x7b, 0xa0, 0x1e,
+	0x10, 0x9c, 0x12, 0xb7, 0xcf, 0x27, 0x66, 0x68, 0xd6, 0x46, 0x78, 0x28, 0x04, 0xdb, 0x22, 0x6f,
+	0x08, 0x81, 0x09, 0x89, 0x35, 0x2b, 0x02, 0x25, 0x87, 0xf1, 0x0b, 0x82, 0x6a, 0xb6, 0x35, 0x4b,
+	0x84, 0x23, 0x05, 0x66, 0xcd, 0x41, 0x1e, 0x13, 0xb6, 0xe0, 0x00, 0xd3, 0x10, 0x01, 0x9b, 0x2d,
+	0x1d, 0x91, 0x32, 0xb6, 0x48, 0x21, 0xdc, 0x00, 0xd3, 0x07, 0x72, 0x37, 0x21, 0x4f, 0x96, 0x2c,
+	0x5b, 0x3d, 0x57, 0x1b, 0xce, 0xd1, 0x52, 0xd8, 0x01, 0x75, 0x3c, 0x60, 0x07, 0x2e, 0x89, 0xf0,
+	0x7e, 0x40, 0xd0, 0x2f, 0xeb, 0x07, 0xeb, 0x70, 0xc5, 0x96, 0x14, 0x98, 0xe5, 0x62, 0x13, 0x82,
+	0x5d, 0xd0, 0x90, 0x08, 0xcf, 0x4f, 0x25, 0xe3, 0xf7, 0x8c, 0x6d, 0xbd, 0x82, 0xd1, 0x55, 0x0a,
+	0xb3, 0x5e, 0x9c, 0xc7, 0xe0, 0x23, 0x45, 0x21, 0x11, 0xf3, 0x7b, 0x98, 0x11, 0xf4, 0x47, 0x51,
+	0xae, 0x96, 0x29, 0xd9, 0xbe, 0xef, 0x14, 0xa4, 0x19, 0xae, 0x54, 0x0f, 0xb7, 0x40, 0x53, 0x4e,
+	0x25, 0x8e, 0x8d, 0x8b, 0x3d, 0x0f, 0x7d, 0x9c, 0x1d, 0x37, 0xd6, 0x63, 0xfe, 0xd6, 0xf1, 0xbc,
+	0xd2, 0x58, 0x3a, 0xc6, 0xc7, 0x9a, 0xcf, 0x31, 0x6a, 0x4f, 0xa2, 0x4f, 0x8a, 0x74, 0xc1, 0x4e,
+	0xd2, 0x9b, 0x59, 0xc3, 0xe6, 0x70, 0x29, 0x5c, 0x1e, 0xab, 0x4f, 0x18, 0xfa, 0x7c, 0xea, 0x58,
+	0xdb, 0x84, 0x8d, 0x8c, 0xc5, 0x63, 0xb0, 0x0f, 0xce, 0xe6, 0x98, 0xde, 0x81, 0x38, 0x25, 0x6e,
+	0x82, 0xd3, 0xf4, 0x55, 0x4c, 0x3d, 0xf4, 0x45, 0x21, 0xaf, 0xdb, 0x91, 0x9b, 0x52, 0xbd, 0xab,
+	0xc5, 0x19, 0xfd, 0x7f, 0x6c, 0x4d, 0xc3, 0x27, 0x60, 0xb1, 0x30, 0xaf, 0xd8, 0xde, 0x2e, 0x8d,
+	0xb9, 0xc9, 0x27, 0xaa, 0xc7, 0xa5, 0x31, 0x63, 0xcb, 0xa3, 0x11, 0xe7, 0x56, 0xff, 0x87, 0x87,
+	0x33, 0xf0, 0x29, 0x58, 0xca, 0xc9, 0xea, 0xa4, 0x28, 0xf4, 0x57, 0x85, 0xbe, 0x6c, 0x47, 0xeb,
+	0x23, 0x53, 0x60, 0x43, 0x3c, 0x92, 0x32, 0x9f, 0x59, 0x00, 0xa5, 0xfb, 0x6f, 0x6a, 0xe3, 0x3e,
+	0xb3, 0xd0, 0x0f, 0xbb, 0xaf, 0x63, 0xc6, 0x7d, 0x89, 0xd1, 0xee, 0xbf, 0xad, 0x8d, 0x73, 0x5f,
+	0x54, 0x59, 0xdc, 0xcf, 0xc3, 0xe5, 0xb1, 0x84, 0xfb, 0xef, 0x4e, 0x1d, 0x6b, 0xd8, 0x7d, 0x1d,
+	0x83, 0xcf, 0x41, 0xab, 0x80, 0x91, 0xa6, 0x24, 0x84, 0x86, 0x7e, 0x9a, 0x8a, 0xff, 0xbc, 0xf7,
+	0x8a, 0x79, 0x63, 0x0c, 0x53, 0xc8, 0x77, 0x8d, 0x3a, 0xe3, 0x9f, 0xc1, 0xf6, 0x3c, 0x0c, 0xc1,
+	0x72, 0xde, 0x4b, 0xdb, 0x54, 0x68, 0xf6, 0x41, 0x35, 0xbb, 0x69, 0x6f, 0xa6, 0x1c, 0x19, 0xed,
+	0x86, 0xf0, 0x18, 0x41, 0xfb, 0x5f, 0xd0, 0xdc, 0x0a, 0x13, 0xf6, 0xda, 0x21, 0x69, 0x12, 0x47,
+	0x29, 0x69, 0x27, 0x60, 0xf9, 0x94, 0x43, 0x0f, 0x21, 0x98, 0x94, 0x97, 0x64, 0x45, 0x5e, 0x92,
+	0xf2, 0x59, 0x5c, 0x9e, 0xe6, 0x2c, 0xe8, 0xcb, 0x33, 0x7b, 0x87, 0xe7, 0x41, 0x23, 0xf5, 0xc3,
+	0x84, 0xaf, 0x85, 0xf1, 0xc6, 0x91, 0xbc, 0xf4, 0x6a, 0x4e, 0x5d, 0xc5, 0xf6, 0x44, 0xe8, 0xfe,
+	0xfc, 0xf1, 0x8f, 0x95, 0x7f, 0x8e, 0x7f, 0xae, 0x54, 0x4e, 0xf8, 0xef, 0x3b, 0xff, 0xed, 0x4f,
+	0xcb, 0x5b, 0x7b, 0xe3, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa6, 0x96, 0x31, 0x96, 0x0d, 0x08,
+	0x00, 0x00,
 }

+ 13 - 1
etcdserver/etcdserverpb/raft_internal.proto

@@ -38,7 +38,7 @@ message InternalRaftRequest {
   AuthEnableRequest auth_enable = 1000;
   AuthDisableRequest auth_disable = 1011;
 
-  AuthenticateRequest authenticate = 1012;
+  InternalAuthenticateRequest authenticate = 1012;
 
   AuthUserAddRequest auth_user_add = 1100;
   AuthUserDeleteRequest auth_user_delete = 1101;
@@ -56,3 +56,15 @@ message InternalRaftRequest {
 
 message EmptyResponse {
 }
+
+// What is the difference between AuthenticateRequest (defined in rpc.proto) and InternalAuthenticateRequest?
+// InternalAuthenticateRequest has a member that is filled by etcdserver and shouldn't be user-facing.
+// For avoiding misusage the field, we have an internal version of AuthenticateRequest.
+message InternalAuthenticateRequest {
+  string name = 1;
+  string password = 2;
+
+  // simple_token is generated in API layer (etcdserver/v3_server.go)
+  string simple_token = 3;
+}
+

+ 11 - 0
etcdserver/server.go

@@ -214,6 +214,8 @@ type EtcdServer struct {
 	// wg is used to wait for the go routines that depends on the server state
 	// to exit when stopping the server.
 	wg sync.WaitGroup
+
+	appliedIndex uint64
 }
 
 // NewServer creates a new EtcdServer from the supplied configuration. The
@@ -1064,6 +1066,7 @@ func (s *EtcdServer) applyEntryNormal(e *raftpb.Entry) {
 	// set the consistent index of current executing entry
 	s.consistIndex.setConsistentIndex(e.Index)
 	ar := s.applyV3Request(&raftReq)
+	s.setAppliedIndex(e.Index)
 	if ar.err != ErrNoSpace || len(s.alarmStore.Get(pb.AlarmType_NOSPACE)) > 0 {
 		s.w.Trigger(id, ar)
 		return
@@ -1313,3 +1316,11 @@ func (s *EtcdServer) restoreAlarms() error {
 	}
 	return nil
 }
+
+func (s *EtcdServer) getAppliedIndex() uint64 {
+	return atomic.LoadUint64(&s.appliedIndex)
+}
+
+func (s *EtcdServer) setAppliedIndex(v uint64) {
+	atomic.StoreUint64(&s.appliedIndex, v)
+}

+ 48 - 1
etcdserver/v3_server.go

@@ -15,6 +15,8 @@
 package etcdserver
 
 import (
+	"strconv"
+	"strings"
 	"time"
 
 	"github.com/coreos/etcd/auth"
@@ -271,7 +273,18 @@ func (s *EtcdServer) AuthDisable(ctx context.Context, r *pb.AuthDisableRequest)
 }
 
 func (s *EtcdServer) Authenticate(ctx context.Context, r *pb.AuthenticateRequest) (*pb.AuthenticateResponse, error) {
-	result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{Authenticate: r})
+	st, err := s.AuthStore().GenSimpleToken()
+	if err != nil {
+		return nil, err
+	}
+
+	internalReq := &pb.InternalAuthenticateRequest{
+		Name:        r.Name,
+		Password:    r.Password,
+		SimpleToken: st,
+	}
+
+	result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{Authenticate: internalReq})
 	if err != nil {
 		return nil, err
 	}
@@ -402,6 +415,36 @@ func (s *EtcdServer) RoleDelete(ctx context.Context, r *pb.AuthRoleDeleteRequest
 	return result.resp.(*pb.AuthRoleDeleteResponse), nil
 }
 
+func (s *EtcdServer) isValidSimpleToken(token string) bool {
+	splitted := strings.Split(token, ".")
+	if len(splitted) != 2 {
+		return false
+	}
+	index, err := strconv.Atoi(splitted[1])
+	if err != nil {
+		return false
+	}
+
+	// CAUTION: below index synchronization is required because this node
+	// might not receive and apply the log entry of Authenticate() RPC.
+	authApplied := false
+	for i := 0; i < 10; i++ {
+		if uint64(index) <= s.getAppliedIndex() {
+			authApplied = true
+			break
+		}
+
+		time.Sleep(100 * time.Millisecond)
+	}
+
+	if !authApplied {
+		plog.Errorf("timeout of waiting Authenticate() RPC")
+		return false
+	}
+
+	return true
+}
+
 func (s *EtcdServer) usernameFromCtx(ctx context.Context) (string, error) {
 	md, ok := metadata.FromContext(ctx)
 	if !ok {
@@ -414,6 +457,10 @@ func (s *EtcdServer) usernameFromCtx(ctx context.Context) (string, error) {
 	}
 
 	token := ts[0]
+	if !s.isValidSimpleToken(token) {
+		return "", ErrInvalidAuthToken
+	}
+
 	username, uok := s.AuthStore().UsernameFromToken(token)
 	if !uok {
 		plog.Warningf("invalid auth token: %s", token)