1
0
Fork 0

Server -http-ca-cert and -node-ca-cert options

The -http-ca-cert and -node-ca-cert options allow the user to specify
trusted X.509 root CA certificates as an alternative to the
-http-no-verify and -node-no-verify options. This behavior is analogous
to the rqlite client -ca-cert option.
master
Zac Medico 6 years ago
parent 8336150318
commit f01e6b1b0a
No known key found for this signature in database
GPG Key ID: D075FB8C104A3D20

@ -21,27 +21,30 @@ const attemptInterval time.Duration = 5 * time.Second
// Join attempts to join the cluster at one of the addresses given in joinAddr.
// It walks through joinAddr in order, and sets the Raft address of the joining
// node as advAddr. It returns the endpoint successfully used to join the cluster.
func Join(joinAddr []string, advAddr string, skipVerify bool) (string, error) {
func Join(joinAddr []string, advAddr string, tlsConfig *tls.Config) (string, error) {
var err error
var j string
logger := log.New(os.Stderr, "[cluster-join] ", log.LstdFlags)
if tlsConfig == nil {
tlsConfig = &tls.Config{InsecureSkipVerify: true}
}
for i := 0; i < numAttempts; i++ {
for _, a := range joinAddr {
j, err = join(a, advAddr, skipVerify)
j, err = join(a, advAddr, tlsConfig)
if err == nil {
// Success!
return j, nil
}
}
logger.Printf("failed to join cluster at %s, sleeping %s before retry", joinAddr, attemptInterval)
logger.Printf("failed to join cluster at %s: %s, sleeping %s before retry", joinAddr, err.Error(), attemptInterval)
time.Sleep(attemptInterval)
}
logger.Printf("failed to join cluster at %s, after %d attempts", joinAddr, numAttempts)
return "", err
}
func join(joinAddr string, advAddr string, skipVerify bool) (string, error) {
func join(joinAddr string, advAddr string, tlsConfig *tls.Config) (string, error) {
// Join using IP address, as that is what Hashicorp Raft works in.
resv, err := net.ResolveTCPAddr("tcp", advAddr)
if err != nil {
@ -53,7 +56,7 @@ func join(joinAddr string, advAddr string, skipVerify bool) (string, error) {
// Enable skipVerify as requested.
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: skipVerify},
TLSClientConfig: tlsConfig,
}
client := &http.Client{Transport: tr}
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {

@ -16,7 +16,7 @@ func Test_SingleJoinOK(t *testing.T) {
}))
defer ts.Close()
j, err := Join([]string{ts.URL}, "127.0.0.1:9090", true)
j, err := Join([]string{ts.URL}, "127.0.0.1:9090", nil)
if err != nil {
t.Fatalf("failed to join a single node: %s", err.Error())
}
@ -31,7 +31,7 @@ func Test_SingleJoinFail(t *testing.T) {
}))
defer ts.Close()
_, err := Join([]string{ts.URL}, "127.0.0.1:9090", true)
_, err := Join([]string{ts.URL}, "127.0.0.1:9090", nil)
if err == nil {
t.Fatalf("expected error when joining bad node")
}
@ -45,7 +45,7 @@ func Test_DoubleJoinOK(t *testing.T) {
}))
defer ts2.Close()
j, err := Join([]string{ts1.URL, ts2.URL}, "127.0.0.1:9090", true)
j, err := Join([]string{ts1.URL, ts2.URL}, "127.0.0.1:9090", nil)
if err != nil {
t.Fatalf("failed to join a single node: %s", err.Error())
}
@ -63,7 +63,7 @@ func Test_DoubleJoinOKSecondNode(t *testing.T) {
}))
defer ts2.Close()
j, err := Join([]string{ts1.URL, ts2.URL}, "127.0.0.1:9090", true)
j, err := Join([]string{ts1.URL, ts2.URL}, "127.0.0.1:9090", nil)
if err != nil {
t.Fatalf("failed to join a single node: %s", err.Error())
}
@ -83,7 +83,7 @@ func Test_DoubleJoinOKSecondNodeRedirect(t *testing.T) {
}))
defer ts2.Close()
j, err := Join([]string{ts2.URL}, "127.0.0.1:9090", true)
j, err := Join([]string{ts2.URL}, "127.0.0.1:9090", nil)
if err != nil {
t.Fatalf("failed to join a single node: %s", err.Error())
}

