Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement config reload support for TLS #506

Merged
merged 6 commits into from
Jun 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions server/configs/basic.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
listen: localhost:4443
19 changes: 19 additions & 0 deletions server/configs/certs/cert.new.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDHjCCAgagAwIBAgIQIzutzCXGYFnGCOLeORrJdDANBgkqhkiG9w0BAQsFADAS
MRAwDgYDVQQKEwdBY21lIENvMB4XDTE3MDYwNzIxMDExMVoXDTE4MDYwNzIxMDEx
MVowEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAL6+3Ab4M5gYDfACnOm5xLSMwEgjFyeFE4HRgWbC+672nm8CKqyPZLkp
ZIkS05ugpMXsOPY7JoTWgHL3L/wE0G2igh5jro88uwH64QzDEPmcS8yWqI1ypJq9
2+86aWxpLYoi549qZW9Vj/IDgPEG72IlanQ1rSCnSCeeF++bFNMSxfPPFiA+KJKL
PIdP5MPxkNGAZ1YAlJSr+vhwQTYxJ5HbGiXMpkmLwIwSadGKAohZg9i7NMiKQBU5
2N/+6U+qtEmW7JyF1/Bkzbuf9wRGN471BXXomm/2GcSlD2PDmyOERvCnAYnYc2BV
QG0/S8YIGa+nOm460tZfuH6KWVCyG98CAwEAAaNwMG4wDgYDVR0PAQH/BAQDAgKk
MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAPBgNVHRMBAf8EBTADAQH/
MCwGA1UdEQQlMCOCCWxvY2FsaG9zdIEWdHlsZXIudHJlYXRAYXBjZXJhLmNvbTAN
BgkqhkiG9w0BAQsFAAOCAQEALFfqxV5BWDOILqh+dEtPBGdNyN9CYLLtmibr3eeS
PvF6jHC0ik2u5nBdqSaI3QtwaF65KXtLdaq9N2UvzehWpcW1Wy0PtKB5QIN/hR7I
aRYEejz+IEnduoK6XKJHR7RO7I9ipI9CdvmKJHB8ogGt7a42lIRQMbIeU+68ceiU
WfoHXPiJyib9uw/ewEgg/J+jnbDMJARnjR76P942iHrg/elUNLBBxarxwGj3tAHF
M2ER7mVj+8Y98Pgw82avX6oxhiDM4N6UbyYhuxjoda4Av4dNb7MABHDhdIgSRI1Y
a6pSdajhUg7Dj51IKPkOZ4d7KT3IPeTbJCI/S+tHNeWlLA==
-----END CERTIFICATE-----
27 changes: 27 additions & 0 deletions server/configs/certs/key.new.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAvr7cBvgzmBgN8AKc6bnEtIzASCMXJ4UTgdGBZsL7rvaebwIq
rI9kuSlkiRLTm6Ckxew49jsmhNaAcvcv/ATQbaKCHmOujzy7AfrhDMMQ+ZxLzJao
jXKkmr3b7zppbGktiiLnj2plb1WP8gOA8QbvYiVqdDWtIKdIJ54X75sU0xLF888W
ID4okos8h0/kw/GQ0YBnVgCUlKv6+HBBNjEnkdsaJcymSYvAjBJp0YoCiFmD2Ls0
yIpAFTnY3/7pT6q0SZbsnIXX8GTNu5/3BEY3jvUFdeiab/YZxKUPY8ObI4RG8KcB
idhzYFVAbT9LxggZr6c6bjrS1l+4fopZULIb3wIDAQABAoIBACr89LWVZntWoH2A
+UArn8tZFVSso+FCOp09TD6Onw5VgmteP6PYRUj9rSy/U3V1hO0eSdAkkI/Lj/NZ
BjV0GE09HLogmQyrETJnCiVIKSE4OlUHd0E5nyNIurJ1paDLK3pAV5OY1Pd8fw55
/6tSdszVxeIe3r/HM5nKJXbYqp7O7rbyI+d9Q5dBlP1cRPHrjpTPVv16MjFhxIPB
R+C0n61CTRjRJRUN9xEwSe7iOy0ynCACtGZpv+srLWx4yj8QBg7+blm+9C67jcBs
bny0VQ344DYHiVNr/cpPezn2LlHWFpCuy+7sVTq9aAZ8k/UDPxOe9eHN93O4WCru
GAa77iECgYEA3inEhpzCLsXvIUxkf2Q4wWBVBs3VjcVcdPw3baCWlwqeV6iCJHTw
WbLQfec7m3kbDZiZvhJtQyqfNndSLwxC9Nb3s2dKhLuzX6AiN84+Z5M8CGor7iFM
pDdDV5igh9kyrMr3iR01/bB2hb5YQPzoAuV9136vP143mMG4HdMaGncCgYEA28wb
ODvhj0y+1heS/swcOG+Bw+/Cwqp/SxkREoBYc7lLNXQQ5VmQM/DOXTbgyo6+u8/n
+3VbT0OjmjMLYodynPkl1qcVTIVc38YbxKsdPU+c30DHU4xyUjBalcMjw4ayqCNx
epzz62PohrASFpQr1r+oVnpPsCSDkmMI4d4Z+9kCgYAlEmMw80eT9oOI0u6SM28l
FaYalI5mMeDTxKKbMIjwe10g04Wj/797uFMCL2vK7dKN2kENbpW894fJ1u9n2mvx
301GKp5Mt+Wet2H+XfQb5H3ICa969SOM44vhOh7PjHbgTp4vyygPRTsB5lljvtAY
a6MsKn+j21z7qJfIoklg0QKBgQCdm6RBFJ9PdEa7mjfrwUzTIxI3/+r2T+/rV9Qo
IiRLBylo8QtUin6e4CP6L2nNlcIrRpAgfiy1j9j2r3eQdXO4H+gEHddmAZNxWst6
oQDcgAQLCpZj0KgBS28JSN6STDo72v56X6WAuyl3uzWdPy6YVOJO8HHH6sb151Ht
NKgJMQKBgDV4QWEXYIUDBU3EU8+rTDaJVSOXIO/XwyWKX4Lx1TT1R26IB6oROV1E
RhEKI45iMWz6NAlqniud3Zk973LKyJ2JpV3NaxqrpG7l02HWJUrKutcFNzfGYthz
QciEF7Tsr/VSSaMKDFVLgRWwDjsIzmnRaypX7O59C+ao5KiAQfFQ
-----END RSA PRIVATE KEY-----
8 changes: 8 additions & 0 deletions server/configs/reload/reload.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,11 @@
debug: true # enable on reload
trace: true # enable on reload
logtime: false

