Skip to content

Commit

Permalink
Merge pull request #65 from hexfusion/bump_v3.4.14
Browse files Browse the repository at this point in the history
Bug 1870189: Bump v3.4.14
  • Loading branch information
openshift-merge-robot authored Mar 2, 2021
2 parents 2afb11e + b60e76b commit c6ddbee
Show file tree
Hide file tree
Showing 97 changed files with 27,839 additions and 2,207 deletions.
2 changes: 2 additions & 0 deletions Documentation/op-guide/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,5 @@ As of version v3.2 if an etcd server is launched with the option `--client-cert-
As of version v3.3 if an etcd server is launched with the option `--peer-cert-allowed-cn` or `--peer-cert-allowed-hostname` filtering of inter-peer connections is enabled. Nodes can only join the etcd cluster if their TLS certificate identity match the allowed one.
See [etcd security page](https://github.com/etcd-io/etcd/blob/master/Documentation/op-guide/security.md) for more details.

## Notes on password strength
`etcdctl` command line interface and etcd API don't check a strength (length, coexistence of numbers and alphabets, etc) of the password during creating a new user or updating password of an existing user. An administrator needs to care about a requirement of password strength by themselves.
6 changes: 3 additions & 3 deletions Documentation/op-guide/gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ title: etcd gateway

## What is etcd gateway

etcd gateway is a simple TCP proxy that forwards network data to the etcd cluster. The gateway is stateless and transparent; it neither inspects client requests nor interferes with cluster responses.
etcd gateway is a simple TCP proxy that forwards network data to the etcd cluster. The gateway is stateless and transparent; it neither inspects client requests nor interferes with cluster responses. It does not terminate TLS connections, do TLS handshakes on behalf of its clients, or verify if the connection is secured.

The gateway supports multiple etcd server endpoints and works on a simple round-robin policy. It only routes to available endpoints and hides failures from its clients. Other retry policies, such as weighted round-robin, may be supported in the future.

Expand Down Expand Up @@ -74,7 +74,7 @@ $ etcd gateway start --discovery-srv=example.com

* Comma-separated list of etcd server targets for forwarding client connections.
* Default: `127.0.0.1:2379`
* Invalid example: `https://127.0.0.1:2379` (gateway does not terminate TLS)
* Invalid example: `https://127.0.0.1:2379` (gateway does not terminate TLS). Note that the gateway does not verify the HTTP schema or inspect the requests, it only forwards requests to the given endpoints.

#### --discovery-srv

Expand Down Expand Up @@ -103,5 +103,5 @@ $ etcd gateway start --discovery-srv=example.com

#### --trusted-ca-file

* Path to the client TLS CA file for the etcd cluster. Used to authenticate endpoints.
* Path to the client TLS CA file for the etcd cluster to verify the endpoints returned from SRV discovery. Note that it is ONLY used for authenticating the discovered endpoints rather than creating connections for data transferring. The gateway never terminates TLS connections or create TLS connections on behalf of its clients.
* Default: (not set)
11 changes: 10 additions & 1 deletion Documentation/op-guide/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Transport security model
---

etcd supports automatic TLS as well as authentication through client certificates for both clients to server as well as peer (server to server / cluster) communication.
etcd supports automatic TLS as well as authentication through client certificates for both clients to server as well as peer (server to server / cluster) communication. **Note that etcd doesn't enable [RBAC based authentication][auth] or the authentication feature in the transport layer by default to reduce friction for users getting started with the database. Further, changing this default would be a breaking change for the project which was established since 2013. An etcd cluster which doesn't enable security features can expose its data to any clients.**

To get up and running, first have a CA certificate and a signed key pair for one member. It is recommended to create and sign a new key pair for every member in a cluster.

Expand Down Expand Up @@ -426,8 +426,17 @@ Make sure to sign the certificates with a Subject Name the member's public IP ad