@ -2,8 +2,11 @@
package main
import (
"crypto/tls"
"crypto/x509"
"flag"
"fmt"
"io/ioutil"
"log"
"net"
"os"
@ -56,9 +59,11 @@ const (
var httpAddr string
var httpAdv string
var authFile string
var x509CACert string
var x509Cert string
var x509Key string
var nodeEncrypt bool
var nodeX509CACert string
var nodeX509Cert string
var nodeX509Key string
var raftAddr string
@ -89,10 +94,12 @@ storage engine. It provides an easy-to-use, fault-tolerant store for relational
func init() {
flag.StringVar(&httpAddr, "http-addr", "localhost:4001", "HTTP server bind address. For HTTPS, set X.509 cert and key")
flag.StringVar(&httpAdv, "http-adv-addr", "", "Advertised HTTP address. If not set, same as HTTP server")
flag.StringVar(&x509CACert, "http-ca-cert", "", "Path to root X.509 certificate for HTTP endpoint")
flag.StringVar(&x509Cert, "http-cert", "", "Path to X.509 certificate for HTTP endpoint")
flag.StringVar(&x509Key, "http-key", "", "Path to X.509 private key for HTTP endpoint")
flag.BoolVar(&noVerify, "http-no-verify", false, "Skip verification of remote HTTPS cert when joining cluster")
flag.BoolVar(&nodeEncrypt, "node-encrypt", false, "Enable node-to-node encryption")
flag.StringVar(&nodeX509CACert, "node-ca-cert", "", "Path to root X.509 certificate for node-to-node encryption")
flag.StringVar(&nodeX509Cert, "node-cert", "cert.pem", "Path to X.509 certificate for node-to-node encryption")
flag.StringVar(&nodeX509Key, "node-key", "key.pem", "Path to X.509 private key for node-to-node encryption")
flag.BoolVar(&noNodeVerify, "node-no-verify", false, "Skip verification of a remote node cert")
@ -169,7 +176,7 @@ func main() {
var mux *tcp.Mux
if nodeEncrypt {
log.Printf("enabling node-to-node encryption with cert: %s, key: %s", nodeX509Cert, nodeX509Key)
mux, err = tcp.NewTLSMux(ln, adv, nodeX509Cert, nodeX509Key)
mux, err = tcp.NewTLSMux(ln, adv, nodeX509Cert, nodeX509Key, nodeX509CACert)
} else {
mux, err = tcp.NewMux(ln, adv)
}
@ -250,7 +257,21 @@ func main() {
if raftAdv != "" {
advAddr = raftAdv
}
if j, err := cluster.Join(joins, advAddr, noVerify); err != nil {
tlsConfig := tls.Config{InsecureSkipVerify: noVerify}
if x509CACert != "" {
asn1Data, err := ioutil.ReadFile(x509CACert)
if err != nil {
log.Fatalf("ioutil.ReadFile failed: %s", err.Error())
}
tlsConfig.RootCAs = x509.NewCertPool()
ok := tlsConfig.RootCAs.AppendCertsFromPEM([]byte(asn1Data))
if !ok {
log.Fatalf("failed to parse root CA certificate(s) in %q", x509CACert)
}
}
if j, err := cluster.Join(joins, advAddr, &tlsConfig); err != nil {
log.Fatalf("failed to join cluster at %s: %s", joins, err.Error())
} else {
log.Println("successfully joined cluster at", j)
@ -287,6 +308,7 @@ func main() {
s = httpd.New(httpAddr, str, nil)
}
s.CACertFile = x509CACert
s.CertFile = x509Cert
s.KeyFile = x509Key
s.Expvar = expvar

@ -5,6 +5,7 @@ package http
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"expvar"
@ -144,6 +145,7 @@ type Service struct {
statusMu sync.RWMutex
statuses map[string]Statuser
CACertFile string // Path to root X.509 certificate.
CertFile string // Path to SSL certificate.
KeyFile string // Path to SSL private key.
@ -184,7 +186,7 @@ func (s *Service) Start() error {
return err
}
} else {
config, err := createTLSConfig(s.CertFile, s.KeyFile)
config, err := createTLSConfig(s.CertFile, s.KeyFile, s.CACertFile)
if err != nil {
return err
}
@ -797,7 +799,7 @@ func writeResponse(w http.ResponseWriter, r *http.Request, j *Response) {
}
// createTLSConfig returns a TLS config from the given cert and key.
func createTLSConfig(certFile, keyFile string) (*tls.Config, error) {
func createTLSConfig(certFile, keyFile, caCertFile string) (*tls.Config, error) {
var err error
config := &tls.Config{}
config.Certificates = make([]tls.Certificate, 1)
@ -805,6 +807,17 @@ func createTLSConfig(certFile, keyFile string) (*tls.Config, error) {
if err != nil {
return nil, err
}
if caCertFile != "" {
asn1Data, err := ioutil.ReadFile(caCertFile)
if err != nil {
return nil, err
}
config.RootCAs = x509.NewCertPool()
ok := config.RootCAs.AppendCertsFromPEM([]byte(asn1Data))
if !ok {
return nil, fmt.Errorf("failed to parse root certificate(s) in %q", caCertFile)
}
}
return config, nil
}

@ -2,9 +2,11 @@ package tcp
import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"os"
@ -26,6 +28,8 @@ type Layer struct {
remoteEncrypted bool
skipVerify bool
nodeX509CACert string
tlsConfig *tls.Config
}
// Addr returns the local address for the layer.
@ -40,10 +44,7 @@ func (l *Layer) Dial(addr string, timeout time.Duration) (net.Conn, error) {
var err error
var conn net.Conn
if l.remoteEncrypted {
conf := &tls.Config{
InsecureSkipVerify: l.skipVerify,
}
conn, err = tls.DialWithDialer(dialer, "tcp", addr, conf)
conn, err = tls.DialWithDialer(dialer, "tcp", addr, l.tlsConfig)
} else {
conn, err = dialer.Dial("tcp", addr)
}
@ -82,6 +83,9 @@ type Mux struct {
// Out-of-band error logger
Logger *log.Logger
// Path to root X.509 certificate.
nodeX509CACert string
// Path to X509 certificate
nodeX509Cert string
@ -90,6 +94,8 @@ type Mux struct {
// Whether to skip verification of other nodes' certificates.
InsecureSkipVerify bool
tlsConfig *tls.Config
}
// NewMux returns a new instance of Mux for ln. If adv is nil,
@ -111,17 +117,20 @@ func NewMux(ln net.Listener, adv net.Addr) (*Mux, error) {
// NewTLSMux returns a new instance of Mux for ln, and encrypts all traffic
// using TLS. If adv is nil, then the addr of ln is used.
func NewTLSMux(ln net.Listener, adv net.Addr, cert, key string) (*Mux, error) {
func NewTLSMux(ln net.Listener, adv net.Addr, cert, key, caCert string) (*Mux, error) {
mux, err := NewMux(ln, adv)
if err != nil {
return nil, err
}
mux.ln, err = newTLSListener(mux.ln, cert, key)
mux.tlsConfig, err = createTLSConfig(cert, key, caCert)
if err != nil {
return nil, err
}
mux.ln = tls.NewListener(ln, mux.tlsConfig)
mux.remoteEncrypted = true
mux.nodeX509CACert = caCert
mux.nodeX509Cert = cert
mux.nodeX509Key = key
@ -168,6 +177,7 @@ func (mux *Mux) Stats() (interface{}, error) {
if mux.remoteEncrypted {
s["certificate"] = mux.nodeX509Cert
s["key"] = mux.nodeX509Key
s["ca_certificate"] = mux.nodeX509CACert
s["skip_verify"] = strconv.FormatBool(mux.InsecureSkipVerify)
}
@ -230,6 +240,8 @@ func (mux *Mux) Listen(header byte) *Layer {
addr: mux.addr,
remoteEncrypted: mux.remoteEncrypted,
skipVerify: mux.InsecureSkipVerify,
nodeX509CACert: mux.nodeX509CACert,
tlsConfig: mux.tlsConfig,
}
return layer
@ -256,8 +268,8 @@ func (ln *listener) Close() error { return nil }
func (ln *listener) Addr() net.Addr { return nil }
// newTLSListener returns a net listener which encrypts the traffic using TLS.
func newTLSListener(ln net.Listener, certFile, keyFile string) (net.Listener, error) {
config, err := createTLSConfig(certFile, keyFile)
func newTLSListener(ln net.Listener, certFile, keyFile, caCertFile string) (net.Listener, error) {
config, err := createTLSConfig(certFile, keyFile, caCertFile)
if err != nil {
return nil, err
}
@ -266,7 +278,7 @@ func newTLSListener(ln net.Listener, certFile, keyFile string) (net.Listener, er
}
// createTLSConfig returns a TLS config from the given cert and key.
func createTLSConfig(certFile, keyFile string) (*tls.Config, error) {
func createTLSConfig(certFile, keyFile, caCertFile string) (*tls.Config, error) {
var err error
config := &tls.Config{}
config.Certificates = make([]tls.Certificate, 1)
@ -274,5 +286,16 @@ func createTLSConfig(certFile, keyFile string) (*tls.Config, error) {
if err != nil {
return nil, err
}
if caCertFile != "" {
asn1Data, err := ioutil.ReadFile(caCertFile)
if err != nil {
return nil, err
}
config.RootCAs = x509.NewCertPool()
ok := config.RootCAs.AppendCertsFromPEM([]byte(asn1Data))
if !ok {
return nil, fmt.Errorf("failed to parse root certificate(s) in %s", caCertFile)
}
}
return config, nil
}

@ -177,7 +177,7 @@ func TestTLSMux(t *testing.T) {
key := x509.KeyFile()
defer os.Remove(key)
mux, err := NewTLSMux(tcpListener, nil, cert, key)
mux, err := NewTLSMux(tcpListener, nil, cert, key, "")
if err != nil {
t.Fatalf("failed to create mux: %s", err.Error())
}
@ -206,7 +206,7 @@ func TestTLSMux_Fail(t *testing.T) {
key := x509.KeyFile()
defer os.Remove(key)
_, err := NewTLSMux(tcpListener, nil, "xxxx", "yyyy")
_, err := NewTLSMux(tcpListener, nil, "xxxx", "yyyy", "")
if err == nil {
t.Fatalf("created mux unexpectedly with bad resources")
}

Loading…
Cancel
Save