diff --git a/auth/simple_token.go b/auth/simple_token.go index 147978e8f3a..24871b0eee0 100644 --- a/auth/simple_token.go +++ b/auth/simple_token.go @@ -37,7 +37,7 @@ const ( // var for testing purposes var ( - simpleTokenTTL = 5 * time.Minute + simpleTokenTTLDefault = 300 * time.Second simpleTokenTTLResolution = 1 * time.Second ) @@ -47,6 +47,7 @@ type simpleTokenTTLKeeper struct { stopc chan struct{} deleteTokenFunc func(string) mu *sync.Mutex + simpleTokenTTL time.Duration } func (tm *simpleTokenTTLKeeper) stop() { @@ -58,12 +59,12 @@ func (tm *simpleTokenTTLKeeper) stop() { } func (tm *simpleTokenTTLKeeper) addSimpleToken(token string) { - tm.tokens[token] = time.Now().Add(simpleTokenTTL) + tm.tokens[token] = time.Now().Add(tm.simpleTokenTTL) } func (tm *simpleTokenTTLKeeper) resetSimpleToken(token string) { if _, ok := tm.tokens[token]; ok { - tm.tokens[token] = time.Now().Add(simpleTokenTTL) + tm.tokens[token] = time.Now().Add(tm.simpleTokenTTL) } } @@ -101,6 +102,7 @@ type tokenSimple struct { simpleTokenKeeper *simpleTokenTTLKeeper simpleTokensMu sync.Mutex simpleTokens map[string]string // token -> username + simpleTokenTTL time.Duration } func (t *tokenSimple) genTokenPrefix() (string, error) { @@ -153,6 +155,10 @@ func (t *tokenSimple) invalidateUser(username string) { } func (t *tokenSimple) enable() { + if t.simpleTokenTTL <= 0 { + t.simpleTokenTTL = simpleTokenTTLDefault + } + delf := func(tk string) { if username, ok := t.simpleTokens[tk]; ok { t.lg.Info( @@ -169,6 +175,7 @@ func (t *tokenSimple) enable() { stopc: make(chan struct{}), deleteTokenFunc: delf, mu: &t.simpleTokensMu, + simpleTokenTTL: t.simpleTokenTTL, } go t.simpleTokenKeeper.run() } @@ -226,13 +233,14 @@ func (t *tokenSimple) isValidSimpleToken(ctx context.Context, token string) bool return false } -func newTokenProviderSimple(lg *zap.Logger, indexWaiter func(uint64) <-chan struct{}) *tokenSimple { +func newTokenProviderSimple(lg *zap.Logger, indexWaiter func(uint64) <-chan struct{}, TokenTTL time.Duration) *tokenSimple { if lg == nil { lg = zap.NewNop() } return &tokenSimple{ - lg: lg, - simpleTokens: make(map[string]string), - indexWaiter: indexWaiter, + lg: lg, + simpleTokens: make(map[string]string), + indexWaiter: indexWaiter, + simpleTokenTTL: TokenTTL, } } diff --git a/auth/simple_token_test.go b/auth/simple_token_test.go index 598095aaaaa..bc6c0f0eb6b 100644 --- a/auth/simple_token_test.go +++ b/auth/simple_token_test.go @@ -24,9 +24,9 @@ import ( // TestSimpleTokenDisabled ensures that TokenProviderSimple behaves correctly when // disabled. func TestSimpleTokenDisabled(t *testing.T) { - initialState := newTokenProviderSimple(zap.NewExample(), dummyIndexWaiter) + initialState := newTokenProviderSimple(zap.NewExample(), dummyIndexWaiter, simpleTokenTTLDefault) - explicitlyDisabled := newTokenProviderSimple(zap.NewExample(), dummyIndexWaiter) + explicitlyDisabled := newTokenProviderSimple(zap.NewExample(), dummyIndexWaiter, simpleTokenTTLDefault) explicitlyDisabled.enable() explicitlyDisabled.disable() @@ -48,7 +48,7 @@ func TestSimpleTokenDisabled(t *testing.T) { // TestSimpleTokenAssign ensures that TokenProviderSimple can correctly assign a // token, look it up with info, and invalidate it by user. func TestSimpleTokenAssign(t *testing.T) { - tp := newTokenProviderSimple(zap.NewExample(), dummyIndexWaiter) + tp := newTokenProviderSimple(zap.NewExample(), dummyIndexWaiter, simpleTokenTTLDefault) tp.enable() ctx := context.WithValue(context.WithValue(context.TODO(), AuthenticateParamIndex{}, uint64(1)), AuthenticateParamSimpleTokenPrefix{}, "dummy") token, err := tp.assign(ctx, "user1", 0) diff --git a/auth/store.go b/auth/store.go index a5fd4755c90..7cf5dc7e1d8 100644 --- a/auth/store.go +++ b/auth/store.go @@ -23,6 +23,7 @@ import ( "strings" "sync" "sync/atomic" + "time" "go.etcd.io/etcd/v3/auth/authpb" "go.etcd.io/etcd/v3/etcdserver/api/v3rpc/rpctypes" @@ -1213,7 +1214,8 @@ func decomposeOpts(lg *zap.Logger, optstr string) (string, map[string]string, er func NewTokenProvider( lg *zap.Logger, tokenOpts string, - indexWaiter func(uint64) <-chan struct{}) (TokenProvider, error) { + indexWaiter func(uint64) <-chan struct{}, + TokenTTL time.Duration) (TokenProvider, error) { tokenType, typeSpecificOpts, err := decomposeOpts(lg, tokenOpts) if err != nil { return nil, ErrInvalidAuthOpts @@ -1224,7 +1226,7 @@ func NewTokenProvider( if lg != nil { lg.Warn("simple token is not cryptographically signed") } - return newTokenProviderSimple(lg, indexWaiter), nil + return newTokenProviderSimple(lg, indexWaiter, TokenTTL), nil case tokenTypeJWT: return newTokenProviderJWT(lg, typeSpecificOpts) diff --git a/auth/store_test.go b/auth/store_test.go index 182d518013c..9f12ad632cf 100644 --- a/auth/store_test.go +++ b/auth/store_test.go @@ -48,7 +48,7 @@ func TestNewAuthStoreRevision(t *testing.T) { b, tPath := backend.NewDefaultTmpBackend() defer os.Remove(tPath) - tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter) + tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter, simpleTokenTTLDefault) if err != nil { t.Fatal(err) } @@ -78,7 +78,7 @@ func TestNewAuthStoreBcryptCost(t *testing.T) { b, tPath := backend.NewDefaultTmpBackend() defer os.Remove(tPath) - tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter) + tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter, simpleTokenTTLDefault) if err != nil { t.Fatal(err) } @@ -98,7 +98,7 @@ func TestNewAuthStoreBcryptCost(t *testing.T) { func setupAuthStore(t *testing.T) (store *authStore, teardownfunc func(t *testing.T)) { b, tPath := backend.NewDefaultTmpBackend() - tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter) + tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter, simpleTokenTTLDefault) if err != nil { t.Fatal(err) } @@ -650,7 +650,7 @@ func TestAuthInfoFromCtxRace(t *testing.T) { b, tPath := backend.NewDefaultTmpBackend() defer os.Remove(tPath) - tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter) + tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter, simpleTokenTTLDefault) if err != nil { t.Fatal(err) } @@ -722,7 +722,7 @@ func TestRecoverFromSnapshot(t *testing.T) { as.Close() - tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter) + tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter, simpleTokenTTLDefault) if err != nil { t.Fatal(err) } @@ -755,13 +755,13 @@ func contains(array []string, str string) bool { func TestHammerSimpleAuthenticate(t *testing.T) { // set TTL values low to try to trigger races - oldTTL, oldTTLRes := simpleTokenTTL, simpleTokenTTLResolution + oldTTL, oldTTLRes := simpleTokenTTLDefault, simpleTokenTTLResolution defer func() { - simpleTokenTTL = oldTTL + simpleTokenTTLDefault = oldTTL simpleTokenTTLResolution = oldTTLRes }() - simpleTokenTTL = 10 * time.Millisecond - simpleTokenTTLResolution = simpleTokenTTL + simpleTokenTTLDefault = 10 * time.Millisecond + simpleTokenTTLResolution = simpleTokenTTLDefault users := make(map[string]struct{}) as, tearDown := setupAuthStore(t) @@ -804,7 +804,7 @@ func TestRolesOrder(t *testing.T) { b, tPath := backend.NewDefaultTmpBackend() defer os.Remove(tPath) - tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter) + tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter, simpleTokenTTLDefault) if err != nil { t.Fatal(err) } @@ -859,7 +859,7 @@ func testAuthInfoFromCtxWithRoot(t *testing.T, opts string) { b, tPath := backend.NewDefaultTmpBackend() defer os.Remove(tPath) - tp, err := NewTokenProvider(zap.NewExample(), opts, dummyIndexWaiter) + tp, err := NewTokenProvider(zap.NewExample(), opts, dummyIndexWaiter, simpleTokenTTLDefault) if err != nil { t.Fatal(err) } diff --git a/embed/config.go b/embed/config.go index 6281c8f242a..c501a66f270 100644 --- a/embed/config.go +++ b/embed/config.go @@ -274,6 +274,9 @@ type Config struct { AuthToken string `json:"auth-token"` BcryptCost uint `json:"bcrypt-cost"` + //The AuthTokenTTL in seconds of the simple token + AuthTokenTTL uint `json:"auth-token-ttl"` + ExperimentalInitialCorruptCheck bool `json:"experimental-initial-corrupt-check"` ExperimentalCorruptCheckTime time.Duration `json:"experimental-corrupt-check-time"` ExperimentalEnableV2V3 string `json:"experimental-enable-v2v3"` @@ -396,8 +399,9 @@ func NewConfig() *Config { CORS: map[string]struct{}{"*": {}}, HostWhitelist: map[string]struct{}{"*": {}}, - AuthToken: "simple", - BcryptCost: uint(bcrypt.DefaultCost), + AuthToken: "simple", + BcryptCost: uint(bcrypt.DefaultCost), + AuthTokenTTL: 300, PreVote: false, // TODO: enable by default in v3.5 diff --git a/embed/etcd.go b/embed/etcd.go index 9d69e6dc89e..815468e1b62 100644 --- a/embed/etcd.go +++ b/embed/etcd.go @@ -185,6 +185,7 @@ func StartEtcd(inCfg *Config) (e *Etcd, err error) { ClientCertAuthEnabled: cfg.ClientTLSInfo.ClientCertAuth, AuthToken: cfg.AuthToken, BcryptCost: cfg.BcryptCost, + TokenTTL: cfg.AuthTokenTTL, CORS: cfg.CORS, HostWhitelist: cfg.HostWhitelist, InitialCorruptCheck: cfg.ExperimentalInitialCorruptCheck, diff --git a/etcdmain/config.go b/etcdmain/config.go index 4239beffc79..3fc21bc94a1 100644 --- a/etcdmain/config.go +++ b/etcdmain/config.go @@ -240,6 +240,7 @@ func newConfig() *config { // auth fs.StringVar(&cfg.ec.AuthToken, "auth-token", cfg.ec.AuthToken, "Specify auth token specific options.") fs.UintVar(&cfg.ec.BcryptCost, "bcrypt-cost", cfg.ec.BcryptCost, "Specify bcrypt algorithm cost factor for auth password hashing.") + fs.UintVar(&cfg.ec.AuthTokenTTL, "auth-token-ttl", cfg.ec.AuthTokenTTL, "The lifetime in seconds of the auth token.") // gateway fs.BoolVar(&cfg.ec.EnableGRPCGateway, "enable-grpc-gateway", true, "Enable GRPC gateway.") diff --git a/etcdmain/help.go b/etcdmain/help.go index b0313565336..320065f4aa6 100644 --- a/etcdmain/help.go +++ b/etcdmain/help.go @@ -164,6 +164,8 @@ Auth: Specify a v3 authentication token type and its options ('simple' or 'jwt'). --bcrypt-cost ` + fmt.Sprintf("%d", bcrypt.DefaultCost) + ` Specify the cost / strength of the bcrypt algorithm for hashing auth passwords. Valid values are between ` + fmt.Sprintf("%d", bcrypt.MinCost) + ` and ` + fmt.Sprintf("%d", bcrypt.MaxCost) + `. + --auth-token-ttl 300 + Time (in seconds) of the auth-token-ttl. Profiling and Monitoring: --enable-pprof 'false' diff --git a/etcdserver/config.go b/etcdserver/config.go index fbd3ef0091c..20f0d46474d 100644 --- a/etcdserver/config.go +++ b/etcdserver/config.go @@ -126,6 +126,7 @@ type ServerConfig struct { AuthToken string BcryptCost uint + TokenTTL uint // InitialCorruptCheck is true to check data corruption on boot // before serving any peer/client traffic. diff --git a/etcdserver/server.go b/etcdserver/server.go index 30fc2797a73..e3192a360fa 100644 --- a/etcdserver/server.go +++ b/etcdserver/server.go @@ -531,6 +531,7 @@ func NewServer(cfg ServerConfig) (srv *EtcdServer, err error) { func(index uint64) <-chan struct{} { return srv.applyWait.Wait(index) }, + time.Duration(cfg.TokenTTL)*time.Second, ) if err != nil { cfg.Logger.Warn("failed to create token provider", zap.Error(err))