diff --git a/cluster/service_mux_test.go b/cluster/service_mux_test.go index 1de7ec28..7b68562d 100644 --- a/cluster/service_mux_test.go +++ b/cluster/service_mux_test.go @@ -106,7 +106,7 @@ func mustNewTLSMux() (net.Listener, *tcp.Mux) { key := x509.KeyFile("") defer os.Remove(key) - mux, err := tcp.NewTLSMux(ln, nil, cert, key, "", true) + mux, err := tcp.NewTLSMux(ln, nil, cert, key, "", true, false) if err != nil { panic(fmt.Sprintf("failed to create TLS mux: %s", err)) } diff --git a/cmd/rqlited/flags.go b/cmd/rqlited/flags.go index 9fba8b5d..f0a1904c 100644 --- a/cmd/rqlited/flags.go +++ b/cmd/rqlited/flags.go @@ -79,6 +79,10 @@ type Config struct { // NoNodeVerify disables checking other nodes' Node X509 certs for validity. NoNodeVerify bool + // NodeVerifyClient indicates whether a node should verify client certificates from + // other nodes. + NodeVerifyClient bool + // NodeID is the Raft ID for the node. NodeID string @@ -380,6 +384,7 @@ func ParseFlags(name, desc string, build *BuildInfo) (*Config, error) { flag.StringVar(&config.NodeX509Cert, "node-cert", "", "Path to X.509 certificate for node-to-node mutual authentication and encryption") flag.StringVar(&config.NodeX509Key, "node-key", "", "Path to X.509 private key for node-to-node mutual authentication and encryption") flag.BoolVar(&config.NoNodeVerify, "node-no-verify", false, "Skip verification of any node-node certificate") + flag.BoolVar(&config.NodeVerifyClient, "node-verify-client", false, "Enable mutual TLS for node-to-node communication") flag.StringVar(&config.AuthFile, "auth", "", "Path to authentication and authorization file. If not set, not enabled") flag.StringVar(&config.RaftAddr, RaftAddrFlag, "localhost:4002", "Raft communication bind address") flag.StringVar(&config.RaftAdv, RaftAdvAddrFlag, "", "Advertised Raft communication address. If not set, same as Raft bind") diff --git a/cmd/rqlited/main.go b/cmd/rqlited/main.go index 4c79fe4c..5d6abc2b 100644 --- a/cmd/rqlited/main.go +++ b/cmd/rqlited/main.go @@ -283,17 +283,19 @@ func startNodeMux(cfg *Config, ln net.Listener) (*tcp.Mux, error) { var mux *tcp.Mux if cfg.NodeX509Cert != "" { var b strings.Builder - b.WriteString(fmt.Sprintf("enabling node-to-node encryption with cert: %s, key: %s", cfg.NodeX509Cert, cfg.NodeX509Key)) + b.WriteString(fmt.Sprintf("enabling node-to-node encryption with cert: %s, key: %s", + cfg.NodeX509Cert, cfg.NodeX509Key)) if cfg.NodeX509CACert != "" { b.WriteString(fmt.Sprintf(", CA cert %s", cfg.NodeX509CACert)) } - if cfg.NoNodeVerify { - b.WriteString(", client verification disabled") + if cfg.NodeVerifyClient { + b.WriteString(", mutual TLS disabled") } else { - b.WriteString(", client verification enabled") + b.WriteString(", mutual TLS enabled") } log.Println(b.String()) - mux, err = tcp.NewTLSMux(ln, adv, cfg.NodeX509Cert, cfg.NodeX509Key, cfg.NodeX509CACert, cfg.NoNodeVerify) + mux, err = tcp.NewTLSMux(ln, adv, cfg.NodeX509Cert, cfg.NodeX509Key, cfg.NodeX509CACert, + cfg.NoNodeVerify, cfg.NodeVerifyClient) } else { mux, err = tcp.NewMux(ln, adv) } diff --git a/http/service.go b/http/service.go index 7424c828..71a12ec2 100644 --- a/http/service.go +++ b/http/service.go @@ -339,9 +339,9 @@ func (s *Service) Start() error { b.WriteString(fmt.Sprintf(", CA cert %s", s.CACertFile)) } if s.ClientVerify { - b.WriteString(", client verification enabled") + b.WriteString(", mutual TLS enabled") } else { - b.WriteString(", client verification disabled") + b.WriteString(", mutual disabled") } // print the message s.logger.Println(b.String()) diff --git a/rtls/config.go b/rtls/config.go index 6b380e02..2523d972 100644 --- a/rtls/config.go +++ b/rtls/config.go @@ -6,15 +6,15 @@ import ( "crypto/tls" "crypto/x509" "fmt" - "io/ioutil" + "os" ) // CreateClientConfig creates a TLS configuration for use by a system that does both // client and server authentication using the same cert, key, and CA cert. If noverify -// is true, the client will not verify the server's certificate and the server will not -// verify the client's certificate. If tls1011 is true, the client will accept TLS 1.0 -// or 1.1. Otherwise, it will require TLS 1.2 or higher. -func CreateConfig(certFile, keyFile, caCertFile string, noverify, tls1011 bool) (*tls.Config, error) { +// is true, the client will not verify the server's certificate. If mutual is true, +// the server will verify the client's certificate. If tls1011 is true, the client will +// accept TLS 1.0 or 1.1. Otherwise, it will require TLS 1.2 or higher. +func CreateConfig(certFile, keyFile, caCertFile string, noverify, mutual, tls1011 bool) (*tls.Config, error) { var err error config := createBaseTLSConfig(noverify, tls1011) @@ -44,7 +44,7 @@ func CreateConfig(certFile, keyFile, caCertFile string, noverify, tls1011 bool) return nil, fmt.Errorf("failed to load CA certificate(s) for client verification in %q", caCertFile) } } - if !noverify { + if mutual { config.ClientAuth = tls.RequireAndVerifyClientCert } return config, nil diff --git a/rtls/config_test.go b/rtls/config_test.go index e8ee7976..5fa66305 100644 --- a/rtls/config_test.go +++ b/rtls/config_test.go @@ -25,14 +25,17 @@ func Test_CreateConfig(t *testing.T) { } caCertFile := mustWriteTempFile(t, caCertPEM) - // create a config with no client verification - config, err := CreateConfig(certFile, keyFile, caCertFile, true, false) + // create a config with no server or client verification + config, err := CreateConfig(certFile, keyFile, caCertFile, true, false, false) if err != nil { t.Fatalf("failed to create config: %v", err) } if config.ClientAuth != tls.NoClientCert { t.Fatalf("expected ClientAuth to be NoClientCert, got %v", config.ClientAuth) } + if !config.InsecureSkipVerify { + t.Fatalf("expected InsecureSkipVerify to be true, got false") + } // Check that the certificate is loaded correctly if len(config.Certificates) != 1 { @@ -65,14 +68,29 @@ func Test_CreateConfig(t *testing.T) { t.Fatalf("expected client CA to be %v, got %v", caCertPool, config.ClientCAs) } - // create a config with client verification - config, err = CreateConfig(certFile, keyFile, "", false, false) + // create a config with server cert verification only + config, err = CreateConfig(certFile, keyFile, caCertFile, false, false, false) + if err != nil { + t.Fatalf("failed to create config: %v", err) + } + if config.ClientAuth != tls.NoClientCert { + t.Fatalf("expected ClientAuth to be NoClientCert, got %v", config.ClientAuth) + } + if config.InsecureSkipVerify { + t.Fatalf("expected InsecureSkipVerify to be false, got true") + } + + // create a config with both server and client verification + config, err = CreateConfig(certFile, keyFile, "", false, true, false) if err != nil { t.Fatalf("failed to create config: %v", err) } if config.ClientAuth != tls.RequireAndVerifyClientCert { t.Fatalf("expected ClientAuth to be RequireAndVerifyClientCert, got %v", config.ClientAuth) } + if config.InsecureSkipVerify { + t.Fatalf("expected InsecureSkipVerify to be false, got true") + } } func Test_CreateServerConfig(t *testing.T) { diff --git a/system_test/helpers.go b/system_test/helpers.go index e6a3343c..8c321a50 100644 --- a/system_test/helpers.go +++ b/system_test/helpers.go @@ -694,7 +694,7 @@ func mustNewOpenTLSMux(certFile, keyPath, addr string) *tcp.Mux { } var mux *tcp.Mux - mux, err = tcp.NewTLSMux(ln, nil, certFile, keyPath, "", true) + mux, err = tcp.NewTLSMux(ln, nil, certFile, keyPath, "", true, false) if err != nil { panic(fmt.Sprintf("failed to create node-to-node mux: %s", err.Error())) } diff --git a/tcp/mux.go b/tcp/mux.go index ef68938e..1887e326 100644 --- a/tcp/mux.go +++ b/tcp/mux.go @@ -94,14 +94,16 @@ 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, caCert string, insecure bool) (*Mux, error) { +// using TLS. If adv is nil, then the addr of ln is used. If insecure is true, +// then the server will not verify the client's certificate. If mutual is true, +// then the server will require the client to present a trusted certificate. +func NewTLSMux(ln net.Listener, adv net.Addr, cert, key, caCert string, insecure, mutual bool) (*Mux, error) { mux, err := NewMux(ln, adv) if err != nil { return nil, err } - mux.tlsConfig, err = rtls.CreateConfig(cert, key, caCert, insecure, false) + mux.tlsConfig, err = rtls.CreateConfig(cert, key, caCert, insecure, mutual, false) if err != nil { return nil, fmt.Errorf("cannot create TLS config: %s", err) } diff --git a/tcp/mux_test.go b/tcp/mux_test.go index 7c12086e..ebaf37f4 100644 --- a/tcp/mux_test.go +++ b/tcp/mux_test.go @@ -176,7 +176,7 @@ func TestTLSMux(t *testing.T) { key := x509.KeyFile("") defer os.Remove(key) - mux, err := NewTLSMux(tcpListener, nil, cert, key, "", true) + mux, err := NewTLSMux(tcpListener, nil, cert, key, "", true, false) if err != nil { t.Fatalf("failed to create mux: %s", err.Error()) } @@ -199,7 +199,7 @@ func TestTLSMux(t *testing.T) { func TestTLSMux_Fail(t *testing.T) { tcpListener := mustTCPListener("127.0.0.1:0") defer tcpListener.Close() - _, err := NewTLSMux(tcpListener, nil, "xxxx", "yyyy", "", true) + _, err := NewTLSMux(tcpListener, nil, "xxxx", "yyyy", "", true, false) if err == nil { t.Fatalf("created mux unexpectedly with bad resources") }