Merge pull request #133 from nats-io/tls

TLS
This commit is contained in:
Derek Collison
2015-11-08 14:39:29 -08:00
28 changed files with 1031 additions and 47 deletions

View File

@@ -2,6 +2,7 @@
# General
- [ ] SSL/TLS support
- [ ] Better user/pass support using bcrypt etc.
- [ ] Pedantic state
- [ ] brew, apt-get, rpm, chocately (windows)
- [ ] Dynamic socket buffer sizes

View File

@@ -220,31 +220,7 @@ func (c *client) traceOp(format, op string, arg []byte) {
c.Tracef(format, opa)
}
// Process the info message if we are a route.
func (c *client) processRouteInfo(info *Info) {
c.mu.Lock()
if c.route == nil {
c.mu.Unlock()
return
}
c.route.remoteID = info.ID
// Check to see if we have this remote already registered.
// This can happen when both servers have routes to each other.
s := c.srv
c.mu.Unlock()
if s.addRoute(c) {
c.Debugf("Registering remote route %q", info.ID)
// Send our local subscriptions to this route.
s.sendLocalSubsToRoute(c)
} else {
c.Debugf("Detected duplicate remote route %q", info.ID)
c.closeConnection()
}
}
// Process the information messages from Clients and other routes.
// Process the information messages from Clients and other Routes.
func (c *client) processInfo(arg []byte) error {
info := Info{}
if err := json.Unmarshal(arg, &info); err != nil {
@@ -293,6 +269,7 @@ func (c *client) processConnect(arg []byte) error {
func (c *client) authTimeout() {
c.sendErr("Authorization Timeout")
c.Debugf("Authorization Timeout")
c.closeConnection()
}

View File

@@ -18,7 +18,7 @@ type serverInfo struct {
Port uint `json:"port"`
Version string `json:"version"`
AuthRequired bool `json:"auth_required"`
SslRequired bool `json:"ssl_required"`
TLSRequired bool `json:"ssl_required"`
MaxPayload int64 `json:"max_payload"`
}
@@ -93,7 +93,7 @@ func TestClientCreateAndInfo(t *testing.T) {
}
// Sanity checks
if info.MaxPayload != MAX_PAYLOAD_SIZE ||
info.AuthRequired || info.SslRequired ||
info.AuthRequired || info.TLSRequired ||
info.Port != DEFAULT_PORT {
t.Fatalf("INFO inconsistent: %+v\n", info)
}

View File

@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFQTCCAymgAwIBAgIJAMBAkt0mj5R8MA0GCSqGSIb3DQEBCwUAMIGLMQswCQYD
VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzAR
BgNVBAoTCkFwY2VyYSBJbmMxEDAOBgNVBAsTB25hdHMuaW8xEjAQBgNVBAMTCWxv
Y2FsaG9zdDEcMBoGCSqGSIb3DQEJARYNZGVyZWtAbmF0cy5pbzAeFw0xNTExMDUx
NzE3MjZaFw0xOTExMDQxNzE3MjZaMBQxEjAQBgNVBAMTCWxvY2FsaG9zdDCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKM5pTCAWQK9DptePVucoWeVAqY8
Pr4ldaHoR4Zm+dUa57OffYu0fxK8Vwyxp3tLhWBWQ6etfR++8BxuhB7OHpIJ330Y
7VNJJXq5Uz0T0acfW5kS/8jqprZQDg1NeYUjGAopPy7hfI65ueC/mVBrbREbyNSM
+c22BM8hLjTNElvHVsc3340DJMTxBqEofjSjsiulzrYqDbpPLNTMSO8mpmETKkVC
mKD8HI8U0i4MgJ/AwUKzVaRAKKlTdUqAQrydK0FEogCNtPe4Ds7kanlqWvoPsD64
vnVyXkKE77xUFcaOShV0Oh3itcVel6mllnj9f7AvgwKX5t0AqqTz6EEci/grnZfr
4ARaPAjvW4OxweKwoYnu8pbeVVHYY4XJQYhJGqJOHwtQ5ayyLs/lKOE0NuyvK5y1
6gDghmwG+WJc3q6BHrEslDKFRTTc74ZgWxQfXZ1N2aQ1Ci/MNJ/xNgo3vpvO5Eik
sGvF3czJSaG6iJtZ/8Kpb6VddGio1TTwKs9CAqJ+PuKBe8B0n3gWcnwICtvGcqs4
zureLRFP4rnM6+PR2LJODijPc/JmoEy/rLbHoJZg/EuxKuaq/FFjpZS/xjtHtcxc
KxE8/vVq9FZWQlJC/lG6GLr7eJ6PMnEM+1YXFWCXU9GY2fgNEzkP7CRk3WMtmUby
15NS42g1KjQ2pqCfAgMBAAGjHjAcMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAA
ATANBgkqhkiG9w0BAQsFAAOCAgEAC7q54dnr9ESCQZrVn+47hZcfweukjJAjgTr6
7lj2yM6AAe2JaK6kds8uXfluxkJQxtLz7PdA0DDLDlAphx3Qx1ss86mcRuhNZWob
GAsKH4GM0y+d2U7ar8GpQRaJXdgRGlKOxgEZ2pyagY4I4dwn/0M35ccHaySz2Xx2
zfNcIIt8Z1B29BmdNGfI+EfUTFkfyqovjh4mFuLEFsCNyluYKYagN4A7P5NEJpUF
/8wf9c6suJCzIjtBkWLOs95syDy1vw92x29vDQClArPksM6G4ReLYUXoygT9y2SI
URPRYYVjGupDcXD989yVIYNYkert6Ib3Cf2wlvvgXe4c3QnT3Rm7jg2RvR7B73Fc
j4EqnOGvI5XGQPFHYBzSJPs9sVVP9b8c7G/SpMTCdd3hK5idOkBAS3WOecnvE23t
JSHQvdegenEFL0yXYe6Rhag1Bj9Q01QizwCBDwoH3Pfvi5ZAHEXW253n6bD3p6OK
NuzoCzSFZBfrzFP4V/2VUtUYKudQ3bJMKKP2snvPyphG/UmGGfZUXb/kVA19Mpd4
TMIaZD7dgo3toXXygPyWzyblRcvMY2UUWM5n43f6JEovFfxEvagErbAbJvCzR1XW
N441LebEnCrYf8XslEsulKd7nGZioi31M4971rtoawpD29HBAlNWHKBR2k4hh90F
laaNJWY=
-----END CERTIFICATE-----

16
server/configs/tls.conf Normal file
View File

@@ -0,0 +1,16 @@
# Simple TLS config file
port: 4443
net: apcera.me # net interface
tls {
cert_file: "./configs/certs/server.pem"
key_file: "./configs/certs/key.pem"
}
authorization {
user: derek
password: buckley
timeout: 1
}

View File

@@ -1,8 +1,10 @@
// Copyright 2012-2013 Apcera Inc. All rights reserved.
// Copyright 2012-2015 Apcera Inc. All rights reserved.
package server
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net"
@@ -30,7 +32,6 @@ type Options struct {
PingInterval time.Duration `json:"ping_interval"`
MaxPingsOut int `json:"ping_max"`
HTTPPort int `json:"http_port"`
SslTimeout float64 `json:"ssl_timeout"`
AuthTimeout float64 `json:"auth_timeout"`
MaxControlLine int `json:"max_control_line"`
MaxPayload int `json:"max_payload"`
@@ -40,6 +41,7 @@ type Options struct {
ClusterUsername string `json:"-"`
ClusterPassword string `json:"-"`
ClusterAuthTimeout float64 `json:"auth_timeout"`
ClusterTLSConfig *tls.Config `json:"-"`
ProfPort int `json:"-"`
PidFile string `json:"-"`
LogFile string `json:"-"`
@@ -48,6 +50,8 @@ type Options struct {
Routes []*url.URL `json:"-"`
RoutesStr string `json:"-"`
BufSize int `json:"-"`
TLSTimeout float64 `json:"tls_timeout"`
TLSConfig *tls.Config `json:"-"`
}
type authorization struct {
@@ -56,6 +60,14 @@ type authorization struct {
timeout float64
}
// This struct holds the parsed tls config information.
type tlsConfig struct {
certFile string
keyFile string
caFile string
verify bool
}
// ProcessConfigFile processes a configuration file.
// FIXME(dlc): Hacky
func ProcessConfigFile(configFile string) (*Options, error) {
@@ -118,6 +130,11 @@ func ProcessConfigFile(configFile string) (*Options, error) {
opts.MaxPending = int(v.(int64))
case "max_connections", "max_conn":
opts.MaxConn = int(v.(int64))
case "tls":
tlsm := v.(map[string]interface{})
if opts.TLSConfig, err = parseTLS(tlsm); err != nil {
return nil, err
}
}
}
return opts, nil
@@ -148,6 +165,12 @@ func parseCluster(cm map[string]interface{}, opts *Options) error {
}
opts.Routes = append(opts.Routes, url)
}
case "tls":
var err error
tlsm := mv.(map[string]interface{})
if opts.ClusterTLSConfig, err = parseTLS(tlsm); err != nil {
return err
}
}
}
return nil
@@ -176,6 +199,84 @@ func parseAuthorization(am map[string]interface{}) authorization {
return auth
}
// Helper function to parse TLS configs.
func parseTLS(tlsm map[string]interface{}) (*tls.Config, error) {
tc := tlsConfig{}
for mk, mv := range tlsm {
switch strings.ToLower(mk) {
case "cert_file":
certFile, ok := mv.(string)
if !ok {
return nil, fmt.Errorf("error parsing tls config, expected 'cert_file' to be filename")
}
tc.certFile = certFile
case "key_file":
keyFile, ok := mv.(string)
if !ok {
return nil, fmt.Errorf("error parsing tls config, expected 'key_file' to be filename")
}
tc.keyFile = keyFile
case "ca_file":
caFile, ok := mv.(string)
if !ok {
return nil, fmt.Errorf("error parsing tls config, expected 'ca_file' to be filename")
}
tc.caFile = caFile
case "verify":
verify, ok := mv.(bool)
if !ok {
return nil, fmt.Errorf("error parsing tls config, expected 'verify' to be a boolean")
}
tc.verify = verify
default:
return nil, fmt.Errorf("error parsing tls config, unknown field [%q]", mk)
}
}
// Now load in cert and private key
cert, err := tls.LoadX509KeyPair(tc.certFile, tc.keyFile)
if err != nil {
return nil, fmt.Errorf("error parsing X509 certificate/key pair: %v", err)
}
cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
if err != nil {
return nil, fmt.Errorf("error parsing certificate: %v", err)
}
// Create TLSConfig
// We will determine the cipher suites that we prefer.
config := tls.Config{
Certificates: []tls.Certificate{cert},
PreferServerCipherSuites: true,
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
// The SHA384 versions are only in Go1.5
// tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
// tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
}
// Require client certificates as needed
if tc.verify == true {
config.ClientAuth = tls.RequireAnyClientCert
}
// Add in CAs if applicable.
if tc.caFile != "" {
rootPEM, err := ioutil.ReadFile(tc.caFile)
if err != nil || rootPEM == nil {
return nil, err
}
pool := x509.NewCertPool()
ok := pool.AppendCertsFromPEM([]byte(rootPEM))
if !ok {
return nil, fmt.Errorf("failed to parse root ca certificate")
}
config.RootCAs = pool
}
return &config, nil
}
// MergeOptions will merge two options giving preference to the flagOpts
// if the item is present.
func MergeOptions(fileOpts, flagOpts *Options) *Options {
@@ -349,8 +450,8 @@ func processOptions(opts *Options) {
if opts.MaxPingsOut == 0 {
opts.MaxPingsOut = DEFAULT_PING_MAX_OUT
}
if opts.SslTimeout == 0 {
opts.SslTimeout = float64(SSL_TIMEOUT) / float64(time.Second)
if opts.TLSTimeout == 0 {
opts.TLSTimeout = float64(SSL_TIMEOUT) / float64(time.Second)
}
if opts.AuthTimeout == 0 {
opts.AuthTimeout = float64(AUTH_TIMEOUT) / float64(time.Second)

View File

@@ -1,8 +1,9 @@
// Copyright 2013-2014 Apcera Inc. All rights reserved.
// Copyright 2013-2015 Apcera Inc. All rights reserved.
package server
import (
"crypto/tls"
"net/url"
"reflect"
"testing"
@@ -16,7 +17,7 @@ func TestDefaultOptions(t *testing.T) {
MaxConn: DEFAULT_MAX_CONNECTIONS,
PingInterval: DEFAULT_PING_INTERVAL,
MaxPingsOut: DEFAULT_PING_MAX_OUT,
SslTimeout: float64(SSL_TIMEOUT) / float64(time.Second),
TLSTimeout: float64(SSL_TIMEOUT) / float64(time.Second),
AuthTimeout: float64(AUTH_TIMEOUT) / float64(time.Second),
MaxControlLine: MAX_CONTROL_LINE_SIZE,
MaxPayload: MAX_PAYLOAD_SIZE,
@@ -77,6 +78,54 @@ func TestConfigFile(t *testing.T) {
}
}
func TestTLSConfigFile(t *testing.T) {
golden := &Options{
Host: "apcera.me",
Port: 4443,
Username: "derek",
Password: "buckley",
AuthTimeout: 1.0,
}
opts, err := ProcessConfigFile("./configs/tls.conf")
if err != nil {
t.Fatalf("Received an error reading config file: %v\n", err)
}
tlsConfig := opts.TLSConfig
if tlsConfig == nil {
t.Fatal("Expected opts.TLSConfig to be non-nil")
}
opts.TLSConfig = nil
if !reflect.DeepEqual(golden, opts) {
t.Fatalf("Options are incorrect.\nexpected: %+v\ngot: %+v",
golden, opts)
}
// Now check TLSConfig a bit more closely
// CipherSuites
ciphers := []uint16{
// tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
// tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
}
if !reflect.DeepEqual(tlsConfig.CipherSuites, ciphers) {
t.Fatalf("Got incorrect cipher suite list: [%+v]", tlsConfig.CipherSuites)
}
if tlsConfig.MinVersion != tls.VersionTLS12 {
t.Fatalf("Expected MinVersion of 1.2 [%v], got [%v]", tls.VersionTLS12, tlsConfig.MinVersion)
}
if tlsConfig.PreferServerCipherSuites != true {
t.Fatal("Expected PreferServerCipherSuites to be true")
}
// Verify hostname is correct in certificate
if len(tlsConfig.Certificates) != 1 {
t.Fatal("Expected 1 certificate")
}
cert := tlsConfig.Certificates[0].Leaf
if err := cert.VerifyHostname("localhost"); err != nil {
t.Fatalf("Could not verify hostname in certificate: %v\n", err)
}
}
func TestMergeOverrides(t *testing.T) {
golden := &Options{
Host: "apcera.me",

View File

@@ -1,9 +1,11 @@
// Copyright 2013-2014 Apcera Inc. All rights reserved.
// Copyright 2013-2015 Apcera Inc. All rights reserved.
package server
import (
"bufio"
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"net"
@@ -24,14 +26,14 @@ type connectInfo struct {
Pedantic bool `json:"pedantic"`
User string `json:"user,omitempty"`
Pass string `json:"pass,omitempty"`
Ssl bool `json:"ssl_required"`
TLS bool `json:"tls_required"`
Name string `json:"name"`
}
const conProto = "CONNECT %s" + _CRLF_
// Lock should be held entering here.
func (c *client) sendConnect() {
func (c *client) sendConnect(tlsRequired bool) {
var user, pass string
if userInfo := c.route.url.User; userInfo != nil {
user = userInfo.Username()
@@ -42,18 +44,43 @@ func (c *client) sendConnect() {
Pedantic: false,
User: user,
Pass: pass,
Ssl: false,
TLS: tlsRequired,
Name: c.srv.info.ID,
}
b, err := json.Marshal(cinfo)
if err != nil {
Errorf("Error marshalling CONNECT to route: %v\n", err)
c.closeConnection()
return
}
c.bw.WriteString(fmt.Sprintf(conProto, b))
c.bw.Flush()
}
// Process the info message if we are a route.
func (c *client) processRouteInfo(info *Info) {
c.mu.Lock()
if c.route == nil {
c.mu.Unlock()
return
}
c.route.remoteID = info.ID
// Check to see if we have this remote already registered.
// This can happen when both servers have routes to each other.
s := c.srv
c.mu.Unlock()
if s.addRoute(c) {
c.Debugf("Registering remote route %q", info.ID)
// Send our local subscriptions to this route.
s.sendLocalSubsToRoute(c)
} else {
c.Debugf("Detected duplicate remote route %q", info.ID)
c.closeConnection()
}
}
// This will send local subscription state to a new route connection.
// FIXME(dlc) - This could be a DOS or perf issue with many clients
// and large subscription space. Plus buffering in place not a good idea.
@@ -88,9 +115,11 @@ func (s *Server) createRoute(conn net.Conn, rURL *url.URL) *client {
r := &route{didSolicit: didSolicit}
c := &client{srv: s, nc: conn, opts: clientOpts{}, typ: ROUTER, route: r}
// Grab JSON info string
// Grab server variables.
s.mu.Lock()
info := s.routeInfoJSON
authRequired := s.routeInfo.AuthRequired
tlsRequired := s.routeInfo.TLSRequired
s.mu.Unlock()
// Grab lock
@@ -101,18 +130,46 @@ func (s *Server) createRoute(conn net.Conn, rURL *url.URL) *client {
c.Debugf("Route connection created")
// Check for TLS
if tlsRequired {
// Copy off the config to add in ServerName if we
tlsConfig := *s.opts.ClusterTLSConfig
// If we solicited, we will act like the client, otherwise the server.
if didSolicit {
c.Debugf("Starting TLS route client handshake")
// Specify the ServerName we are expecting.
host, _, _ := net.SplitHostPort(rURL.Host)
tlsConfig.ServerName = host
c.nc = tls.Client(c.nc, &tlsConfig)
} else {
c.Debugf("Starting TLS route server handshake")
c.nc = tls.Server(c.nc, &tlsConfig)
}
conn := c.nc.(*tls.Conn)
err := conn.Handshake()
if err != nil {
c.Debugf("TLS route handshake error: %v", err)
c.closeConnection()
return nil
}
// Rewrap bw
c.bw = bufio.NewWriterSize(c.nc, s.opts.BufSize)
}
// Queue Connect proto if we solicited the connection.
if didSolicit {
r.url = rURL
c.Debugf("Route connect msg sent")
c.sendConnect()
c.sendConnect(tlsRequired)
}
// Send our info to the other side.
s.sendInfo(c, info)
// Check for Auth required state for incoming connections.
if s.routeInfo.AuthRequired && !didSolicit {
if authRequired && !didSolicit {
ttl := secondsToDuration(s.opts.ClusterAuthTimeout)
c.setAuthTimer(ttl)
}
@@ -295,14 +352,18 @@ func (s *Server) routeAcceptLoop(ch chan struct{}) {
// StartRouting will start the accept loop on the cluster host:port
// and will actively try to connect to listed routes.
func (s *Server) StartRouting() {
// Check for TLSConfig
tlsReq := s.opts.ClusterTLSConfig != nil
info := Info{
ID: s.info.ID,
Version: s.info.Version,
Host: s.opts.ClusterHost,
Port: s.opts.ClusterPort,
AuthRequired: false,
SslRequired: false,
MaxPayload: MAX_PAYLOAD_SIZE,
TLSRequired: tlsReq,
SSLRequired: tlsReq,
TLSVerify: tlsReq,
MaxPayload: s.info.MaxPayload,
}
// Check for Auth items
if s.opts.ClusterUsername != "" {

View File

@@ -3,6 +3,8 @@
package server
import (
"bufio"
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
@@ -30,7 +32,9 @@ type Info struct {
Host string `json:"host"`
Port int `json:"port"`
AuthRequired bool `json:"auth_required"`
SslRequired bool `json:"ssl_required"`
SSLRequired bool `json:"ssl_required"` // ssl json used for older clients
TLSRequired bool `json:"tls_required"`
TLSVerify bool `json:"tls_verify"`
MaxPayload int `json:"max_payload"`
}
@@ -73,6 +77,11 @@ type stats struct {
// New will setup a new server struct after parsing the options.
func New(opts *Options) *Server {
processOptions(opts)
// Process TLS options, including whether we require client certificates.
tlsReq := opts.TLSConfig != nil
verify := (tlsReq == true && opts.TLSConfig.ClientAuth == tls.RequireAnyClientCert)
info := Info{
ID: genID(),
Version: VERSION,
@@ -80,7 +89,9 @@ func New(opts *Options) *Server {
Host: opts.Host,
Port: opts.Port,
AuthRequired: false,
SslRequired: false,
TLSRequired: tlsReq,
SSLRequired: tlsReq,
TLSVerify: verify,
MaxPayload: opts.MaxPayload,
}
@@ -393,6 +404,7 @@ func (s *Server) createClient(conn net.Conn) *client {
s.mu.Lock()
info := s.infoJSON
authRequired := s.info.AuthRequired
tlsRequired := s.info.TLSRequired
s.mu.Unlock()
// Grab lock
@@ -412,6 +424,19 @@ func (s *Server) createClient(conn net.Conn) *client {
// Send our information.
s.sendInfo(c, info)
// Check for TLS
if tlsRequired {
c.Debugf("Starting TLS client connection handshake")
c.nc = tls.Server(c.nc, s.opts.TLSConfig)
conn := c.nc.(*tls.Conn)
err := conn.Handshake()
if err != nil {
c.Debugf("TLS handshake error: %v", err)
}
// Rewrap bw
c.bw = bufio.NewWriterSize(c.nc, s.opts.BufSize)
}
// Unlock to register
c.mu.Unlock()

62
test/cluster_tls_test.go Normal file
View File

@@ -0,0 +1,62 @@
// Copyright 2013-2015 Apcera Inc. All rights reserved.
package test
import (
"testing"
"time"
"github.com/nats-io/gnatsd/server"
)
func runTLSServers(t *testing.T) (srvA, srvB *server.Server, optsA, optsB *server.Options) {
srvA, optsA = RunServerWithConfig("./configs/srv_a_tls.conf")
srvB, optsB = RunServerWithConfig("./configs/srv_b_tls.conf")
return
}
func TestTLSClusterConfig(t *testing.T) {
srvA, srvB, _, _ := runTLSServers(t)
defer srvA.Shutdown()
defer srvB.Shutdown()
// Wait for the setup
time.Sleep(1 * time.Second)
if numRoutesA := srvA.NumRoutes(); numRoutesA != 1 {
t.Fatalf("Expected one route for srvA, got %d\n", numRoutesA)
}
if numRoutesB := srvB.NumRoutes(); numRoutesB != 1 {
t.Fatalf("Expected one route for srvB, got %d\n", numRoutesB)
}
}
func TestBasicTLSClusterPubSub(t *testing.T) {
srvA, srvB, optsA, optsB := runTLSServers(t)
defer srvA.Shutdown()
defer srvB.Shutdown()
// Wait for the setup
time.Sleep(500 * time.Millisecond)
clientA := createClientConn(t, optsA.Host, optsA.Port)
defer clientA.Close()
clientB := createClientConn(t, optsB.Host, optsB.Port)
defer clientB.Close()
sendA, expectA := setupConn(t, clientA)
sendA("SUB foo 22\r\n")
sendA("PING\r\n")
expectA(pongRe)
sendB, expectB := setupConn(t, clientB)
sendB("PUB foo 2\r\nok\r\n")
sendB("PING\r\n")
expectB(pongRe)
expectMsgs := expectMsgsCommand(t, expectA)
matches := expectMsgs(1)
checkMsg(t, matches[0], "foo", "22", "", "2", "ok")
}

38
test/configs/certs/ca.pem Normal file
View File

@@ -0,0 +1,38 @@
-----BEGIN CERTIFICATE-----
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
+Svp
-----END CERTIFICATE-----

View File

@@ -0,0 +1,30 @@
-----BEGIN CERTIFICATE-----
MIIFPDCCAySgAwIBAgIJAO+k4G7bNTypMA0GCSqGSIb3DQEBCwUAMIGLMQswCQYD
VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzAR
BgNVBAoTCkFwY2VyYSBJbmMxEDAOBgNVBAsTB25hdHMuaW8xEjAQBgNVBAMTCWxv
Y2FsaG9zdDEcMBoGCSqGSIb3DQEJARYNZGVyZWtAbmF0cy5pbzAeFw0xNTExMDUy
MzEwNDdaFw0xOTExMDQyMzEwNDdaMBYxFDASBgNVBAMTC25hdHMtY2xpZW50MIIC
IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEArgLxszD5/vDrDUwwIEgQx9I0
J/H6MXPO0Tj9D2BnR+nwjCe9M03fsq4Il96BVzoaAiAQD1r4NyAX2adKydlnE3/m
bUFiSVHErJceEi9aSs+WlLdmKEgU2qrsIal9KzthlI786qtjb7OFSCxP14R4xYA5
dlZXhJ9oUuFhVTdaVmRMzWuWj8RbBx8VptSZ0f7Q+Uv8GuB0kyiVkv6GYcH/IWuI
7jnM0QcVWBmxJfWmqd0yx/FLlX/LRXqdiyoFSIlMaP0VOwto3uEhAoBk83Z+/zrZ
Brymx1Nnz3qzTCf8/mdMjPuWibXDTLbo0/Kf6neHs6wxx8irb1ZfIwhn8grXTcgd
rg9bfcyyUOBey7QXiedpU0xFqoH26E+Aq+CV4R56i1sJKsSYEGu8O69H8zu5dgan
LZRhcCHcZhMe7Nbiu5BcuOW4r3rGDMTLXSugEX91iy5jJaYmRjtPN5imQIJtf+GK
Vq7YLv4MQV6R3xRiZXaocCae1qzIMc4kxCKvZTmxuJsvIUPjNnGumwbjV/a2fLFX
9tMqUKyEmiPtFtqNH/kmkHCQ5FGYIIj3wGuD5yWfK5Tr3iHOdNJoNNPgPBg9tMRw
j3+W8+uyBxc+FUEb8a9m3R4VmAYyiqgzCA0DWZBF1fOYLWfRnwS5OBKiP4OUlUEb
YZUEzfvDbLOwQrb123cCAwEAAaMXMBUwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJ
KoZIhvcNAQELBQADggIBACNKPbvaXwl5rRTqFw37Am1r6e+LkUg9dFogSwXDnuT/
RRZJi5MHsC5MUOkHB28lTmPwkAogs+LBmKrM0Npzk6OPkT/LCgKqpVoz2Tc1nGMI
Jy8jxPYogMmDCOhoEoC7zsWABMLiX5KDAuKommk61w7AwKu4kK198ngwbfF2fzdH
1DUGID7iV4fyPGI+pCU3Ullv51c5xkhqjVy1JYdYc0+s6rFyVTibSABa7PfHE2ML
A+cNFWoKQhugVHQU7qYvuWvnEqZro2T6nmSmpK3oOaUgVnDuY2q4JwiMbZAtuyD7
8LFwCim49WzgYcfs/BwKlUrTV/QBYurruHWjElZzwA39/ZlbnOjJJ85j/YqxR+4S
fK/KktegyrPJU3fxdl2+77zVlfgzxaQ//58vx5LgXWhl2KeHyakeD0jQFVn1R7GD
bynAlHlSOr+nGkwP2WVqXKf+l/gb/gUEY7bC8fCVRCctkcK+smEl+sIKH3O9JY8l
rBWjOXkMY91ZDh77hfTNni/s2/DGAoNrEft8rgu3/NPxhCTfQH3ranCryth9mF6I
qsOFr5/81WGKqU+Kec8st/RSU2vBjBp41HILAEEhUiB6prhc9B3+exwkvQSPz22W
PIvhkzqeOYRoEDE2bWGC1ukd818qvQp618eLBmJSvwGh4YfUcmgqHaEk2NjoPIMV
-----END CERTIFICATE-----

View File

@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFQTCCAymgAwIBAgIJAO+k4G7bNTyoMA0GCSqGSIb3DQEBCwUAMIGLMQswCQYD
VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzAR
BgNVBAoTCkFwY2VyYSBJbmMxEDAOBgNVBAsTB25hdHMuaW8xEjAQBgNVBAMTCWxv
Y2FsaG9zdDEcMBoGCSqGSIb3DQEJARYNZGVyZWtAbmF0cy5pbzAeFw0xNTExMDUy
MzA2MzRaFw0xOTExMDQyMzA2MzRaMBQxEjAQBgNVBAMTCWxvY2FsaG9zdDCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALYBy3IEY3kqlf5h2vEtk9CB4Vnt
AD+eaVc3Y9xuFHJ4k0ScsjwrYH3YcwQW0fDpOQCI0102YoQT7tPCBT+rC0w1mM82
0ZSKS/y2SIK9cM6LHWkUbQcWfeaL+uz2HB3jTEm8tmEEFTLBJFMbMpUsvjA5GLqG
URswsNjYEl8M9wS1BETw2e+eCFa4wxq9oGHp/Dgh0vZglHzQL5osEpRct1aaQo6O
jWzZc1Cgx4SxmMOoMWF8BQzlO7aikbZEJxk03TIFNph/azJt7mviMseW72mP+bX9
sm/8bsINiYgJMM2HAyjIgFVMXX8AYfEFC4wozYloLqn0yy9TdjhyGbsUjg0yTd4l
A9LkGKroBdY1drPSek5Nj9br27UGGGfU2ddAD5xYBIeeFY+3nqST868oIXB/m1P7
1p8tpkgujx/RqKr3nvOqBHizmoQaWZsPC3X/Jc4NvVHihpuNzN/u1D5mxGhxsx+R
qnrIkhS0IqNrokggPZazugmHntd95HgTb3JpjY3RGEYXAQNr+mZGUCc+CVu0mhFX
xAMZcfVp5nDg4hKHiaRv0KcaqBmnn8AB5w5FiTppzUbRP0zz7GkwrdulwR6c2Eb5
75+/022TbgCx8B9SH4zJRTj5mtrK56eFgTcnuXB+YnWaP7/7qmKIZzxrd3UDvnza
bhnMiiIK7vL8qiOTAgMBAAGjHjAcMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAA
ATANBgkqhkiG9w0BAQsFAAOCAgEAOrh8XfW6quwBAcCxHf6/uvu/iNq4yHCg2qH6
VtWs/x38I2t3BRSNsLsJGieh6yLlZDzOus+XYui4uDE50XmcwaIsY0VcXnvdyZVZ
w9+lMyfp00kRF1o3B6eVxq0pRE5VB0cai7XI7tyfpRwGzA+oNLF4vBvxAHm9Ony5
Q57DC/HFzyUogdkMYciO/kd9oa4HosDEXwaE8UvZUL8OVl/dptMXLL/GGwzZsUAE
1sLAbgm044YChLUDzgBAtDTkB/HNkcPzSKwULuskhe7ndoaEQNXVZuP7quGiZ/W1
1lE59gnmnyG8ySFCL05jHrKLtFAJe88gQjgDK65ZJv4W/k7ocmT+HhCxWyQWcX6v
abJ0EssqeSQuzRMuZebMJJ8s46d6RcYuMdIX3RDXq+1moJDFopE7lgNrlRhWgaky
Og8f/u8s1j75tk1YaYcY9uBKjKk7f681R9wMumkd6IEmEvkUwHNFsctxi4fGI7h1
PRdKL0DlhVmnpHlKs6Kvm2sJ3twSAGSrC4u0LuxACeR3XbiBfyhFV/291LSuw/y1
JtWOW5koh0g1k9xtkiu3/ePVdG/CLp796IyRhdB1jP/vD7W5RLLG/VAlomfjsPsB
AnwFYbVZ8KrmMKYUpTJOH31CRzFdOB6nWqXu5tk3nOtLKo1nIOuVtmp9XLz3VtHe
NiZPnqA=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFYzCCA0ugAwIBAgIJAO+k4G7bNTyuMA0GCSqGSIb3DQEBCwUAMIGLMQswCQYD
VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzAR
BgNVBAoTCkFwY2VyYSBJbmMxEDAOBgNVBAsTB25hdHMuaW8xEjAQBgNVBAMTCWxv
Y2FsaG9zdDEcMBoGCSqGSIb3DQEJARYNZGVyZWtAbmF0cy5pbzAeFw0xNTExMDgy
MjA4MzBaFw0xOTExMDcyMjA4MzBaMBcxFTATBgNVBAMTDG5hdHMtY2x1c3RlcjCC
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAM3qpumhhgGCKwQhy02XOueK
cDs6s79TyGt1Q9mFmO3ZZgowO0lo+qOlHgBfHBrMtZU4tQ4ImrYzLSw1YDd/6DAX
iyUmbzymRYCShF8gzr4v8OGt/M8zuha9L7TAGT+hE+remG6WVT1eYdo3VpwRCxhr
9ysgO23wkU9VggTBzSEhsxzkosppkKMe8llOwOXuZeweh17VsCDDGJqarZd3PRan
RbshQ7Dk4QTmXr8kpinVvwI7TpiEtaGPi8eeMYuJ3MBrSS5465p5ELYZo8GUD/lT
U/li9eUbSduHDlHjzmnRjcwxnJW8jksJs0OJimAjg0kjyd3Bwla5xtT9c3ooDyg0
LRVV7KWAcVcLqLNvjNDJ3ROHDwzpg7wgwCMkZvp6KRiljonsHg36GMVhfh6JPxpD
5LmREK/dNBEzU6iYAEsBl4LihbREAUwdpkDNFOmox70VURHlMf0q3gBqBlooE9Ob
JadKjms+2yBEDuJQhO9hbbYJMifgdsE6DPQq57uLSZm8rKZHhIbQitVj/3Cw/U/7
uYF2Z0biZz24nnOUATxeksnli5mbAZJRfcbVvILlcUorBwEerKGRnGrhE1rmaxJ2
DsPbG1gtv9nyabYjSi2r8Qt3ghu+7KQujx6Wq8J4ext8QCjxz8VuQefv67kGsnTk
+/Yv8NI3AOb0tqxEk5wlAgMBAAGjPTA7MBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcE
fwAAATAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDQYJKoZIhvcNAQEL
BQADggIBAITQlXY4Sf3HvU9wnuKhrlTCqBBLkIQd5Vp9JKGZdtKLuK25KG0FkPEx
CWNQyBsKU6CXnP8L+n+7fcGN+Oz1hPtaczS+NExqWBpDELg5Fqi6TWgnhGBt34op
kI5/HjmyfrlA9Uy+uRh+ydoESi7B/svoaTroITbPN+WF3u7/unJkdqV9cTp4ndTr
iJCJXuXTQIqVAACfXmSpBi8oSJuE/MVCUdr7DPBB8jPDox1kpOZdEllyzp+4Bx3u
nGYxsRNyyIAH4fL9yyU9xJxN0fmNm8Xtc5EV89F4NM/qcVUQwcfNT/SyKnIIfovm
rs3It3mL+Pb8e+3SnDDfyXTOVIN94jMKaBXATB3vY/Ek1T+DkUZI0x/7llme580J
tTGK9O3yuRjyJsiG3echCwS5PkdPRf9+iqn/nHBF+f/GivB8MlQAxRNWajsKoOXF
nmLFcc0NpdPPKa4tH7dnLV9SbPDljuJn88W5I62DkJQwx/MnIL/xxn3CGVU/q7qt
k9DQVxAQaPoEPuQHLyHMkPibTV6tGCghAz+l4zerAbz1SklJ8nzqrkW1pf2hfZC6
jJZAGs0vXIJ4tWCnHNnPZzqaIopXeB97wusWAByHkhDGtQAGBdOmWTm8Ev1QLTDP
8ZGSPsotfQArQc/Usd6pWo8m40cG0GZyP24zvXRf1/x2003owN2e
-----END CERTIFICATE-----

View File

@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFYzCCA0ugAwIBAgIJAO+k4G7bNTyvMA0GCSqGSIb3DQEBCwUAMIGLMQswCQYD
VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzAR
BgNVBAoTCkFwY2VyYSBJbmMxEDAOBgNVBAsTB25hdHMuaW8xEjAQBgNVBAMTCWxv
Y2FsaG9zdDEcMBoGCSqGSIb3DQEJARYNZGVyZWtAbmF0cy5pbzAeFw0xNTExMDgy
MjA4MzdaFw0xOTExMDcyMjA4MzdaMBcxFTATBgNVBAMTDG5hdHMtY2x1c3RlcjCC
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMtmkDRmDs4DssN1lxzUNa8r
hXoddfOnaiUpZD54vu0gJqJ4qu9US5TPsAfVPsnKUvDCtZFrxKGpBbmFwnhuOJaM
UcQuq1n1VqhbVXI+v9Dm6qqj439kn79hq77F7jZHSM8Sdem3Qu7qZR5zosGgKgf3
IvFEclk+WddSKB8udtTmrWlBgAUxdLmLZ5C5iSO2DLtWTo1rfzlDZP6ZYkeNgyUt
cHDolS6Mhi7NHiZedRovB13xHs6YA5zDx6i7s7wfypBmCIywLwU5dlewW2Bpueq5
pbmqFk4TT8f0Ui6CEH+lqlMRpDuUtJmZjwx2kmxzsOpU/kTp76HF7gpiLpWZWfme
rjA9EiKJhrJYfp6tj2IrZ+4tsKBn4uWGG4Osx4quEfRR3fVBmOKlx9Lw6WPa9rvu
GzF61obs3D2gCeOK0qjocdvOWZ5jGBh7HVHOF4c+9H7CrVIMpgkkBgdz1KnkW6Oo
JXK8ZczHhwbLb+lAphe60vY0prXe2x6MSl0M+uVXtaewIlVjJZ9fVO7CGpZvviWl
9qzOlMUGmMayekpyYNv7zBvGJ/tlx76XG1N8KeGEq++lIPcNDVOXIp6ny93g6nRO
JNbBMOPaU/mfdo9Flz+1PibPinTKY5iT+w7c57ox9iOxtTrVI9SQrTlf0cRQGhzo
LddoXVD2i8mGCp/Kc0z1AgMBAAGjPTA7MBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcE
fwAAATAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDQYJKoZIhvcNAQEL
BQADggIBAB7avQgjetyggPL/DvyrdyygLnWm3Hg90vg5fecbV6W6TXDd122xyZdk
hZIXNts17u5yKreXYdzaeg4lKGTM/NFLVwsnmEjHAmqmwTSVfmh711NJGaKe8Lz7
2Io5R/HzPQC2cvJ41lMxLowdAkFfdYQUFlzB2IJzq+QWzsFvTypYXb19T8JgpQvh
NYZjUkYmV1UxuHLiIeuLSm6osBADeVI5bjtD61oFAoS3W/UOYDWK+CgDkyFunDhb
fmwO6ibFmOzx3F+zS3mnlJPuFzlCtB9LrOHhoXnVR5o1e/eQD5LNPvydd0RznVbH
duQ8YGI8JC4/hOxg85X5MCWkMSZ41S4sT8rGpp0gF+jOCFwMwCHDe/zkm9y1F51j
wPfKfwxlD44V4KuPRl7Kf2NtTVjMf4iJ1Hm2OYqgFvjD5TybM7vMeuR4e1swOXMn
7GNjiJTNcEMUEIaB5zYjw/NI7DAvOsFQuxH2+X61N2AUe9YhC/PVG35lsuVFPOFy
zYBsonVb0CoSgrhc+NXGrIAcgEDZcgK8wii8/eVggOCfRl+gwbTJB10cS0AXVuj1
RUwsIwK4xqynU/imLp/DG5TuEED9pHuxUSTwaZ8JG9ybYmZGyjdMNno4WJtNjnka
1zK861lEtsunus1Dm4zREdGBSZDKaRYSmjkfH+2kj8XnZrhpGn1v
-----END CERTIFICATE-----

View File

@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,29 @@
# Copyright 2012-2015 Apcera Inc. All rights reserved.
# Cluster Server A
port: 4222
cluster {
host: '127.0.0.1'
port: 4244
tls {
# Route cert
cert_file: "./configs/certs/srva-cert.pem"
# Private key
key_file: "./configs/certs/srva-key.pem"
# Optional certificate authority verifying connected routes
# Required when we have self-signed CA, etc.
ca_file: "./configs/certs/ca.pem"
}
# Routes are actively solicited and connected to from this server.
# Other servers can connect to us if they supply the correct credentials
# in their routes definitions from above.
routes = [
nats-route://127.0.0.1:4246
]
}

View File

@@ -0,0 +1,29 @@
# Copyright 2012-2015 Apcera Inc. All rights reserved.
# Cluster Server B
port: 4224
cluster {
host: '127.0.0.1'
port: 4246
tls {
# Route cert
cert_file: "./configs/certs/srvb-cert.pem"
# Private key
key_file: "./configs/certs/srvb-key.pem"
# Optional certificate authority verifying connected routes
# Required when we have self-signed CA, etc.
ca_file: "./configs/certs/ca.pem"
}
# Routes are actively solicited and connected to from this server.
# Other servers can connect to us if they supply the correct credentials
# in their routes definitions from above.
routes = [
nats-route://127.0.0.1:4244
]
}

18
test/configs/tls.conf Normal file
View File

@@ -0,0 +1,18 @@
# Simple TLS config file
port: 4443
net: localhost
tls {
# Server cert
cert_file: "./configs/certs/server-cert.pem"
# Server private key
key_file: "./configs/certs/server-key.pem"
}
authorization {
user: derek
password: boo
timeout: 1
}

View File

@@ -0,0 +1,16 @@
# Simple TLS config file
port: 4443
net: localhost
tls {
# Server cert
cert_file: "./configs/certs/server-cert.pem"
# Server private key
key_file: "./configs/certs/server-key.pem"
# Optional certificate authority for clients
ca_file: "./configs/certs/ca.pem"
# Require a client certificate
verify: true
}

View File

@@ -34,12 +34,11 @@ func TestMaxPayload(t *testing.T) {
if err != nil {
t.Fatalf("Could not make a raw connection to the server: %v", err)
}
info := make([]byte, 200)
info := make([]byte, 512)
_, err = conn.Read(info)
if err != nil {
t.Fatalf("Expected an info message to be sent by the server: %s", err)
}
pub := fmt.Sprintf("PUB bar %d\r\n", size)
conn.Write([]byte(pub))
if err != nil {

View File

@@ -19,3 +19,16 @@ func TestServerConfig(t *testing.T) {
opts.MaxPayload, sinfo.MaxPayload)
}
}
func TestTLSConfig(t *testing.T) {
srv, opts := RunServerWithConfig("./configs/tls.conf")
defer srv.Shutdown()
c := createClientConn(t, opts.Host, opts.Port)
defer c.Close()
sinfo := checkInfoMsg(t, c)
if sinfo.TLSRequired != true {
t.Fatal("Expected TLSRequired to be true when configured")
}
}

View File

@@ -15,6 +15,7 @@ import (
"strings"
"time"
"github.com/nats-io/gnatsd/auth"
"github.com/nats-io/gnatsd/server"
)
@@ -53,7 +54,16 @@ func RunServerWithConfig(configFile string) (srv *server.Server, opts *server.Op
panic(fmt.Sprintf("Error processing configuration file: %v", err))
}
opts.NoSigs, opts.NoLog = true, true
srv = RunServer(opts)
// Check for auth
var a server.Auth
if opts.Authorization != "" {
a = &auth.Token{Token: opts.Authorization}
}
if opts.Username != "" {
a = &auth.Plain{Username: opts.Username, Password: opts.Password}
}
srv = RunServerWithAuth(opts, a)
return
}

131
test/tls_test.go Normal file
View File

@@ -0,0 +1,131 @@
// Copyright 2015 Apcera Inc. All rights reserved.
package test
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"testing"
"github.com/nats-io/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("nats://%s:%s@%s/", opts.Username, opts.Password, endpoint)
nc, err := nats.Connect(nurl)
if err == nil {
t.Fatalf("Expected error trying to connect to secure server")
}
// Do simple SecureConnect
nc, err = nats.SecureConnect(fmt.Sprintf("nats://%s/", endpoint))
if err == nil {
t.Fatalf("Expected error trying to connect to secure server with no auth")
}
nc, err = nats.SecureConnect(nurl)
if err != nil {
t.Fatalf("Got an error on SecureConnect: %+v\n", err)
}
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")
}
defer nc.Close()
// Now do more advanced checking, verifying servername and using rootCA.
// Setup our own TLSConfig using RootCA from our self signed cert.
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{
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 TestTLSClientCertificate(t *testing.T) {
srv, opts := RunServerWithConfig("./configs/tlsverify.conf")
defer srv.Shutdown()
nurl := fmt.Sprintf("nats://%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")
}
_, err = nats.SecureConnect(nurl)
if err == nil {
t.Fatalf("Expected error trying to secure connect to secure server without a certificate")
}
// Load client certificate to sucessfully 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()
}