mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-14 02:07:59 -07:00
When TLS and authorization is enabled, the authorization timeout can fire during the TLS handshake, causing the server to write the authorization timeout error string into the client socket, injecting what becomes bad data into the TLS handshake. This creates misleading errors on the client such as tls: oversized record received with length 21024. This moves the authorization timeout scheduling to after the TLS handshake to avoid the race. This should be safe since TLS has its own handshake timeout. Added a unit test that fails with the old behavior and passes with the new. LMK if you can think of a better way to test this. Fixes #432
324 lines
8.3 KiB
Go
324 lines
8.3 KiB
Go
// Copyright 2015 Apcera Inc. All rights reserved.
|
|
|
|
package test
|
|
|
|
import (
|
|
"bufio"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nats-io/gnatsd/server"
|
|
"github.com/nats-io/go-nats"
|
|
)
|
|
|
|
func TestTLSConnection(t *testing.T) {
|
|
srv, opts := RunServerWithConfig("./configs/tls.conf")
|
|
defer srv.Shutdown()
|
|
|
|
endpoint := fmt.Sprintf("%s:%d", opts.Host, opts.Port)
|
|
nurl := fmt.Sprintf("tls://%s:%s@%s/", opts.Username, opts.Password, endpoint)
|
|
nc, err := nats.Connect(nurl)
|
|
if err == nil {
|
|
nc.Close()
|
|
t.Fatalf("Expected error trying to connect to secure server")
|
|
}
|
|
|
|
// Do simple SecureConnect
|
|
nc, err = nats.Connect(fmt.Sprintf("tls://%s/", endpoint))
|
|
if err == nil {
|
|
nc.Close()
|
|
t.Fatalf("Expected error trying to connect to secure server with no auth")
|
|
}
|
|
|
|
// Now do more advanced checking, verifying servername and using rootCA.
|
|
|
|
nc, err = nats.Connect(nurl, nats.RootCAs("./configs/certs/ca.pem"))
|
|
if err != nil {
|
|
t.Fatalf("Got an error on Connect with Secure Options: %+v\n", err)
|
|
}
|
|
defer nc.Close()
|
|
|
|
subj := "foo-tls"
|
|
sub, _ := nc.SubscribeSync(subj)
|
|
|
|
nc.Publish(subj, []byte("We are Secure!"))
|
|
nc.Flush()
|
|
nmsgs, _ := sub.QueuedMsgs()
|
|
if nmsgs != 1 {
|
|
t.Fatalf("Expected to receive a message over the TLS connection")
|
|
}
|
|
}
|
|
|
|
func TestTLSClientCertificate(t *testing.T) {
|
|
srv, opts := RunServerWithConfig("./configs/tlsverify.conf")
|
|
defer srv.Shutdown()
|
|
|
|
nurl := fmt.Sprintf("tls://%s:%d", opts.Host, opts.Port)
|
|
|
|
_, err := nats.Connect(nurl)
|
|
if err == nil {
|
|
t.Fatalf("Expected error trying to connect to secure server without a certificate")
|
|
}
|
|
|
|
// Load client certificate to successfully connect.
|
|
certFile := "./configs/certs/client-cert.pem"
|
|
keyFile := "./configs/certs/client-key.pem"
|
|
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
|
if err != nil {
|
|
t.Fatalf("error parsing X509 certificate/key pair: %v", err)
|
|
}
|
|
|
|
// Load in root CA for server verification
|
|
rootPEM, err := ioutil.ReadFile("./configs/certs/ca.pem")
|
|
if err != nil || rootPEM == nil {
|
|
t.Fatalf("failed to read root certificate")
|
|
}
|
|
pool := x509.NewCertPool()
|
|
ok := pool.AppendCertsFromPEM([]byte(rootPEM))
|
|
if !ok {
|
|
t.Fatalf("failed to parse root certificate")
|
|
}
|
|
|
|
config := &tls.Config{
|
|
Certificates: []tls.Certificate{cert},
|
|
ServerName: opts.Host,
|
|
RootCAs: pool,
|
|
MinVersion: tls.VersionTLS12,
|
|
}
|
|
|
|
copts := nats.DefaultOptions
|
|
copts.Url = nurl
|
|
copts.Secure = true
|
|
copts.TLSConfig = config
|
|
|
|
nc, err := copts.Connect()
|
|
if err != nil {
|
|
t.Fatalf("Got an error on Connect with Secure Options: %+v\n", err)
|
|
}
|
|
nc.Flush()
|
|
defer nc.Close()
|
|
}
|
|
|
|
func TestTLSVerifyClientCertificate(t *testing.T) {
|
|
srv, opts := RunServerWithConfig("./configs/tlsverify_noca.conf")
|
|
defer srv.Shutdown()
|
|
|
|
nurl := fmt.Sprintf("tls://%s:%d", opts.Host, opts.Port)
|
|
|
|
// The client is configured properly, but the server has no CA
|
|
// to verify the client certificate. Connection should fail.
|
|
nc, err := nats.Connect(nurl,
|
|
nats.ClientCert("./configs/certs/client-cert.pem", "./configs/certs/client-key.pem"),
|
|
nats.RootCAs("./configs/certs/ca.pem"))
|
|
if err == nil {
|
|
nc.Close()
|
|
t.Fatal("Expected failure to connect, did not")
|
|
}
|
|
}
|
|
|
|
func TestTLSConnectionTimeout(t *testing.T) {
|
|
opts := LoadConfig("./configs/tls.conf")
|
|
opts.TLSTimeout = 0.25
|
|
|
|
srv := RunServer(opts)
|
|
defer srv.Shutdown()
|
|
|
|
// Dial with normal TCP
|
|
endpoint := fmt.Sprintf("%s:%d", opts.Host, opts.Port)
|
|
conn, err := net.Dial("tcp", endpoint)
|
|
if err != nil {
|
|
t.Fatalf("Could not connect to %q", endpoint)
|
|
}
|
|
defer conn.Close()
|
|
|
|
// Read deadlines
|
|
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
|
|
|
|
// Read the INFO string.
|
|
br := bufio.NewReader(conn)
|
|
info, err := br.ReadString('\n')
|
|
if err != nil {
|
|
t.Fatalf("Failed to read INFO - %v", err)
|
|
}
|
|
if !strings.HasPrefix(info, "INFO ") {
|
|
t.Fatalf("INFO response incorrect: %s\n", info)
|
|
}
|
|
wait := time.Duration(opts.TLSTimeout * float64(time.Second))
|
|
time.Sleep(wait)
|
|
// Read deadlines
|
|
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
|
|
tlsErr, err := br.ReadString('\n')
|
|
if err == nil && !strings.Contains(tlsErr, "-ERR 'Secure Connection - TLS Required") {
|
|
t.Fatalf("TLS Timeout response incorrect: %q\n", tlsErr)
|
|
}
|
|
}
|
|
|
|
// Ensure there is no race between authorization timeout and TLS handshake.
|
|
func TestTLSAuthorizationShortTimeout(t *testing.T) {
|
|
opts := LoadConfig("./configs/tls.conf")
|
|
opts.AuthTimeout = 0.001
|
|
|
|
srv := RunServer(opts)
|
|
defer srv.Shutdown()
|
|
|
|
endpoint := fmt.Sprintf("%s:%d", opts.Host, opts.Port)
|
|
nurl := fmt.Sprintf("tls://%s:%s@%s/", opts.Username, opts.Password, endpoint)
|
|
|
|
// Expect an error here (no CA) but not a TLS oversized record error which
|
|
// indicates the authorization timeout fired too soon.
|
|
_, err := nats.Connect(nurl)
|
|
if err == nil {
|
|
t.Fatal("Expected error trying to connect to secure server")
|
|
}
|
|
if strings.Contains(err.Error(), "oversized record") {
|
|
t.Fatal("Corrupted TLS handshake:", err)
|
|
}
|
|
}
|
|
|
|
func stressConnect(t *testing.T, wg *sync.WaitGroup, errCh chan error, url string, index int) {
|
|
defer wg.Done()
|
|
|
|
subName := fmt.Sprintf("foo.%d", index)
|
|
|
|
for i := 0; i < 33; i++ {
|
|
nc, err := nats.Connect(url, nats.RootCAs("./configs/certs/ca.pem"))
|
|
if err != nil {
|
|
errCh <- fmt.Errorf("Unable to create TLS connection: %v\n", err)
|
|
return
|
|
}
|
|
defer nc.Close()
|
|
|
|
sub, err := nc.SubscribeSync(subName)
|
|
if err != nil {
|
|
errCh <- fmt.Errorf("Unable to subscribe on '%s': %v\n", subName, err)
|
|
return
|
|
}
|
|
|
|
if err := nc.Publish(subName, []byte("secure data")); err != nil {
|
|
errCh <- fmt.Errorf("Unable to send on '%s': %v\n", subName, err)
|
|
}
|
|
|
|
if _, err := sub.NextMsg(2 * time.Second); err != nil {
|
|
errCh <- fmt.Errorf("Unable to get next message: %v\n", err)
|
|
}
|
|
|
|
nc.Close()
|
|
}
|
|
|
|
errCh <- nil
|
|
}
|
|
|
|
func TestTLSStressConnect(t *testing.T) {
|
|
opts, err := server.ProcessConfigFile("./configs/tls.conf")
|
|
if err != nil {
|
|
panic(fmt.Sprintf("Error processing configuration file: %v", err))
|
|
}
|
|
opts.NoSigs, opts.NoLog = true, true
|
|
|
|
// For this test, remove the authorization
|
|
opts.Username = ""
|
|
opts.Password = ""
|
|
|
|
// Increase ssl timeout
|
|
opts.TLSTimeout = 2.0
|
|
|
|
srv := RunServer(opts)
|
|
defer srv.Shutdown()
|
|
|
|
nurl := fmt.Sprintf("tls://%s:%d", opts.Host, opts.Port)
|
|
|
|
threadCount := 3
|
|
|
|
errCh := make(chan error, threadCount)
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(threadCount)
|
|
|
|
for i := 0; i < threadCount; i++ {
|
|
go stressConnect(t, &wg, errCh, nurl, i)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
var lastError error
|
|
for i := 0; i < threadCount; i++ {
|
|
err := <-errCh
|
|
if err != nil {
|
|
lastError = err
|
|
}
|
|
}
|
|
|
|
if lastError != nil {
|
|
t.Fatalf("%v\n", lastError)
|
|
}
|
|
}
|
|
|
|
func TestTLSBadAuthError(t *testing.T) {
|
|
srv, opts := RunServerWithConfig("./configs/tls.conf")
|
|
defer srv.Shutdown()
|
|
|
|
endpoint := fmt.Sprintf("%s:%d", opts.Host, opts.Port)
|
|
nurl := fmt.Sprintf("tls://%s:%s@%s/", opts.Username, "NOT_THE_PASSWORD", endpoint)
|
|
|
|
_, err := nats.Connect(nurl, nats.RootCAs("./configs/certs/ca.pem"))
|
|
if err == nil {
|
|
t.Fatalf("Expected error trying to connect to secure server")
|
|
}
|
|
if err.Error() != nats.ErrAuthorization.Error() {
|
|
t.Fatalf("Excpected and auth violation, got %v\n", err)
|
|
}
|
|
}
|
|
|
|
func TestTLSConnectionCurvePref(t *testing.T) {
|
|
srv, opts := RunServerWithConfig("./configs/tls_curve_pref.conf")
|
|
defer srv.Shutdown()
|
|
|
|
if len(opts.TLSConfig.CurvePreferences) != 1 {
|
|
t.Fatal("Invalid curve preference loaded.")
|
|
}
|
|
|
|
if opts.TLSConfig.CurvePreferences[0] != tls.CurveP256 {
|
|
t.Fatalf("Invalid curve preference loaded [%v].", opts.TLSConfig.CurvePreferences[0])
|
|
}
|
|
|
|
endpoint := fmt.Sprintf("%s:%d", opts.Host, opts.Port)
|
|
nurl := fmt.Sprintf("tls://%s:%s@%s/", opts.Username, opts.Password, endpoint)
|
|
nc, err := nats.Connect(nurl)
|
|
if err == nil {
|
|
nc.Close()
|
|
t.Fatalf("Expected error trying to connect to secure server")
|
|
}
|
|
|
|
// Do simple SecureConnect
|
|
nc, err = nats.Connect(fmt.Sprintf("tls://%s/", endpoint))
|
|
if err == nil {
|
|
nc.Close()
|
|
t.Fatalf("Expected error trying to connect to secure server with no auth")
|
|
}
|
|
|
|
// Now do more advanced checking, verifying servername and using rootCA.
|
|
|
|
nc, err = nats.Connect(nurl, nats.RootCAs("./configs/certs/ca.pem"))
|
|
if err != nil {
|
|
t.Fatalf("Got an error on Connect with Secure Options: %+v\n", err)
|
|
}
|
|
defer nc.Close()
|
|
|
|
subj := "foo-tls"
|
|
sub, _ := nc.SubscribeSync(subj)
|
|
|
|
nc.Publish(subj, []byte("We are Secure!"))
|
|
nc.Flush()
|
|
nmsgs, _ := sub.QueuedMsgs()
|
|
if nmsgs != 1 {
|
|
t.Fatalf("Expected to receive a message over the TLS connection")
|
|
}
|
|
}
|