The certificate needs to be signed for the member's FQDN in its Subject Name, use Subject Alternative Names (short IP SANs) to add the IP address. The `etcd-ca` tool provides `--domain=` option for its `new-cert` command, and openssl can make [it][alt-name] too.

### Does etcd encrypt data stored on disk drives?
No. etcd doesn't encrypt key/value data stored on disk drives. If a user need to encrypt data stored on etcd, there are some options:
* Let client applications encrypt and decrypt the data
* Use a feature of underlying storage systems for encrypting stored data like [dm-crypt]

### I’m seeing a log warning that "directory X exist without recommended permission -rwx------"
When etcd create certain new directories it sets file permission to 700 to prevent unprivileged access as possible. However, if user has already created a directory with own preference, etcd uses the existing directory and logs a warning message if the permission is different than 700.

[cfssl]: https://github.com/cloudflare/cfssl
[tls-setup]: ../../hack/tls-setup
[tls-guide]: https://github.com/coreos/docs/blob/master/os/generate-self-signed-certificates.md
[alt-name]: http://wiki.cacert.org/FAQ/subjectAltName
[auth]: authentication.md
[dm-crypt]: https://en.wikipedia.org/wiki/Dm-crypt
25 changes: 18 additions & 7 deletions auth/simple_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const (

// var for testing purposes
var (
simpleTokenTTL = 5 * time.Minute
simpleTokenTTLDefault = 300 * time.Second
simpleTokenTTLResolution = 1 * time.Second
)

Expand All @@ -47,6 +47,7 @@ type simpleTokenTTLKeeper struct {
stopc chan struct{}
deleteTokenFunc func(string)
mu *sync.Mutex
simpleTokenTTL time.Duration
}

func (tm *simpleTokenTTLKeeper) stop() {
Expand All @@ -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)
}
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -157,6 +159,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 {
if t.lg != nil {
Expand All @@ -177,6 +183,7 @@ func (t *tokenSimple) enable() {
stopc: make(chan struct{}),
deleteTokenFunc: delf,
mu: &t.simpleTokensMu,
simpleTokenTTL: t.simpleTokenTTL,
}
go t.simpleTokenKeeper.run()
}
Expand Down Expand Up @@ -234,10 +241,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,
}
}
6 changes: 3 additions & 3 deletions auth/simple_token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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)
Expand Down
11 changes: 7 additions & 4 deletions auth/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"strings"
"sync"
"sync/atomic"
"time"

