Skip to content

Commit 54ed4de

Browse files
authored
Merge pull request #9801 from gyuho/cipher-suites
*: support TLS cipher suite whitelist
2 parents 94f2e45 + 1a47c28 commit 54ed4de

16 files changed

+464
-20
lines changed

CHANGELOG-3.2.md

+17
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,23 @@
33
Previous change logs can be found at [CHANGELOG-3.1](https://github.com/coreos/etcd/blob/master/CHANGELOG-3.1.md).
44

55

6+
## [v3.2.22](https://github.com/coreos/etcd/releases/tag/v3.2.22) (TBD 2018-06)
7+
8+
See [code changes](https://github.com/coreos/etcd/compare/v3.2.21...v3.2.22) and [v3.2 upgrade guide](https://github.com/coreos/etcd/blob/master/Documentation/upgrades/upgrade_3_2.md) for any breaking changes. **Again, before running upgrades from any previous release, please make sure to read change logs below and [v3.2 upgrade guide](https://github.com/coreos/etcd/blob/master/Documentation/upgrades/upgrade_3_2.md).**
9+
10+
### etcd server
11+
12+
- Support TLS cipher suite whitelisting.
13+
- To block [weak cipher suites](https://github.com/coreos/etcd/issues/8320).
14+
- TLS handshake fails when client hello is requested with invalid cipher suites.
15+
- Add [`etcd --cipher-suites`](https://github.com/coreos/etcd/pull/9801) flag.
16+
- If empty, Go auto-populates the list.
17+
18+
### Go
19+
20+
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
21+
22+
623
## [v3.2.21](https://github.com/coreos/etcd/releases/tag/v3.2.21) (2018-05-31)
724

825
See [code changes](https://github.com/coreos/etcd/compare/v3.2.20...v3.2.21) and [v3.2 upgrade guide](https://github.com/coreos/etcd/blob/master/Documentation/upgrades/upgrade_3_2.md) for any breaking changes. **Again, before running upgrades from any previous release, please make sure to read change logs below and [v3.2 upgrade guide](https://github.com/coreos/etcd/blob/master/Documentation/upgrades/upgrade_3_2.md).**

CHANGELOG-3.3.md

+17
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,23 @@
33
Previous change logs can be found at [CHANGELOG-3.2](https://github.com/coreos/etcd/blob/master/CHANGELOG-3.2.md).
44

55

6+
## [v3.3.7](https://github.com/coreos/etcd/releases/tag/v3.3.7) (TBD 2018-06)
7+
8+
See [code changes](https://github.com/coreos/etcd/compare/v3.3.6...v3.3.7) and [v3.3 upgrade guide](https://github.com/coreos/etcd/blob/master/Documentation/upgrades/upgrade_3_3.md) for any breaking changes. **Again, before running upgrades from any previous release, please make sure to read change logs below and [v3.3 upgrade guide](https://github.com/coreos/etcd/blob/master/Documentation/upgrades/upgrade_3_3.md).**
9+
10+
### etcd server
11+
12+
- Support TLS cipher suite whitelisting.
13+
- To block [weak cipher suites](https://github.com/coreos/etcd/issues/8320).
14+
- TLS handshake fails when client hello is requested with invalid cipher suites.
15+
- Add [`etcd --cipher-suites`](https://github.com/coreos/etcd/pull/9801) flag.
16+
- If empty, Go auto-populates the list.
17+
18+
### Go
19+
20+
- Compile with [*Go 1.9.6*](https://golang.org/doc/devel/release.html#go1.9).
21+
22+
623
## [v3.3.6](https://github.com/coreos/etcd/releases/tag/v3.3.6) (2018-05-31)
724

825
See [code changes](https://github.com/coreos/etcd/compare/v3.3.5...v3.3.6) and [v3.3 upgrade guide](https://github.com/coreos/etcd/blob/master/Documentation/upgrades/upgrade_3_3.md) for any breaking changes. **Again, before running upgrades from any previous release, please make sure to read change logs below and [v3.3 upgrade guide](https://github.com/coreos/etcd/blob/master/Documentation/upgrades/upgrade_3_3.md).**

CHANGELOG-3.4.md

+15-1
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,12 @@ See [code changes](https://github.com/coreos/etcd/compare/v3.3.0...v3.4.0) and [
156156

157157
See [security doc](https://github.com/coreos/etcd/blob/master/Documentation/op-guide/security.md) for more details.
158158

159+
- Support TLS cipher suite whitelisting.
160+
- To block [weak cipher suites](https://github.com/coreos/etcd/issues/8320).
161+
- TLS handshake fails when client hello is requested with invalid cipher suites.
162+
- Add [`etcd --client-cipher-suites`](https://github.com/coreos/etcd/pull/9801) flag.
163+
- Add [`etcd --peer-cipher-suites`](https://github.com/coreos/etcd/pull/9801) flag.
164+
- If empty, Go auto-populates the list.
159165
- Add [`etcd --host-whitelist`](https://github.com/coreos/etcd/pull/9372) flag, [`etcdserver.Config.HostWhitelist`](https://github.com/coreos/etcd/pull/9372), and [`embed.Config.HostWhitelist`](https://github.com/coreos/etcd/pull/9372), to prevent ["DNS Rebinding"](https://en.wikipedia.org/wiki/DNS_rebinding) attack.
160166
- Any website can simply create an authorized DNS name, and direct DNS to `"localhost"` (or any other address). Then, all HTTP endpoints of etcd server listening on `"localhost"` becomes accessible, thus vulnerable to [DNS rebinding attacks (CVE-2018-5702)](https://bugs.chromium.org/p/project-zero/issues/detail?id=1447#c2).
161167
- Client origin enforce policy works as follow:
@@ -166,7 +172,6 @@ See [security doc](https://github.com/coreos/etcd/blob/master/Documentation/op-g
166172
- When specifying hostnames, loopback addresses are not added automatically. To allow loopback interfaces, add them to whitelist manually (e.g. `"localhost"`, `"127.0.0.1"`, etc.).
167173
- e.g. `etcd --host-whitelist example.com`, then the server will reject all HTTP requests whose Host field is not `example.com` (also rejects requests to `"localhost"`).
168174
- Support [`etcd --cors`](https://github.com/coreos/etcd/pull/9490) in v3 HTTP requests (gRPC gateway).
169-
- Support [TLS cipher suite lists](TODO).
170175
- Support [`ttl` field for `etcd` Authentication JWT token](https://github.com/coreos/etcd/pull/8302).
171176
- e.g. `etcd --auth-token jwt,pub-key=<pub key path>,priv-key=<priv key path>,sign-method=<sign method>,ttl=5m`.
172177
- Allow empty token provider in [`etcdserver.ServerConfig.AuthToken`](https://github.com/coreos/etcd/pull/9369).
@@ -207,6 +212,11 @@ See [security doc](https://github.com/coreos/etcd/blob/master/Documentation/op-g
207212
- If not given, etcd queries `_etcd-server-ssl._tcp.[YOUR_HOST]` and `_etcd-server._tcp.[YOUR_HOST]`.
208213
- If `--discovery-srv-name="foo"`, then query `_etcd-server-ssl-foo._tcp.[YOUR_HOST]` and `_etcd-server-foo._tcp.[YOUR_HOST]`.
209214
- Useful for operating multiple etcd clusters under the same domain.
215+
- Support TLS cipher suite whitelisting.
216+
- To block [weak cipher suites](https://github.com/coreos/etcd/issues/8320).
217+
- TLS handshake fails when client hello is requested with invalid cipher suites.
218+
- Add [`etcd --cipher-suites`](https://github.com/coreos/etcd/pull/9801) flag.
219+
- If empty, Go auto-populates the list.
210220
- Support [`etcd --cors`](https://github.com/coreos/etcd/pull/9490) in v3 HTTP requests (gRPC gateway).
211221
- Rename [`etcd --log-output` to `--log-outputs`](https://github.com/coreos/etcd/pull/9624) to support multiple log outputs.
212222
- **`etcd --log-output` will be deprecated in v3.5**.
@@ -271,6 +281,10 @@ Note: **v3.5 will deprecate `etcd --log-package-levels` flag for `capnslog`**; `
271281

272282
### Package `embed`
273283

284+
- Add [`embed.Config.CipherSuites`](https://github.com/coreos/etcd/pull/9801) to specify a list of supported cipher suites for TLS handshake between client/server and peers.
285+
- If empty, Go auto-populates the list.
286+
- Both `embed.Config.ClientTLSInfo.CipherSuites` and `embed.Config.CipherSuites` cannot be non-empty at the same time.
287+
- If not empty, specify either `embed.Config.ClientTLSInfo.CipherSuites` or `embed.Config.CipherSuites`.
274288
- Add [`embed.Config.InitialElectionTickAdvance`](https://github.com/coreos/etcd/pull/9591) to enable/disable initial election tick fast-forward.
275289
- `embed.NewConfig()` would return `*embed.Config` with `InitialElectionTickAdvance` as true by default.
276290
- Define [`embed.CompactorModePeriodic`](https://godoc.org/github.com/coreos/etcd/embed#pkg-variables) for `compactor.ModePeriodic`.

Documentation/op-guide/security.md

+45
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ The peer options work the same way as the client-to-server options:
3838

3939
If either a client-to-server or peer certificate is supplied the key must also be set. All of these configuration options are also available through the environment variables, `ETCD_CA_FILE`, `ETCD_PEER_CA_FILE` and so on.
4040

41+
`--cipher-suites`: Comma-separated list of supported TLS cipher suites between server/client and peers (empty will be auto-populated by Go). Available from v3.2.22+, v3.3.7+, and v3.4+.
42+
4143
## Example 1: Client-to-server transport security with HTTPS
4244

4345
For this, have a CA certificate (`ca.crt`) and signed key pair (`server.crt`, `server.key`) ready.
@@ -122,6 +124,49 @@ And also the response from the server:
122124
}
123125
```
124126

127+
Specify cipher suites to block [weak TLS cipher suites](https://github.com/coreos/etcd/issues/8320).
128+
129+
TLS handshake would fail when client hello is requested with invalid cipher suites.
130+
131+
For instance:
132+
133+
```bash
134+
$ etcd \
135+
--cert-file ./server.crt \
136+
--key-file ./server.key \
137+
--trusted-ca-file ./ca.crt \
138+
--cipher-suites TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
139+
```
140+
141+
Then, client requests must specify one of the cipher suites specified in the server:
142+
143+
```bash
144+
# valid cipher suite
145+
$ curl \
146+
--cacert ./ca.crt \
147+
--cert ./server.crt \
148+
--key ./server.key \
149+
-L [CLIENT-URL]/metrics \
150+
--ciphers ECDHE-RSA-AES128-GCM-SHA256
151+
152+
# request succeeds
153+
etcd_server_version{server_version="3.2.22"} 1
154+
...
155+
```
156+
157+
```bash
158+
# invalid cipher suite
159+
$ curl \
160+
--cacert ./ca.crt \
161+
--cert ./server.crt \
162+
--key ./server.key \
163+
-L [CLIENT-URL]/metrics \
164+
--ciphers ECDHE-RSA-DES-CBC3-SHA
165+
166+
# request fails with
167+
(35) error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure
168+
```
169+
125170
## Example 3: Transport security & client certificates in a cluster
126171

127172
etcd supports the same model as above for **peer communication**, that means the communication between etcd members in a cluster.

embed/config.go

+52-18
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"github.com/coreos/etcd/pkg/flags"
3333
"github.com/coreos/etcd/pkg/netutil"
3434
"github.com/coreos/etcd/pkg/srv"
35+
"github.com/coreos/etcd/pkg/tlsutil"
3536
"github.com/coreos/etcd/pkg/transport"
3637
"github.com/coreos/etcd/pkg/types"
3738

@@ -175,6 +176,11 @@ type Config struct {
175176
PeerTLSInfo transport.TLSInfo
176177
PeerAutoTLS bool
177178

179+
// CipherSuites is a list of supported TLS cipher suites between
180+
// client/server and peers. If empty, Go auto-populates the list.
181+
// Note that cipher suites are prioritized in the given order.
182+
CipherSuites []string `json:"cipher-suites"`
183+
178184
ClusterState string `json:"initial-cluster-state"`
179185
DNSCluster string `json:"discovery-srv"`
180186
DNSClusterServiceName string `json:"discovery-srv-name"`
@@ -510,6 +516,24 @@ func (cfg *configYAML) configFromFile(path string) error {
510516
return cfg.Validate()
511517
}
512518

519+
func updateCipherSuites(tls *transport.TLSInfo, ss []string) error {
520+
if len(tls.CipherSuites) > 0 && len(ss) > 0 {
521+
return fmt.Errorf("TLSInfo.CipherSuites is already specified (given %v)", ss)
522+
}
523+
if len(ss) > 0 {
524+
cs := make([]uint16, len(ss))
525+
for i, s := range ss {
526+
var ok bool
527+
cs[i], ok = tlsutil.GetCipherSuite(s)
528+
if !ok {
529+
return fmt.Errorf("unexpected TLS cipher suite %q", s)
530+
}
531+
}
532+
tls.CipherSuites = cs
533+
}
534+
return nil
535+
}
536+
513537
// Validate ensures that '*embed.Config' fields are properly configured.
514538
func (cfg *Config) Validate() error {
515539
if err := cfg.setupLogging(); err != nil {
@@ -703,39 +727,49 @@ func (cfg Config) defaultClientHost() bool {
703727
}
704728

705729
func (cfg *Config) ClientSelfCert() (err error) {
706-
if cfg.ClientAutoTLS && cfg.ClientTLSInfo.Empty() {
707-
chosts := make([]string, len(cfg.LCUrls))
708-
for i, u := range cfg.LCUrls {
709-
chosts[i] = u.Host
710-
}
711-
cfg.ClientTLSInfo, err = transport.SelfCert(cfg.logger, filepath.Join(cfg.Dir, "fixtures", "client"), chosts)
712-
return err
713-
} else if cfg.ClientAutoTLS {
730+
if !cfg.ClientAutoTLS {
731+
return nil
732+
}
733+
if !cfg.ClientTLSInfo.Empty() {
714734
if cfg.logger != nil {
715735
cfg.logger.Warn("ignoring client auto TLS since certs given")
716736
} else {
717737
plog.Warningf("ignoring client auto TLS since certs given")
718738
}
739+
return nil
719740
}
720-
return nil
741+
chosts := make([]string, len(cfg.LCUrls))
742+
for i, u := range cfg.LCUrls {
743+
chosts[i] = u.Host
744+
}
745+
cfg.ClientTLSInfo, err = transport.SelfCert(cfg.logger, filepath.Join(cfg.Dir, "fixtures", "client"), chosts)
746+
if err != nil {
747+
return err
748+
}
749+
return updateCipherSuites(&cfg.ClientTLSInfo, cfg.CipherSuites)
721750
}
722751

723752
func (cfg *Config) PeerSelfCert() (err error) {
724-
if cfg.PeerAutoTLS && cfg.PeerTLSInfo.Empty() {
725-
phosts := make([]string, len(cfg.LPUrls))
726-
for i, u := range cfg.LPUrls {
727-
phosts[i] = u.Host
728-
}
729-
cfg.PeerTLSInfo, err = transport.SelfCert(cfg.logger, filepath.Join(cfg.Dir, "fixtures", "peer"), phosts)
730-
return err
731-
} else if cfg.PeerAutoTLS {
753+
if !cfg.PeerAutoTLS {
754+
return nil
755+
}
756+
if !cfg.PeerTLSInfo.Empty() {
732757
if cfg.logger != nil {
733758
cfg.logger.Warn("ignoring peer auto TLS since certs given")
734759
} else {
735760
plog.Warningf("ignoring peer auto TLS since certs given")
736761
}
762+
return nil
737763
}
738-
return nil
764+
phosts := make([]string, len(cfg.LPUrls))
765+
for i, u := range cfg.LPUrls {
766+
phosts[i] = u.Host
767+
}
768+
cfg.PeerTLSInfo, err = transport.SelfCert(cfg.logger, filepath.Join(cfg.Dir, "fixtures", "peer"), phosts)
769+
if err != nil {
770+
return err
771+
}
772+
return updateCipherSuites(&cfg.PeerTLSInfo, cfg.CipherSuites)
739773
}
740774

741775
// UpdateDefaultClusterFromName updates cluster advertise URLs with, if available, default host,

embed/etcd.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,9 @@ func stopServers(ctx context.Context, ss *servers) {
375375
func (e *Etcd) Err() <-chan error { return e.errc }
376376

377377
func configurePeerListeners(cfg *Config) (peers []*peerListener, err error) {
378+
if err = updateCipherSuites(&cfg.PeerTLSInfo, cfg.CipherSuites); err != nil {
379+
return nil, err
380+
}
378381
if err = cfg.PeerSelfCert(); err != nil {
379382
if cfg.logger != nil {
380383
cfg.logger.Fatal("failed to get peer self-signed certs", zap.Error(err))
@@ -384,7 +387,11 @@ func configurePeerListeners(cfg *Config) (peers []*peerListener, err error) {
384387
}
385388
if !cfg.PeerTLSInfo.Empty() {
386389
if cfg.logger != nil {
387-
cfg.logger.Info("starting with peer TLS", zap.String("tls-info", fmt.Sprintf("%+v", cfg.PeerTLSInfo)))
390+
cfg.logger.Info(
391+
"starting with peer TLS",
392+
zap.String("tls-info", fmt.Sprintf("%+v", cfg.PeerTLSInfo)),
393+
zap.Strings("cipher-suites", cfg.CipherSuites),
394+
)
388395
} else {
389396
plog.Infof("peerTLS: %s", cfg.PeerTLSInfo)
390397
}
@@ -505,6 +512,9 @@ func (e *Etcd) servePeers() (err error) {
505512
}
506513

507514
func configureClientListeners(cfg *Config) (sctxs map[string]*serveCtx, err error) {
515+
if err = updateCipherSuites(&cfg.ClientTLSInfo, cfg.CipherSuites); err != nil {
516+
return nil, err
517+
}
508518
if err = cfg.ClientSelfCert(); err != nil {
509519
if cfg.logger != nil {
510520
cfg.logger.Fatal("failed to get client self-signed certs", zap.Error(err))
@@ -623,6 +633,7 @@ func (e *Etcd) serveClients() (err error) {
623633
e.cfg.logger.Info(
624634
"starting with client TLS",
625635
zap.String("tls-info", fmt.Sprintf("%+v", e.cfg.ClientTLSInfo)),
636+
zap.Strings("cipher-suites", e.cfg.CipherSuites),
626637
)
627638
} else {
628639
plog.Infof("ClientTLS: %s", e.cfg.ClientTLSInfo)

etcdmain/config.go

+3
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ func newConfig() *config {
208208
fs.BoolVar(&cfg.ec.PeerAutoTLS, "peer-auto-tls", false, "Peer TLS using generated certificates")
209209
fs.StringVar(&cfg.ec.PeerTLSInfo.CRLFile, "peer-crl-file", "", "Path to the peer certificate revocation list file.")
210210
fs.StringVar(&cfg.ec.PeerTLSInfo.AllowedCN, "peer-cert-allowed-cn", "", "Allowed CN for inter peer authentication.")
211+
fs.Var(flags.NewStringsValue(""), "cipher-suites", "Comma-separated list of supported TLS cipher suites between client/server and peers (empty will be auto-populated by Go).")
211212

212213
fs.Var(
213214
flags.NewUniqueURLsWithExceptions("*", "*"),
@@ -309,6 +310,8 @@ func (cfg *config) configFromCmdLine() error {
309310
cfg.ec.CORS = flags.UniqueURLsMapFromFlag(cfg.cf.flagSet, "cors")
310311
cfg.ec.HostWhitelist = flags.UniqueStringsMapFromFlag(cfg.cf.flagSet, "host-whitelist")
311312

313+
cfg.ec.CipherSuites = flags.StringsFromFlag(cfg.cf.flagSet, "cipher-suites")
314+
312315
// TODO: remove this in v3.5
313316
output := flags.UniqueStringsMapFromFlag(cfg.cf.flagSet, "log-output")
314317
oss1 := make([]string, 0, len(output))

etcdmain/help.go

+2
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ Security:
142142
Peer TLS using self-generated certificates if --peer-key-file and --peer-cert-file are not provided.
143143
--peer-crl-file ''
144144
Path to the peer certificate revocation list file.
145+
--cipher-suites ''
146+
Comma-separated list of supported TLS cipher suites between client/server and peers (empty will be auto-populated by Go).
145147
--cors '*'
146148
Comma-separated whitelist of origins for CORS, or cross-origin resource sharing, (empty or * means allow all).
147149
--host-whitelist '*'

0 commit comments

Comments
 (0)