*: make bcrypt-cost configurable
parent
62dfb89a89
commit
bf432648ae
|
@ -375,6 +375,10 @@ Follow the instructions when using these flags.
|
||||||
+ Example option of JWT: '--auth-token jwt,pub-key=app.rsa.pub,priv-key=app.rsa,sign-method=RS512,ttl=10m'
|
+ Example option of JWT: '--auth-token jwt,pub-key=app.rsa.pub,priv-key=app.rsa,sign-method=RS512,ttl=10m'
|
||||||
+ default: "simple"
|
+ default: "simple"
|
||||||
|
|
||||||
|
### --bcrypt-cost
|
||||||
|
+ Specify the cost / strength of the bcrypt algorithm for hashing auth passwords. Valid values are between 4 and 31.
|
||||||
|
+ default: 10
|
||||||
|
|
||||||
## Experimental flags
|
## Experimental flags
|
||||||
|
|
||||||
### --experimental-corrupt-check-time
|
### --experimental-corrupt-check-time
|
||||||
|
|
|
@ -66,9 +66,6 @@ var (
|
||||||
ErrInvalidAuthToken = errors.New("auth: invalid auth token")
|
ErrInvalidAuthToken = errors.New("auth: invalid auth token")
|
||||||
ErrInvalidAuthOpts = errors.New("auth: invalid auth options")
|
ErrInvalidAuthOpts = errors.New("auth: invalid auth options")
|
||||||
ErrInvalidAuthMgmt = errors.New("auth: invalid auth management")
|
ErrInvalidAuthMgmt = errors.New("auth: invalid auth management")
|
||||||
|
|
||||||
// BcryptCost is the algorithm cost / strength for hashing auth passwords
|
|
||||||
BcryptCost = bcrypt.DefaultCost
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -205,6 +202,7 @@ type authStore struct {
|
||||||
rangePermCache map[string]*unifiedRangePermissions // username -> unifiedRangePermissions
|
rangePermCache map[string]*unifiedRangePermissions // username -> unifiedRangePermissions
|
||||||
|
|
||||||
tokenProvider TokenProvider
|
tokenProvider TokenProvider
|
||||||
|
bcryptCost int // the algorithm cost / strength for hashing auth passwords
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as *authStore) AuthEnable() error {
|
func (as *authStore) AuthEnable() error {
|
||||||
|
@ -371,7 +369,7 @@ func (as *authStore) UserAdd(r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse,
|
||||||
return nil, ErrUserEmpty
|
return nil, ErrUserEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
hashed, err := bcrypt.GenerateFromPassword([]byte(r.Password), BcryptCost)
|
hashed, err := bcrypt.GenerateFromPassword([]byte(r.Password), as.bcryptCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if as.lg != nil {
|
if as.lg != nil {
|
||||||
as.lg.Warn(
|
as.lg.Warn(
|
||||||
|
@ -452,7 +450,7 @@ func (as *authStore) UserDelete(r *pb.AuthUserDeleteRequest) (*pb.AuthUserDelete
|
||||||
func (as *authStore) UserChangePassword(r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) {
|
func (as *authStore) UserChangePassword(r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) {
|
||||||
// TODO(mitake): measure the cost of bcrypt.GenerateFromPassword()
|
// TODO(mitake): measure the cost of bcrypt.GenerateFromPassword()
|
||||||
// If the cost is too high, we should move the encryption to outside of the raft
|
// If the cost is too high, we should move the encryption to outside of the raft
|
||||||
hashed, err := bcrypt.GenerateFromPassword([]byte(r.Password), BcryptCost)
|
hashed, err := bcrypt.GenerateFromPassword([]byte(r.Password), as.bcryptCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if as.lg != nil {
|
if as.lg != nil {
|
||||||
as.lg.Warn(
|
as.lg.Warn(
|
||||||
|
@ -1060,7 +1058,23 @@ func (as *authStore) IsAuthEnabled() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAuthStore creates a new AuthStore.
|
// NewAuthStore creates a new AuthStore.
|
||||||
func NewAuthStore(lg *zap.Logger, be backend.Backend, tp TokenProvider) *authStore {
|
func NewAuthStore(lg *zap.Logger, be backend.Backend, tp TokenProvider, bcryptCost int) *authStore {
|
||||||
|
if bcryptCost < bcrypt.MinCost || bcryptCost > bcrypt.MaxCost {
|
||||||
|
if lg != nil {
|
||||||
|
lg.Warn(
|
||||||
|
"use default bcrypt cost instead of the invalid given cost",
|
||||||
|
zap.Int("min-cost", bcrypt.MinCost),
|
||||||
|
zap.Int("max-cost", bcrypt.MaxCost),
|
||||||
|
zap.Int("default-cost", bcrypt.DefaultCost),
|
||||||
|
zap.Int("given-cost", bcryptCost))
|
||||||
|
} else {
|
||||||
|
plog.Warningf("Use default bcrypt-cost %d instead of the invalid value %d",
|
||||||
|
bcrypt.DefaultCost, bcryptCost)
|
||||||
|
}
|
||||||
|
|
||||||
|
bcryptCost = bcrypt.DefaultCost
|
||||||
|
}
|
||||||
|
|
||||||
tx := be.BatchTx()
|
tx := be.BatchTx()
|
||||||
tx.Lock()
|
tx.Lock()
|
||||||
|
|
||||||
|
@ -1083,6 +1097,7 @@ func NewAuthStore(lg *zap.Logger, be backend.Backend, tp TokenProvider) *authSto
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
rangePermCache: make(map[string]*unifiedRangePermissions),
|
rangePermCache: make(map[string]*unifiedRangePermissions),
|
||||||
tokenProvider: tp,
|
tokenProvider: tp,
|
||||||
|
bcryptCost: bcryptCost,
|
||||||
}
|
}
|
||||||
|
|
||||||
if enabled {
|
if enabled {
|
||||||
|
@ -1342,3 +1357,7 @@ func (as *authStore) HasRole(user, role string) bool {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (as *authStore) BcryptCost() int {
|
||||||
|
return as.bcryptCost
|
||||||
|
}
|
||||||
|
|
|
@ -34,8 +34,6 @@ import (
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { BcryptCost = bcrypt.MinCost }
|
|
||||||
|
|
||||||
func dummyIndexWaiter(index uint64) <-chan struct{} {
|
func dummyIndexWaiter(index uint64) <-chan struct{} {
|
||||||
ch := make(chan struct{})
|
ch := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -54,27 +52,49 @@ func TestNewAuthStoreRevision(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
as := NewAuthStore(zap.NewExample(), b, tp)
|
as := NewAuthStore(zap.NewExample(), b, tp, bcrypt.MinCost)
|
||||||
err = enableAuthAndCreateRoot(as)
|
err = enableAuthAndCreateRoot(as)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
old := as.Revision()
|
old := as.Revision()
|
||||||
b.Close()
|
|
||||||
as.Close()
|
as.Close()
|
||||||
|
b.Close()
|
||||||
|
|
||||||
// no changes to commit
|
// no changes to commit
|
||||||
b2 := backend.NewDefaultBackend(tPath)
|
b2 := backend.NewDefaultBackend(tPath)
|
||||||
as = NewAuthStore(zap.NewExample(), b2, tp)
|
as = NewAuthStore(zap.NewExample(), b2, tp, bcrypt.MinCost)
|
||||||
new := as.Revision()
|
new := as.Revision()
|
||||||
b2.Close()
|
|
||||||
as.Close()
|
as.Close()
|
||||||
|
b2.Close()
|
||||||
|
|
||||||
if old != new {
|
if old != new {
|
||||||
t.Fatalf("expected revision %d, got %d", old, new)
|
t.Fatalf("expected revision %d, got %d", old, new)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestNewAuthStoreBryptCost ensures that NewAuthStore uses default when given bcrypt-cost is invalid
|
||||||
|
func TestNewAuthStoreBcryptCost(t *testing.T) {
|
||||||
|
b, tPath := backend.NewDefaultTmpBackend()
|
||||||
|
defer os.Remove(tPath)
|
||||||
|
|
||||||
|
tp, err := NewTokenProvider(zap.NewExample(), "simple", dummyIndexWaiter)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidCosts := [2]int{bcrypt.MinCost - 1, bcrypt.MaxCost + 1}
|
||||||
|
for _, invalidCost := range invalidCosts {
|
||||||
|
as := NewAuthStore(zap.NewExample(), b, tp, invalidCost)
|
||||||
|
if as.BcryptCost() != bcrypt.DefaultCost {
|
||||||
|
t.Fatalf("expected DefaultCost when bcryptcost is invalid")
|
||||||
|
}
|
||||||
|
as.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func setupAuthStore(t *testing.T) (store *authStore, teardownfunc func(t *testing.T)) {
|
func setupAuthStore(t *testing.T) (store *authStore, teardownfunc func(t *testing.T)) {
|
||||||
b, tPath := backend.NewDefaultTmpBackend()
|
b, tPath := backend.NewDefaultTmpBackend()
|
||||||
|
|
||||||
|
@ -82,7 +102,7 @@ func setupAuthStore(t *testing.T) (store *authStore, teardownfunc func(t *testin
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
as := NewAuthStore(zap.NewExample(), b, tp)
|
as := NewAuthStore(zap.NewExample(), b, tp, bcrypt.MinCost)
|
||||||
err = enableAuthAndCreateRoot(as)
|
err = enableAuthAndCreateRoot(as)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -519,7 +539,7 @@ func TestAuthInfoFromCtxRace(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
as := NewAuthStore(zap.NewExample(), b, tp)
|
as := NewAuthStore(zap.NewExample(), b, tp, bcrypt.MinCost)
|
||||||
defer as.Close()
|
defer as.Close()
|
||||||
|
|
||||||
donec := make(chan struct{})
|
donec := make(chan struct{})
|
||||||
|
@ -585,7 +605,7 @@ func TestRecoverFromSnapshot(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
as2 := NewAuthStore(zap.NewExample(), as.be, tp)
|
as2 := NewAuthStore(zap.NewExample(), as.be, tp, bcrypt.MinCost)
|
||||||
defer func(a *authStore) {
|
defer func(a *authStore) {
|
||||||
a.Close()
|
a.Close()
|
||||||
}(as2)
|
}(as2)
|
||||||
|
@ -667,7 +687,7 @@ func TestRolesOrder(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
as := NewAuthStore(zap.NewExample(), b, tp)
|
as := NewAuthStore(zap.NewExample(), b, tp, bcrypt.MinCost)
|
||||||
err = enableAuthAndCreateRoot(as)
|
err = enableAuthAndCreateRoot(as)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -713,7 +733,7 @@ func TestAuthInfoFromCtxWithRoot(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
as := NewAuthStore(zap.NewExample(), b, tp)
|
as := NewAuthStore(zap.NewExample(), b, tp, bcrypt.MinCost)
|
||||||
defer as.Close()
|
defer as.Close()
|
||||||
|
|
||||||
if err = enableAuthAndCreateRoot(as); err != nil {
|
if err = enableAuthAndCreateRoot(as); err != nil {
|
||||||
|
|
|
@ -22,15 +22,10 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/etcd/auth"
|
|
||||||
"github.com/coreos/etcd/integration"
|
"github.com/coreos/etcd/integration"
|
||||||
"github.com/coreos/etcd/pkg/testutil"
|
"github.com/coreos/etcd/pkg/testutil"
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { auth.BcryptCost = bcrypt.MinCost }
|
|
||||||
|
|
||||||
// TestMain sets up an etcd cluster if running the examples.
|
// TestMain sets up an etcd cluster if running the examples.
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
useCluster, hasRunArg := false, false // default to running only Test*
|
useCluster, hasRunArg := false, false // default to running only Test*
|
||||||
|
|
|
@ -38,6 +38,7 @@ import (
|
||||||
"github.com/ghodss/yaml"
|
"github.com/ghodss/yaml"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -242,7 +243,8 @@ type Config struct {
|
||||||
// embed.StartEtcd(cfg)
|
// embed.StartEtcd(cfg)
|
||||||
ServiceRegister func(*grpc.Server) `json:"-"`
|
ServiceRegister func(*grpc.Server) `json:"-"`
|
||||||
|
|
||||||
AuthToken string `json:"auth-token"`
|
AuthToken string `json:"auth-token"`
|
||||||
|
BcryptCost uint `json:"bcrypt-cost"`
|
||||||
|
|
||||||
ExperimentalInitialCorruptCheck bool `json:"experimental-initial-corrupt-check"`
|
ExperimentalInitialCorruptCheck bool `json:"experimental-initial-corrupt-check"`
|
||||||
ExperimentalCorruptCheckTime time.Duration `json:"experimental-corrupt-check-time"`
|
ExperimentalCorruptCheckTime time.Duration `json:"experimental-corrupt-check-time"`
|
||||||
|
@ -364,7 +366,8 @@ func NewConfig() *Config {
|
||||||
CORS: map[string]struct{}{"*": {}},
|
CORS: map[string]struct{}{"*": {}},
|
||||||
HostWhitelist: map[string]struct{}{"*": {}},
|
HostWhitelist: map[string]struct{}{"*": {}},
|
||||||
|
|
||||||
AuthToken: "simple",
|
AuthToken: "simple",
|
||||||
|
BcryptCost: uint(bcrypt.DefaultCost),
|
||||||
|
|
||||||
PreVote: false, // TODO: enable by default in v3.5
|
PreVote: false, // TODO: enable by default in v3.5
|
||||||
|
|
||||||
|
|
|
@ -183,6 +183,7 @@ func StartEtcd(inCfg *Config) (e *Etcd, err error) {
|
||||||
StrictReconfigCheck: cfg.StrictReconfigCheck,
|
StrictReconfigCheck: cfg.StrictReconfigCheck,
|
||||||
ClientCertAuthEnabled: cfg.ClientTLSInfo.ClientCertAuth,
|
ClientCertAuthEnabled: cfg.ClientTLSInfo.ClientCertAuth,
|
||||||
AuthToken: cfg.AuthToken,
|
AuthToken: cfg.AuthToken,
|
||||||
|
BcryptCost: cfg.BcryptCost,
|
||||||
CORS: cfg.CORS,
|
CORS: cfg.CORS,
|
||||||
HostWhitelist: cfg.HostWhitelist,
|
HostWhitelist: cfg.HostWhitelist,
|
||||||
InitialCorruptCheck: cfg.ExperimentalInitialCorruptCheck,
|
InitialCorruptCheck: cfg.ExperimentalInitialCorruptCheck,
|
||||||
|
|
|
@ -237,6 +237,7 @@ func newConfig() *config {
|
||||||
|
|
||||||
// auth
|
// auth
|
||||||
fs.StringVar(&cfg.ec.AuthToken, "auth-token", cfg.ec.AuthToken, "Specify auth token specific options.")
|
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.")
|
||||||
|
|
||||||
// experimental
|
// experimental
|
||||||
fs.BoolVar(&cfg.ec.ExperimentalInitialCorruptCheck, "experimental-initial-corrupt-check", cfg.ec.ExperimentalInitialCorruptCheck, "Enable to check data corruption before serving any client/peer traffic.")
|
fs.BoolVar(&cfg.ec.ExperimentalInitialCorruptCheck, "experimental-initial-corrupt-check", cfg.ec.ExperimentalInitialCorruptCheck, "Enable to check data corruption before serving any client/peer traffic.")
|
||||||
|
|
|
@ -15,9 +15,11 @@
|
||||||
package etcdmain
|
package etcdmain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/coreos/etcd/embed"
|
"github.com/coreos/etcd/embed"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -148,6 +150,8 @@ Security:
|
||||||
Auth:
|
Auth:
|
||||||
--auth-token 'simple'
|
--auth-token 'simple'
|
||||||
Specify a v3 authentication token type and its options ('simple' or 'jwt').
|
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) + `.
|
||||||
|
|
||||||
Profiling and Monitoring:
|
Profiling and Monitoring:
|
||||||
--enable-pprof 'false'
|
--enable-pprof 'false'
|
||||||
|
|
|
@ -103,7 +103,8 @@ type ServerConfig struct {
|
||||||
// ClientCertAuthEnabled is true when cert has been signed by the client CA.
|
// ClientCertAuthEnabled is true when cert has been signed by the client CA.
|
||||||
ClientCertAuthEnabled bool
|
ClientCertAuthEnabled bool
|
||||||
|
|
||||||
AuthToken string
|
AuthToken string
|
||||||
|
BcryptCost uint
|
||||||
|
|
||||||
// InitialCorruptCheck is true to check data corruption on boot
|
// InitialCorruptCheck is true to check data corruption on boot
|
||||||
// before serving any peer/client traffic.
|
// before serving any peer/client traffic.
|
||||||
|
|
|
@ -559,7 +559,7 @@ func NewServer(cfg ServerConfig) (srv *EtcdServer, err error) {
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
srv.authStore = auth.NewAuthStore(srv.getLogger(), srv.be, tp)
|
srv.authStore = auth.NewAuthStore(srv.getLogger(), srv.be, tp, int(cfg.BcryptCost))
|
||||||
if num := cfg.AutoCompactionRetention; num != 0 {
|
if num := cfg.AutoCompactionRetention; num != 0 {
|
||||||
srv.compactor, err = compactor.New(cfg.Logger, cfg.AutoCompactionMode, num, srv.kv, srv)
|
srv.compactor, err = compactor.New(cfg.Logger, cfg.AutoCompactionMode, num, srv.kv, srv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -54,6 +54,7 @@ import (
|
||||||
|
|
||||||
"github.com/soheilhy/cmux"
|
"github.com/soheilhy/cmux"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/grpclog"
|
"google.golang.org/grpc/grpclog"
|
||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
|
@ -611,7 +612,8 @@ func mustNewMember(t *testing.T, mcfg memberConfig) *member {
|
||||||
if m.MaxRequestBytes == 0 {
|
if m.MaxRequestBytes == 0 {
|
||||||
m.MaxRequestBytes = embed.DefaultMaxRequestBytes
|
m.MaxRequestBytes = embed.DefaultMaxRequestBytes
|
||||||
}
|
}
|
||||||
m.AuthToken = "simple" // for the purpose of integration testing, simple token is enough
|
m.AuthToken = "simple" // for the purpose of integration testing, simple token is enough
|
||||||
|
m.BcryptCost = uint(bcrypt.MinCost) // use min bcrypt cost to speedy up integration testing
|
||||||
|
|
||||||
m.grpcServerOpts = []grpc.ServerOption{}
|
m.grpcServerOpts = []grpc.ServerOption{}
|
||||||
if mcfg.grpcKeepAliveMinTime > time.Duration(0) {
|
if mcfg.grpcKeepAliveMinTime > time.Duration(0) {
|
||||||
|
|
Loading…
Reference in New Issue