Browse Source

etcd: server SSL and client cert auth configuration is more explicit

etcd does not provide enough flexibility to configure server SSL and
client authentication separately. When configuring server SSL the
`--ca-file` flag is required to trust self-signed SSL certificates
used to service client requests.

The `--ca-file` has the side effect of enabling client cert
authentication. This can be surprising for those looking to simply
secure communication between an etcd server and client.

Resolve this issue by introducing four new flags:

    --client-cert-auth
    --peer-client-cert-auth
    --trusted-ca-file
    --peer-trusted-ca-file

These new flags will allow etcd to support a more explicit SSL
configuration for both etcd clients and peers.

Example usage:

Start etcd with server SSL and no client cert authentication:

    etcd -name etcd0 \
    --advertise-client-urls https://etcd0.example.com:2379 \
    --cert-file etcd0.example.com.crt \
    --key-file etcd0.example.com.key \
    --trusted-ca-file ca.crt

Start etcd with server SSL and enable client cert authentication:

    etcd -name etcd0 \
    --advertise-client-urls https://etcd0.example.com:2379 \
    --cert-file etcd0.example.com.crt \
    --key-file etcd0.example.com.key \
    --trusted-ca-file ca.crt \
    --client-cert-auth

Start etcd with server SSL and client cert authentication for both
peer and client endpoints:

    etcd -name etcd0 \
    --advertise-client-urls https://etcd0.example.com:2379 \
    --cert-file etcd0.example.com.crt \
    --key-file etcd0.example.com.key \
    --trusted-ca-file ca.crt \
    --client-cert-auth \
    --peer-cert-file etcd0.example.com.crt \
    --peer-key-file etcd0.example.com.key \
    --peer-trusted-ca-file ca.crt \
    --peer-client-cert-auth

This change is backwards compatible with etcd versions 2.0.0+. The
current behavior of the `--ca-file` flag is preserved.

Fixes #2499.
Kelsey Hightower 10 years ago
parent
commit
8dd8b1cdc2
3 changed files with 66 additions and 28 deletions
  1. 4 0
      etcdmain/config.go
  2. 8 0
      etcdmain/help.go
  3. 54 28
      pkg/transport/listener.go

+ 4 - 0
etcdmain/config.go

