Merge branch 'master' into tls_add_cipher

This commit is contained in:
Colin Sullivan
2015-11-23 10:55:43 -07:00
15 changed files with 370 additions and 103 deletions

View File

@@ -1,6 +1,5 @@
language: go
go:
- 1.4
- 1.5
install:
- DST=~/gopath/src/github.com/nats-io

View File

@@ -1,6 +1,6 @@
# gnatsd
[![License][License-Image]][License-Url] [![Build][Build-Status-Image]][Build-Status-Url] [![Release][Release-Image]][Release-Url] [![Coverage][Coverage-Image]][Coverage-Url]
[![License][License-Image]][License-Url] [![ReportCard][ReportCard-Image]][ReportCard-Url] [![Build][Build-Status-Image]][Build-Status-Url] [![Release][Release-Image]][Release-Url] [![Coverage][Coverage-Image]][Coverage-Url]
A High Performance [NATS](https://nats.io) Server written in [Go.](http://golang.org)
@@ -319,4 +319,6 @@ IN THE SOFTWARE.
[Release-Url]: https://github.com/nats-io/gnatsd/releases/tag/v0.6.8
[Release-image]: http://img.shields.io/badge/release-v0.6.8-1eb0fc.svg
[Coverage-Url]: https://coveralls.io/r/nats-io/gnatsd?branch=master
[Coverage-image]: https://img.shields.io/coveralls/nats-io/gnatsd.svg
[Coverage-image]: https://img.shields.io/coveralls/nats-io/gnatsd.svg
[ReportCard-Url]: http://goreportcard.com/report/nats-io/gnatsd
[ReportCard-Image]: http://goreportcard.com/badge/nats-io/gnatsd

View File

@@ -55,6 +55,12 @@ func main() {
flag.StringVar(&opts.RoutesStr, "routes", "", "Routes to actively solicit a connection.")
flag.BoolVar(&showTlsHelp, "help_tls", false, "TLS help.")
flag.BoolVar(&opts.TLS, "tls", false, "Enable TLS.")
flag.BoolVar(&opts.TLSVerify, "tlsverify", false, "Enable TLS with client verification.")
flag.StringVar(&opts.TLSCert, "tlscert", "", "Server certificate file.")
flag.StringVar(&opts.TLSKey, "tlskey", "", "Private key for server certificate.")
flag.StringVar(&opts.TLSCaCert, "tlscacert", "", "Client certificate CA for verification.")
// Not public per se, will be replaced with dynamic system, but can be used to lower memory footprint when
// lots of connections present.
flag.IntVar(&opts.BufSize, "bs", 0, "Read/Write buffer size per client connection.")
@@ -104,6 +110,9 @@ func main() {
}
opts.Routes = newroutes
// Configure TLS based on any present flags
configureTLS(&opts)
// Create the server with appropriate options.
s := server.New(&opts)
@@ -154,3 +163,29 @@ func configureLogger(s *server.Server, opts *server.Options) {
s.SetLogger(log, opts.Debug, opts.Trace)
}
func configureTLS(opts *server.Options) {
// If no trigger flags, ignore the others
if !opts.TLS && !opts.TLSVerify {
return
}
if opts.TLSCert == "" {
server.PrintAndDie("TLS Server certificate must be present and valid.")
}
if opts.TLSKey == "" {
server.PrintAndDie("TLS Server private key must be present and valid.")
}
tc := server.TLSConfigOpts{}
tc.CertFile = opts.TLSCert
tc.KeyFile = opts.TLSKey
tc.CaFile = opts.TLSCaCert
if opts.TLSVerify {
tc.Verify = true
}
var err error
if opts.TLSConfig, err = server.GenTLSConfig(&tc); err != nil {
server.PrintAndDie(err.Error())
}
}

View File

@@ -0,0 +1,33 @@
// Copyright 2015 Apcera Inc. All rights reserved.
// +build go1.4,!go1.5
package server
import (
"crypto/tls"
)
// Where we maintain all of the available 1.4 ciphers
var cipherMap = map[string]uint16{
"TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA,
"TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
"TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
"TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
}
func defaultCipherSuites() []uint16 {
return []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
}
}

View File

@@ -0,0 +1,38 @@
// Copyright 2015 Apcera Inc. All rights reserved.
// +build go1.5
package server
import (
"crypto/tls"
)
// Where we maintain all of the available 1.5 ciphers
var cipherMap = map[string]uint16{
"TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA,
"TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
"TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
"TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
}
func defaultCipherSuites() []uint16 {
return []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,
}
}

