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,
|
||||
initialToken: "new",
|
||||
}
|
||||
configAutoTLS = etcdProcessClusterConfig{
|
||||
clusterSize: 3,
|
||||
isPeerTLS: true,
|
||||
isPeerAutoTLS: true,
|
||||
initialToken: "new",
|
||||
}
|
||||
configTLS = etcdProcessClusterConfig{
|
||||
clusterSize: 3,
|
||||
proxySize: 0,
|
||||
@ -94,6 +100,7 @@ func configStandalone(cfg etcdProcessClusterConfig) *etcdProcessClusterConfig {
|
||||
}
|
||||
|
||||
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 TestBasicOpsPeerTLS(t *testing.T) { testBasicOpsPutGet(t, &configPeerTLS) }
|
||||
func TestBasicOpsClientTLS(t *testing.T) { testBasicOpsPutGet(t, &configClientTLS) }
|
||||
@ -170,11 +177,12 @@ type etcdProcessConfig struct {
|
||||
}
|
||||
|
||||
type etcdProcessClusterConfig struct {
|
||||
clusterSize int
|
||||
proxySize int
|
||||
isClientTLS bool
|
||||
isPeerTLS bool
|
||||
initialToken string
|
||||
clusterSize int
|
||||
proxySize int
|
||||
isClientTLS bool
|
||||
isPeerTLS bool
|
||||
isPeerAutoTLS bool
|
||||
initialToken string
|
||||
}
|
||||
|
||||
// newEtcdProcessCluster launches a new cluster from etcd processes, returning
|
||||
@ -325,12 +333,16 @@ func (cfg *etcdProcessClusterConfig) tlsArgs() (args []string) {
|
||||
args = append(args, tlsClientArgs...)
|
||||
}
|
||||
if cfg.isPeerTLS {
|
||||
tlsPeerArgs := []string{
|
||||
"--peer-cert-file", certPath,
|
||||
"--peer-key-file", privateKeyPath,
|
||||
"--peer-ca-file", caPath,
|
||||
if cfg.isPeerAutoTLS {
|
||||
args = append(args, "--peer-auto-tls=true")
|
||||
} else {
|
||||
tlsPeerArgs := []string{
|
||||
"--peer-cert-file", certPath,
|
||||
"--peer-key-file", privateKeyPath,
|
||||
"--peer-ca-file", caPath,
|
||||
}
|
||||
args = append(args, tlsPeerArgs...)
|
||||
}
|
||||
args = append(args, tlsPeerArgs...)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
@ -111,6 +111,7 @@ type config struct {
|
||||
|
||||
// security
|
||||
clientTLSInfo, peerTLSInfo transport.TLSInfo
|
||||
peerAutoTLS bool
|
||||
|
||||
// logging
|
||||
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.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.BoolVar(&cfg.peerAutoTLS, "peer-auto-tls", false, "Peer TLS using generated certificates")
|
||||
|
||||
// logging
|
||||
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)
|
||||
}
|
||||
|
||||
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() {
|
||||
plog.Infof("peerTLS: %s", cfg.peerTLSInfo)
|
||||
}
|
||||
|
||||
plns := make([]net.Listener, 0)
|
||||
for _, u := range cfg.lpurls {
|
||||
if u.Scheme == "http" && !cfg.peerTLSInfo.Empty() {
|
||||
|
@ -15,13 +15,21 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -79,6 +87,8 @@ type TLSInfo struct {
|
||||
TrustedCAFile string
|
||||
ClientCertAuth bool
|
||||
|
||||
selfCert bool
|
||||
|
||||
// parseFunc exists to simplify testing. Typically, parseFunc
|
||||
// should be left nil. In that case, tls.X509KeyPair will be used.
|
||||
parseFunc func([]byte, []byte) (tls.Certificate, error)
|
||||
@ -92,6 +102,78 @@ func (info TLSInfo) Empty() bool {
|
||||
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) {
|
||||
if 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
|
||||
}
|
||||
|
||||
|
@ -54,6 +54,10 @@ func TestNewListenerTLSInfo(t *testing.T) {
|
||||
defer os.Remove(tmp)
|
||||
tlsInfo := TLSInfo{CertFile: tmp, KeyFile: tmp}
|
||||
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)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected NewListener error: %v", err)
|
||||
@ -249,3 +253,20 @@ func TestNewListenerUnixSocket(t *testing.T) {
|
||||
}
|
||||
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