"go.etcd.io/etcd/auth/authpb"
"go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes"
Expand Down Expand Up @@ -59,6 +60,7 @@ var (
ErrRoleNotFound = errors.New("auth: role not found")
ErrRoleEmpty = errors.New("auth: role name is empty")
ErrAuthFailed = errors.New("auth: authentication failed, invalid user ID or password")
ErrNoPasswordUser = errors.New("auth: authentication failed, password was given for no password user")
ErrPermissionDenied = errors.New("auth: permission denied")
ErrRoleNotGranted = errors.New("auth: role is not granted to the user")
ErrPermissionNotGranted = errors.New("auth: permission is not granted to the role")
Expand Down Expand Up @@ -360,7 +362,7 @@ func (as *authStore) CheckPassword(username, password string) (uint64, error) {
}

if user.Options != nil && user.Options.NoPassword {
return 0, ErrAuthFailed
return 0, ErrNoPasswordUser
}

return getRevision(tx), nil
Expand Down Expand Up @@ -994,7 +996,7 @@ func (as *authStore) IsAdminPermitted(authInfo *AuthInfo) error {
if !as.IsAuthEnabled() {
return nil
}
if authInfo == nil {
if authInfo == nil || authInfo.Username == "" {
return ErrUserEmpty
}

Expand Down Expand Up @@ -1351,7 +1353,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
Expand All @@ -1364,7 +1367,7 @@ func NewTokenProvider(
} else {
plog.Warningf("simple token is not cryptographically signed")
}
return newTokenProviderSimple(lg, indexWaiter), nil
return newTokenProviderSimple(lg, indexWaiter, TokenTTL), nil

case tokenTypeJWT:
return newTokenProviderJWT(lg, typeSpecificOpts)
Expand Down
28 changes: 17 additions & 11 deletions auth/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -626,7 +626,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)
}
Expand Down Expand Up @@ -658,6 +658,12 @@ func TestIsAdminPermitted(t *testing.T) {
t.Errorf("expected %v, got %v", ErrUserNotFound, err)
}

// empty user
err = as.IsAdminPermitted(&AuthInfo{Username: "", Revision: 1})
if err != ErrUserEmpty {
t.Errorf("expected %v, got %v", ErrUserEmpty, err)
}

// non-admin user
err = as.IsAdminPermitted(&AuthInfo{Username: "foo", Revision: 1})
if err != ErrPermissionDenied {
Expand Down Expand Up @@ -692,7 +698,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)
}
Expand Down Expand Up @@ -725,13 +731,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)
Expand Down Expand Up @@ -774,7 +780,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)
}
Expand Down Expand Up @@ -829,7 +835,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)
}
Expand Down
4 changes: 2 additions & 2 deletions clientv3/integration/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ func TestUserErrorAuth(t *testing.T) {
authSetupRoot(t, authapi.Auth)

// unauthenticated client
if _, err := authapi.UserAdd(context.TODO(), "foo", "bar"); err != rpctypes.ErrUserNotFound {
t.Fatalf("expected %v, got %v", rpctypes.ErrUserNotFound, err)
if _, err := authapi.UserAdd(context.TODO(), "foo", "bar"); err != rpctypes.ErrUserEmpty {
t.Fatalf("expected %v, got %v", rpctypes.ErrUserEmpty, err)
}

// wrong id or password
Expand Down
24 changes: 24 additions & 0 deletions clientv3/integration/watch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,30 @@ func testWatchWithProgressNotify(t *testing.T, watchOnPut bool) {
}
}

func TestConfigurableWatchProgressNotifyInterval(t *testing.T) {
progressInterval := 200 * time.Millisecond
clus := integration.NewClusterV3(t,
&integration.ClusterConfig{
Size: 3,
WatchProgressNotifyInterval: progressInterval,
})
defer clus.Terminate(t)

opts := []clientv3.OpOption{clientv3.WithProgressNotify()}
rch := clus.RandClient().Watch(context.Background(), "foo", opts...)

timeout := 1 * time.Second // we expect to receive watch progress notify in 2 * progressInterval,
// but for CPU-starved situation it may take longer. So we use 1 second here for timeout.
select {
case resp := <-rch: // waiting for a watch progress notify response
if !resp.IsProgressNotify() {
t.Fatalf("expected resp.IsProgressNotify() == true")
}
case <-time.After(timeout):
t.Fatalf("timed out waiting for watch progress notify response in %v", timeout)
}
}

func TestWatchRequestProgress(t *testing.T) {
testCases := []struct {
name string
Expand Down
10 changes: 10 additions & 0 deletions clientv3/retry_interceptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ func (c *Client) streamClientInterceptor(logger *zap.Logger, optFuncs ...retryOp
intOpts := reuseOrNewWithCallOptions(defaultOptions, optFuncs)
return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
ctx = withVersion(ctx)
// getToken automatically
// TODO(cfc4n): keep this code block, remove codes about getToken in client.go after pr #12165 merged.
if c.authTokenBundle != nil {
// equal to c.Username != "" && c.Password != ""
err := c.getToken(ctx)
if err != nil && rpctypes.Error(err) != rpctypes.ErrAuthNotEnabled {
logger.Error("clientv3/retry_interceptor: getToken failed", zap.Error(err))
return nil, err
}
}
grpcOpts, retryOpts := filterCallOptions(opts)
callOpts := reuseOrNewWithCallOptions(intOpts, retryOpts)
// short circuit for simplicity, and avoiding allocations.
Expand Down
Loading

0 comments on commit c6ddbee

Please sign in to comment.