View File

@@ -7,6 +7,7 @@ net: apcera.me # net interface
tls {
cert_file: "./configs/certs/server.pem"
key_file: "./configs/certs/key.pem"
timeout: 0.5
}
authorization {

View File

@@ -21,8 +21,6 @@ tls {
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
# "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
# "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
]
}

View File

@@ -35,11 +35,11 @@ const (
// DEFAULT_MAX_CONNECTIONS is the default maximum connections allowed.
DEFAULT_MAX_CONNECTIONS = (64 * 1024)
// SSL_TIMEOUT is the TLS/SSL wait time.
SSL_TIMEOUT = 500 * time.Millisecond
// TLS_TIMEOUT is the TLS wait time.
TLS_TIMEOUT = 500 * time.Millisecond
// AUTH_TIMEOUT is the authorization wait time.
AUTH_TIMEOUT = 2 * SSL_TIMEOUT
AUTH_TIMEOUT = 2 * TLS_TIMEOUT
// DEFAULT_PING_INTERVAL is how often pings are sent to clients and routes.
DEFAULT_PING_INTERVAL = 2 * time.Minute

View File

@@ -42,6 +42,7 @@ type Options struct {
ClusterUsername string `json:"-"`
ClusterPassword string `json:"-"`
ClusterAuthTimeout float64 `json:"auth_timeout"`
ClusterTLSTimeout float64 `json:"-"`
ClusterTLSConfig *tls.Config `json:"-"`
ProfPort int `json:"-"`
PidFile string `json:"-"`
@@ -52,6 +53,11 @@ type Options struct {
RoutesStr string `json:"-"`
BufSize int `json:"-"`
TLSTimeout float64 `json:"tls_timeout"`
TLS bool `json:"-"`
TLSVerify bool `json:"-"`
TLSCert string `json:"-"`
TLSKey string `json:"-"`
TLSCaCert string `json:"-"`
TLSConfig *tls.Config `json:"-"`
}
@@ -62,12 +68,14 @@ type authorization struct {
}
// This struct holds the parsed tls config information.
type tlsConfig struct {
certFile string
keyFile string
caFile string
verify bool
ciphers []uint16
// It's public so we can use it for flag parsing
type TLSConfigOpts struct {
CertFile string
KeyFile string
CaFile string
Verify bool
Timeout float64
Ciphers []uint16
}
// ProcessConfigFile processes a configuration file.
@@ -134,9 +142,14 @@ func ProcessConfigFile(configFile string) (*Options, error) {
opts.MaxConn = int(v.(int64))
case "tls":
tlsm := v.(map[string]interface{})
if opts.TLSConfig, err = parseTLS(tlsm); err != nil {
tc, err := parseTLS(tlsm)
if err != nil {
return nil, err
}
if opts.TLSConfig, err = GenTLSConfig(tc); err != nil {
return nil, err
}
opts.TLSTimeout = tc.Timeout
}
}
return opts, nil
@@ -168,11 +181,15 @@ 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 {
tc, err := parseTLS(tlsm)
if err != nil {
return err
}
if opts.ClusterTLSConfig, err = GenTLSConfig(tc); err != nil {
return err
}
opts.ClusterTLSTimeout = tc.Timeout
}
}
return nil
@@ -201,25 +218,6 @@ func parseAuthorization(am map[string]interface{}) authorization {
return auth
}
// keep one place where we hold all of the available ciphers
var cipherMap = map[string]uint16{
"TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA,
"TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
"TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
"TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
// go 1.5 "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
// go 1.5 "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
}
// For Usage...
func PrintTlsHelpAndDie() {
@@ -263,8 +261,8 @@ func parseCipher(cipherName string) (uint16, error) {
}
// Helper function to parse TLS configs.
func parseTLS(tlsm map[string]interface{}) (*tls.Config, error) {
tc := tlsConfig{}
func parseTLS(tlsm map[string]interface{}) (*TLSConfigOpts, error) {
tc := TLSConfigOpts{}
for mk, mv := range tlsm {
switch strings.ToLower(mk) {
case "cert_file":
@@ -272,53 +270,64 @@ func parseTLS(tlsm map[string]interface{}) (*tls.Config, error) {
if !ok {
return nil, fmt.Errorf("error parsing tls config, expected 'cert_file' to be filename")
}
tc.certFile = certFile
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
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
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
tc.Verify = verify
case "cipher_suites":
ra := mv.([]interface{})
if len(ra) == 0 {
return nil, fmt.Errorf("error parsing tls config, 'cipher_suites' cannot be empty.")
}
tc.ciphers = make([]uint16, 0, len(ra))
tc.Ciphers = make([]uint16, 0, len(ra))
for _, r := range ra {
cipher, err := parseCipher(r.(string))
if err != nil {
return nil, err
}
tc.ciphers = append(tc.ciphers, cipher)
tc.Ciphers = append(tc.Ciphers, cipher)
}
case "timeout":
at := float64(0)
switch mv.(type) {
case int64:
at = float64(mv.(int64))
case float64:
at = mv.(float64)
}
tc.Timeout = at
default:
return nil, fmt.Errorf("error parsing tls config, unknown field [%q]", mk)
}
}
// use some sane defaults
if tc.ciphers == nil {
tc.ciphers = []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
}
// If cipher suites were not specified then use the defaults
if tc.Ciphers == nil {
tc.Ciphers = defaultCipherSuites()
}
return &tc, nil
}
func GenTLSConfig(tc *TLSConfigOpts) (*tls.Config, error) {
// Now load in cert and private key
cert, err := tls.LoadX509KeyPair(tc.certFile, tc.keyFile)
cert, err := tls.LoadX509KeyPair(tc.CertFile, tc.KeyFile)
if err != nil {
return nil, fmt.Errorf("error parsing X509 certificate/key pair: %v", err)
}
@@ -333,16 +342,16 @@ func parseTLS(tlsm map[string]interface{}) (*tls.Config, error) {
Certificates: []tls.Certificate{cert},
PreferServerCipherSuites: true,
MinVersion: tls.VersionTLS12,
CipherSuites: tc.ciphers,
CipherSuites: tc.Ciphers,
}
// Require client certificates as needed
if tc.verify == true {
if tc.Verify == true {
config.ClientAuth = tls.RequireAnyClientCert
}
// Add in CAs if applicable.
if tc.caFile != "" {
rootPEM, err := ioutil.ReadFile(tc.caFile)
if tc.CaFile != "" {
rootPEM, err := ioutil.ReadFile(tc.CaFile)
if err != nil || rootPEM == nil {
return nil, err
}
@@ -531,11 +540,14 @@ func processOptions(opts *Options) {
opts.MaxPingsOut = DEFAULT_PING_MAX_OUT
}
if opts.TLSTimeout == 0 {
opts.TLSTimeout = float64(SSL_TIMEOUT) / float64(time.Second)
opts.TLSTimeout = float64(TLS_TIMEOUT) / float64(time.Second)
}
if opts.AuthTimeout == 0 {
opts.AuthTimeout = float64(AUTH_TIMEOUT) / float64(time.Second)
}
if opts.ClusterTLSTimeout == 0 {
opts.ClusterTLSTimeout = float64(TLS_TIMEOUT) / float64(time.Second)
}
if opts.ClusterAuthTimeout == 0 {
opts.ClusterAuthTimeout = float64(AUTH_TIMEOUT) / float64(time.Second)
}

View File

@@ -17,12 +17,13 @@ func TestDefaultOptions(t *testing.T) {
MaxConn: DEFAULT_MAX_CONNECTIONS,
PingInterval: DEFAULT_PING_INTERVAL,
MaxPingsOut: DEFAULT_PING_MAX_OUT,
TLSTimeout: float64(SSL_TIMEOUT) / float64(time.Second),
TLSTimeout: float64(TLS_TIMEOUT) / float64(time.Second),
AuthTimeout: float64(AUTH_TIMEOUT) / float64(time.Second),
MaxControlLine: MAX_CONTROL_LINE_SIZE,
MaxPayload: MAX_PAYLOAD_SIZE,
MaxPending: MAX_PENDING_SIZE,
ClusterAuthTimeout: float64(AUTH_TIMEOUT) / float64(time.Second),
ClusterTLSTimeout: float64(TLS_TIMEOUT) / float64(time.Second),
BufSize: DEFAULT_BUF_SIZE,
}
@@ -85,6 +86,7 @@ func TestTLSConfigFile(t *testing.T) {
Username: "derek",
Password: "buckley",
AuthTimeout: 1.0,
TLSTimeout: 0.5,
}
opts, err := ProcessConfigFile("./configs/tls.conf")
if err != nil {
@@ -100,13 +102,8 @@ func TestTLSConfigFile(t *testing.T) {
golden, opts)
}
// Now check TLSConfig a bit more closely
// The default 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,
}
// CipherSuites
ciphers := defaultCipherSuites()
if !reflect.DeepEqual(tlsConfig.CipherSuites, ciphers) {
t.Fatalf("Got incorrect cipher suite list: [%+v]", tlsConfig.CipherSuites)
}
@@ -150,8 +147,6 @@ func TestTLSConfigFile(t *testing.T) {
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
//tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
//tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
}
if !reflect.DeepEqual(tlsConfig.CipherSuites, ciphers) {
@@ -167,7 +162,7 @@ func TestTLSConfigFile(t *testing.T) {
// Test an empty cipher entry in a config file.
opts, err = ProcessConfigFile("./configs/tls_empty_cipher.conf")
if err == nil {
t.Fatalf("Did not receive an error from a empty cipher.")
t.Fatalf("Did not receive an error from empty cipher_suites.")
}
}

View File

@@ -160,14 +160,31 @@ func (s *Server) createRoute(conn net.Conn, rURL *url.URL) *client {
}
conn := c.nc.(*tls.Conn)
err := conn.Handshake()
if err != nil {
// Setup the timeout
ttl := secondsToDuration(s.opts.ClusterTLSTimeout)
time.AfterFunc(ttl, func() { tlsTimeout(c, conn) })
conn.SetReadDeadline(time.Now().Add(ttl))
c.mu.Unlock()
if err := conn.Handshake(); err != nil {
c.Debugf("TLS route handshake error: %v", err)
c.sendErr("Secure Connection - TLS Required")
c.closeConnection()
return nil
}
// Reset the read deadline
conn.SetReadDeadline(time.Time{})
// Re-Grab lock
c.mu.Lock()
// Rewrap bw
c.bw = bufio.NewWriterSize(c.nc, s.opts.BufSize)
c.Debugf("TLS handshake complete")
cs := conn.ConnectionState()
c.Debugf("TLS version %s, cipher suite %s", tlsVersion(cs.Version), tlsCipher(cs.CipherSuite))
}
// Queue Connect proto if we solicited the connection.
@@ -366,7 +383,7 @@ func (s *Server) routeAcceptLoop(ch chan struct{}) {
continue
}
tmpDelay = ACCEPT_MIN_SLEEP
s.createRoute(conn, nil)
go s.createRoute(conn, nil)
}
Debugf("Router accept loop exiting..")
s.done <- true
@@ -415,7 +432,7 @@ func (s *Server) reConnectToRoute(rUrl *url.URL) {
}
func (s *Server) connectToRoute(rUrl *url.URL) {
for s.isRunning() {
for s.isRunning() && rUrl != nil {
Debugf("Trying to connect to route on %s", rUrl.Host)
conn, err := net.DialTimeout("tcp", rUrl.Host, DEFAULT_ROUTE_DIAL)
if err != nil {

View File

@@ -294,6 +294,11 @@ func (s *Server) AcceptLoop() {
return
}
// Alert of TLS enabled.
if s.opts.TLSConfig != nil {
Noticef("TLS required for client connections")
}
Noticef("gnatsd is ready")
// Setup state that can enable shutdown
@@ -333,7 +338,7 @@ func (s *Server) AcceptLoop() {
continue
}
tmpDelay = ACCEPT_MIN_SLEEP
s.createClient(conn)
go s.createClient(conn)
}
Noticef("Server Exiting..")
s.done <- true
@@ -424,19 +429,6 @@ 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()
@@ -445,9 +437,113 @@ func (s *Server) createClient(conn net.Conn) *client {
s.clients[c.cid] = c
s.mu.Unlock()
// Check for TLS
if tlsRequired {
// Re-Grab lock
c.mu.Lock()
c.Debugf("Starting TLS client connection handshake")
c.nc = tls.Server(c.nc, s.opts.TLSConfig)
conn := c.nc.(*tls.Conn)
// Setup the timeout
ttl := secondsToDuration(s.opts.TLSTimeout)
time.AfterFunc(ttl, func() { tlsTimeout(c, conn) })
conn.SetReadDeadline(time.Now().Add(ttl))
// Force handshake
c.mu.Unlock()
if err := conn.Handshake(); err != nil {
c.Debugf("TLS handshake error: %v", err)
c.sendErr("Secure Connection - TLS Required")
c.closeConnection()
return nil
}
// Reset the read deadline
conn.SetReadDeadline(time.Time{})
// Re-Grab lock
c.mu.Lock()
// Rewrap bw
c.bw = bufio.NewWriterSize(c.nc, s.opts.BufSize)
c.Debugf("TLS handshake complete")
cs := conn.ConnectionState()
c.Debugf("TLS version %s, cipher suite %s", tlsVersion(cs.Version), tlsCipher(cs.CipherSuite))
c.mu.Unlock()
}
return c
}
// Handle closing down a connection when the handshake has timedout.
func tlsTimeout(c *client, conn *tls.Conn) {
c.mu.Lock()
nc := c.nc
c.mu.Unlock()
// Check if already closed
if nc == nil {
return
}
cs := conn.ConnectionState()
if cs.HandshakeComplete == false {
c.Debugf("TLS handshake timeout")
c.sendErr("Secure Connection - TLS Required")
c.closeConnection()
}
}
// Seems silly we have to write these
func tlsVersion(ver uint16) string {
switch ver {
case tls.VersionTLS10:
return "1.0"
case tls.VersionTLS11:
return "1.1"
case tls.VersionTLS12:
return "1.2"
}
return fmt.Sprintf("Unknown [%x]", ver)
}
// We use hex here so we don't need multiple versions
func tlsCipher(cs uint16) string {
switch cs {
case 0x0005:
return "TLS_RSA_WITH_RC4_128_SHA"
case 0x000a:
return "TLS_RSA_WITH_3DES_EDE_CBC_SHA"
case 0x002f:
return "TLS_RSA_WITH_AES_128_CBC_SHA"
case 0x0035:
return "TLS_RSA_WITH_AES_256_CBC_SHA"
case 0xc007:
return "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA"
case 0xc009:
return "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"
case 0xc00a:
return "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA"
case 0xc011:
return "TLS_ECDHE_RSA_WITH_RC4_128_SHA"
case 0xc012:
return "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA"
case 0xc013:
return "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"
case 0xc014:
return "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"
case 0xc02f:
return "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
case 0xc02b:
return "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
case 0xc030:
return "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
case 0xc02c:
return "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
}
return fmt.Sprintf("Unknown [%x]", cs)
}
// Assume the lock is held upon entry.
func (s *Server) sendInfo(c *client, info []byte) {
c.nc.Write(info)

View File

@@ -18,8 +18,8 @@ Server Options:
Logging Options:
-l, --log FILE File to redirect log output
-T, --logtime Timestamp log entries (default: true)
-s, --syslog Enable syslog as log method.
-r, --remote_syslog Syslog server addr (udp://localhost:514).
-s, --syslog Enable syslog as log method
-r, --remote_syslog Syslog server addr (udp://localhost:514)
-D, --debug Enable debugging output
-V, --trace Trace the raw protocol
-DV Debug and Trace
@@ -28,6 +28,13 @@ Authorization Options:
--user user User required for connections
--pass password Password required for connections
TLS Options:
--tls Enable TLS, do not verify clients (default: false)
--tlscert FILE Server certificate file
--tlskey FILE Private key for server certificate
--tlsverify Enable TLS, very client certificates
--tlscacert client Client certificate CA for verification
Cluster Options:
--routes [rurl-1, rurl-2] Routes to solicit and connect

View File

@@ -9,6 +9,8 @@ tls {
cert_file: "./configs/certs/server-cert.pem"
# Server private key
key_file: "./configs/certs/server-key.pem"
# Specified time for handshake to complete
timeout: 0.25
}
authorization {

View File

@@ -3,11 +3,15 @@
package test
import (
"bufio"
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net"
"strings"
"testing"
"time"
"github.com/nats-io/nats"
)
@@ -29,21 +33,6 @@ func TestTLSConnection(t *testing.T) {
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")
@@ -71,8 +60,17 @@ func TestTLSConnection(t *testing.T) {
if err != nil {
t.Fatalf("Got an error on Connect with Secure Options: %+v\n", err)
}
nc.Flush()
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) {
@@ -129,3 +127,37 @@ func TestTLSClientCertificate(t *testing.T) {
nc.Flush()
defer nc.Close()
}
func TestTLSConnectionTimeout(t *testing.T) {
srv, opts := RunServerWithConfig("./configs/tls.conf")
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)
}
}