From d718810256b106323f9420d545b0559ae1880ba3 Mon Sep 17 00:00:00 2001 From: Colin Sullivan Date: Fri, 15 Nov 2019 14:44:56 -0700 Subject: [PATCH] Add TLS support for NATS Connections (#13) * Add TLS support for NATS connections Signed-off-by: Colin Sullivan --- main.go | 9 +++++--- surveyor/surveyor.go | 29 ++++++++++++++++++-------- surveyor/surveyor_test.go | 44 ++++++++++++++++++++++++++++++++++++--- test/certs/oo.sh | 5 ----- test/test.go | 16 +++++++------- test/test_test.go | 6 ++++++ test/tls.conf | 13 ++++++++++++ 7 files changed, 95 insertions(+), 27 deletions(-) delete mode 100644 test/certs/oo.sh create mode 100644 test/tls.conf diff --git a/main.go b/main.go index bd5be7f..90b0489 100644 --- a/main.go +++ b/main.go @@ -42,9 +42,12 @@ func main() { flag.IntVar(&opts.ListenPort, "p", surveyor.DefaultListenPort, "Port to listen on.") flag.StringVar(&opts.ListenAddress, "addr", surveyor.DefaultListenAddress, "Network host to listen on.") flag.StringVar(&opts.ListenAddress, "a", surveyor.DefaultListenAddress, "Network host to listen on.") - flag.StringVar(&opts.CertFile, "tlscert", "", "Server certificate file (Enables HTTPS).") - flag.StringVar(&opts.KeyFile, "tlskey", "", "Private key for server certificate (used with HTTPS).") - flag.StringVar(&opts.CaFile, "tlscacert", "", "Client certificate CA for verification (used with HTTPS).") + flag.StringVar(&opts.CertFile, "tlscert", "", "Client certificate file for NATS connections.") + flag.StringVar(&opts.KeyFile, "tlskey", "", "Client private key for NATS connections.") + flag.StringVar(&opts.CaFile, "tlscacert", "", "Client certificate CA on NATS connecctions.") + flag.StringVar(&opts.HTTPCertFile, "http_tlscert", "", "Server certificate file (Enables HTTPS).") + flag.StringVar(&opts.HTTPKeyFile, "http_tlskey", "", "Private key for server certificate (used with HTTPS).") + flag.StringVar(&opts.HTTPCaFile, "http_tlscacert", "", "Client certificate CA for verification (used with HTTPS).") flag.StringVar(&opts.HTTPUser, "http_user", "", "Enable basic auth and set user name for HTTP scrapes.") flag.StringVar(&opts.HTTPPassword, "http_pass", "", "Set the password for HTTP scrapes. NATS bcrypt supported.") flag.StringVar(&opts.Prefix, "prefix", "", "Replace the default prefix for all the metrics.") diff --git a/surveyor/surveyor.go b/surveyor/surveyor.go index 0171723..09c5528 100644 --- a/surveyor/surveyor.go +++ b/surveyor/surveyor.go @@ -58,6 +58,9 @@ type Options struct { CertFile string KeyFile string CaFile string + HTTPCertFile string + HTTPKeyFile string + HTTPCaFile string NATSServerURL string HTTPUser string // User in metrics scrape by Prometheus. HTTPPassword string @@ -120,6 +123,14 @@ func connect(opts *Options) (*nats.Conn, error) { })) nopts = append(nopts, nats.MaxReconnects(10240)) + // NATS TLS Options + if opts.CaFile != "" { + nopts = append(nopts, nats.RootCAs(opts.CaFile)) + } + if opts.CertFile != "" { + nopts = append(nopts, nats.ClientCert(opts.CertFile, opts.KeyFile)) + } + nc, err := nats.Connect(opts.URLs, nopts...) if err != nil { return nil, err @@ -164,17 +175,17 @@ func (s *Surveyor) createCollector() error { } // generates the TLS config for https -func (s *Surveyor) generateTLSConfig() (*tls.Config, error) { +func (s *Surveyor) generateHTTPTLSConfig() (*tls.Config, error) { // Load in cert and private key - cert, err := tls.LoadX509KeyPair(s.opts.CertFile, s.opts.KeyFile) + cert, err := tls.LoadX509KeyPair(s.opts.HTTPCertFile, s.opts.HTTPKeyFile) if err != nil { return nil, fmt.Errorf("error parsing X509 certificate/key pair (%s, %s): %v", - s.opts.CertFile, s.opts.KeyFile, err) + s.opts.HTTPCertFile, s.opts.HTTPKeyFile, err) } cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0]) if err != nil { return nil, fmt.Errorf("error parsing certificate (%s): %v", - s.opts.CertFile, err) + s.opts.HTTPCertFile, err) } // Create our TLS configuration config := &tls.Config{ @@ -183,10 +194,10 @@ func (s *Surveyor) generateTLSConfig() (*tls.Config, error) { MinVersion: tls.VersionTLS12, } // Add in CAs if applicable. - if s.opts.CaFile != "" { - rootPEM, err := ioutil.ReadFile(s.opts.CaFile) + if s.opts.HTTPCaFile != "" { + rootPEM, err := ioutil.ReadFile(s.opts.HTTPCaFile) if err != nil || rootPEM == nil { - return nil, fmt.Errorf("failed to load root ca certificate (%s): %v", s.opts.CaFile, err) + return nil, fmt.Errorf("failed to load root ca certificate (%s): %v", s.opts.HTTPCaFile, err) } pool := x509.NewCertPool() ok := pool.AppendCertsFromPEM(rootPEM) @@ -284,11 +295,11 @@ func (s *Surveyor) startHTTP() error { // If a certificate file has been specified, setup TLS with the // key provided. - if s.opts.CertFile != "" { + if s.opts.HTTPCertFile != "" { proto = "https" // debug log.Printf("Certificate file specfied; using https.") - config, err = s.generateTLSConfig() + config, err = s.generateHTTPTLSConfig() if err != nil { return err } diff --git a/surveyor/surveyor_test.go b/surveyor/surveyor_test.go index ba5797b..047f976 100644 --- a/surveyor/surveyor_test.go +++ b/surveyor/surveyor_test.go @@ -238,14 +238,52 @@ func TestSurveyor_NoSystemAccount(t *testing.T) { } } +func TestSurveyor_ClientTLSFail(t *testing.T) { + ns := st.StartServer(t, "../test/r1s1.conf") + st.ConnectAndVerify(t, ns.ClientURL()) + defer ns.Shutdown() + + opts := getTestOptions() + opts.CaFile = caCertFile + opts.CertFile = clientCert + opts.KeyFile = clientKey + + _, err := NewSurveyor(opts) + if err == nil { + t.Fatalf("Connected to a server that required TLS") + } +} + +func TestSurveyor_ClientTLS(t *testing.T) { + ns := st.StartServer(t, "../test/tls.conf") + defer ns.Shutdown() + + opts := getTestOptions() + opts.URLs = "127.0.0.1:4223" + opts.CaFile = caCertFile + opts.CertFile = clientCert + opts.KeyFile = clientKey + + s, err := NewSurveyor(opts) + if err != nil { + t.Fatalf("couldn't create surveyor: %v", err) + } + if err = s.Start(); err != nil { + t.Fatalf("start error: %v", err) + } + defer s.Stop() + + pollAndCheckDefault(t, "nats_core_mem_bytes") +} + func TestSurveyor_HTTPS(t *testing.T) { sc := st.NewSuperCluster(t) defer sc.Shutdown() opts := getTestOptions() - opts.CaFile = caCertFile - opts.CertFile = serverCert - opts.KeyFile = serverKey + opts.HTTPCaFile = caCertFile + opts.HTTPCertFile = serverCert + opts.HTTPKeyFile = serverKey s, err := NewSurveyor(opts) if err != nil { diff --git a/test/certs/oo.sh b/test/certs/oo.sh deleted file mode 100644 index 831cc0c..0000000 --- a/test/certs/oo.sh +++ /dev/null @@ -1,5 +0,0 @@ -cp ~/go/src/github.com/nats-io/nats-server/test/configs/certs/ca.pem ca.pem -cp ~/go/src/github.com/nats-io/nats-server/test/configs/certs/client-cert.pem client-cert.pem -cp ~/go/src/github.com/nats-io/nats-server/test/configs/certs/client-key.pem client-key.pem -cp ~/go/src/github.com/nats-io/nats-server/test/configs/certs/server-cert.pem server-cert.pem -cp ~/go/src/github.com/nats-io/nats-server/test/configs/certs/server-key.pem server-key.pem diff --git a/test/test.go b/test/test.go index c18d0a4..6d4df08 100644 --- a/test/test.go +++ b/test/test.go @@ -95,8 +95,8 @@ func StartBasicServer() *ns.Server { panic("Unable to start NATS Server in Go Routine") } -// startServer starts a a NATS server -func startServer(t *testing.T, confFile string) *ns.Server { +// StartServer starts a a NATS server +func StartServer(t *testing.T, confFile string) *ns.Server { resetPreviousHTTPConnections() opts, err := ns.ProcessConfigFile(confFile) @@ -138,7 +138,7 @@ var configFiles = []string{"../test/r1s1.conf", "../test/r1s2.conf", "../test/r2 func NewSuperCluster(t *testing.T) *SuperCluster { sc := &SuperCluster{} for _, f := range configFiles { - sc.Servers = append(sc.Servers, startServer(t, f)) + sc.Servers = append(sc.Servers, StartServer(t, f)) } sc.setupClientsAndVerify(t) return sc @@ -146,8 +146,8 @@ func NewSuperCluster(t *testing.T) *SuperCluster { // NewSingleServer creates a single NATS server with a system account func NewSingleServer(t *testing.T) *ns.Server { - s := startServer(t, "../test/r1s1.conf") - connectAndVerify(t, s.ClientURL()) + s := StartServer(t, "../test/r1s1.conf") + ConnectAndVerify(t, s.ClientURL()) return s } @@ -161,7 +161,9 @@ func (sc *SuperCluster) Shutdown() { } } -func connectAndVerify(t *testing.T, url string) *nats.Conn { +// ConnectAndVerify connects to a server a verifies it is +// ready to process messages. +func ConnectAndVerify(t *testing.T, url string) *nats.Conn { c, err := nats.Connect(url, nats.UserCredentials("../test/myuser.creds")) if err != nil { t.Fatalf("Couldn't connect a client to %s: %v", url, err) @@ -185,7 +187,7 @@ func connectAndVerify(t *testing.T, url string) *nats.Conn { func (sc *SuperCluster) setupClientsAndVerify(t *testing.T) { for _, s := range sc.Servers { - c := connectAndVerify(t, s.ClientURL()) + c := ConnectAndVerify(t, s.ClientURL()) sc.Clients = append(sc.Clients, c) } diff --git a/test/test_test.go b/test/test_test.go index e553289..ab36f32 100644 --- a/test/test_test.go +++ b/test/test_test.go @@ -34,3 +34,9 @@ func TestStartSingleServer(t *testing.T) { ns := NewSingleServer(t) ns.Shutdown() } + +func TestStartServers(t *testing.T) { + ns := StartServer(t, "../test/r1s1.conf") + ConnectAndVerify(t, ns.ClientURL()) + ns.Shutdown() +} diff --git a/test/tls.conf b/test/tls.conf new file mode 100644 index 0000000..6a266f0 --- /dev/null +++ b/test/tls.conf @@ -0,0 +1,13 @@ +# Simple TLS config file + +listen: 127.0.0.1:4223 + +tls { + # Server cert + cert_file: "../test/certs/server-cert.pem" + # Server private key + key_file: "../test/certs/server-key.pem" + # Specified time for handshake to complete + timeout: 2 +} +