@@ -172,9 +172,13 @@ func NewConfig() *config {
 	fs.StringVar(&cfg.clientTLSInfo.CAFile, "ca-file", "", "Path to the client server TLS CA file.")
 	fs.StringVar(&cfg.clientTLSInfo.CertFile, "cert-file", "", "Path to the client server TLS cert file.")
 	fs.StringVar(&cfg.clientTLSInfo.KeyFile, "key-file", "", "Path to the client server TLS key file.")
+	fs.BoolVar(&cfg.clientTLSInfo.ClientCertAuth, "client-cert-auth", false, "Enable client cert authentication.")
+	fs.StringVar(&cfg.clientTLSInfo.TrustedCAFile, "trusted-ca-file", "", "Path to the client server TLS trusted CA key file.")
 	fs.StringVar(&cfg.peerTLSInfo.CAFile, "peer-ca-file", "", "Path to the peer server TLS CA file.")
 	fs.StringVar(&cfg.peerTLSInfo.CertFile, "peer-cert-file", "", "Path to the peer server TLS cert file.")
 	fs.StringVar(&cfg.peerTLSInfo.KeyFile, "peer-key-file", "", "Path to the peer server TLS key file.")
+	fs.BoolVar(&cfg.peerTLSInfo.ClientCertAuth, "peer-client-cert-auth", false, "Enable peer client cert authentication.")
+	fs.StringVar(&cfg.peerTLSInfo.TrustedCAFile, "peer-trusted-ca-file", "", "Path to the peer server TLS trusted CA file.")
 
 	// unsafe
 	fs.BoolVar(&cfg.forceNewCluster, "force-new-cluster", false, "Force to create a new one member cluster")

+ 8 - 0
etcdmain/help.go

@@ -81,12 +81,20 @@ security flags:
 		path to the client server TLS cert file.
 	--key-file ''
 		path to the client server TLS key file.
+	--client-cert-auth 'false'
+		enable client cert authentication.
+	--trusted-ca-file ''
+		path to the client server TLS trusted CA key file.
 	--peer-ca-file ''
 		path to the peer server TLS CA file.
 	--peer-cert-file ''
 		path to the peer server TLS cert file.
 	--peer-key-file ''
 		path to the peer server TLS key file.
+	--peer-client-cert-auth 'false'
+		enable peer client cert authentication.
+	--peer-trusted-ca-file ''
+		path to the peer server TLS trusted CA file.
 
 
 unsafe flags:

+ 54 - 28
pkg/transport/listener.go

@@ -66,9 +66,11 @@ func NewTransport(info TLSInfo) (*http.Transport, error) {
 }
 
 type TLSInfo struct {
-	CertFile string
-	KeyFile  string
-	CAFile   string
+	CertFile       string
+	KeyFile        string
+	CAFile         string
+	TrustedCAFile  string
+	ClientCertAuth bool
 
 	// parseFunc exists to simplify testing. Typically, parseFunc
 	// should be left nil. In that case, tls.X509KeyPair will be used.
@@ -115,29 +117,47 @@ func (info TLSInfo) baseConfig() (*tls.Config, error) {
 	return cfg, nil
 }
 
-// ServerConfig generates a tls.Config object for use by an HTTP server
+// cafiles returns a list of CA file paths.
+func (info TLSInfo) cafiles() []string {
+	cs := make([]string, 0)
+	if info.CAFile != "" {
+		cs = append(cs, info.CAFile)
+	}
+	if info.TrustedCAFile != "" {
+		cs = append(cs, info.TrustedCAFile)
+	}
+	return cs
+}
+
+// ServerConfig generates a tls.Config object for use by an HTTP server.
 func (info TLSInfo) ServerConfig() (*tls.Config, error) {
 	cfg, err := info.baseConfig()
 	if err != nil {
 		return nil, err
 	}
 
-	if info.CAFile != "" {
+	cfg.ClientAuth = tls.NoClientCert
+	if info.CAFile != "" || info.ClientCertAuth {
 		cfg.ClientAuth = tls.RequireAndVerifyClientCert
-		cp, err := newCertPool(info.CAFile)
+	}
+
+	CAFiles := info.cafiles()
+	if len(CAFiles) > 0 {
+		cp, err := newCertPool(CAFiles)
 		if err != nil {
 			return nil, err
 		}
 		cfg.ClientCAs = cp
-	} else {
-		cfg.ClientAuth = tls.NoClientCert
 	}
 
 	return cfg, nil
 }
 
-// ClientConfig generates a tls.Config object for use by an HTTP client
-func (info TLSInfo) ClientConfig() (cfg *tls.Config, err error) {
+// ClientConfig generates a tls.Config object for use by an HTTP client.
+func (info TLSInfo) ClientConfig() (*tls.Config, error) {
+	var cfg *tls.Config
+	var err error
+
 	if !info.Empty() {
 		cfg, err = info.baseConfig()
 		if err != nil {
@@ -147,34 +167,40 @@ func (info TLSInfo) ClientConfig() (cfg *tls.Config, err error) {
 		cfg = &tls.Config{}
 	}
 
-	if info.CAFile != "" {
-		cfg.RootCAs, err = newCertPool(info.CAFile)
+	CAFiles := info.cafiles()
+	if len(CAFiles) > 0 {
+		cfg.RootCAs, err = newCertPool(CAFiles)
 		if err != nil {
-			return
+			return nil, err
 		}
 	}
 
-	return
+	return cfg, nil
 }
 
-// newCertPool creates x509 certPool with provided CA file
-func newCertPool(CAFile string) (*x509.CertPool, error) {
+// newCertPool creates x509 certPool with provided CA files.
+func newCertPool(CAFiles []string) (*x509.CertPool, error) {
 	certPool := x509.NewCertPool()
-	pemByte, err := ioutil.ReadFile(CAFile)
-	if err != nil {
-		return nil, err
-	}
 
-	for {
-		var block *pem.Block
-		block, pemByte = pem.Decode(pemByte)
-		if block == nil {
-			return certPool, nil
-		}
-		cert, err := x509.ParseCertificate(block.Bytes)
+	for _, CAFile := range CAFiles {
+		pemByte, err := ioutil.ReadFile(CAFile)
 		if err != nil {
 			return nil, err
 		}
-		certPool.AddCert(cert)
+
+		for {
+			var block *pem.Block
+			block, pemByte = pem.Decode(pemByte)
+			if block == nil {
+				break
+			}
+			cert, err := x509.ParseCertificate(block.Bytes)
+			if err != nil {
+				return nil, err
+			}
+			certPool.AddCert(cert)
+		}
 	}
+
+	return certPool, nil
 }