Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
56536de551 | |||
a0ebf8cb1c | |||
13715724b8 | |||
22d65d8cc2 | |||
6c2add4142 | |||
6a3842776b | |||
641bddca0f | |||
21a1162ad1 | |||
e2cb9cbaec | |||
243074c5c5 | |||
26a73f2fa1 |
@ -112,6 +112,8 @@ type etcdProcessClusterConfig struct {
|
|||||||
isClientAutoTLS bool
|
isClientAutoTLS bool
|
||||||
isClientCRL bool
|
isClientCRL bool
|
||||||
|
|
||||||
|
cipherSuites []string
|
||||||
|
|
||||||
forceNewCluster bool
|
forceNewCluster bool
|
||||||
initialToken string
|
initialToken string
|
||||||
quotaBackendBytes int64
|
quotaBackendBytes int64
|
||||||
@ -296,6 +298,10 @@ func (cfg *etcdProcessClusterConfig) tlsArgs() (args []string) {
|
|||||||
args = append(args, "--client-crl-file", crlPath, "--client-cert-auth")
|
args = append(args, "--client-crl-file", crlPath, "--client-cert-auth")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(cfg.cipherSuites) > 0 {
|
||||||
|
args = append(args, "--cipher-suites", strings.Join(cfg.cipherSuites, ","))
|
||||||
|
}
|
||||||
|
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ package e2e
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
@ -23,19 +24,42 @@ import (
|
|||||||
|
|
||||||
"github.com/coreos/etcd/clientv3"
|
"github.com/coreos/etcd/clientv3"
|
||||||
"github.com/coreos/etcd/pkg/testutil"
|
"github.com/coreos/etcd/pkg/testutil"
|
||||||
|
"github.com/coreos/etcd/pkg/transport"
|
||||||
"github.com/coreos/etcd/pkg/types"
|
"github.com/coreos/etcd/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCtlV3MoveLeader(t *testing.T) {
|
func TestCtlV3MoveLeaderSecure(t *testing.T) {
|
||||||
|
testCtlV3MoveLeader(t, configTLS)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCtlV3MoveLeaderInsecure(t *testing.T) {
|
||||||
|
testCtlV3MoveLeader(t, configNoTLS)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCtlV3MoveLeader(t *testing.T, cfg etcdProcessClusterConfig) {
|
||||||
defer testutil.AfterTest(t)
|
defer testutil.AfterTest(t)
|
||||||
|
|
||||||
epc := setupEtcdctlTest(t, &configNoTLS, true)
|
epc := setupEtcdctlTest(t, &cfg, true)
|
||||||
defer func() {
|
defer func() {
|
||||||
if errC := epc.Close(); errC != nil {
|
if errC := epc.Close(); errC != nil {
|
||||||
t.Fatalf("error closing etcd processes (%v)", errC)
|
t.Fatalf("error closing etcd processes (%v)", errC)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
var tcfg *tls.Config
|
||||||
|
if cfg.clientTLS == clientTLS {
|
||||||
|
tinfo := transport.TLSInfo{
|
||||||
|
CertFile: certPath,
|
||||||
|
KeyFile: privateKeyPath,
|
||||||
|
TrustedCAFile: caPath,
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
tcfg, err = tinfo.ClientConfig()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var leadIdx int
|
var leadIdx int
|
||||||
var leaderID uint64
|
var leaderID uint64
|
||||||
var transferee uint64
|
var transferee uint64
|
||||||
@ -43,6 +67,7 @@ func TestCtlV3MoveLeader(t *testing.T) {
|
|||||||
cli, err := clientv3.New(clientv3.Config{
|
cli, err := clientv3.New(clientv3.Config{
|
||||||
Endpoints: []string{ep},
|
Endpoints: []string{ep},
|
||||||
DialTimeout: 3 * time.Second,
|
DialTimeout: 3 * time.Second,
|
||||||
|
TLS: tcfg,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -127,6 +127,8 @@ type cURLReq struct {
|
|||||||
header string
|
header string
|
||||||
|
|
||||||
metricsURLScheme string
|
metricsURLScheme string
|
||||||
|
|
||||||
|
ciphers string
|
||||||
}
|
}
|
||||||
|
|
||||||
// cURLPrefixArgs builds the beginning of a curl command for a given key
|
// cURLPrefixArgs builds the beginning of a curl command for a given key
|
||||||
@ -165,6 +167,10 @@ func cURLPrefixArgs(clus *etcdProcessCluster, method string, req cURLReq) []stri
|
|||||||
cmdArgs = append(cmdArgs, "-H", req.header)
|
cmdArgs = append(cmdArgs, "-H", req.header)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.ciphers != "" {
|
||||||
|
cmdArgs = append(cmdArgs, "--ciphers", req.ciphers)
|
||||||
|
}
|
||||||
|
|
||||||
switch method {
|
switch method {
|
||||||
case "POST", "PUT":
|
case "POST", "PUT":
|
||||||
dt := req.value
|
dt := req.value
|
||||||
|
@ -17,6 +17,7 @@ package e2e
|
|||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
@ -24,6 +25,7 @@ import (
|
|||||||
epb "github.com/coreos/etcd/etcdserver/api/v3election/v3electionpb"
|
epb "github.com/coreos/etcd/etcdserver/api/v3election/v3electionpb"
|
||||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||||
"github.com/coreos/etcd/pkg/testutil"
|
"github.com/coreos/etcd/pkg/testutil"
|
||||||
|
"github.com/coreos/etcd/version"
|
||||||
|
|
||||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||||
)
|
)
|
||||||
@ -390,3 +392,45 @@ type campaignResponse struct {
|
|||||||
Lease string `json:"lease,omitempty"`
|
Lease string `json:"lease,omitempty"`
|
||||||
} `json:"leader,omitempty"`
|
} `json:"leader,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestV3CurlCipherSuitesValid(t *testing.T) { testV3CurlCipherSuites(t, true) }
|
||||||
|
func TestV3CurlCipherSuitesMismatch(t *testing.T) { testV3CurlCipherSuites(t, false) }
|
||||||
|
func testV3CurlCipherSuites(t *testing.T, valid bool) {
|
||||||
|
cc := configClientTLS
|
||||||
|
cc.clusterSize = 1
|
||||||
|
cc.cipherSuites = []string{
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
|
||||||
|
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
|
||||||
|
}
|
||||||
|
testFunc := cipherSuiteTestValid
|
||||||
|
if !valid {
|
||||||
|
testFunc = cipherSuiteTestMismatch
|
||||||
|
}
|
||||||
|
testCtl(t, testFunc, withCfg(cc))
|
||||||
|
}
|
||||||
|
|
||||||
|
func cipherSuiteTestValid(cx ctlCtx) {
|
||||||
|
if err := cURLGet(cx.epc, cURLReq{
|
||||||
|
endpoint: "/metrics",
|
||||||
|
expected: fmt.Sprintf(`etcd_server_version{server_version="%s"} 1`, version.Version),
|
||||||
|
metricsURLScheme: cx.cfg.metricsURLScheme,
|
||||||
|
ciphers: "ECDHE-RSA-AES128-GCM-SHA256", // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
||||||
|
}); err != nil {
|
||||||
|
cx.t.Fatalf("failed get with curl (%v)", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cipherSuiteTestMismatch(cx ctlCtx) {
|
||||||
|
if err := cURLGet(cx.epc, cURLReq{
|
||||||
|
endpoint: "/metrics",
|
||||||
|
expected: "alert handshake failure",
|
||||||
|
metricsURLScheme: cx.cfg.metricsURLScheme,
|
||||||
|
ciphers: "ECDHE-RSA-DES-CBC3-SHA", // TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
|
||||||
|
}); err != nil {
|
||||||
|
cx.t.Fatalf("failed get with curl (%v)", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -31,6 +31,7 @@ import (
|
|||||||
"github.com/coreos/etcd/pkg/cors"
|
"github.com/coreos/etcd/pkg/cors"
|
||||||
"github.com/coreos/etcd/pkg/netutil"
|
"github.com/coreos/etcd/pkg/netutil"
|
||||||
"github.com/coreos/etcd/pkg/srv"
|
"github.com/coreos/etcd/pkg/srv"
|
||||||
|
"github.com/coreos/etcd/pkg/tlsutil"
|
||||||
"github.com/coreos/etcd/pkg/transport"
|
"github.com/coreos/etcd/pkg/transport"
|
||||||
"github.com/coreos/etcd/pkg/types"
|
"github.com/coreos/etcd/pkg/types"
|
||||||
|
|
||||||
@ -183,6 +184,11 @@ type Config struct {
|
|||||||
PeerTLSInfo transport.TLSInfo
|
PeerTLSInfo transport.TLSInfo
|
||||||
PeerAutoTLS bool
|
PeerAutoTLS bool
|
||||||
|
|
||||||
|
// CipherSuites is a list of supported TLS cipher suites between
|
||||||
|
// client/server and peers. If empty, Go auto-populates the list.
|
||||||
|
// Note that cipher suites are prioritized in the given order.
|
||||||
|
CipherSuites []string `json:"cipher-suites"`
|
||||||
|
|
||||||
// debug
|
// debug
|
||||||
|
|
||||||
Debug bool `json:"debug"`
|
Debug bool `json:"debug"`
|
||||||
@ -426,6 +432,24 @@ func (cfg *configYAML) configFromFile(path string) error {
|
|||||||
return cfg.Validate()
|
return cfg.Validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateCipherSuites(tls *transport.TLSInfo, ss []string) error {
|
||||||
|
if len(tls.CipherSuites) > 0 && len(ss) > 0 {
|
||||||
|
return fmt.Errorf("TLSInfo.CipherSuites is already specified (given %v)", ss)
|
||||||
|
}
|
||||||
|
if len(ss) > 0 {
|
||||||
|
cs := make([]uint16, len(ss))
|
||||||
|
for i, s := range ss {
|
||||||
|
var ok bool
|
||||||
|
cs[i], ok = tlsutil.GetCipherSuite(s)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unexpected TLS cipher suite %q", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tls.CipherSuites = cs
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Validate ensures that '*embed.Config' fields are properly configured.
|
// Validate ensures that '*embed.Config' fields are properly configured.
|
||||||
func (cfg *Config) Validate() error {
|
func (cfg *Config) Validate() error {
|
||||||
if err := checkBindURLs(cfg.LPUrls); err != nil {
|
if err := checkBindURLs(cfg.LPUrls); err != nil {
|
||||||
@ -562,31 +586,41 @@ func (cfg Config) defaultClientHost() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Config) ClientSelfCert() (err error) {
|
func (cfg *Config) ClientSelfCert() (err error) {
|
||||||
if cfg.ClientAutoTLS && cfg.ClientTLSInfo.Empty() {
|
if !cfg.ClientAutoTLS {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !cfg.ClientTLSInfo.Empty() {
|
||||||
|
plog.Warningf("ignoring client auto TLS since certs given")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
chosts := make([]string, len(cfg.LCUrls))
|
chosts := make([]string, len(cfg.LCUrls))
|
||||||
for i, u := range cfg.LCUrls {
|
for i, u := range cfg.LCUrls {
|
||||||
chosts[i] = u.Host
|
chosts[i] = u.Host
|
||||||
}
|
}
|
||||||
cfg.ClientTLSInfo, err = transport.SelfCert(filepath.Join(cfg.Dir, "fixtures", "client"), chosts)
|
cfg.ClientTLSInfo, err = transport.SelfCert(filepath.Join(cfg.Dir, "fixtures", "client"), chosts)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if cfg.ClientAutoTLS {
|
|
||||||
plog.Warningf("ignoring client auto TLS since certs given")
|
|
||||||
}
|
}
|
||||||
return nil
|
return updateCipherSuites(&cfg.ClientTLSInfo, cfg.CipherSuites)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Config) PeerSelfCert() (err error) {
|
func (cfg *Config) PeerSelfCert() (err error) {
|
||||||
if cfg.PeerAutoTLS && cfg.PeerTLSInfo.Empty() {
|
if !cfg.PeerAutoTLS {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !cfg.PeerTLSInfo.Empty() {
|
||||||
|
plog.Warningf("ignoring peer auto TLS since certs given")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
phosts := make([]string, len(cfg.LPUrls))
|
phosts := make([]string, len(cfg.LPUrls))
|
||||||
for i, u := range cfg.LPUrls {
|
for i, u := range cfg.LPUrls {
|
||||||
phosts[i] = u.Host
|
phosts[i] = u.Host
|
||||||
}
|
}
|
||||||
cfg.PeerTLSInfo, err = transport.SelfCert(filepath.Join(cfg.Dir, "fixtures", "peer"), phosts)
|
cfg.PeerTLSInfo, err = transport.SelfCert(filepath.Join(cfg.Dir, "fixtures", "peer"), phosts)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if cfg.PeerAutoTLS {
|
|
||||||
plog.Warningf("ignoring peer auto TLS since certs given")
|
|
||||||
}
|
}
|
||||||
return nil
|
return updateCipherSuites(&cfg.PeerTLSInfo, cfg.CipherSuites)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateDefaultClusterFromName updates cluster advertise URLs with, if available, default host,
|
// UpdateDefaultClusterFromName updates cluster advertise URLs with, if available, default host,
|
||||||
|
@ -42,7 +42,7 @@ import (
|
|||||||
"github.com/coreos/etcd/rafthttp"
|
"github.com/coreos/etcd/rafthttp"
|
||||||
|
|
||||||
"github.com/coreos/pkg/capnslog"
|
"github.com/coreos/pkg/capnslog"
|
||||||
"github.com/grpc-ecosystem/go-grpc-prometheus"
|
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
|
||||||
"github.com/soheilhy/cmux"
|
"github.com/soheilhy/cmux"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
@ -302,6 +302,9 @@ func stopServers(ctx context.Context, ss *servers) {
|
|||||||
func (e *Etcd) Err() <-chan error { return e.errc }
|
func (e *Etcd) Err() <-chan error { return e.errc }
|
||||||
|
|
||||||
func startPeerListeners(cfg *Config) (peers []*peerListener, err error) {
|
func startPeerListeners(cfg *Config) (peers []*peerListener, err error) {
|
||||||
|
if err = updateCipherSuites(&cfg.PeerTLSInfo, cfg.CipherSuites); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if err = cfg.PeerSelfCert(); err != nil {
|
if err = cfg.PeerSelfCert(); err != nil {
|
||||||
plog.Fatalf("could not get certs (%v)", err)
|
plog.Fatalf("could not get certs (%v)", err)
|
||||||
}
|
}
|
||||||
@ -387,6 +390,9 @@ func (e *Etcd) servePeers() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func startClientListeners(cfg *Config) (sctxs map[string]*serveCtx, err error) {
|
func startClientListeners(cfg *Config) (sctxs map[string]*serveCtx, err error) {
|
||||||
|
if err = updateCipherSuites(&cfg.ClientTLSInfo, cfg.CipherSuites); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if err = cfg.ClientSelfCert(); err != nil {
|
if err = cfg.ClientSelfCert(); err != nil {
|
||||||
plog.Fatalf("could not get certs (%v)", err)
|
plog.Fatalf("could not get certs (%v)", err)
|
||||||
}
|
}
|
||||||
|
@ -109,11 +109,12 @@ func (*discardValue) Type() string { return "" }
|
|||||||
|
|
||||||
func clientConfigFromCmd(cmd *cobra.Command) *clientConfig {
|
func clientConfigFromCmd(cmd *cobra.Command) *clientConfig {
|
||||||
fs := cmd.InheritedFlags()
|
fs := cmd.InheritedFlags()
|
||||||
|
if strings.HasPrefix(cmd.Use, "watch") {
|
||||||
// silence "pkg/flags: unrecognized environment variable ETCDCTL_WATCH_KEY=foo" warnings
|
// silence "pkg/flags: unrecognized environment variable ETCDCTL_WATCH_KEY=foo" warnings
|
||||||
// silence "pkg/flags: unrecognized environment variable ETCDCTL_WATCH_RANGE_END=bar" warnings
|
// silence "pkg/flags: unrecognized environment variable ETCDCTL_WATCH_RANGE_END=bar" warnings
|
||||||
fs.AddFlag(&pflag.Flag{Name: "watch-key", Value: &discardValue{}})
|
fs.AddFlag(&pflag.Flag{Name: "watch-key", Value: &discardValue{}})
|
||||||
fs.AddFlag(&pflag.Flag{Name: "watch-range-end", Value: &discardValue{}})
|
fs.AddFlag(&pflag.Flag{Name: "watch-range-end", Value: &discardValue{}})
|
||||||
|
}
|
||||||
flags.SetPflagsFromEnv("ETCDCTL", fs)
|
flags.SetPflagsFromEnv("ETCDCTL", fs)
|
||||||
|
|
||||||
debug, err := cmd.Flags().GetBool("debug")
|
debug, err := cmd.Flags().GetBool("debug")
|
||||||
|
@ -17,7 +17,6 @@ package command
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/coreos/etcd/clientv3"
|
"github.com/coreos/etcd/clientv3"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -53,16 +52,12 @@ func transferLeadershipCommandFunc(cmd *cobra.Command, args []string) {
|
|||||||
var leaderCli *clientv3.Client
|
var leaderCli *clientv3.Client
|
||||||
var leaderID uint64
|
var leaderID uint64
|
||||||
for _, ep := range eps {
|
for _, ep := range eps {
|
||||||
cli, err := clientv3.New(clientv3.Config{
|
cfg := clientConfigFromCmd(cmd)
|
||||||
Endpoints: []string{ep},
|
cfg.endpoints = []string{ep}
|
||||||
DialTimeout: 3 * time.Second,
|
cli := cfg.mustClient()
|
||||||
})
|
resp, serr := cli.Status(ctx, ep)
|
||||||
if err != nil {
|
if serr != nil {
|
||||||
ExitWithError(ExitError, err)
|
ExitWithError(ExitError, serr)
|
||||||
}
|
|
||||||
resp, err := cli.Status(ctx, ep)
|
|
||||||
if err != nil {
|
|
||||||
ExitWithError(ExitError, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.Header.GetMemberId() == resp.Leader {
|
if resp.Header.GetMemberId() == resp.Leader {
|
||||||
|
@ -190,6 +190,8 @@ func newConfig() *config {
|
|||||||
fs.StringVar(&cfg.ec.PeerTLSInfo.CRLFile, "peer-crl-file", "", "Path to the peer certificate revocation list file.")
|
fs.StringVar(&cfg.ec.PeerTLSInfo.CRLFile, "peer-crl-file", "", "Path to the peer certificate revocation list file.")
|
||||||
fs.StringVar(&cfg.ec.PeerTLSInfo.AllowedCN, "peer-cert-allowed-cn", "", "Allowed CN for inter peer authentication.")
|
fs.StringVar(&cfg.ec.PeerTLSInfo.AllowedCN, "peer-cert-allowed-cn", "", "Allowed CN for inter peer authentication.")
|
||||||
|
|
||||||
|
fs.Var(flags.NewStringsValueV2(""), "cipher-suites", "Comma-separated list of supported TLS cipher suites between client/server and peers (empty will be auto-populated by Go).")
|
||||||
|
|
||||||
// logging
|
// logging
|
||||||
fs.BoolVar(&cfg.ec.Debug, "debug", false, "Enable debug-level logging for etcd.")
|
fs.BoolVar(&cfg.ec.Debug, "debug", false, "Enable debug-level logging for etcd.")
|
||||||
fs.StringVar(&cfg.ec.LogPkgLevels, "log-package-levels", "", "Specify a particular log level for each etcd package (eg: 'etcdmain=CRITICAL,etcdserver=DEBUG').")
|
fs.StringVar(&cfg.ec.LogPkgLevels, "log-package-levels", "", "Specify a particular log level for each etcd package (eg: 'etcdmain=CRITICAL,etcdserver=DEBUG').")
|
||||||
@ -275,6 +277,8 @@ func (cfg *config) configFromCmdLine() error {
|
|||||||
cfg.ec.ListenMetricsUrls = []url.URL(u)
|
cfg.ec.ListenMetricsUrls = []url.URL(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfg.ec.CipherSuites = flags.StringsFromFlagV2(cfg.cf.flagSet, "cipher-suites")
|
||||||
|
|
||||||
cfg.ec.ClusterState = cfg.cf.clusterState.String()
|
cfg.ec.ClusterState = cfg.cf.clusterState.String()
|
||||||
cfg.cp.Fallback = cfg.cf.fallback.String()
|
cfg.cp.Fallback = cfg.cf.fallback.String()
|
||||||
cfg.cp.Proxy = cfg.cf.proxy.String()
|
cfg.cp.Proxy = cfg.cf.proxy.String()
|
||||||
|
@ -160,6 +160,8 @@ security flags:
|
|||||||
peer TLS using self-generated certificates if --peer-key-file and --peer-cert-file are not provided.
|
peer TLS using self-generated certificates if --peer-key-file and --peer-cert-file are not provided.
|
||||||
--peer-crl-file ''
|
--peer-crl-file ''
|
||||||
path to the peer certificate revocation list file.
|
path to the peer certificate revocation list file.
|
||||||
|
--cipher-suites ''
|
||||||
|
comma-separated list of supported TLS cipher suites between client/server and peers (empty will be auto-populated by Go).
|
||||||
|
|
||||||
logging flags
|
logging flags
|
||||||
|
|
||||||
|
71
integration/v3_tls_test.go
Normal file
71
integration/v3_tls_test.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// Copyright 2018 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/etcd/clientv3"
|
||||||
|
"github.com/coreos/etcd/pkg/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTLSClientCipherSuitesValid(t *testing.T) { testTLSCipherSuites(t, true) }
|
||||||
|
func TestTLSClientCipherSuitesMismatch(t *testing.T) { testTLSCipherSuites(t, false) }
|
||||||
|
|
||||||
|
// testTLSCipherSuites ensures mismatching client-side cipher suite
|
||||||
|
// fail TLS handshake with the server.
|
||||||
|
func testTLSCipherSuites(t *testing.T, valid bool) {
|
||||||
|
defer testutil.AfterTest(t)
|
||||||
|
|
||||||
|
cipherSuites := []uint16{
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||||
|
}
|
||||||
|
srvTLS, cliTLS := testTLSInfo, testTLSInfo
|
||||||
|
if valid {
|
||||||
|
srvTLS.CipherSuites, cliTLS.CipherSuites = cipherSuites, cipherSuites
|
||||||
|
} else {
|
||||||
|
srvTLS.CipherSuites, cliTLS.CipherSuites = cipherSuites[:2], cipherSuites[2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
clus := NewClusterV3(t, &ClusterConfig{Size: 1, ClientTLS: &srvTLS})
|
||||||
|
defer clus.Terminate(t)
|
||||||
|
|
||||||
|
cc, err := cliTLS.ClientConfig()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cli, cerr := clientv3.New(clientv3.Config{
|
||||||
|
Endpoints: []string{clus.Members[0].GRPCAddr()},
|
||||||
|
DialTimeout: time.Second,
|
||||||
|
TLS: cc,
|
||||||
|
})
|
||||||
|
if cli != nil {
|
||||||
|
cli.Close()
|
||||||
|
}
|
||||||
|
if !valid && cerr != context.DeadlineExceeded {
|
||||||
|
t.Fatalf("expected %v with TLS handshake failure, got %v", context.DeadlineExceeded, cerr)
|
||||||
|
}
|
||||||
|
if valid && cerr != nil {
|
||||||
|
t.Fatalf("expected TLS handshake success, got %v", cerr)
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,12 @@
|
|||||||
|
|
||||||
package flags
|
package flags
|
||||||
|
|
||||||
import "errors"
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
// NewStringsFlag creates a new string flag for which any one of the given
|
// NewStringsFlag creates a new string flag for which any one of the given
|
||||||
// strings is a valid value, and any other value is an error.
|
// strings is a valid value, and any other value is an error.
|
||||||
@ -47,3 +52,34 @@ func (ss *StringsFlag) Set(s string) error {
|
|||||||
func (ss *StringsFlag) String() string {
|
func (ss *StringsFlag) String() string {
|
||||||
return ss.val
|
return ss.val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StringsValueV2 wraps "sort.StringSlice".
|
||||||
|
type StringsValueV2 sort.StringSlice
|
||||||
|
|
||||||
|
// Set parses a command line set of strings, separated by comma.
|
||||||
|
// Implements "flag.Value" interface.
|
||||||
|
func (ss *StringsValueV2) Set(s string) error {
|
||||||
|
*ss = strings.Split(s, ",")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements "flag.Value" interface.
|
||||||
|
func (ss *StringsValueV2) String() string { return strings.Join(*ss, ",") }
|
||||||
|
|
||||||
|
// NewStringsValueV2 implements string slice as "flag.Value" interface.
|
||||||
|
// Given value is to be separated by comma.
|
||||||
|
func NewStringsValueV2(s string) (ss *StringsValueV2) {
|
||||||
|
if s == "" {
|
||||||
|
return &StringsValueV2{}
|
||||||
|
}
|
||||||
|
ss = new(StringsValueV2)
|
||||||
|
if err := ss.Set(s); err != nil {
|
||||||
|
plog.Panicf("new StringsValueV2 should never fail: %v", err)
|
||||||
|
}
|
||||||
|
return ss
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringsFromFlagV2 returns a string slice from the flag.
|
||||||
|
func StringsFromFlagV2(fs *flag.FlagSet, flagName string) []string {
|
||||||
|
return []string(*fs.Lookup(flagName).Value.(*StringsValueV2))
|
||||||
|
}
|
||||||
|
51
pkg/tlsutil/cipher_suites.go
Normal file
51
pkg/tlsutil/cipher_suites.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2018 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package tlsutil
|
||||||
|
|
||||||
|
import "crypto/tls"
|
||||||
|
|
||||||
|
// cipher suites implemented by Go
|
||||||
|
// https://github.com/golang/go/blob/dev.boringcrypto.go1.10/src/crypto/tls/cipher_suites.go
|
||||||
|
var cipherSuites = map[string]uint16{
|
||||||
|
"TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA,
|
||||||
|
"TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
"TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||||
|
"TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
"TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||||
|
"TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
"TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||||
|
"TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
||||||
|
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCipherSuite returns the corresponding cipher suite,
|
||||||
|
// and boolean value if it is supported.
|
||||||
|
func GetCipherSuite(s string) (uint16, bool) {
|
||||||
|
v, ok := cipherSuites[s]
|
||||||
|
return v, ok
|
||||||
|
}
|
42
pkg/tlsutil/cipher_suites_test.go
Normal file
42
pkg/tlsutil/cipher_suites_test.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Copyright 2018 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package tlsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/importer"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetCipherSuites(t *testing.T) {
|
||||||
|
pkg, err := importer.For("source", nil).Import("crypto/tls")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cm := make(map[string]uint16)
|
||||||
|
for _, s := range pkg.Scope().Names() {
|
||||||
|
if strings.HasPrefix(s, "TLS_RSA_") || strings.HasPrefix(s, "TLS_ECDHE_") {
|
||||||
|
v, ok := GetCipherSuite(s)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Go implements missing cipher suite %q (%v)", s, v)
|
||||||
|
}
|
||||||
|
cm[s] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(cm, cipherSuites) {
|
||||||
|
t.Fatalf("found unmatched cipher suites %v (Go) != %v", cm, cipherSuites)
|
||||||
|
}
|
||||||
|
}
|
@ -72,6 +72,11 @@ type TLSInfo struct {
|
|||||||
// connection will be closed immediately afterwards.
|
// connection will be closed immediately afterwards.
|
||||||
HandshakeFailure func(*tls.Conn, error)
|
HandshakeFailure func(*tls.Conn, error)
|
||||||
|
|
||||||
|
// CipherSuites is a list of supported cipher suites.
|
||||||
|
// If empty, Go auto-populates it by default.
|
||||||
|
// Note that cipher suites are prioritized in the given order.
|
||||||
|
CipherSuites []uint16
|
||||||
|
|
||||||
selfCert bool
|
selfCert bool
|
||||||
|
|
||||||
// parseFunc exists to simplify testing. Typically, parseFunc
|
// parseFunc exists to simplify testing. Typically, parseFunc
|
||||||
@ -178,6 +183,10 @@ func (info TLSInfo) baseConfig() (*tls.Config, error) {
|
|||||||
ServerName: info.ServerName,
|
ServerName: info.ServerName,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(info.CipherSuites) > 0 {
|
||||||
|
cfg.CipherSuites = info.CipherSuites
|
||||||
|
}
|
||||||
|
|
||||||
if info.AllowedCN != "" {
|
if info.AllowedCN != "" {
|
||||||
cfg.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
cfg.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||||
for _, chains := range verifiedChains {
|
for _, chains := range verifiedChains {
|
||||||
|
73
pkg/transport/transport_test.go
Normal file
73
pkg/transport/transport_test.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// Copyright 2018 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestNewTransportTLSInvalidCipherSuites expects a client with invalid
|
||||||
|
// cipher suites fail to handshake with the server.
|
||||||
|
func TestNewTransportTLSInvalidCipherSuites(t *testing.T) {
|
||||||
|
tlsInfo, del, err := createSelfCert()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create cert: %v", err)
|
||||||
|
}
|
||||||
|
defer del()
|
||||||
|
|
||||||
|
cipherSuites := []uint16{
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||||
|
}
|
||||||
|
|
||||||
|
// make server and client have unmatched cipher suites
|
||||||
|
srvTLS, cliTLS := *tlsInfo, *tlsInfo
|
||||||
|
srvTLS.CipherSuites, cliTLS.CipherSuites = cipherSuites[:2], cipherSuites[2:]
|
||||||
|
|
||||||
|
ln, err := NewListener("127.0.0.1:0", "https", &srvTLS)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected NewListener error: %v", err)
|
||||||
|
}
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
donec := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
ln.Accept()
|
||||||
|
donec <- struct{}{}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
tr, err := NewTransport(cliTLS, 3*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected NewTransport error: %v", err)
|
||||||
|
}
|
||||||
|
cli := &http.Client{Transport: tr}
|
||||||
|
_, gerr := cli.Get("https://" + ln.Addr().String())
|
||||||
|
if gerr == nil || !strings.Contains(gerr.Error(), "tls: handshake failure") {
|
||||||
|
t.Fatal("expected client TLS handshake error")
|
||||||
|
}
|
||||||
|
ln.Close()
|
||||||
|
donec <- struct{}{}
|
||||||
|
}()
|
||||||
|
<-donec
|
||||||
|
<-donec
|
||||||
|
}
|
@ -168,8 +168,10 @@ main() {
|
|||||||
done
|
done
|
||||||
gcloud docker -- login -u _json_key -p "$(cat /etc/gcp-key-etcd-development.json)" https://gcr.io
|
gcloud docker -- login -u _json_key -p "$(cat /etc/gcp-key-etcd-development.json)" https://gcr.io
|
||||||
|
|
||||||
|
docker push quay.io/coreos/etcd:${RELEASE_VERSION}
|
||||||
|
gcloud docker -- push gcr.io/etcd-development/etcd:${RELEASE_VERSION}
|
||||||
if [ "${MINOR_VERSION}" != "3.1" ]; then
|
if [ "${MINOR_VERSION}" != "3.1" ]; then
|
||||||
for TARGET_ARCH in "-arm64" "-ppc64le" ""; do
|
for TARGET_ARCH in "-arm64" "-ppc64le"; do
|
||||||
docker push quay.io/coreos/etcd:${RELEASE_VERSION}${TARGET_ARCH}
|
docker push quay.io/coreos/etcd:${RELEASE_VERSION}${TARGET_ARCH}
|
||||||
gcloud docker -- push gcr.io/etcd-development/etcd:${RELEASE_VERSION}${TARGET_ARCH}
|
gcloud docker -- push gcr.io/etcd-development/etcd:${RELEASE_VERSION}${TARGET_ARCH}
|
||||||
done
|
done
|
||||||
@ -208,7 +210,6 @@ main() {
|
|||||||
# TODO: signing process
|
# TODO: signing process
|
||||||
echo ""
|
echo ""
|
||||||
echo "WARNING: The release has not been signed and published to github. This must be done manually."
|
echo "WARNING: The release has not been signed and published to github. This must be done manually."
|
||||||
echo "WARNING: version/version.go has not been updated to ${RELEASE_VERSION}+git. This must be done manually."
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Success."
|
echo "Success."
|
||||||
exit 0
|
exit 0
|
||||||
|
@ -26,7 +26,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
// MinClusterVersion is the min cluster version this etcd binary is compatible with.
|
// MinClusterVersion is the min cluster version this etcd binary is compatible with.
|
||||||
MinClusterVersion = "3.0.0"
|
MinClusterVersion = "3.0.0"
|
||||||
Version = "3.3.6"
|
Version = "3.3.7"
|
||||||
APIVersion = "unknown"
|
APIVersion = "unknown"
|
||||||
|
|
||||||
// Git SHA Value will be set during build
|
// Git SHA Value will be set during build
|
||||||
|
Reference in New Issue
Block a user