# Enable TLS on reload
tls {
cert_file: "../test/configs/certs/server-cert.pem"
key_file: "../test/configs/certs/server-key.pem"
ca_file: "../test/configs/certs/ca.pem"
verify: true
}
1 change: 1 addition & 0 deletions server/configs/tls_test.conf
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ listen: localhost:4443
tls {
cert_file: "./configs/certs/server.pem"
key_file: "./configs/certs/key.pem"
timeout: 2
}
12 changes: 12 additions & 0 deletions server/configs/tls_verify_test.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

# Simple TLS config file

listen: localhost:4443

tls {
cert_file: "./configs/certs/cert.new.pem"
key_file: "./configs/certs/key.new.pem"
ca_file: "./configs/certs/cert.new.pem"
verify: true
timeout: 2
}
2 changes: 1 addition & 1 deletion server/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func (c *client) parse(buf []byte) error {
var b byte

mcl := MAX_CONTROL_LINE_SIZE
if c.srv != nil && c.srv.opts != nil {
if c.srv != nil && c.srv.getOpts() != nil {
mcl = c.srv.getOpts().MaxControlLine
}

Expand Down
24 changes: 24 additions & 0 deletions server/reload.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package server

import (
"crypto/tls"
"errors"
"fmt"
"reflect"
Expand Down Expand Up @@ -41,6 +42,24 @@ func (d *debugOption) Apply(server *Server) {
server.Noticef("Reloaded: debug = %v", d.newValue)
}

// tlsOption implements the option interface for the `tls` setting.
type tlsOption struct {
newValue *tls.Config
}

// Apply the tls change.
func (t *tlsOption) Apply(server *Server) {
tlsRequired := t.newValue != nil
server.info.TLSRequired = tlsRequired
message := "disabled"
if tlsRequired {
server.info.TLSVerify = (t.newValue.ClientAuth == tls.RequireAndVerifyClientCert)
message = "enabled"
}
server.generateServerInfoJSON()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was about to ask to surround this with server lock/unlock but I realize that this is done higher in the caller stack. So we are good here.

server.Noticef("Reloaded: tls = %s", message)
}

// Reload reads the current configuration file and applies any supported
// changes. This returns an error if the server was not started with a config
// file or an option which doesn't support hot-swapping was changed.
Expand Down Expand Up @@ -99,6 +118,11 @@ func (s *Server) diffOptions(newOpts *Options) ([]option, error) {
diffOpts = append(diffOpts, &traceOption{newValue.(bool)})
case "debug":
diffOpts = append(diffOpts, &debugOption{newValue.(bool)})
case "tlsconfig":
diffOpts = append(diffOpts, &tlsOption{newValue.(*tls.Config)})
case "tlstimeout":
// TLSTimeout change is picked up when Options is swapped.
continue
default:
// Bail out if attempting to reload any unsupported options.
return nil, fmt.Errorf("Config reload not supported for %s", field.Name)
Expand Down
210 changes: 203 additions & 7 deletions server/reload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
package server

import (
"fmt"
"os"
"path/filepath"
"reflect"
"testing"
"time"

"github.com/nats-io/go-nats"
)

// Ensure Reload returns an error when attempting to reload a server that did
Expand Down Expand Up @@ -193,12 +196,205 @@ func TestConfigReload(t *testing.T) {
}

// Ensure config changed.
var updatedGolden *Options = &Options{}
*updatedGolden = *golden
updatedGolden.Trace = true
updatedGolden.Debug = true
if !reflect.DeepEqual(updatedGolden, server.getOpts()) {
t.Fatalf("Options are incorrect.\nexpected: %+v\ngot: %+v",
updatedGolden, server.getOpts())
updated := server.getOpts()
if !updated.Trace {
t.Fatal("Expected Trace to be true")
}
if !updated.Debug {
t.Fatal("Expected Debug to be true")
}
if updated.TLSConfig == nil {
t.Fatal("Expected TLSConfig to be non-nil")
}
if !server.info.TLSRequired {
t.Fatal("Expected TLSRequired to be true")
}
if !server.info.TLSVerify {
t.Fatal("Expected TLSVerify to be true")
}
}

// Ensure Reload supports TLS config changes. Test this by starting a server
// with TLS enabled, connect to it to verify, reload config using a different
// key pair and client verification enabled, ensure reconnect fails, then
// ensure reconnect succeeds when the client provides a cert.
func TestConfigReloadRotateTLS(t *testing.T) {
dir, err := os.Getwd()
if err != nil {
t.Fatalf("Error getting working directory: %v", err)
}
config := filepath.Join(dir, "tmp.conf")

if err := os.Symlink("./configs/tls_test.conf", config); err != nil {
t.Fatalf("Error creating symlink: %v", err)
}
defer os.Remove(config)

opts, err := ProcessConfigFile(config)
if err != nil {
t.Fatalf("Error processing config file: %v", err)
}

server := RunServer(opts)
defer server.Shutdown()

// Ensure we can connect as a sanity check.
addr := fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port)
nc, err := nats.Connect(addr, nats.Secure())
if err != nil {
t.Fatalf("Error creating client: %v", err)
}
defer nc.Close()
sub, err := nc.SubscribeSync("foo")
if err != nil {
t.Fatalf("Error subscribing: %v", err)
}
defer sub.Unsubscribe()

// Rotate cert and enable client verification.
if err := os.Remove(config); err != nil {
t.Fatalf("Error deleting symlink: %v", err)
}
if err := os.Symlink("./configs/tls_verify_test.conf", config); err != nil {
t.Fatalf("Error creating symlink: %v", err)
}
if err := server.Reload(); err != nil {
t.Fatalf("Error reloading config: %v", err)
}

// Ensure connecting fails.
if _, err := nats.Connect(addr, nats.Secure()); err == nil {
t.Fatal("Expected connect to fail")
}

// Ensure connecting succeeds when client presents cert.
cert := nats.ClientCert("./configs/certs/cert.new.pem", "./configs/certs/key.new.pem")
conn, err := nats.Connect(addr, cert, nats.RootCAs("./configs/certs/cert.new.pem"))
if err != nil {
t.Fatalf("Error creating client: %v", err)
}
conn.Close()

// Ensure the original connection can still publish/receive.
if err := nc.Publish("foo", []byte("hello")); err != nil {
t.Fatalf("Error publishing: %v", err)
}
msg, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Error receiving msg: %v", err)
}
if string(msg.Data) != "hello" {
t.Fatalf("Msg is incorrect.\nexpected: %+v\ngot: %+v", []byte("hello"), msg.Data)
}
}

// Ensure Reload supports enabling TLS. Test this by starting a server without
// TLS enabled, connect to it to verify, reload config with TLS enabled, ensure
// reconnect fails, then ensure reconnect succeeds when using secure.
func TestConfigReloadEnableTLS(t *testing.T) {
dir, err := os.Getwd()
if err != nil {
t.Fatalf("Error getting working directory: %v", err)
}
config := filepath.Join(dir, "tmp.conf")

if err := os.Symlink("./configs/basic.conf", config); err != nil {
t.Fatalf("Error creating symlink: %v", err)
}
defer os.Remove(config)

opts, err := ProcessConfigFile(config)
if err != nil {
t.Fatalf("Error processing config file: %v", err)
}

server := RunServer(opts)
defer server.Shutdown()

// Ensure we can connect as a sanity check.
addr := fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port)
nc, err := nats.Connect(addr)
if err != nil {
t.Fatalf("Error creating client: %v", err)
}
nc.Close()

// Enable TLS.
if err := os.Remove(config); err != nil {
t.Fatalf("Error deleting symlink: %v", err)
}
if err := os.Symlink("./configs/tls_test.conf", config); err != nil {
t.Fatalf("Error creating symlink: %v", err)
}
if err := server.Reload(); err != nil {
t.Fatalf("Error reloading config: %v", err)
}

// Ensure connecting fails.
if _, err := nats.Connect(addr); err == nil {
t.Fatal("Expected connect to fail")
}

// Ensure connecting succeeds when using secure.
nc, err = nats.Connect(addr, nats.Secure())
if err != nil {
t.Fatalf("Error creating client: %v", err)
}
nc.Close()
}

// Ensure Reload supports disabling TLS. Test this by starting a server with
// TLS enabled, connect to it to verify, reload config with TLS disabled,
// ensure reconnect fails, then ensure reconnect succeeds when connecting
// without secure.
func TestConfigReloadDisableTLS(t *testing.T) {
dir, err := os.Getwd()
if err != nil {
t.Fatalf("Error getting working directory: %v", err)
}
config := filepath.Join(dir, "tmp.conf")

if err := os.Symlink("./configs/tls_test.conf", config); err != nil {
t.Fatalf("Error creating symlink: %v", err)
}
defer os.Remove(config)

opts, err := ProcessConfigFile(config)
if err != nil {
t.Fatalf("Error processing config file: %v", err)
}

server := RunServer(opts)
defer server.Shutdown()

// Ensure we can connect as a sanity check.
addr := fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port)
nc, err := nats.Connect(addr, nats.Secure())
if err != nil {
t.Fatalf("Error creating client: %v", err)
}
nc.Close()

// Disable TLS.
if err := os.Remove(config); err != nil {
t.Fatalf("Error deleting symlink: %v", err)
}
if err := os.Symlink("./configs/basic.conf", config); err != nil {
t.Fatalf("Error creating symlink: %v", err)
}
if err := server.Reload(); err != nil {
t.Fatalf("Error reloading config: %v", err)
}

// Ensure connecting fails.
if _, err := nats.Connect(addr, nats.Secure()); err == nil {
t.Fatal("Expected connect to fail")
}

// Ensure connecting succeeds when not using secure.
nc, err = nats.Connect(addr)
if err != nil {
t.Fatalf("Error creating client: %v", err)
}
nc.Close()
}