Compare commits

...

62 Commits

Author SHA1 Message Date
Gyuho Lee
2c834459e1 version: 3.3.25
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-08-24 12:33:27 -07:00
Gyuho Lee
43d6162d3f Merge pull request #12246 from SVilgelm/fix-import-path
Fix import path to fileutils in listener
2020-08-24 12:31:29 -07:00
Gyuho Lee
d01dda54dd Merge pull request #12251 from spzala/automated-cherry-pick-of-#12242-upstream-release-3.3
Automated cherry pick of #12242
2020-08-24 12:30:42 -07:00
Sahdev P. Zala
864d9f4127 pkg: file stat warning
Provide warning and doc instead of enforcing file permission.
2020-08-24 11:32:31 -04:00
Sergey Vilgelm
386ebbb704 Fix import path to fileutils in listener
transport/listener: change the import path of fileutil

Version 3.3 still uses the github.com/coreos/etcd prefix, but the transport/listener package
used the go.etcd.io/etcd path prefix.
2020-08-22 07:27:15 -05:00
Gyuho Lee
bdd57848dc scripts/release: logging release version
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-08-18 11:45:15 -07:00
Gyuho Lee
fd9a5b0be5 go.mod/sum: delete temporarily
Release version name is being overwritten by the scripts...

Will add back after release.

Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-08-18 11:44:47 -07:00
Gyuho Lee
f9e5264765 version: v3.3.24
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-08-18 09:32:00 -07:00
Gyuho Lee
f78bdce575 Merge pull request #12215 from wenjiaswe/automated-cherry-pick-of-#12106-upstream-release-3.3
Automated cherry pick of #12106
2020-08-13 21:37:14 -07:00
Yuchen Zhou
cc5cc3ae40 etcdserver: change protobuf field type from int to int64 (#12000) 2020-08-13 15:55:41 -07:00
Gyuho Lee
5bc8f1650c etcdserver: add OS level FD metrics
Similar counts are exposed via Prometheus.
This adds the one that are perceived by etcd server.

e.g.

os_fd_limit 120000
os_fd_used 14
process_cpu_seconds_total 0.31
process_max_fds 120000
process_open_fds 17

Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-08-12 18:40:03 -07:00
Gyuho Lee
0bed5fffd4 pkg/runtime: optimize FDUsage by removing sort
No need sort when we just want the counts.

Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-08-12 18:39:10 -07:00
Gyuho Lee
4873f5516b version: add "3.3.23"
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-07-16 15:15:48 -07:00
Sahdev Zala
b16bfbed53 Merge pull request #12128 from spzala/automated-cherry-pick-of-#12012-upstream-release-3.3
Automated cherry pick of #12012
2020-07-13 10:53:04 -04:00
Hitoshi Mitake
604be01b61 Documentation: note on data encryption 2020-07-13 09:51:28 -04:00
Gyuho Lee
bfc2267eba Merge pull request #12113 from spzala/automated-cherry-pick-of-#12018-upstream-release-3.3
Automated cherry pick of #12018
2020-07-07 10:32:07 -07:00
Sahdev P. Zala
ac37d3499e pkg: consider umask when use MkdirAll
os.MkdirAll creates directory before umask so make sure that a desired
permission is set after creating a directory with MkdirAll. Use the
existing TouchDirAll function which checks for permission if dir is already
exist and when create a new dir.
2020-07-07 12:02:55 -04:00
Gyuho Lee
e542d1aed8 Merge pull request #12090 from tangcong/automated-cherry-pick-of-#11997-origin-release-3.3
Automated cherry pick of #11997
2020-07-06 13:00:48 -07:00
Gyuho Lee
140edf0dc6 Merge pull request #12104 from spzala/automated-cherry-pick-of-#12092-upstream-release-3.3
Automated cherry pick of #12092
2020-07-06 11:47:52 -07:00
Gyuho Lee
6c15e40dbd Merge pull request #12057 from spzala/automated-cherry-pick-of-#11608-upstream-release-3.3
Automated cherry pick of #11608
2020-07-06 11:47:44 -07:00
Gyuho Lee
13f92b45d6 Merge pull request #12087 from spzala/automated-cherry-pick-of-#11807-upstream-release-3.3
Automated cherry pick of #11807
2020-07-06 11:47:36 -07:00
Sahdev Zala
1255e3f0c8 Update grpc_proxy.go
Using the plog.Warningf instead of zap which was added from 3.4
2020-07-05 12:31:58 -04:00
Hitoshi Mitake
4ae0875b34 etcdmain: let grpc proxy warn about insecure-skip-tls-verify 2020-07-05 12:10:07 -04:00
tangcong
44b0318929 pkg/fileutil: print desired file permission in error log 2020-06-29 10:00:23 +08:00
Sahdev P. Zala
abd80f383e wal: fix panic when decoder not set
Handle the related panic and clarify doc.
2020-06-27 17:23:17 -04:00
Gyuho Lee
3076b616ab Merge pull request #12075 from cfc4n/automated-cherry-pick-of-#11987-upstream-release-3.3
Automated cherry pick of #11987
2020-06-26 11:29:41 -07:00
Gyuho Lee
c88a2c8cc1 Merge pull request #12078 from cfc4n/automated-cherry-pick-of-#11980-upstream-release-3.3
Automated cherry pick of #11980
2020-06-26 11:28:47 -07:00
Gyuho Lee
0b74a4dbdb Merge pull request #12082 from spzala/automated-cherry-pick-of-#11945-upstream-release-3.3
Automated cherry pick of #11945
2020-06-26 11:28:28 -07:00
Gyuho Lee
e959cda568 Merge pull request #12083 from spzala/automated-cherry-pick-of-#11793-upstream-release-3.3
Automated cherry pick of #11793
2020-06-26 11:28:17 -07:00
Sahdev P. Zala
a3e242c085 Discovery: do not allow passing negative cluster size
When an etcd instance attempts to perform service discovery, if a
cluster size with negative value  is provided, the etcd instance
will panic without recovery because of
2020-06-26 14:04:51 -04:00
Gyuho Lee
bccb40b7d9 wal: check out of range slice in "ReadAll", "decoder"
wal: add slice bound checks in decoder

CHANGELOG-3.5: add wal slice bound check
CHANGELOG-3.5: add "decodeRecord"

Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-06-25 21:00:05 -04:00
Changxin Miao
6be5c54c94 pkg: Fix dir permission check on Windows 2020-06-25 20:21:54 -04:00
cfc4n
ba7ff1eea9 auth: Customize simpleTokenTTL settings.
see https://github.com/etcd-io/etcd/issues/11978 for more detail.
2020-06-25 20:17:49 +08:00
cfc4n
8c885ad9a9 mvcc: chanLen 1024 is to biger,and it used more memory. 128 seems to be enough. Sometimes the consumption speed is more than the production speed.
See https://github.com/etcd-io/etcd/issues/11906 for more detail.
2020-06-25 19:51:51 +08:00
Gyuho Lee
cdc1c8f02f Merge pull request #12050 from spzala/automated-cherry-pick-of-#11845-upstream-release-3.3
Automated cherry pick of #11845
2020-06-24 20:42:14 -07:00
Gyuho Lee
94857c925a Merge pull request #12052 from spzala/automated-cherry-pick-of-#11830-upstream-release-3.3
Automated cherry pick of #11830
2020-06-24 20:42:06 -07:00
Gyuho Lee
56bf4c4779 Merge pull request #12053 from spzala/automated-cherry-pick-of-#11841-upstream-release-3.3
Automated cherry pick of #11841
2020-06-24 20:41:58 -07:00
Gyuho Lee
2e601c4611 Merge pull request #12058 from spzala/automated-cherry-pick-of-#11818-upstream-release-3.3
Automated cherry pick of #11818
2020-06-24 20:41:21 -07:00
Gyuho Lee
6992211021 Merge pull request #12059 from spzala/automated-cherry-pick-of-#11787-upstream-release-3.3
Automated cherry pick of #11787
2020-06-24 20:41:12 -07:00
Gyuho Lee
829f484165 Merge pull request #12063 from cfc4n/automated-cherry-pick-of-#11986-upstream-release-3.3
Automated cherry pick of #11986
2020-06-24 20:40:45 -07:00
Gyuho Lee
05f5b69673 Merge pull request #12067 from cfc4n/automated-cherry-pick-of-#12005-upstream-release-3.3
Automated cherry pick of #12005
2020-06-24 20:40:13 -07:00
Gyuho Lee
d18eeef0e7 Merge pull request #12069 from cfc4n/release-3.3
go.mod: fix incorrect package dependency when etcd clientv3 used as libary.
2020-06-24 20:40:02 -07:00
Gyuho Lee
1a79fe3758 Merge pull request #12071 from spzala/automated-cherry-pick-of-#12060-upstream-release-3.3
Automated cherry pick of #12060
2020-06-24 20:39:25 -07:00
Gyuho Lee
599beaee41 Merge pull request #12073 from spzala/automated-cherry-pick-of-#11798-upstream-release-3.3
Automated cherry pick of #11798
2020-06-24 20:39:00 -07:00
Sahdev P. Zala
bde76af5fa pkg: check file stats
modify file util.
2020-06-24 21:28:16 -04:00
Xiang Li
b85fc84c26 doc: add TLS related warnings 2020-06-24 16:41:53 -04:00
CFC4N
c3780bb216 go.mod: fix incorrect package dependency when etcd clientv3 used as libary.
Fixes: https://github.com/etcd-io/etcd/issues/12068
2020-06-24 21:45:06 +08:00
cfc4n
999df4e5a1 auth: return incorrect result 'ErrUserNotFound' when client request without username or username was empty.
Fiexs https://github.com/etcd-io/etcd/issues/12004 .
2020-06-24 19:10:51 +08:00
cfc4n
c4db372810 etcdserver:FDUsage set ticker to 10 minute from 5 seconds. This ticker will check File Descriptor Requirements ,and count all fds in used. And recorded some logs when in used >= limit/5*4. Just recorded message. If fds was more than 10K,It's low performance due to FDUsage() works. So need to increase it.
see https://github.com/etcd-io/etcd/issues/11969 for more detail.
2020-06-24 13:21:30 +08:00
Sahdev P. Zala
64f8b86e0d embed: fix compaction runtime err
Handle negative value input which currently gives a runtime error.
2020-06-23 14:47:58 -04:00
Hitoshi Mitake
585814082b etcdserver: don't let InternalAuthenticateRequest have password 2020-06-23 14:16:44 -04:00
Hitoshi Mitake
c511894ee5 Merge pull request #12051 from spzala/automated-cherry-pick-of-#11796-upstream-release-3.3
Automated cherry pick of #11796
2020-06-23 23:21:45 +09:00
Hitoshi Mitake
a89c2512ea etcdctl, etcdmain: warn about --insecure-skip-tls-verify options 2020-06-22 19:53:45 -04:00
Hitoshi Mitake
9e00f6f37f Documentation: note on the policy of insecure by default 2020-06-22 19:51:04 -04:00
Hitoshi Mitake
da1d42d111 Documentation: note on password strength 2020-06-22 19:48:51 -04:00
Xiang Li
f6b822dfe8 etcdmain: best effort detection of self pointing in tcp proxy 2020-06-22 19:39:34 -04:00
Gyuho Lee
3bf09a5859 Merge pull request #11758 from jingyih/automated-cherry-pick-of-#11754-upstream-release-3.3
Automated cherry pick of #11754 on release-3.3
2020-06-21 23:21:55 -07:00
Gyuho Lee
282cce72fd version: 3.3.22
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-05-20 15:42:36 -07:00
tangcong
a9d14cbb64 wal: add TestValidSnapshotEntriesAfterPurgeWal testcase
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-05-20 15:08:10 -07:00
tangcong
8ce10ea4a5 wal: fix crc mismatch crash bug 2020-05-20 11:39:00 -07:00
Gyuho Lee
669285f515 rafthttp: log snapshot downloads
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-05-20 11:01:13 -07:00
Changxin Miao
8781e1d44c etcdserver: watch stream got closed once one request is not permitted (#11708) 2020-04-06 07:09:15 -07:00
37 changed files with 390 additions and 79 deletions

View File

@@ -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` filtering of CN inter-peer connections is enabled. Nodes can only join the etcd cluster if their CN 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.

View File

@@ -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.
@@ -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
@@ -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)

View File

@@ -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.
@@ -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]
### Im 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

View File

@@ -35,7 +35,7 @@ const (
// var for testing purposes
var (
simpleTokenTTL = 5 * time.Minute
simpleTokenTTLDefault = 300 * time.Second
simpleTokenTTLResolution = 1 * time.Second
)
@@ -45,6 +45,7 @@ type simpleTokenTTLKeeper struct {
stopc chan struct{}
deleteTokenFunc func(string)
mu *sync.Mutex
simpleTokenTTL time.Duration
}
func (tm *simpleTokenTTLKeeper) stop() {
@@ -56,12 +57,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)
}
}
@@ -98,6 +99,7 @@ type tokenSimple struct {
simpleTokenKeeper *simpleTokenTTLKeeper
simpleTokensMu sync.Mutex
simpleTokens map[string]string // token -> username
simpleTokenTTL time.Duration
}
func (t *tokenSimple) genTokenPrefix() (string, error) {
@@ -146,6 +148,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 {
plog.Infof("deleting token %s for user %s", tk, username)
@@ -158,6 +164,7 @@ func (t *tokenSimple) enable() {
stopc: make(chan struct{}),
deleteTokenFunc: delf,
mu: &t.simpleTokensMu,
simpleTokenTTL: t.simpleTokenTTL,
}
go t.simpleTokenKeeper.run()
}
@@ -215,9 +222,10 @@ func (t *tokenSimple) isValidSimpleToken(ctx context.Context, token string) bool
return false
}
func newTokenProviderSimple(indexWaiter func(uint64) <-chan struct{}) *tokenSimple {
func newTokenProviderSimple(indexWaiter func(uint64) <-chan struct{}, TokenTTL time.Duration) *tokenSimple {
return &tokenSimple{
simpleTokens: make(map[string]string),
indexWaiter: indexWaiter,
simpleTokens: make(map[string]string),
indexWaiter: indexWaiter,
simpleTokenTTL: TokenTTL,
}
}

View File

@@ -22,9 +22,9 @@ import (
// TestSimpleTokenDisabled ensures that TokenProviderSimple behaves correctly when
// disabled.
func TestSimpleTokenDisabled(t *testing.T) {
initialState := newTokenProviderSimple(dummyIndexWaiter)
initialState := newTokenProviderSimple(dummyIndexWaiter, simpleTokenTTLDefault)
explicitlyDisabled := newTokenProviderSimple(dummyIndexWaiter)
explicitlyDisabled := newTokenProviderSimple(dummyIndexWaiter, simpleTokenTTLDefault)
explicitlyDisabled.enable()
explicitlyDisabled.disable()
@@ -46,7 +46,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(dummyIndexWaiter)
tp := newTokenProviderSimple(dummyIndexWaiter, simpleTokenTTLDefault)
tp.enable()
ctx := context.WithValue(context.WithValue(context.TODO(), AuthenticateParamIndex{}, uint64(1)), AuthenticateParamSimpleTokenPrefix{}, "dummy")
token, err := tp.assign(ctx, "user1", 0)

View File

@@ -23,6 +23,7 @@ import (
"strings"
"sync"
"sync/atomic"
"time"
"github.com/coreos/etcd/auth/authpb"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
@@ -812,7 +813,7 @@ func (as *authStore) IsAdminPermitted(authInfo *AuthInfo) error {
if !as.isAuthEnabled() {
return nil
}
if authInfo == nil {
if authInfo == nil || authInfo.Username == "" {
return ErrUserEmpty
}
@@ -1087,7 +1088,11 @@ func decomposeOpts(optstr string) (string, map[string]string, error) {
}
func NewTokenProvider(tokenOpts string, indexWaiter func(uint64) <-chan struct{}) (TokenProvider, error) {
// NewTokenProvider creates a new token provider.
func NewTokenProvider(
tokenOpts string,
indexWaiter func(uint64) <-chan struct{},
TokenTTL time.Duration) (TokenProvider, error) {
tokenType, typeSpecificOpts, err := decomposeOpts(tokenOpts)
if err != nil {
return nil, ErrInvalidAuthOpts
@@ -1096,7 +1101,7 @@ func NewTokenProvider(tokenOpts string, indexWaiter func(uint64) <-chan struct{}
switch tokenType {
case tokenTypeSimple:
plog.Warningf("simple token is not cryptographically signed")
return newTokenProviderSimple(indexWaiter), nil
return newTokenProviderSimple(indexWaiter, TokenTTL), nil
case tokenTypeJWT:
return newTokenProviderJWT(typeSpecificOpts)

View File

@@ -48,7 +48,7 @@ func TestNewAuthStoreRevision(t *testing.T) {
b, tPath := backend.NewDefaultTmpBackend()
defer os.Remove(tPath)
tp, err := NewTokenProvider(tokenTypeSimple, dummyIndexWaiter)
tp, err := NewTokenProvider(tokenTypeSimple, dummyIndexWaiter, simpleTokenTTLDefault)
if err != nil {
t.Fatal(err)
}
@@ -76,7 +76,7 @@ func TestNewAuthStoreRevision(t *testing.T) {
func setupAuthStore(t *testing.T) (store *authStore, teardownfunc func(t *testing.T)) {
b, tPath := backend.NewDefaultTmpBackend()
tp, err := NewTokenProvider(tokenTypeSimple, dummyIndexWaiter)
tp, err := NewTokenProvider(tokenTypeSimple, dummyIndexWaiter, simpleTokenTTLDefault)
if err != nil {
t.Fatal(err)
}
@@ -513,7 +513,7 @@ func TestAuthInfoFromCtxRace(t *testing.T) {
b, tPath := backend.NewDefaultTmpBackend()
defer os.Remove(tPath)
tp, err := NewTokenProvider(tokenTypeSimple, dummyIndexWaiter)
tp, err := NewTokenProvider(tokenTypeSimple, dummyIndexWaiter, simpleTokenTTLDefault)
if err != nil {
t.Fatal(err)
}
@@ -545,6 +545,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 {
@@ -579,7 +585,7 @@ func TestRecoverFromSnapshot(t *testing.T) {
as.Close()
tp, err := NewTokenProvider(tokenTypeSimple, dummyIndexWaiter)
tp, err := NewTokenProvider(tokenTypeSimple, dummyIndexWaiter, simpleTokenTTLDefault)
if err != nil {
t.Fatal(err)
}
@@ -612,13 +618,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)
@@ -661,7 +667,7 @@ func TestRolesOrder(t *testing.T) {
b, tPath := backend.NewDefaultTmpBackend()
defer os.Remove(tPath)
tp, err := NewTokenProvider(tokenTypeSimple, dummyIndexWaiter)
tp, err := NewTokenProvider(tokenTypeSimple, dummyIndexWaiter, simpleTokenTTLDefault)
if err != nil {
t.Fatal(err)
}
@@ -716,7 +722,7 @@ func testAuthInfoFromCtxWithRoot(t *testing.T, opts string) {
b, tPath := backend.NewDefaultTmpBackend()
defer os.Remove(tPath)
tp, err := NewTokenProvider(opts, dummyIndexWaiter)
tp, err := NewTokenProvider(opts, dummyIndexWaiter, simpleTokenTTLDefault)
if err != nil {
t.Fatal(err)
}

View File

@@ -63,8 +63,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

View File

@@ -211,7 +211,7 @@ func (d *discovery) createSelf(contents string) error {
return err
}
func (d *discovery) checkCluster() ([]*client.Node, int, uint64, error) {
func (d *discovery) checkCluster() ([]*client.Node, uint64, uint64, error) {
configKey := path.Join("/", d.cluster, "_config")
ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
// find cluster size
@@ -230,7 +230,7 @@ func (d *discovery) checkCluster() ([]*client.Node, int, uint64, error) {
}
return nil, 0, 0, err
}
size, err := strconv.Atoi(resp.Node.Value)
size, err := strconv.ParseUint(resp.Node.Value, 10, 0)
if err != nil {
return nil, 0, 0, ErrBadSizeKey
}
@@ -261,7 +261,7 @@ func (d *discovery) checkCluster() ([]*client.Node, int, uint64, error) {
if path.Base(nodes[i].Key) == path.Base(d.selfKey()) {
break
}
if i >= size-1 {
if uint64(i) >= size-1 {
return nodes[:size], size, resp.Index, ErrFullCluster
}
}
@@ -280,7 +280,7 @@ func (d *discovery) logAndBackoffForRetry(step string) {
d.clock.Sleep(retryTimeInSecond)
}
func (d *discovery) checkClusterRetry() ([]*client.Node, int, uint64, error) {
func (d *discovery) checkClusterRetry() ([]*client.Node, uint64, uint64, error) {
if d.retries < nRetries {
d.logAndBackoffForRetry("cluster status check")
return d.checkCluster()
@@ -300,8 +300,8 @@ func (d *discovery) waitNodesRetry() ([]*client.Node, error) {
return nil, ErrTooManyRetries
}
func (d *discovery) waitNodes(nodes []*client.Node, size int, index uint64) ([]*client.Node, error) {
if len(nodes) > size {
func (d *discovery) waitNodes(nodes []*client.Node, size uint64, index uint64) ([]*client.Node, error) {
if uint64(len(nodes)) > size {
nodes = nodes[:size]
}
// watch from the next index
@@ -317,8 +317,8 @@ func (d *discovery) waitNodes(nodes []*client.Node, size int, index uint64) ([]*
}
// wait for others
for len(all) < size {
plog.Noticef("found %d peer(s), waiting for %d more", len(all), size-len(all))
for uint64(len(all)) < size {
plog.Noticef("found %d peer(s), waiting for %d more", len(all), int(size-uint64(len(all))))
resp, err := w.Next(context.Background())
if err != nil {
if ce, ok := err.(*client.ClusterError); ok {
@@ -338,7 +338,7 @@ func (d *discovery) selfKey() string {
return path.Join("/", d.cluster, d.id.String())
}
func nodesToCluster(ns []*client.Node, size int) (string, error) {
func nodesToCluster(ns []*client.Node, size uint64) (string, error) {
s := make([]string, len(ns))
for i, n := range ns {
s[i] = n.Value
@@ -348,7 +348,7 @@ func nodesToCluster(ns []*client.Node, size int) (string, error) {
if err != nil {
return us, ErrInvalidURL
}
if m.Len() != size {
if uint64(m.Len()) != size {
return us, ErrDuplicateName
}
return us, nil

View File

@@ -215,7 +215,7 @@ func TestCheckCluster(t *testing.T) {
if reflect.DeepEqual(ns, tt.nodes) {
t.Errorf("#%d: nodes = %v, want %v", i, ns, tt.nodes)
}
if size != tt.wsize {
if size != uint64(tt.wsize) {
t.Errorf("#%d: size = %v, want %d", i, size, tt.wsize)
}
if index != tt.index {
@@ -299,7 +299,7 @@ func TestWaitNodes(t *testing.T) {
fc.Advance(time.Second * (0x1 << i))
}
}()
g, err := d.waitNodes(tt.nodes, 3, 0) // we do not care about index in this test
g, err := d.waitNodes(tt.nodes, uint64(3), 0) // we do not care about index in this test
if err != nil {
t.Errorf("#%d: err = %v, want %v", i, err, nil)
}
@@ -346,7 +346,7 @@ func TestCreateSelf(t *testing.T) {
func TestNodesToCluster(t *testing.T) {
tests := []struct {
nodes []*client.Node
size int
size uint64
wcluster string
werr error
}{

View File

@@ -222,6 +222,9 @@ type Config struct {
// Experimental flags
//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"`
@@ -284,6 +287,7 @@ func NewConfig() *Config {
Metrics: "basic",
EnableV2: DefaultEnableV2,
AuthToken: "simple",
AuthTokenTTL: 300,
}
cfg.InitialCluster = cfg.InitialClusterFromName(cfg.Name)
return cfg

View File

@@ -171,6 +171,7 @@ func StartEtcd(inCfg *Config) (e *Etcd, err error) {
StrictReconfigCheck: cfg.StrictReconfigCheck,
ClientCertAuthEnabled: cfg.ClientTLSInfo.ClientCertAuth,
AuthToken: cfg.AuthToken,
TokenTTL: cfg.AuthTokenTTL,
InitialCorruptCheck: cfg.ExperimentalInitialCorruptCheck,
CorruptCheckTime: cfg.ExperimentalCorruptCheckTime,
Debug: cfg.Debug,
@@ -564,7 +565,7 @@ func (e *Etcd) errHandler(err error) {
func parseCompactionRetention(mode, retention string) (ret time.Duration, err error) {
h, err := strconv.Atoi(retention)
if err == nil {
if err == nil && h >= 0 {
switch mode {
case compactor.ModeRevision:
ret = time.Duration(int64(h))

View File

@@ -59,7 +59,7 @@ func init() {
// TODO: secure by default when etcd enables secure gRPC by default.
rootCmd.PersistentFlags().BoolVar(&globalFlags.Insecure, "insecure-transport", true, "disable transport security for client connections")
rootCmd.PersistentFlags().BoolVar(&globalFlags.InsecureDiscovery, "insecure-discovery", true, "accept insecure SRV records describing cluster endpoints")
rootCmd.PersistentFlags().BoolVar(&globalFlags.InsecureSkipVerify, "insecure-skip-tls-verify", false, "skip server certificate verification")
rootCmd.PersistentFlags().BoolVar(&globalFlags.InsecureSkipVerify, "insecure-skip-tls-verify", false, "skip server certificate verification (CAUTION: this option should be enabled only for testing purposes)")
rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.CertFile, "cert", "", "identify secure client using this TLS certificate file")
rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.KeyFile, "key", "", "identify secure client using this TLS key file")
rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.CAFile, "cacert", "", "verify certificates of TLS-enabled secure servers using this CA bundle")

View File

@@ -215,6 +215,7 @@ func newConfig() *config {
// auth
fs.StringVar(&cfg.ec.AuthToken, "auth-token", cfg.ec.AuthToken, "Specify auth token specific options.")
fs.UintVar(&cfg.ec.AuthTokenTTL, "auth-token-ttl", cfg.ec.AuthTokenTTL, "The lifetime in seconds of the auth token.")
// experimental
fs.BoolVar(&cfg.ec.ExperimentalInitialCorruptCheck, "experimental-initial-corrupt-check", cfg.ec.ExperimentalInitialCorruptCheck, "Enable to check data corruption before serving any client/peer traffic.")

View File

@@ -218,7 +218,7 @@ func startProxy(cfg *config) error {
}
cfg.ec.Dir = filepath.Join(cfg.ec.Dir, "proxy")
err = os.MkdirAll(cfg.ec.Dir, fileutil.PrivateDirMode)
err = fileutil.TouchDirAll(cfg.ec.Dir)
if err != nil {
return err
}

View File

@@ -68,7 +68,7 @@ func newGatewayStartCommand() *cobra.Command {
cmd.Flags().StringVar(&gatewayListenAddr, "listen-addr", "127.0.0.1:23790", "listen address")
cmd.Flags().StringVar(&gatewayDNSCluster, "discovery-srv", "", "DNS domain used to bootstrap initial cluster")
cmd.Flags().BoolVar(&gatewayInsecureDiscovery, "insecure-discovery", false, "accept insecure SRV records")
cmd.Flags().StringVar(&gatewayCA, "trusted-ca-file", "", "path to the client server TLS CA file.")
cmd.Flags().StringVar(&gatewayCA, "trusted-ca-file", "", "path to the client server TLS CA file for verifying the discovered endpoints when discovery-srv is provided.")
cmd.Flags().StringSliceVar(&gatewayEndpoints, "endpoints", []string{"127.0.0.1:2379"}, "comma separated etcd cluster endpoints")
@@ -112,6 +112,40 @@ func startGateway(cmd *cobra.Command, args []string) {
}
}
lhost, lport, err := net.SplitHostPort(gatewayListenAddr)
if err != nil {
fmt.Println("failed to validate listen address:", gatewayListenAddr)
os.Exit(1)
}
laddrs, err := net.LookupHost(lhost)
if err != nil {
fmt.Println("failed to resolve listen host:", lhost)
os.Exit(1)
}
laddrsMap := make(map[string]bool)
for _, addr := range laddrs {
laddrsMap[addr] = true
}
for _, srv := range srvs.SRVs {
eaddrs, err := net.LookupHost(srv.Target)
if err != nil {
fmt.Println("failed to resolve endpoint host:", srv.Target)
os.Exit(1)
}
if fmt.Sprintf("%d", srv.Port) != lport {
continue
}
for _, ea := range eaddrs {
if laddrsMap[ea] {
fmt.Printf("SRV or endpoint (%s:%d->%s:%d) should not resolve to the gateway listen addr (%s)\n", srv.Target, srv.Port, ea, srv.Port, gatewayListenAddr)
os.Exit(1)
}
}
}
if len(srvs.Endpoints) == 0 {
plog.Fatalf("no endpoints found")
}

View File

@@ -127,7 +127,7 @@ func newGRPCProxyStartCommand() *cobra.Command {
cmd.Flags().StringVar(&grpcProxyCert, "cert", "", "identify secure connections with etcd servers using this TLS certificate file")
cmd.Flags().StringVar(&grpcProxyKey, "key", "", "identify secure connections with etcd servers using this TLS key file")
cmd.Flags().StringVar(&grpcProxyCA, "cacert", "", "verify certificates of TLS-enabled secure etcd servers using this CA bundle")
cmd.Flags().BoolVar(&grpcProxyInsecureSkipTLSVerify, "insecure-skip-tls-verify", false, "skip authentication of etcd server TLS certificates")
cmd.Flags().BoolVar(&grpcProxyInsecureSkipTLSVerify, "insecure-skip-tls-verify", false, "skip authentication of etcd server TLS certificates (CAUTION: this option should be enabled only for testing purposes)")
// client TLS for connecting to proxy
cmd.Flags().StringVar(&grpcProxyListenCert, "cert-file", "", "identify secure connections to the proxy using this TLS certificate file")
@@ -267,6 +267,9 @@ func newClientCfg(eps []string) (*clientv3.Config, error) {
return nil, err
}
clientTLS.InsecureSkipVerify = grpcProxyInsecureSkipTLSVerify
if clientTLS.InsecureSkipVerify {
plog.Warningf("--insecure-skip-tls-verify was given, this grpc proxy process skips authentication of etcd server TLS certificates. This option should be enabled only for testing purposes.")
}
cfg.TLS = clientTLS
plog.Infof("ClientTLS: %s", tls)
}

View File

@@ -193,6 +193,8 @@ profiling flags:
auth flags:
--auth-token 'simple'
Specify a v3 authentication token type and its options ('simple' or 'jwt').
--auth-token-ttl 300
Time (in seconds) of the auth-token-ttl.
experimental flags:
--experimental-initial-corrupt-check 'false'

View File

@@ -230,9 +230,10 @@ func (sws *serverWatchStream) recvLoop() error {
select {
case sws.ctrlStream <- wr:
continue
case <-sws.closec:
return nil
}
return nil
}
filters := FiltersFromRequest(creq)

View File

@@ -95,6 +95,7 @@ type ServerConfig struct {
ClientCertAuthEnabled bool
AuthToken string
TokenTTL uint
// InitialCorruptCheck is true to check data corruption on boot
// before serving any peer/client traffic.

View File

@@ -137,7 +137,7 @@ type loggableValueCompare struct {
Result Compare_CompareResult `protobuf:"varint,1,opt,name=result,proto3,enum=etcdserverpb.Compare_CompareResult"`
Target Compare_CompareTarget `protobuf:"varint,2,opt,name=target,proto3,enum=etcdserverpb.Compare_CompareTarget"`
Key []byte `protobuf:"bytes,3,opt,name=key,proto3"`
ValueSize int `protobuf:"bytes,7,opt,name=value_size,proto3"`
ValueSize int64 `protobuf:"varint,7,opt,name=value_size,proto3"`
RangeEnd []byte `protobuf:"bytes,64,opt,name=range_end,proto3"`
}
@@ -146,7 +146,7 @@ func newLoggableValueCompare(c *Compare, cv *Compare_Value) *loggableValueCompar
c.Result,
c.Target,
c.Key,
len(cv.Value),
int64(len(cv.Value)),
c.RangeEnd,
}
}
@@ -160,7 +160,7 @@ func (*loggableValueCompare) ProtoMessage() {}
// To preserve proto encoding of the key bytes, a faked out proto type is used here.
type loggablePutRequest struct {
Key []byte `protobuf:"bytes,1,opt,name=key,proto3"`
ValueSize int `protobuf:"varint,2,opt,name=value_size,proto3"`
ValueSize int64 `protobuf:"varint,2,opt,name=value_size,proto3"`
Lease int64 `protobuf:"varint,3,opt,name=lease,proto3"`
PrevKv bool `protobuf:"varint,4,opt,name=prev_kv,proto3"`
IgnoreValue bool `protobuf:"varint,5,opt,name=ignore_value,proto3"`
@@ -170,7 +170,7 @@ type loggablePutRequest struct {
func NewLoggablePutRequest(request *PutRequest) *loggablePutRequest {
return &loggablePutRequest{
request.Key,
len(request.Value),
int64(len(request.Value)),
request.Lease,
request.PrevKv,
request.IgnoreValue,

View File

@@ -129,6 +129,19 @@ var (
Help: "Server or member ID in hexadecimal format. 1 for 'server_id' label with current ID.",
},
[]string{"server_id"})
fdUsed = prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "os",
Subsystem: "fd",
Name: "used",
Help: "The number of used file descriptors.",
})
fdLimit = prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "os",
Subsystem: "fd",
Name: "limit",
Help: "The file descriptor limit.",
})
)
func init() {
@@ -149,6 +162,8 @@ func init() {
prometheus.MustRegister(currentVersion)
prometheus.MustRegister(currentGoVersion)
prometheus.MustRegister(serverID)
prometheus.MustRegister(fdUsed)
prometheus.MustRegister(fdLimit)
currentVersion.With(prometheus.Labels{
"server_version": version.Version,
@@ -159,7 +174,12 @@ func init() {
}
func monitorFileDescriptor(done <-chan struct{}) {
ticker := time.NewTicker(5 * time.Second)
// This ticker will check File Descriptor Requirements ,and count all fds in used.
// And recorded some logs when in used >= limit/5*4. Just recorded message.
// If fds was more than 10K,It's low performance due to FDUsage() works.
// So need to increase it.
// See https://github.com/etcd-io/etcd/issues/11969 for more detail.
ticker := time.NewTicker(10 * time.Minute)
defer ticker.Stop()
for {
used, err := runtime.FDUsage()
@@ -167,11 +187,13 @@ func monitorFileDescriptor(done <-chan struct{}) {
plog.Errorf("cannot monitor file descriptor usage (%v)", err)
return
}
fdUsed.Set(float64(used))
limit, err := runtime.FDLimit()
if err != nil {
plog.Errorf("cannot monitor file descriptor usage (%v)", err)
return
}
fdLimit.Set(float64(limit))
if used >= limit/5*4 {
plog.Warningf("80%% of the file descriptor limit is used [used = %d, limit = %d]", used, limit)
}

View File

@@ -461,6 +461,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 {
plog.Warningf("failed to create token provider,err is %v", err)

View File

@@ -378,9 +378,10 @@ func (s *EtcdServer) Authenticate(ctx context.Context, r *pb.AuthenticateRequest
return nil, err
}
// internalReq doesn't need to have Password because the above s.AuthStore().CheckPassword() already did it.
// In addition, it will let a WAL entry not record password as a plain text.
internalReq := &pb.InternalAuthenticateRequest{
Name: r.Name,
Password: r.Password,
SimpleToken: st,
}

View File

@@ -28,9 +28,8 @@ import (
var (
// chanBufLen is the length of the buffered chan
// for sending out watched events.
// TODO: find a good buf value. 1024 is just a random one that
// seems to be reasonable.
chanBufLen = 1024
// See https://github.com/etcd-io/etcd/issues/11906 for more detail.
chanBufLen = 128
// maxWatchersPerSync is the number of watchers to sync in a single batch
maxWatchersPerSync = 512

View File

@@ -18,5 +18,10 @@ package fileutil
import "os"
const (
// PrivateDirMode grants owner to make/remove files inside the directory.
PrivateDirMode = 0700
)
// OpenDir opens a directory for syncing.
func OpenDir(path string) (*os.File, error) { return os.Open(path) }

View File

@@ -21,6 +21,11 @@ import (
"syscall"
)
const (
// PrivateDirMode grants owner to make/remove files inside the directory.
PrivateDirMode = 0777
)
// OpenDir opens a directory in windows with write access for syncing.
func OpenDir(path string) (*os.File, error) {
fd, err := openDir(path)

View File

@@ -29,8 +29,6 @@ import (
const (
// PrivateFileMode grants owner to read/write a file.
PrivateFileMode = 0600
// PrivateDirMode grants owner to make/remove files inside the directory.
PrivateDirMode = 0700
)
var (
@@ -65,14 +63,22 @@ func ReadDir(dirpath string) ([]string, error) {
// TouchDirAll is similar to os.MkdirAll. It creates directories with 0700 permission if any directory
// does not exists. TouchDirAll also ensures the given directory is writable.
func TouchDirAll(dir string) error {
// If path is already a directory, MkdirAll does nothing
// and returns nil.
err := os.MkdirAll(dir, PrivateDirMode)
if err != nil {
// if mkdirAll("a/text") and "text" is not
// a directory, this will return syscall.ENOTDIR
return err
// If path is already a directory, MkdirAll does nothing and returns nil, so,
// first check if dir exist with an expected permission mode.
if Exist(dir) {
err := CheckDirPermission(dir, PrivateDirMode)
if err != nil {
plog.Warningf("check file permission: %v", err)
}
} else {
err := os.MkdirAll(dir, PrivateDirMode)
if err != nil {
// if mkdirAll("a/text") and "text" is not
// a directory, this will return syscall.ENOTDIR
return err
}
}
return IsDirWriteable(dir)
}
@@ -120,3 +126,22 @@ func ZeroToEnd(f *os.File) error {
_, err = f.Seek(off, io.SeekStart)
return err
}
// CheckDirPermission checks permission on an existing dir.
// Returns error if dir is empty or exist with a different permission than specified.
func CheckDirPermission(dir string, perm os.FileMode) error {
if !Exist(dir) {
return fmt.Errorf("directory %q empty, cannot check permission.", dir)
}
//check the existing permission on the directory
dirInfo, err := os.Stat(dir)
if err != nil {
return err
}
dirMode := dirInfo.Mode().Perm()
if dirMode != perm {
err = fmt.Errorf("directory %q exist, but the permission is %q. The recommended permission is %q to prevent possible unprivileged access to the data.", dir, dirInfo.Mode(), os.FileMode(PrivateDirMode))
return err
}
return nil
}

View File

@@ -163,3 +163,21 @@ func TestZeroToEnd(t *testing.T) {
}
}
}
func TestDirPermission(t *testing.T) {
tmpdir, err := ioutil.TempDir(os.TempDir(), "foo")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
tmpdir2 := filepath.Join(tmpdir, "testpermission")
// create a new dir with 0700
if err = CreateDirAll(tmpdir2); err != nil {
t.Fatal(err)
}
// check dir permission with mode different than created dir
if err = CheckDirPermission(tmpdir2, 0600); err == nil {
t.Errorf("expected error, got nil")
}
}

View File

@@ -16,7 +16,7 @@
package runtime
import (
"io/ioutil"
"os"
"syscall"
)
@@ -29,9 +29,20 @@ func FDLimit() (uint64, error) {
}
func FDUsage() (uint64, error) {
fds, err := ioutil.ReadDir("/proc/self/fd")
return countFiles("/proc/self/fd")
}
// countFiles reads the directory named by dirname and returns the count.
// This is same as stdlib "io/ioutil.ReadDir" but without sorting.
func countFiles(dirname string) (uint64, error) {
f, err := os.Open(dirname)
if err != nil {
return 0, err
}
return uint64(len(fds)), nil
list, err := f.Readdir(-1)
f.Close()
if err != nil {
return 0, err
}
return uint64(len(list)), nil
}

View File

@@ -31,6 +31,7 @@ import (
"strings"
"time"
"github.com/coreos/etcd/pkg/fileutil"
"github.com/coreos/etcd/pkg/tlsutil"
)
@@ -101,7 +102,8 @@ func (info TLSInfo) Empty() bool {
}
func SelfCert(dirpath string, hosts []string, additionalUsages ...x509.ExtKeyUsage) (info TLSInfo, err error) {
if err = os.MkdirAll(dirpath, 0700); err != nil {
err = fileutil.TouchDirAll(dirpath)
if err != nil {
return
}

View File

@@ -221,9 +221,10 @@ func (h *snapshotHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
downloadTook := time.Since(start)
dbSize := humanize.Bytes(uint64(n))
receivedBytes.WithLabelValues(from).Add(float64(n))
plog.Infof("successfully received and saved database snapshot [index: %d, from: %s, raft message size: %s, db size: %s]", m.Snapshot.Metadata.Index, types.ID(m.From), msgSize, dbSize)
plog.Infof("successfully received and saved database snapshot [index: %d, from: %s, raft message size: %s, db size: %s, took: %s]", m.Snapshot.Metadata.Index, types.ID(m.From), msgSize, dbSize, downloadTook)
if err := h.r.Process(context.TODO(), m); err != nil {
switch v := err.(type) {

View File

@@ -86,6 +86,9 @@ main() {
echo "Wrong etcd version in version/version.go. Expected ${etcd_version} but got ${VERSION}. Aborting."
exit 1
fi
echo "bin/etcd --version:"
bin/etcd --version
sleep 3
if [[ ! -z $(git status -s) ]]; then
echo "Committing version/version.go update."
@@ -152,6 +155,9 @@ main() {
# Sanity checks.
./release/etcd-${RELEASE_VERSION}-$(go env GOOS)-amd64/etcd --version | grep -q "etcd Version: ${VERSION}" || true
./release/etcd-${RELEASE_VERSION}-$(go env GOOS)-amd64/etcdctl version | grep -q "etcdctl version: ${VERSION}" || true
echo "./release/etcd-${RELEASE_VERSION}-$(go env GOOS)-amd64/etcd --version:"
./release/etcd-${RELEASE_VERSION}-$(go env GOOS)-amd64/etcd --version
sleep 3
# Upload artifacts.
if [ "${NO_UPLOAD}" == 1 ]; then

View File

@@ -26,7 +26,7 @@ import (
var (
// MinClusterVersion is the min cluster version this etcd binary is compatible with.
MinClusterVersion = "3.0.0"
Version = "3.3.21"
Version = "3.3.25"
APIVersion = "unknown"
// Git SHA Value will be set during build

View File

@@ -59,6 +59,11 @@ func (d *decoder) decode(rec *walpb.Record) error {
return d.decodeRecord(rec)
}
// raft max message size is set to 1 MB in etcd server
// assume projects set reasonable message size limit,
// thus entry size should never exceed 10 MB
const maxWALEntrySizeLimit = int64(10 * 1024 * 1024)
func (d *decoder) decodeRecord(rec *walpb.Record) error {
if len(d.brs) == 0 {
return io.EOF
@@ -79,6 +84,9 @@ func (d *decoder) decodeRecord(rec *walpb.Record) error {
}
recBytes, padBytes := decodeFrameSize(l)
if recBytes >= maxWALEntrySizeLimit-padBytes {
return ErrMaxWALEntrySizeLimitExceeded
}
data := make([]byte, recBytes+padBytes)
if _, err = io.ReadFull(d.brs[0], data); err != nil {

View File

@@ -55,12 +55,15 @@ var (
plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "wal")
ErrMetadataConflict = errors.New("wal: conflicting metadata found")
ErrFileNotFound = errors.New("wal: file not found")
ErrCRCMismatch = errors.New("wal: crc mismatch")
ErrSnapshotMismatch = errors.New("wal: snapshot mismatch")
ErrSnapshotNotFound = errors.New("wal: snapshot not found")
crcTable = crc32.MakeTable(crc32.Castagnoli)
ErrMetadataConflict = errors.New("wal: conflicting metadata found")
ErrFileNotFound = errors.New("wal: file not found")
ErrCRCMismatch = errors.New("wal: crc mismatch")
ErrSnapshotMismatch = errors.New("wal: snapshot mismatch")
ErrSnapshotNotFound = errors.New("wal: snapshot not found")
ErrSliceOutOfRange = errors.New("wal: slice bounds out of range")
ErrMaxWALEntrySizeLimitExceeded = errors.New("wal: max entry size limit exceeded")
ErrDecoderNotFound = errors.New("wal: decoder not found")
crcTable = crc32.MakeTable(crc32.Castagnoli)
)
// WAL is a logical representation of the stable storage.
@@ -90,7 +93,8 @@ type WAL struct {
}
// Create creates a WAL ready for appending records. The given metadata is
// recorded at the head of each WAL file, and can be retrieved with ReadAll.
// recorded at the head of each WAL file, and can be retrieved with ReadAll
// after the file is Open.
func Create(dirpath string, metadata []byte) (*WAL, error) {
if Exist(dirpath) {
return nil, os.ErrExist
@@ -321,6 +325,10 @@ func (w *WAL) ReadAll() (metadata []byte, state raftpb.HardState, ents []raftpb.
defer w.mu.Unlock()
rec := &walpb.Record{}
if w.decoder == nil {
return nil, state, nil, ErrDecoderNotFound
}
decoder := w.decoder
var match bool
@@ -328,8 +336,15 @@ func (w *WAL) ReadAll() (metadata []byte, state raftpb.HardState, ents []raftpb.
switch rec.Type {
case entryType:
e := mustUnmarshalEntry(rec.Data)
// 0 <= e.Index-w.start.Index - 1 < len(ents)
if e.Index > w.start.Index {
ents = append(ents[:e.Index-w.start.Index-1], e)
// prevent "panic: runtime error: slice bounds out of range [:13038096702221461992] with capacity 0"
up := e.Index - w.start.Index - 1
if up > uint64(len(ents)) {
// return error before append call causes runtime panic
return nil, state, nil, ErrSliceOutOfRange
}
ents = append(ents[:up], e)
}
w.enti = e.Index
case stateType:
@@ -456,6 +471,14 @@ func ValidSnapshotEntries(walDir string) ([]walpb.Snapshot, error) {
snaps = append(snaps, loadedSnap)
case stateType:
state = mustUnmarshalState(rec.Data)
case crcType:
crc := decoder.crc.Sum32()
// current crc of decoder must match the crc of the record.
// do no need to match 0 crc, since the decoder is a new one at this case.
if crc != 0 && rec.Validate(crc) != nil {
return nil, ErrCRCMismatch
}
decoder.updateCRC(rec.Crc)
}
}
// We do not have to read out all the WAL entries

View File

@@ -18,6 +18,7 @@ import (
"bytes"
"io"
"io/ioutil"
"math"
"os"
"path"
"path/filepath"
@@ -576,6 +577,35 @@ func TestOpenForRead(t *testing.T) {
}
}
func TestOpenWithMaxIndex(t *testing.T) {
p, err := ioutil.TempDir(os.TempDir(), "waltest")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(p)
// create WAL
w, err := Create(p, nil)
if err != nil {
t.Fatal(err)
}
defer w.Close()
es := []raftpb.Entry{{Index: uint64(math.MaxInt64)}}
if err = w.Save(raftpb.HardState{}, es); err != nil {
t.Fatal(err)
}
w.Close()
w, err = Open(p, walpb.Snapshot{})
if err != nil {
t.Fatal(err)
}
_, _, _, err = w.ReadAll()
if err == nil || err != ErrSliceOutOfRange {
t.Fatalf("err = %v, want ErrSliceOutOfRange", err)
}
}
func TestSaveEmpty(t *testing.T) {
var buf bytes.Buffer
var est raftpb.HardState
@@ -905,3 +935,80 @@ func TestValidSnapshotEntries(t *testing.T) {
t.Errorf("expected walSnaps %+v, got %+v", expected, walSnaps)
}
}
// TestValidSnapshotEntriesAfterPurgeWal ensure that there are many wal files, and after cleaning the first wal file,
// it can work well.
func TestValidSnapshotEntriesAfterPurgeWal(t *testing.T) {
oldSegmentSizeBytes := SegmentSizeBytes
SegmentSizeBytes = 64
defer func() {
SegmentSizeBytes = oldSegmentSizeBytes
}()
p, err := ioutil.TempDir(os.TempDir(), "waltest")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(p)
snap0 := walpb.Snapshot{Index: 0, Term: 0}
snap1 := walpb.Snapshot{Index: 1, Term: 1}
state1 := raftpb.HardState{Commit: 1, Term: 1}
snap2 := walpb.Snapshot{Index: 2, Term: 1}
snap3 := walpb.Snapshot{Index: 3, Term: 2}
state2 := raftpb.HardState{Commit: 3, Term: 2}
func() {
w, werr := Create(p, nil)
if werr != nil {
t.Fatal(werr)
}
defer w.Close()
// snap0 is implicitly created at index 0, term 0
if err = w.SaveSnapshot(snap1); err != nil {
t.Fatal(err)
}
if err = w.Save(state1, nil); err != nil {
t.Fatal(err)
}
if err = w.SaveSnapshot(snap2); err != nil {
t.Fatal(err)
}
if err = w.SaveSnapshot(snap3); err != nil {
t.Fatal(err)
}
for i := 0; i < 128; i++ {
if err = w.Save(state2, nil); err != nil {
t.Fatal(err)
}
}
}()
files, _, ferr := selectWALFiles(p, snap0)
if ferr != nil {
t.Fatal(ferr)
}
os.Remove(p + "/" + files[0])
_, err = ValidSnapshotEntries(p)
if err != nil {
t.Fatal(err)
}
}
// TestReadAllFail ensure ReadAll error if used without opening the WAL
func TestReadAllFail(t *testing.T) {
dir, err := ioutil.TempDir(os.TempDir(), "waltest")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
// create initial WAL
f, err := Create(dir, []byte("metadata"))
if err != nil {
t.Fatal(err)
}
f.Close()
// try to read without opening the WAL
_, _, _, err = f.ReadAll()
if err == nil || err != ErrDecoderNotFound {
t.Fatalf("err = %v, want ErrDecoderNotFound", err)
}
}