mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-15 10:40:41 -07:00
Merge pull request #506 from nats-io/tls_reload
Implement config reload support for TLS
This commit is contained in:
1
server/configs/basic.conf
Normal file
1
server/configs/basic.conf
Normal file
@@ -0,0 +1 @@
|
||||
listen: localhost:4443
|
||||
19
server/configs/certs/cert.new.pem
Normal file
19
server/configs/certs/cert.new.pem
Normal file
@@ -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
server/configs/certs/key.new.pem
Normal file
27
server/configs/certs/key.new.pem
Normal file
@@ -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-----
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -6,4 +6,5 @@ listen: localhost:4443
|
||||
tls {
|
||||
cert_file: "./configs/certs/server.pem"
|
||||
key_file: "./configs/certs/key.pem"
|
||||
timeout: 2
|
||||
}
|
||||
|
||||
12
server/configs/tls_verify_test.conf
Normal file
12
server/configs/tls_verify_test.conf
Normal file
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
@@ -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()
|
||||
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.
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user