mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
Merge pull request #4484 from heyitsanthony/auto-tls
automatic peer TLS
This commit is contained in:
commit
3fed78ae7b
@ -43,6 +43,12 @@ var (
|
|||||||
isPeerTLS: false,
|
isPeerTLS: false,
|
||||||
initialToken: "new",
|
initialToken: "new",
|
||||||
}
|
}
|
||||||
|
configAutoTLS = etcdProcessClusterConfig{
|
||||||
|
clusterSize: 3,
|
||||||
|
isPeerTLS: true,
|
||||||
|
isPeerAutoTLS: true,
|
||||||
|
initialToken: "new",
|
||||||
|
}
|
||||||
configTLS = etcdProcessClusterConfig{
|
configTLS = etcdProcessClusterConfig{
|
||||||
clusterSize: 3,
|
clusterSize: 3,
|
||||||
proxySize: 0,
|
proxySize: 0,
|
||||||
@ -94,6 +100,7 @@ func configStandalone(cfg etcdProcessClusterConfig) *etcdProcessClusterConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBasicOpsNoTLS(t *testing.T) { testBasicOpsPutGet(t, &configNoTLS) }
|
func TestBasicOpsNoTLS(t *testing.T) { testBasicOpsPutGet(t, &configNoTLS) }
|
||||||
|
func TestBasicOpsAutoTLS(t *testing.T) { testBasicOpsPutGet(t, &configAutoTLS) }
|
||||||
func TestBasicOpsAllTLS(t *testing.T) { testBasicOpsPutGet(t, &configTLS) }
|
func TestBasicOpsAllTLS(t *testing.T) { testBasicOpsPutGet(t, &configTLS) }
|
||||||
func TestBasicOpsPeerTLS(t *testing.T) { testBasicOpsPutGet(t, &configPeerTLS) }
|
func TestBasicOpsPeerTLS(t *testing.T) { testBasicOpsPutGet(t, &configPeerTLS) }
|
||||||
func TestBasicOpsClientTLS(t *testing.T) { testBasicOpsPutGet(t, &configClientTLS) }
|
func TestBasicOpsClientTLS(t *testing.T) { testBasicOpsPutGet(t, &configClientTLS) }
|
||||||
@ -170,11 +177,12 @@ type etcdProcessConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type etcdProcessClusterConfig struct {
|
type etcdProcessClusterConfig struct {
|
||||||
clusterSize int
|
clusterSize int
|
||||||
proxySize int
|
proxySize int
|
||||||
isClientTLS bool
|
isClientTLS bool
|
||||||
isPeerTLS bool
|
isPeerTLS bool
|
||||||
initialToken string
|
isPeerAutoTLS bool
|
||||||
|
initialToken string
|
||||||
}
|
}
|
||||||
|
|
||||||
// newEtcdProcessCluster launches a new cluster from etcd processes, returning
|
// newEtcdProcessCluster launches a new cluster from etcd processes, returning
|
||||||
@ -325,12 +333,16 @@ func (cfg *etcdProcessClusterConfig) tlsArgs() (args []string) {
|
|||||||
args = append(args, tlsClientArgs...)
|
args = append(args, tlsClientArgs...)
|
||||||
}
|
}
|
||||||
if cfg.isPeerTLS {
|
if cfg.isPeerTLS {
|
||||||
tlsPeerArgs := []string{
|
if cfg.isPeerAutoTLS {
|
||||||
"--peer-cert-file", certPath,
|
args = append(args, "--peer-auto-tls=true")
|
||||||
"--peer-key-file", privateKeyPath,
|
} else {
|
||||||
"--peer-ca-file", caPath,
|
tlsPeerArgs := []string{
|
||||||
|
"--peer-cert-file", certPath,
|
||||||
|
"--peer-key-file", privateKeyPath,
|
||||||
|
"--peer-ca-file", caPath,
|
||||||
|
}
|
||||||
|
args = append(args, tlsPeerArgs...)
|
||||||
}
|
}
|
||||||
args = append(args, tlsPeerArgs...)
|
|
||||||
}
|
}
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
@ -111,6 +111,7 @@ type config struct {
|
|||||||
|
|
||||||
// security
|
// security
|
||||||
clientTLSInfo, peerTLSInfo transport.TLSInfo
|
clientTLSInfo, peerTLSInfo transport.TLSInfo
|
||||||
|
peerAutoTLS bool
|
||||||
|
|
||||||
// logging
|
// logging
|
||||||
debug bool
|
debug bool
|
||||||
@ -211,6 +212,7 @@ func NewConfig() *config {
|
|||||||
fs.StringVar(&cfg.peerTLSInfo.KeyFile, "peer-key-file", "", "Path to the peer server TLS key 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.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.")
|
fs.StringVar(&cfg.peerTLSInfo.TrustedCAFile, "peer-trusted-ca-file", "", "Path to the peer server TLS trusted CA file.")
|
||||||
|
fs.BoolVar(&cfg.peerAutoTLS, "peer-auto-tls", false, "Peer TLS using generated certificates")
|
||||||
|
|
||||||
// logging
|
// logging
|
||||||
fs.BoolVar(&cfg.debug, "debug", false, "Enable debug-level logging for etcd.")
|
fs.BoolVar(&cfg.debug, "debug", false, "Enable debug-level logging for etcd.")
|
||||||
|
@ -203,9 +203,23 @@ func startEtcd(cfg *config) (<-chan struct{}, error) {
|
|||||||
return nil, fmt.Errorf("error setting up initial cluster: %v", err)
|
return nil, fmt.Errorf("error setting up initial cluster: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.peerAutoTLS && cfg.peerTLSInfo.Empty() {
|
||||||
|
phosts := make([]string, 0)
|
||||||
|
for _, u := range cfg.lpurls {
|
||||||
|
phosts = append(phosts, u.Host)
|
||||||
|
}
|
||||||
|
cfg.peerTLSInfo, err = transport.SelfCert(cfg.dir, phosts)
|
||||||
|
if err != nil {
|
||||||
|
plog.Fatalf("could not get certs (%v)", err)
|
||||||
|
}
|
||||||
|
} else if cfg.peerAutoTLS {
|
||||||
|
plog.Warningf("ignoring peer auto TLS since certs given")
|
||||||
|
}
|
||||||
|
|
||||||
if !cfg.peerTLSInfo.Empty() {
|
if !cfg.peerTLSInfo.Empty() {
|
||||||
plog.Infof("peerTLS: %s", cfg.peerTLSInfo)
|
plog.Infof("peerTLS: %s", cfg.peerTLSInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
plns := make([]net.Listener, 0)
|
plns := make([]net.Listener, 0)
|
||||||
for _, u := range cfg.lpurls {
|
for _, u := range cfg.lpurls {
|
||||||
if u.Scheme == "http" && !cfg.peerTLSInfo.Empty() {
|
if u.Scheme == "http" && !cfg.peerTLSInfo.Empty() {
|
||||||
|
@ -15,13 +15,21 @@
|
|||||||
package transport
|
package transport
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -79,6 +87,8 @@ type TLSInfo struct {
|
|||||||
TrustedCAFile string
|
TrustedCAFile string
|
||||||
ClientCertAuth bool
|
ClientCertAuth bool
|
||||||
|
|
||||||
|
selfCert bool
|
||||||
|
|
||||||
// parseFunc exists to simplify testing. Typically, parseFunc
|
// parseFunc exists to simplify testing. Typically, parseFunc
|
||||||
// should be left nil. In that case, tls.X509KeyPair will be used.
|
// should be left nil. In that case, tls.X509KeyPair will be used.
|
||||||
parseFunc func([]byte, []byte) (tls.Certificate, error)
|
parseFunc func([]byte, []byte) (tls.Certificate, error)
|
||||||
@ -92,6 +102,78 @@ func (info TLSInfo) Empty() bool {
|
|||||||
return info.CertFile == "" && info.KeyFile == ""
|
return info.CertFile == "" && info.KeyFile == ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SelfCert(dirpath string, hosts []string) (info TLSInfo, err error) {
|
||||||
|
if err = os.MkdirAll(dirpath, 0700); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
certPath := path.Join(dirpath, "cert.pem")
|
||||||
|
keyPath := path.Join(dirpath, "key.pem")
|
||||||
|
_, errcert := os.Stat(certPath)
|
||||||
|
_, errkey := os.Stat(keyPath)
|
||||||
|
if errcert == nil && errkey == nil {
|
||||||
|
info.CertFile = certPath
|
||||||
|
info.KeyFile = keyPath
|
||||||
|
info.selfCert = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||||
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl := x509.Certificate{
|
||||||
|
SerialNumber: serialNumber,
|
||||||
|
Subject: pkix.Name{Organization: []string{"etcd"}},
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: time.Now().Add(365 * (24 * time.Hour)),
|
||||||
|
|
||||||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, host := range hosts {
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
tmpl.IPAddresses = append(tmpl.IPAddresses, ip)
|
||||||
|
} else {
|
||||||
|
tmpl.DNSNames = append(tmpl.DNSNames, strings.Split(host, ":")[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
derBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, &priv.PublicKey, priv)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
certOut, err := os.Create(certPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||||
|
certOut.Close()
|
||||||
|
|
||||||
|
b, err := x509.MarshalECPrivateKey(priv)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: b})
|
||||||
|
keyOut.Close()
|
||||||
|
|
||||||
|
return SelfCert(dirpath, hosts)
|
||||||
|
}
|
||||||
|
|
||||||
func (info TLSInfo) baseConfig() (*tls.Config, error) {
|
func (info TLSInfo) baseConfig() (*tls.Config, error) {
|
||||||
if info.KeyFile == "" || info.CertFile == "" {
|
if info.KeyFile == "" || info.CertFile == "" {
|
||||||
return nil, fmt.Errorf("KeyFile and CertFile must both be present[key: %v, cert: %v]", info.KeyFile, info.CertFile)
|
return nil, fmt.Errorf("KeyFile and CertFile must both be present[key: %v, cert: %v]", info.KeyFile, info.CertFile)
|
||||||
@ -182,6 +264,9 @@ func (info TLSInfo) ClientConfig() (*tls.Config, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if info.selfCert {
|
||||||
|
cfg.InsecureSkipVerify = true
|
||||||
|
}
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +54,10 @@ func TestNewListenerTLSInfo(t *testing.T) {
|
|||||||
defer os.Remove(tmp)
|
defer os.Remove(tmp)
|
||||||
tlsInfo := TLSInfo{CertFile: tmp, KeyFile: tmp}
|
tlsInfo := TLSInfo{CertFile: tmp, KeyFile: tmp}
|
||||||
tlsInfo.parseFunc = fakeCertificateParserFunc(tls.Certificate{}, nil)
|
tlsInfo.parseFunc = fakeCertificateParserFunc(tls.Certificate{}, nil)
|
||||||
|
testNewListenerTLSInfoAccept(t, tlsInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNewListenerTLSInfoAccept(t *testing.T, tlsInfo TLSInfo) {
|
||||||
ln, err := NewListener("127.0.0.1:0", "https", tlsInfo)
|
ln, err := NewListener("127.0.0.1:0", "https", tlsInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected NewListener error: %v", err)
|
t.Fatalf("unexpected NewListener error: %v", err)
|
||||||
@ -249,3 +253,20 @@ func TestNewListenerUnixSocket(t *testing.T) {
|
|||||||
}
|
}
|
||||||
l.Close()
|
l.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestNewListenerTLSInfoSelfCert tests that a new certificate accepts connections.
|
||||||
|
func TestNewListenerTLSInfoSelfCert(t *testing.T) {
|
||||||
|
tmpdir, err := ioutil.TempDir(os.TempDir(), "tlsdir")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpdir)
|
||||||
|
tlsinfo, err := SelfCert(tmpdir, []string{"127.0.0.1"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if tlsinfo.Empty() {
|
||||||
|
t.Fatalf("tlsinfo should have certs (%+v)", tlsinfo)
|
||||||
|
}
|
||||||
|
testNewListenerTLSInfoAccept(t, tlsinfo)
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user