mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 11:48:43 -07:00
2024 lines
53 KiB
Go
2024 lines
53 KiB
Go
// Copyright 2015-2020 The NATS Authors
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package test
|
|
|
|
import (
|
|
"bufio"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nats-io/nats.go"
|
|
|
|
"github.com/nats-io/nats-server/v2/server"
|
|
)
|
|
|
|
var noOpErrHandler = func(_ *nats.Conn, _ *nats.Subscription, _ error) {}
|
|
|
|
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")
|
|
}
|
|
}
|
|
|
|
// TestTLSInProcessConnection checks that even if TLS is enabled on the server,
|
|
// that an in-process connection that does *not* use TLS still connects successfully.
|
|
func TestTLSInProcessConnection(t *testing.T) {
|
|
srv, opts := RunServerWithConfig("./configs/tls.conf")
|
|
defer srv.Shutdown()
|
|
|
|
nc, err := nats.Connect("", nats.InProcessServer(srv), nats.UserInfo(opts.Username, opts.Password))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if nc.TLSRequired() {
|
|
t.Fatalf("Shouldn't have required TLS for in-process connection")
|
|
}
|
|
|
|
if _, err = nc.TLSConnectionState(); err == nil {
|
|
t.Fatal("Should have got an error retrieving TLS connection state")
|
|
}
|
|
}
|
|
|
|
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 := os.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.GetDefaultOptions()
|
|
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 TestTLSClientCertificateHasUserID(t *testing.T) {
|
|
srv, opts := RunServerWithConfig("./configs/tls_cert_id.conf")
|
|
defer srv.Shutdown()
|
|
nurl := fmt.Sprintf("tls://%s:%d", opts.Host, opts.Port)
|
|
nc, err := nats.Connect(nurl,
|
|
nats.ClientCert("./configs/certs/client-id-auth-cert.pem", "./configs/certs/client-id-auth-key.pem"),
|
|
nats.RootCAs("./configs/certs/ca.pem"))
|
|
if err != nil {
|
|
t.Fatalf("Expected to connect, got %v", err)
|
|
}
|
|
defer nc.Close()
|
|
}
|
|
|
|
func TestTLSClientCertificateCheckWithAllowedConnectionTypes(t *testing.T) {
|
|
conf := createConfFile(t, []byte(
|
|
`
|
|
listen: "127.0.0.1:-1"
|
|
tls {
|
|
cert_file: "./configs/certs/server-cert.pem"
|
|
key_file: "./configs/certs/server-key.pem"
|
|
timeout: 2
|
|
ca_file: "./configs/certs/ca.pem"
|
|
verify_and_map: true
|
|
}
|
|
authorization {
|
|
users = [
|
|
{user: derek@nats.io, permissions: { publish:"foo" }, allowed_connection_types: ["WEBSOCKET"]}
|
|
]
|
|
}
|
|
`))
|
|
s, o := RunServerWithConfig(conf)
|
|
defer s.Shutdown()
|
|
|
|
nurl := fmt.Sprintf("tls://%s:%d", o.Host, o.Port)
|
|
nc, err := nats.Connect(nurl,
|
|
nats.ClientCert("./configs/certs/client-id-auth-cert.pem", "./configs/certs/client-id-auth-key.pem"),
|
|
nats.RootCAs("./configs/certs/ca.pem"))
|
|
if err == nil {
|
|
if nc != nil {
|
|
nc.Close()
|
|
}
|
|
t.Fatal("Expected connection to fail, it did not")
|
|
}
|
|
}
|
|
|
|
func TestTLSClientCertificateCNBasedAuth(t *testing.T) {
|
|
srv, opts := RunServerWithConfig("./configs/tls_cert_cn.conf")
|
|
defer srv.Shutdown()
|
|
nurl := fmt.Sprintf("tls://%s:%d", opts.Host, opts.Port)
|
|
errCh1 := make(chan error)
|
|
errCh2 := make(chan error)
|
|
|
|
// Using the default permissions
|
|
nc1, err := nats.Connect(nurl,
|
|
nats.ClientCert("./configs/certs/tlsauth/client.pem", "./configs/certs/tlsauth/client-key.pem"),
|
|
nats.RootCAs("./configs/certs/tlsauth/ca.pem"),
|
|
nats.ErrorHandler(func(_ *nats.Conn, _ *nats.Subscription, err error) {
|
|
errCh1 <- err
|
|
}),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Expected to connect, got %v", err)
|
|
}
|
|
defer nc1.Close()
|
|
|
|
// Admin permissions can publish to '>'
|
|
nc2, err := nats.Connect(nurl,
|
|
nats.ClientCert("./configs/certs/tlsauth/client2.pem", "./configs/certs/tlsauth/client2-key.pem"),
|
|
nats.RootCAs("./configs/certs/tlsauth/ca.pem"),
|
|
nats.ErrorHandler(func(_ *nats.Conn, _ *nats.Subscription, err error) {
|
|
errCh2 <- err
|
|
}),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Expected to connect, got %v", err)
|
|
}
|
|
defer nc2.Close()
|
|
|
|
err = nc1.Publish("foo.bar", []byte("hi"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err = nc1.SubscribeSync("foo.>")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc1.Flush()
|
|
|
|
sub, err := nc2.SubscribeSync(">")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc2.Flush()
|
|
err = nc2.Publish("hello", []byte("hi"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc2.Flush()
|
|
|
|
_, err = sub.NextMsg(1 * time.Second)
|
|
if err != nil {
|
|
t.Fatalf("Error during wait for next message: %s", err)
|
|
}
|
|
|
|
// Wait for a couple of errors
|
|
var count int
|
|
Loop:
|
|
for {
|
|
select {
|
|
case err := <-errCh1:
|
|
if err != nil {
|
|
count++
|
|
}
|
|
if count == 2 {
|
|
break Loop
|
|
}
|
|
case err := <-errCh2:
|
|
if err != nil {
|
|
t.Fatalf("Received unexpected auth error from client: %s", err)
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatalf("Timed out expecting auth errors")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTLSClientCertificateSANsBasedAuth(t *testing.T) {
|
|
// In this test we have 3 clients, one with permissions defined
|
|
// for SAN 'app.nats.dev', other for SAN 'app.nats.prod' and another
|
|
// one without the default permissions for the CN.
|
|
srv, opts := RunServerWithConfig("./configs/tls_cert_san_auth.conf")
|
|
defer srv.Shutdown()
|
|
nurl := fmt.Sprintf("tls://%s:%d", opts.Host, opts.Port)
|
|
defaultErrCh := make(chan error)
|
|
devErrCh := make(chan error)
|
|
prodErrCh := make(chan error)
|
|
|
|
// default: Using the default permissions (no SANs)
|
|
//
|
|
// Subject: CN = www.nats.io
|
|
//
|
|
defaultc, err := nats.Connect(nurl,
|
|
nats.ClientCert("./configs/certs/sans/client.pem", "./configs/certs/sans/client-key.pem"),
|
|
nats.RootCAs("./configs/certs/sans/ca.pem"),
|
|
nats.ErrorHandler(func(_ *nats.Conn, _ *nats.Subscription, err error) {
|
|
defaultErrCh <- err
|
|
}),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Expected to connect, got %v", err)
|
|
}
|
|
defer defaultc.Close()
|
|
|
|
// dev: Using SAN 'app.nats.dev' with permissions to its own sandbox.
|
|
//
|
|
// Subject: CN = www.nats.io
|
|
//
|
|
// X509v3 Subject Alternative Name:
|
|
// DNS:app.nats.dev, DNS:*.app.nats.dev
|
|
//
|
|
devc, err := nats.Connect(nurl,
|
|
nats.ClientCert("./configs/certs/sans/dev.pem", "./configs/certs/sans/dev-key.pem"),
|
|
nats.RootCAs("./configs/certs/sans/ca.pem"),
|
|
nats.ErrorHandler(func(_ *nats.Conn, _ *nats.Subscription, err error) {
|
|
devErrCh <- err
|
|
}),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Expected to connect, got %v", err)
|
|
}
|
|
defer devc.Close()
|
|
|
|
// prod: Using SAN 'app.nats.prod' with all permissions.
|
|
//
|
|
// Subject: CN = www.nats.io
|
|
//
|
|
// X509v3 Subject Alternative Name:
|
|
// DNS:app.nats.prod, DNS:*.app.nats.prod
|
|
//
|
|
prodc, err := nats.Connect(nurl,
|
|
nats.ClientCert("./configs/certs/sans/prod.pem", "./configs/certs/sans/prod-key.pem"),
|
|
nats.RootCAs("./configs/certs/sans/ca.pem"),
|
|
nats.ErrorHandler(func(_ *nats.Conn, _ *nats.Subscription, err error) {
|
|
prodErrCh <- err
|
|
}),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Expected to connect, got %v", err)
|
|
}
|
|
defer prodc.Close()
|
|
|
|
// No permissions to publish or subscribe on foo.>
|
|
err = devc.Publish("foo.bar", []byte("hi"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err = devc.SubscribeSync("foo.>")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
devc.Flush()
|
|
|
|
prodSub, err := prodc.SubscribeSync(">")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
prodc.Flush()
|
|
err = prodc.Publish("hello", []byte("hi"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
prodc.Flush()
|
|
|
|
// prod: can receive message on wildcard subscription.
|
|
_, err = prodSub.NextMsg(1 * time.Second)
|
|
if err != nil {
|
|
t.Fatalf("Error during wait for next message: %s", err)
|
|
}
|
|
|
|
// dev: enough permissions to publish to sandbox subject.
|
|
devSub, err := devc.SubscribeSync("sandbox.>")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
devc.Flush()
|
|
err = devc.Publish("sandbox.foo.bar", []byte("hi!"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// both dev and prod clients can receive message
|
|
_, err = devSub.NextMsg(1 * time.Second)
|
|
if err != nil {
|
|
t.Fatalf("Error during wait for next message: %s", err)
|
|
}
|
|
_, err = prodSub.NextMsg(1 * time.Second)
|
|
if err != nil {
|
|
t.Fatalf("Error during wait for next message: %s", err)
|
|
}
|
|
|
|
// default: no enough permissions
|
|
_, err = defaultc.SubscribeSync("sandbox.>")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defaultSub, err := defaultc.SubscribeSync("public.>")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defaultc.Flush()
|
|
err = devc.Publish("public.foo.bar", []byte("hi!"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err = defaultSub.NextMsg(1 * time.Second)
|
|
if err != nil {
|
|
t.Fatalf("Error during wait for next message: %s", err)
|
|
}
|
|
|
|
// Wait for a couple of errors
|
|
var count int
|
|
Loop:
|
|
for {
|
|
select {
|
|
case err := <-defaultErrCh:
|
|
if err != nil {
|
|
count++
|
|
}
|
|
if count == 3 {
|
|
break Loop
|
|
}
|
|
case err := <-devErrCh:
|
|
if err != nil {
|
|
count++
|
|
}
|
|
if count == 3 {
|
|
break Loop
|
|
}
|
|
case err := <-prodErrCh:
|
|
if err != nil {
|
|
t.Fatalf("Received unexpected auth error from client: %s", err)
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatalf("Timed out expecting auth errors")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTLSClientCertificateTLSAuthMultipleOptions(t *testing.T) {
|
|
srv, opts := RunServerWithConfig("./configs/tls_cert_san_emails.conf")
|
|
defer srv.Shutdown()
|
|
nurl := fmt.Sprintf("tls://%s:%d", opts.Host, opts.Port)
|
|
defaultErrCh := make(chan error)
|
|
devErrCh := make(chan error)
|
|
prodErrCh := make(chan error)
|
|
|
|
// default: Using the default permissions, there are SANs
|
|
// present in the cert but they are not users in the NATS config
|
|
// so the subject is used instead.
|
|
//
|
|
// Subject: CN = www.nats.io
|
|
//
|
|
// X509v3 Subject Alternative Name:
|
|
// DNS:app.nats.dev, DNS:*.app.nats.dev
|
|
//
|
|
defaultc, err := nats.Connect(nurl,
|
|
nats.ClientCert("./configs/certs/sans/dev.pem", "./configs/certs/sans/dev-key.pem"),
|
|
nats.RootCAs("./configs/certs/sans/ca.pem"),
|
|
nats.ErrorHandler(func(_ *nats.Conn, _ *nats.Subscription, err error) {
|
|
defaultErrCh <- err
|
|
}),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Expected to connect, got %v", err)
|
|
}
|
|
defer defaultc.Close()
|
|
|
|
// dev: Using SAN to validate user, even if emails are present in the config.
|
|
//
|
|
// Subject: CN = www.nats.io
|
|
//
|
|
// X509v3 Subject Alternative Name:
|
|
// DNS:app.nats.dev, email:admin@app.nats.dev, email:root@app.nats.dev
|
|
//
|
|
devc, err := nats.Connect(nurl,
|
|
nats.ClientCert("./configs/certs/sans/dev-email.pem", "./configs/certs/sans/dev-email-key.pem"),
|
|
nats.RootCAs("./configs/certs/sans/ca.pem"),
|
|
nats.ErrorHandler(func(_ *nats.Conn, _ *nats.Subscription, err error) {
|
|
devErrCh <- err
|
|
}),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Expected to connect, got %v", err)
|
|
}
|
|
defer devc.Close()
|
|
|
|
// prod: Using SAN '*.app.nats.prod' with all permissions, which is not the first SAN.
|
|
//
|
|
// Subject: CN = www.nats.io
|
|
//
|
|
// X509v3 Subject Alternative Name:
|
|
// DNS:app.nats.prod, DNS:*.app.nats.prod
|
|
//
|
|
prodc, err := nats.Connect(nurl,
|
|
nats.ClientCert("./configs/certs/sans/prod.pem", "./configs/certs/sans/prod-key.pem"),
|
|
nats.RootCAs("./configs/certs/sans/ca.pem"),
|
|
nats.ErrorHandler(func(_ *nats.Conn, _ *nats.Subscription, err error) {
|
|
prodErrCh <- err
|
|
}),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Expected to connect, got %v", err)
|
|
}
|
|
defer prodc.Close()
|
|
|
|
// No permissions to publish or subscribe on foo.>
|
|
err = devc.Publish("foo.bar", []byte("hi"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err = devc.SubscribeSync("foo.>")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
devc.Flush()
|
|
|
|
prodSub, err := prodc.SubscribeSync(">")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
prodc.Flush()
|
|
err = prodc.Publish("hello", []byte("hi"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
prodc.Flush()
|
|
|
|
// prod: can receive message on wildcard subscription.
|
|
_, err = prodSub.NextMsg(1 * time.Second)
|
|
if err != nil {
|
|
t.Fatalf("Error during wait for next message: %s", err)
|
|
}
|
|
|
|
// dev: enough permissions to publish to sandbox subject.
|
|
devSub, err := devc.SubscribeSync("sandbox.>")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
devc.Flush()
|
|
err = devc.Publish("sandbox.foo.bar", []byte("hi!"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// both dev and prod clients can receive message
|
|
_, err = devSub.NextMsg(1 * time.Second)
|
|
if err != nil {
|
|
t.Fatalf("Error during wait for next message: %s", err)
|
|
}
|
|
_, err = prodSub.NextMsg(1 * time.Second)
|
|
if err != nil {
|
|
t.Fatalf("Error during wait for next message: %s", err)
|
|
}
|
|
|
|
// default: no enough permissions
|
|
_, err = defaultc.SubscribeSync("sandbox.>")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defaultSub, err := defaultc.SubscribeSync("public.>")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defaultc.Flush()
|
|
err = devc.Publish("public.foo.bar", []byte("hi!"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err = defaultSub.NextMsg(1 * time.Second)
|
|
if err != nil {
|
|
t.Fatalf("Error during wait for next message: %s", err)
|
|
}
|
|
|
|
// Wait for a couple of errors
|
|
var count int
|
|
Loop:
|
|
for {
|
|
select {
|
|
case err := <-defaultErrCh:
|
|
if err != nil {
|
|
count++
|
|
}
|
|
if count == 3 {
|
|
break Loop
|
|
}
|
|
case err := <-devErrCh:
|
|
if err != nil {
|
|
count++
|
|
}
|
|
if count == 3 {
|
|
break Loop
|
|
}
|
|
case err := <-prodErrCh:
|
|
if err != nil {
|
|
t.Fatalf("Received unexpected auth error from client: %s", err)
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatalf("Timed out expecting auth errors")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTLSRoutesCertificateCNBasedAuth(t *testing.T) {
|
|
// Base config for the servers
|
|
optsA := LoadConfig("./configs/tls_cert_cn_routes.conf")
|
|
optsB := LoadConfig("./configs/tls_cert_cn_routes.conf")
|
|
optsC := LoadConfig("./configs/tls_cert_cn_routes_invalid_auth.conf")
|
|
|
|
// TLS map should have been enabled
|
|
if !optsA.Cluster.TLSMap || !optsB.Cluster.TLSMap || !optsC.Cluster.TLSMap {
|
|
t.Error("Expected Cluster TLS verify and map feature to be activated")
|
|
}
|
|
|
|
routeURLs := "nats://localhost:9935, nats://localhost:9936, nats://localhost:9937"
|
|
|
|
optsA.Host = "127.0.0.1"
|
|
optsA.Port = 9335
|
|
optsA.Cluster.Name = "xyz"
|
|
optsA.Cluster.Host = optsA.Host
|
|
optsA.Cluster.Port = 9935
|
|
optsA.Routes = server.RoutesFromStr(routeURLs)
|
|
optsA.NoSystemAccount = true
|
|
srvA := RunServer(optsA)
|
|
defer srvA.Shutdown()
|
|
|
|
optsB.Host = "127.0.0.1"
|
|
optsB.Port = 9336
|
|
optsB.Cluster.Name = "xyz"
|
|
optsB.Cluster.Host = optsB.Host
|
|
optsB.Cluster.Port = 9936
|
|
optsB.Routes = server.RoutesFromStr(routeURLs)
|
|
optsB.NoSystemAccount = true
|
|
srvB := RunServer(optsB)
|
|
defer srvB.Shutdown()
|
|
|
|
optsC.Host = "127.0.0.1"
|
|
optsC.Port = 9337
|
|
optsC.Cluster.Name = "xyz"
|
|
optsC.Cluster.Host = optsC.Host
|
|
optsC.Cluster.Port = 9937
|
|
optsC.Routes = server.RoutesFromStr(routeURLs)
|
|
optsC.NoSystemAccount = true
|
|
srvC := RunServer(optsC)
|
|
defer srvC.Shutdown()
|
|
|
|
// srvC is not connected to srvA and srvB due to wrong cert
|
|
checkClusterFormed(t, srvA, srvB)
|
|
|
|
nc1, err := nats.Connect(fmt.Sprintf("%s:%d", optsA.Host, optsA.Port), nats.Name("A"))
|
|
if err != nil {
|
|
t.Fatalf("Expected to connect, got %v", err)
|
|
}
|
|
defer nc1.Close()
|
|
|
|
nc2, err := nats.Connect(fmt.Sprintf("%s:%d", optsB.Host, optsB.Port), nats.Name("B"))
|
|
if err != nil {
|
|
t.Fatalf("Expected to connect, got %v", err)
|
|
}
|
|
defer nc2.Close()
|
|
|
|
// This client is partitioned from rest of cluster due to using wrong cert.
|
|
nc3, err := nats.Connect(fmt.Sprintf("%s:%d", optsC.Host, optsC.Port), nats.Name("C"))
|
|
if err != nil {
|
|
t.Fatalf("Expected to connect, got %v", err)
|
|
}
|
|
defer nc3.Close()
|
|
|
|
// Allowed via permissions to broadcast to other members of cluster.
|
|
publicSub, err := nc1.SubscribeSync("public.>")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Not part of cluster permissions so message is not forwarded.
|
|
privateSub, err := nc1.SubscribeSync("private.>")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// This ensures that srvA has received the sub, not srvB
|
|
nc1.Flush()
|
|
|
|
checkFor(t, time.Second, 15*time.Millisecond, func() error {
|
|
if n := srvB.NumSubscriptions(); n != 1 {
|
|
return fmt.Errorf("Expected 1 sub, got %v", n)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
// Not forwarded by cluster so won't be received.
|
|
err = nc2.Publish("private.foo", []byte("private message on server B"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = nc2.Publish("public.foo", []byte("public message from server B to server A"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc2.Flush()
|
|
err = nc3.Publish("public.foo", []byte("dropped message since unauthorized server"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc3.Flush()
|
|
|
|
// Message will be received since allowed via permissions
|
|
_, err = publicSub.NextMsg(1 * time.Second)
|
|
if err != nil {
|
|
t.Fatalf("Error during wait for next message: %s", err)
|
|
}
|
|
msg, err := privateSub.NextMsg(500 * time.Millisecond)
|
|
if err == nil {
|
|
t.Errorf("Received unexpected message from private sub: %+v", msg)
|
|
}
|
|
msg, err = publicSub.NextMsg(500 * time.Millisecond)
|
|
if err == nil {
|
|
t.Errorf("Received unexpected message from private sub: %+v", msg)
|
|
}
|
|
}
|
|
|
|
func TestTLSGatewaysCertificateCNBasedAuth(t *testing.T) {
|
|
// Base config for the servers
|
|
optsA := LoadConfig("./configs/tls_cert_cn_gateways.conf")
|
|
optsB := LoadConfig("./configs/tls_cert_cn_gateways.conf")
|
|
optsC := LoadConfig("./configs/tls_cert_cn_gateways_invalid_auth.conf")
|
|
|
|
// TLS map should have been enabled
|
|
if !optsA.Gateway.TLSMap || !optsB.Gateway.TLSMap || !optsC.Gateway.TLSMap {
|
|
t.Error("Expected Cluster TLS verify and map feature to be activated")
|
|
}
|
|
|
|
gwA, err := url.Parse("nats://localhost:9995")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
gwB, err := url.Parse("nats://localhost:9996")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
gwC, err := url.Parse("nats://localhost:9997")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
optsA.Host = "127.0.0.1"
|
|
optsA.Port = -1
|
|
optsA.Gateway.Name = "A"
|
|
optsA.Gateway.Port = 9995
|
|
|
|
optsB.Host = "127.0.0.1"
|
|
optsB.Port = -1
|
|
optsB.Gateway.Name = "B"
|
|
optsB.Gateway.Port = 9996
|
|
|
|
optsC.Host = "127.0.0.1"
|
|
optsC.Port = -1
|
|
optsC.Gateway.Name = "C"
|
|
optsC.Gateway.Port = 9997
|
|
|
|
gateways := make([]*server.RemoteGatewayOpts, 3)
|
|
gateways[0] = &server.RemoteGatewayOpts{
|
|
Name: optsA.Gateway.Name,
|
|
URLs: []*url.URL{gwA},
|
|
}
|
|
gateways[1] = &server.RemoteGatewayOpts{
|
|
Name: optsB.Gateway.Name,
|
|
URLs: []*url.URL{gwB},
|
|
}
|
|
gateways[2] = &server.RemoteGatewayOpts{
|
|
Name: optsC.Gateway.Name,
|
|
URLs: []*url.URL{gwC},
|
|
}
|
|
optsA.Gateway.Gateways = gateways
|
|
optsB.Gateway.Gateways = gateways
|
|
optsC.Gateway.Gateways = gateways
|
|
|
|
server.SetGatewaysSolicitDelay(100 * time.Millisecond)
|
|
defer server.ResetGatewaysSolicitDelay()
|
|
|
|
srvA := RunServer(optsA)
|
|
defer srvA.Shutdown()
|
|
|
|
srvB := RunServer(optsB)
|
|
defer srvB.Shutdown()
|
|
|
|
srvC := RunServer(optsC)
|
|
defer srvC.Shutdown()
|
|
|
|
// Because we need to use "localhost" in the gw URLs (to match
|
|
// hostname in the user/CN), the server may try to connect to
|
|
// a [::1], etc.. that may or may not work, so give a lot of
|
|
// time for that to complete ok.
|
|
waitForOutboundGateways(t, srvA, 1, 5*time.Second)
|
|
waitForOutboundGateways(t, srvB, 1, 5*time.Second)
|
|
|
|
nc1, err := nats.Connect(srvA.Addr().String(), nats.Name("A"))
|
|
if err != nil {
|
|
t.Fatalf("Expected to connect, got %v", err)
|
|
}
|
|
defer nc1.Close()
|
|
|
|
nc2, err := nats.Connect(srvB.Addr().String(), nats.Name("B"))
|
|
if err != nil {
|
|
t.Fatalf("Expected to connect, got %v", err)
|
|
}
|
|
defer nc2.Close()
|
|
|
|
nc3, err := nats.Connect(srvC.Addr().String(), nats.Name("C"))
|
|
if err != nil {
|
|
t.Fatalf("Expected to connect, got %v", err)
|
|
}
|
|
defer nc3.Close()
|
|
|
|
received := make(chan *nats.Msg)
|
|
_, err = nc1.Subscribe("foo", func(msg *nats.Msg) {
|
|
select {
|
|
case received <- msg:
|
|
default:
|
|
}
|
|
})
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
_, err = nc3.Subscribe("help", func(msg *nats.Msg) {
|
|
nc3.Publish(msg.Reply, []byte("response"))
|
|
})
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
go func() {
|
|
for range time.NewTicker(10 * time.Millisecond).C {
|
|
if nc2.IsClosed() {
|
|
return
|
|
}
|
|
nc2.Publish("foo", []byte("bar"))
|
|
}
|
|
}()
|
|
|
|
select {
|
|
case <-received:
|
|
case <-time.After(2 * time.Second):
|
|
t.Error("Timed out waiting for gateway messages")
|
|
}
|
|
|
|
msg, err := nc2.Request("help", []byte("should fail"), 100*time.Millisecond)
|
|
if err == nil {
|
|
t.Errorf("Expected to not receive any messages, got: %+v", msg)
|
|
}
|
|
}
|
|
|
|
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 TestClientTLSAndNonTLSConnections(t *testing.T) {
|
|
s, opts := RunServerWithConfig("./configs/tls_mixed.conf")
|
|
defer s.Shutdown()
|
|
|
|
surl := fmt.Sprintf("tls://%s:%d", opts.Host, opts.Port)
|
|
nc, err := nats.Connect(surl, nats.RootCAs("./configs/certs/ca.pem"))
|
|
if err != nil {
|
|
t.Fatalf("Failed to connect with TLS: %v", err)
|
|
}
|
|
defer nc.Close()
|
|
|
|
// Now also make sure we can connect through plain text.
|
|
nurl := fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port)
|
|
nc2, err := nats.Connect(nurl)
|
|
if err != nil {
|
|
t.Fatalf("Failed to connect without TLS: %v", err)
|
|
}
|
|
defer nc2.Close()
|
|
|
|
// Make sure they can go back and forth.
|
|
sub, err := nc.SubscribeSync("foo")
|
|
if err != nil {
|
|
t.Fatalf("Error subscribing: %v", err)
|
|
}
|
|
sub2, err := nc2.SubscribeSync("bar")
|
|
if err != nil {
|
|
t.Fatalf("Error subscribing: %v", err)
|
|
}
|
|
nc.Flush()
|
|
nc2.Flush()
|
|
|
|
nmsgs := 100
|
|
for i := 0; i < nmsgs; i++ {
|
|
nc2.Publish("foo", []byte("HELLO FROM PLAINTEXT"))
|
|
nc.Publish("bar", []byte("HELLO FROM TLS"))
|
|
}
|
|
// Now wait for the messages.
|
|
checkFor(t, time.Second, 10*time.Millisecond, func() error {
|
|
if msgs, _, err := sub.Pending(); err != nil || msgs != nmsgs {
|
|
return fmt.Errorf("Did not receive the correct number of messages: %d", msgs)
|
|
}
|
|
if msgs, _, err := sub2.Pending(); err != nil || msgs != nmsgs {
|
|
return fmt.Errorf("Did not receive the correct number of messages: %d", msgs)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
}
|
|
|
|
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 strings.ToLower(err.Error()) != nats.ErrAuthorization.Error() {
|
|
t.Fatalf("Expected 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")
|
|
}
|
|
}
|
|
|
|
type captureSlowConsumerLogger struct {
|
|
dummyLogger
|
|
ch chan string
|
|
gotIt bool
|
|
}
|
|
|
|
func (l *captureSlowConsumerLogger) Noticef(format string, v ...interface{}) {
|
|
msg := fmt.Sprintf(format, v...)
|
|
if strings.Contains(msg, "Slow Consumer") {
|
|
l.Lock()
|
|
if !l.gotIt {
|
|
l.gotIt = true
|
|
l.ch <- msg
|
|
}
|
|
l.Unlock()
|
|
}
|
|
}
|
|
|
|
func TestTLSTimeoutNotReportSlowConsumer(t *testing.T) {
|
|
oa, err := server.ProcessConfigFile("./configs/srv_a_tls.conf")
|
|
if err != nil {
|
|
t.Fatalf("Unable to load config file: %v", err)
|
|
}
|
|
|
|
// Override TLSTimeout to very small value so that handshake fails
|
|
oa.Cluster.TLSTimeout = 0.0000001
|
|
sa := RunServer(oa)
|
|
defer sa.Shutdown()
|
|
|
|
ch := make(chan string, 1)
|
|
cscl := &captureSlowConsumerLogger{ch: ch}
|
|
sa.SetLogger(cscl, false, false)
|
|
|
|
ob, err := server.ProcessConfigFile("./configs/srv_b_tls.conf")
|
|
if err != nil {
|
|
t.Fatalf("Unable to load config file: %v", err)
|
|
}
|
|
|
|
sb := RunServer(ob)
|
|
defer sb.Shutdown()
|
|
|
|
// Watch the logger for a bit and make sure we don't get any
|
|
// Slow Consumer error.
|
|
select {
|
|
case e := <-ch:
|
|
t.Fatalf("Unexpected slow consumer error: %s", e)
|
|
case <-time.After(500 * time.Millisecond):
|
|
// ok
|
|
}
|
|
}
|
|
|
|
func TestTLSHandshakeFailureMemUsage(t *testing.T) {
|
|
for i, test := range []struct {
|
|
name string
|
|
config string
|
|
}{
|
|
{
|
|
"connect to TLS client port",
|
|
`
|
|
port: -1
|
|
%s
|
|
`,
|
|
},
|
|
{
|
|
"connect to TLS route port",
|
|
`
|
|
port: -1
|
|
cluster {
|
|
port: -1
|
|
%s
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
"connect to TLS gateway port",
|
|
`
|
|
port: -1
|
|
gateway {
|
|
name: "A"
|
|
port: -1
|
|
%s
|
|
}
|
|
`,
|
|
},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
content := fmt.Sprintf(test.config, `
|
|
tls {
|
|
cert_file: "configs/certs/server-cert.pem"
|
|
key_file: "configs/certs/server-key.pem"
|
|
ca_file: "configs/certs/ca.pem"
|
|
timeout: 5
|
|
}
|
|
`)
|
|
conf := createConfFile(t, []byte(content))
|
|
s, opts := RunServerWithConfig(conf)
|
|
defer s.Shutdown()
|
|
|
|
var port int
|
|
switch i {
|
|
case 0:
|
|
port = opts.Port
|
|
case 1:
|
|
port = s.ClusterAddr().Port
|
|
case 2:
|
|
port = s.GatewayAddr().Port
|
|
}
|
|
|
|
varz, _ := s.Varz(nil)
|
|
base := varz.Mem
|
|
buf := make([]byte, 1024*1024)
|
|
for i := 0; i < 100; i++ {
|
|
conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port))
|
|
if err != nil {
|
|
t.Fatalf("Error on dial: %v", err)
|
|
}
|
|
conn.SetWriteDeadline(time.Now().Add(10 * time.Millisecond))
|
|
conn.Write(buf)
|
|
conn.Close()
|
|
}
|
|
|
|
varz, _ = s.Varz(nil)
|
|
newval := (varz.Mem - base) / (1024 * 1024)
|
|
// May need to adjust that, but right now, with 100 clients
|
|
// we are at about 20MB for client port, 11MB for route
|
|
// and 6MB for gateways, so pick 50MB as the threshold
|
|
if newval >= 50 {
|
|
t.Fatalf("Consumed too much memory: %v MB", newval)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTLSClientAuthWithRDNSequence(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
config string
|
|
certs nats.Option
|
|
err error
|
|
rerr error
|
|
}{
|
|
// To generate certs for these tests:
|
|
// USE `regenerate_rdns_svid.sh` TO REGENERATE
|
|
//
|
|
// ```
|
|
// openssl req -newkey rsa:2048 -nodes -keyout client-$CLIENT_ID.key -subj "/C=US/ST=CA/L=Los Angeles/OU=NATS/O=NATS/CN=*.example.com/DC=example/DC=com" -addext extendedKeyUsage=clientAuth -out client-$CLIENT_ID.csr
|
|
// openssl x509 -req -extfile <(printf "subjectAltName=DNS:localhost,DNS:example.com,DNS:www.example.com") -days 1825 -in client-$CLIENT_ID.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out client-$CLIENT_ID.pem
|
|
// ```
|
|
//
|
|
// To confirm subject from cert:
|
|
//
|
|
// ```
|
|
// openssl x509 -in client-$CLIENT_ID.pem -text | grep Subject:
|
|
// ```
|
|
//
|
|
{
|
|
"connect with tls using full RDN sequence",
|
|
`
|
|
port: -1
|
|
%s
|
|
|
|
authorization {
|
|
users = [
|
|
{ user = "CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=CA,C=US,DC=foo1,DC=foo2" }
|
|
]
|
|
}
|
|
`,
|
|
// C = US, ST = CA, L = Los Angeles, O = NATS, OU = NATS, CN = localhost, DC = foo1, DC = foo2
|
|
nats.ClientCert("./configs/certs/rdns/client-a.pem", "./configs/certs/rdns/client-a.key"),
|
|
nil,
|
|
nil,
|
|
},
|
|
{
|
|
"connect with tls using full RDN sequence in original order",
|
|
`
|
|
port: -1
|
|
%s
|
|
|
|
authorization {
|
|
users = [
|
|
{ user = "DC=foo2,DC=foo1,CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=CA,C=US" }
|
|
]
|
|
}
|
|
`,
|
|
// C = US, ST = CA, L = Los Angeles, O = NATS, OU = NATS, CN = localhost, DC = foo1, DC = foo2
|
|
nats.ClientCert("./configs/certs/rdns/client-a.pem", "./configs/certs/rdns/client-a.key"),
|
|
nil,
|
|
nil,
|
|
},
|
|
{
|
|
"connect with tls using partial RDN sequence has different permissions",
|
|
`
|
|
port: -1
|
|
%s
|
|
|
|
authorization {
|
|
users = [
|
|
{ user = "DC=foo2,DC=foo1,CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=CA,C=US" },
|
|
{ user = "CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=CA,C=US,DC=foo1,DC=foo2" },
|
|
{ user = "CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=CA,C=US",
|
|
permissions = { subscribe = { deny = ">" }} }
|
|
]
|
|
}
|
|
`,
|
|
// C = US, ST = California, L = Los Angeles, O = NATS, OU = NATS, CN = localhost
|
|
nats.ClientCert("./configs/certs/rdns/client-b.pem", "./configs/certs/rdns/client-b.key"),
|
|
nil,
|
|
errors.New("nats: timeout"),
|
|
},
|
|
{
|
|
"connect with tls and RDN sequence partially matches",
|
|
`
|
|
port: -1
|
|
%s
|
|
|
|
authorization {
|
|
users = [
|
|
{ user = "DC=foo2,DC=foo1,CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=CA,C=US" }
|
|
{ user = "CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=CA,C=US,DC=foo1,DC=foo2" }
|
|
{ user = "CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=CA,C=US"},
|
|
]
|
|
}
|
|
`,
|
|
// Cert is:
|
|
//
|
|
// C = US, ST = CA, L = Los Angeles, O = NATS, OU = NATS, CN = localhost, DC = foo3, DC = foo4
|
|
//
|
|
// but it will actually match the user without DCs so will not get an error:
|
|
//
|
|
// C = US, ST = CA, L = Los Angeles, O = NATS, OU = NATS, CN = localhost
|
|
//
|
|
nats.ClientCert("./configs/certs/rdns/client-c.pem", "./configs/certs/rdns/client-c.key"),
|
|
nil,
|
|
nil,
|
|
},
|
|
{
|
|
"connect with tls and RDN sequence does not match",
|
|
`
|
|
port: -1
|
|
%s
|
|
|
|
authorization {
|
|
users = [
|
|
{ user = "CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=CA,C=US,DC=foo1,DC=foo2" },
|
|
{ user = "DC=foo2,DC=foo1,CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=CA,C=US" }
|
|
]
|
|
}
|
|
`,
|
|
// C = US, ST = California, L = Los Angeles, O = NATS, OU = NATS, CN = localhost, DC = foo3, DC = foo4
|
|
//
|
|
nats.ClientCert("./configs/certs/rdns/client-c.pem", "./configs/certs/rdns/client-c.key"),
|
|
errors.New("nats: Authorization Violation"),
|
|
nil,
|
|
},
|
|
{
|
|
"connect with tls and RDN sequence with space after comma not should matter",
|
|
`
|
|
port: -1
|
|
%s
|
|
|
|
authorization {
|
|
users = [
|
|
{ user = "DC=foo2, DC=foo1, CN=localhost, OU=NATS, O=NATS, L=Los Angeles, ST=CA, C=US" }
|
|
]
|
|
}
|
|
`,
|
|
// C=US/ST=California/L=Los Angeles/O=NATS/OU=NATS/CN=localhost/DC=foo1/DC=foo2
|
|
//
|
|
nats.ClientCert("./configs/certs/rdns/client-a.pem", "./configs/certs/rdns/client-a.key"),
|
|
nil,
|
|
nil,
|
|
},
|
|
{
|
|
"connect with tls and full RDN sequence respects order",
|
|
`
|
|
port: -1
|
|
%s
|
|
|
|
authorization {
|
|
users = [
|
|
{ user = "DC=com, DC=example, CN=*.example.com, O=NATS, OU=NATS, L=Los Angeles, ST=CA, C=US" }
|
|
]
|
|
}
|
|
`,
|
|
//
|
|
// C = US, ST = CA, L = Los Angeles, OU = NATS, O = NATS, CN = *.example.com, DC = example, DC = com
|
|
//
|
|
nats.ClientCert("./configs/certs/rdns/client-d.pem", "./configs/certs/rdns/client-d.key"),
|
|
nil,
|
|
nil,
|
|
},
|
|
{
|
|
"connect with tls and full RDN sequence with added domainComponents and spaces also matches",
|
|
`
|
|
port: -1
|
|
%s
|
|
|
|
authorization {
|
|
users = [
|
|
{ user = "CN=*.example.com,OU=NATS,O=NATS,L=Los Angeles,ST=CA,C=US,DC=example,DC=com" }
|
|
]
|
|
}
|
|
`,
|
|
//
|
|
// C = US, ST = CA, L = Los Angeles, OU = NATS, O = NATS, CN = *.example.com, DC = example, DC = com
|
|
//
|
|
nats.ClientCert("./configs/certs/rdns/client-d.pem", "./configs/certs/rdns/client-d.key"),
|
|
nil,
|
|
nil,
|
|
},
|
|
{
|
|
"connect with tls and full RDN sequence with correct order takes precedence over others matches",
|
|
`
|
|
port: -1
|
|
%s
|
|
|
|
authorization {
|
|
users = [
|
|
{ user = "CN=*.example.com,OU=NATS,O=NATS,L=Los Angeles,ST=CA,C=US,DC=example,DC=com",
|
|
permissions = { subscribe = { deny = ">" }} }
|
|
|
|
# This should take precedence since it is in the RFC2253 order.
|
|
{ user = "DC=com,DC=example,CN=*.example.com,O=NATS,OU=NATS,L=Los Angeles,ST=CA,C=US" }
|
|
|
|
{ user = "CN=*.example.com,OU=NATS,O=NATS,L=Los Angeles,ST=CA,C=US",
|
|
permissions = { subscribe = { deny = ">" }} }
|
|
]
|
|
}
|
|
`,
|
|
//
|
|
// C = US, ST = CA, L = Los Angeles, OU = NATS, O = NATS, CN = *.example.com, DC = example, DC = com
|
|
//
|
|
nats.ClientCert("./configs/certs/rdns/client-d.pem", "./configs/certs/rdns/client-d.key"),
|
|
nil,
|
|
nil,
|
|
},
|
|
{
|
|
"connect with tls and RDN includes multiple CN elements",
|
|
`
|
|
port: -1
|
|
%s
|
|
|
|
authorization {
|
|
users = [
|
|
{ user = "DC=com,DC=acme,OU=Organic Units,OU=Users,CN=jdoe,CN=123456,CN=John Doe" }
|
|
]
|
|
}
|
|
`,
|
|
//
|
|
// OpenSSL: -subj "/CN=John Doe/CN=123456/CN=jdoe/OU=Users/OU=Organic Units/DC=acme/DC=com"
|
|
// Go: CN=jdoe,OU=Users+OU=Organic Units
|
|
// RFC2253: DC=com,DC=acme,OU=Organic Units,OU=Users,CN=jdoe,CN=123456,CN=John Doe
|
|
//
|
|
nats.ClientCert("./configs/certs/rdns/client-e.pem", "./configs/certs/rdns/client-e.key"),
|
|
nil,
|
|
nil,
|
|
},
|
|
{
|
|
"connect with tls and DN includes a multi value RDN",
|
|
`
|
|
port: -1
|
|
%s
|
|
|
|
authorization {
|
|
users = [
|
|
{ user = "CN=John Doe,DC=DEV+O=users,DC=OpenSSL,DC=org" }
|
|
]
|
|
}
|
|
`,
|
|
//
|
|
// OpenSSL: -subj "/DC=org/DC=OpenSSL/DC=DEV+O=users/CN=John Doe" -multivalue-rdn
|
|
// Go: CN=John Doe,O=users
|
|
// RFC2253: CN=John Doe,DC=DEV+O=users,DC=OpenSSL,DC=org
|
|
//
|
|
nats.ClientCert("./configs/certs/rdns/client-f.pem", "./configs/certs/rdns/client-f.key"),
|
|
nil,
|
|
nil,
|
|
},
|
|
{
|
|
"connect with tls and DN includes a multi value RDN but there is no match",
|
|
`
|
|
port: -1
|
|
%s
|
|
|
|
authorization {
|
|
users = [
|
|
{ user = "CN=John Doe,DC=DEV,DC=OpenSSL,DC=org" }
|
|
]
|
|
}
|
|
`,
|
|
//
|
|
// OpenSSL: -subj "/DC=org/DC=OpenSSL/DC=DEV+O=users/CN=John Doe" -multivalue-rdn
|
|
// Go: CN=John Doe,O=users
|
|
// RFC2253: CN=John Doe,DC=DEV+O=users,DC=OpenSSL,DC=org
|
|
//
|
|
nats.ClientCert("./configs/certs/rdns/client-f.pem", "./configs/certs/rdns/client-f.key"),
|
|
errors.New("nats: Authorization Violation"),
|
|
nil,
|
|
},
|
|
{
|
|
"connect with tls and DN includes a multi value RDN that are reordered",
|
|
`
|
|
port: -1
|
|
%s
|
|
|
|
authorization {
|
|
users = [
|
|
{ user = "CN=John Doe,O=users+DC=DEV,DC=OpenSSL,DC=org" }
|
|
]
|
|
}
|
|
`,
|
|
//
|
|
// OpenSSL: -subj "/DC=org/DC=OpenSSL/DC=DEV+O=users/CN=John Doe" -multivalue-rdn
|
|
// Go: CN=John Doe,O=users
|
|
// RFC2253: CN=John Doe,DC=DEV+O=users,DC=OpenSSL,DC=org
|
|
//
|
|
nats.ClientCert("./configs/certs/rdns/client-f.pem", "./configs/certs/rdns/client-f.key"),
|
|
nil,
|
|
nil,
|
|
},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
content := fmt.Sprintf(test.config, `
|
|
tls {
|
|
cert_file: "configs/certs/rdns/server.pem"
|
|
key_file: "configs/certs/rdns/server.key"
|
|
ca_file: "configs/certs/rdns/ca.pem"
|
|
timeout: 5
|
|
verify_and_map: true
|
|
}
|
|
`)
|
|
conf := createConfFile(t, []byte(content))
|
|
s, opts := RunServerWithConfig(conf)
|
|
defer s.Shutdown()
|
|
|
|
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port),
|
|
test.certs,
|
|
nats.RootCAs("./configs/certs/rdns/ca.pem"),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if test.err == nil && err != nil {
|
|
t.Errorf("Expected to connect, got %v", err)
|
|
} else if test.err != nil && err == nil {
|
|
t.Errorf("Expected error on connect")
|
|
} else if test.err != nil && err != nil {
|
|
// Error on connect was expected
|
|
if test.err.Error() != err.Error() {
|
|
t.Errorf("Expected error %s, got: %s", test.err, err)
|
|
}
|
|
return
|
|
}
|
|
defer nc.Close()
|
|
|
|
nc.Subscribe("ping", func(m *nats.Msg) {
|
|
m.Respond([]byte("pong"))
|
|
})
|
|
nc.Flush()
|
|
|
|
_, err = nc.Request("ping", []byte("ping"), 250*time.Millisecond)
|
|
if test.rerr != nil && err == nil {
|
|
t.Errorf("Expected error getting response")
|
|
} else if test.rerr == nil && err != nil {
|
|
t.Errorf("Expected response")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTLSClientAuthWithRDNSequenceReordered(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
config string
|
|
certs nats.Option
|
|
err error
|
|
rerr error
|
|
}{
|
|
{
|
|
"connect with tls and DN includes a multi value RDN that are reordered",
|
|
`
|
|
port: -1
|
|
%s
|
|
|
|
authorization {
|
|
users = [
|
|
{ user = "DC=org,DC=OpenSSL,O=users+DC=DEV,CN=John Doe" }
|
|
]
|
|
}
|
|
`,
|
|
//
|
|
// OpenSSL: -subj "/DC=org/DC=OpenSSL/DC=DEV+O=users/CN=John Doe" -multivalue-rdn
|
|
// Go: CN=John Doe,O=users
|
|
// RFC2253: CN=John Doe,DC=DEV+O=users,DC=OpenSSL,DC=org
|
|
//
|
|
nats.ClientCert("./configs/certs/rdns/client-f.pem", "./configs/certs/rdns/client-f.key"),
|
|
nil,
|
|
nil,
|
|
},
|
|
{
|
|
"connect with tls and DN includes a multi value RDN that are reordered but not equal RDNs",
|
|
`
|
|
port: -1
|
|
%s
|
|
|
|
authorization {
|
|
users = [
|
|
{ user = "DC=org,DC=OpenSSL,O=users,CN=John Doe" }
|
|
]
|
|
}
|
|
`,
|
|
//
|
|
// OpenSSL: -subj "/DC=org/DC=OpenSSL/DC=DEV+O=users/CN=John Doe" -multivalue-rdn
|
|
// Go: CN=John Doe,O=users
|
|
// RFC2253: CN=John Doe,DC=DEV+O=users,DC=OpenSSL,DC=org
|
|
//
|
|
nats.ClientCert("./configs/certs/rdns/client-f.pem", "./configs/certs/rdns/client-f.key"),
|
|
errors.New("nats: Authorization Violation"),
|
|
nil,
|
|
},
|
|
{
|
|
"connect with tls and DN includes a multi value RDN that are reordered but not equal RDNs",
|
|
`
|
|
port: -1
|
|
%s
|
|
|
|
authorization {
|
|
users = [
|
|
{ user = "DC=OpenSSL, DC=org, O=users, CN=John Doe" }
|
|
]
|
|
}
|
|
`,
|
|
//
|
|
// OpenSSL: -subj "/DC=org/DC=OpenSSL/DC=DEV+O=users/CN=John Doe" -multivalue-rdn
|
|
// Go: CN=John Doe,O=users
|
|
// RFC2253: CN=John Doe,DC=DEV+O=users,DC=OpenSSL,DC=org
|
|
//
|
|
nats.ClientCert("./configs/certs/rdns/client-f.pem", "./configs/certs/rdns/client-f.key"),
|
|
errors.New("nats: Authorization Violation"),
|
|
nil,
|
|
},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
content := fmt.Sprintf(test.config, `
|
|
tls {
|
|
cert_file: "configs/certs/rdns/server.pem"
|
|
key_file: "configs/certs/rdns/server.key"
|
|
ca_file: "configs/certs/rdns/ca.pem"
|
|
timeout: 5
|
|
verify_and_map: true
|
|
}
|
|
`)
|
|
conf := createConfFile(t, []byte(content))
|
|
s, opts := RunServerWithConfig(conf)
|
|
defer s.Shutdown()
|
|
|
|
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port),
|
|
test.certs,
|
|
nats.RootCAs("./configs/certs/rdns/ca.pem"),
|
|
)
|
|
if test.err == nil && err != nil {
|
|
t.Errorf("Expected to connect, got %v", err)
|
|
} else if test.err != nil && err == nil {
|
|
t.Errorf("Expected error on connect")
|
|
} else if test.err != nil && err != nil {
|
|
// Error on connect was expected
|
|
if test.err.Error() != err.Error() {
|
|
t.Errorf("Expected error %s, got: %s", test.err, err)
|
|
}
|
|
return
|
|
}
|
|
defer nc.Close()
|
|
|
|
nc.Subscribe("ping", func(m *nats.Msg) {
|
|
m.Respond([]byte("pong"))
|
|
})
|
|
nc.Flush()
|
|
|
|
_, err = nc.Request("ping", []byte("ping"), 250*time.Millisecond)
|
|
if test.rerr != nil && err == nil {
|
|
t.Errorf("Expected error getting response")
|
|
} else if test.rerr == nil && err != nil {
|
|
t.Errorf("Expected response")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTLSClientSVIDAuth(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
config string
|
|
certs nats.Option
|
|
err error
|
|
rerr error
|
|
}{
|
|
{
|
|
"connect with tls using certificate with URIs",
|
|
`
|
|
port: -1
|
|
%s
|
|
|
|
authorization {
|
|
users = [
|
|
{
|
|
user = "spiffe://localhost/my-nats-service/user-a"
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
nats.ClientCert("./configs/certs/svid/client-a.pem", "./configs/certs/svid/client-a.key"),
|
|
nil,
|
|
nil,
|
|
},
|
|
{
|
|
"connect with tls using certificate with limited different permissions",
|
|
`
|
|
port: -1
|
|
%s
|
|
|
|
authorization {
|
|
users = [
|
|
{
|
|
user = "spiffe://localhost/my-nats-service/user-a"
|
|
},
|
|
{
|
|
user = "spiffe://localhost/my-nats-service/user-b"
|
|
permissions = { subscribe = { deny = ">" }}
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
nats.ClientCert("./configs/certs/svid/client-b.pem", "./configs/certs/svid/client-b.key"),
|
|
nil,
|
|
errors.New("nats: timeout"),
|
|
},
|
|
{
|
|
"connect with tls without URIs in permissions will still match SAN",
|
|
`
|
|
port: -1
|
|
%s
|
|
|
|
authorization {
|
|
users = [
|
|
{
|
|
user = "O=SPIRE,C=US"
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
nats.ClientCert("./configs/certs/svid/client-b.pem", "./configs/certs/svid/client-b.key"),
|
|
nil,
|
|
nil,
|
|
},
|
|
{
|
|
"connect with tls but no permissions",
|
|
`
|
|
port: -1
|
|
%s
|
|
|
|
authorization {
|
|
users = [
|
|
{
|
|
user = "spiffe://localhost/my-nats-service/user-c"
|
|
}
|
|
]
|
|
}
|
|
`,
|
|
nats.ClientCert("./configs/certs/svid/client-a.pem", "./configs/certs/svid/client-a.key"),
|
|
errors.New("nats: Authorization Violation"),
|
|
nil,
|
|
},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
content := fmt.Sprintf(test.config, `
|
|
tls {
|
|
cert_file: "configs/certs/svid/server.pem"
|
|
key_file: "configs/certs/svid/server.key"
|
|
ca_file: "configs/certs/svid/ca.pem"
|
|
timeout: 5
|
|
insecure: true
|
|
verify_and_map: true
|
|
}
|
|
`)
|
|
conf := createConfFile(t, []byte(content))
|
|
s, opts := RunServerWithConfig(conf)
|
|
defer s.Shutdown()
|
|
|
|
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port),
|
|
test.certs,
|
|
nats.RootCAs("./configs/certs/svid/ca.pem"),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if test.err == nil && err != nil {
|
|
t.Errorf("Expected to connect, got %v", err)
|
|
} else if test.err != nil && err == nil {
|
|
t.Errorf("Expected error on connect")
|
|
} else if test.err != nil && err != nil {
|
|
// Error on connect was expected
|
|
if test.err.Error() != err.Error() {
|
|
t.Errorf("Expected error %s, got: %s", test.err, err)
|
|
}
|
|
return
|
|
}
|
|
defer nc.Close()
|
|
|
|
nc.Subscribe("ping", func(m *nats.Msg) {
|
|
m.Respond([]byte("pong"))
|
|
})
|
|
nc.Flush()
|
|
|
|
_, err = nc.Request("ping", []byte("ping"), 250*time.Millisecond)
|
|
if test.rerr != nil && err == nil {
|
|
t.Errorf("Expected error getting response")
|
|
} else if test.rerr == nil && err != nil {
|
|
t.Errorf("Expected response")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTLSPinnedCertsClient(t *testing.T) {
|
|
tmpl := `
|
|
host: localhost
|
|
port: -1
|
|
tls {
|
|
ca_file: "configs/certs/ca.pem"
|
|
cert_file: "configs/certs/server-cert.pem"
|
|
key_file: "configs/certs/server-key.pem"
|
|
# Require a client certificate and map user id from certificate
|
|
verify: true
|
|
pinned_certs: ["%s"]
|
|
}`
|
|
|
|
confFileName := createConfFile(t, []byte(fmt.Sprintf(tmpl, "aaaaaaaa09fde09451411ba3b42c0f74727d61a974c69fd3cf5257f39c75f0e9")))
|
|
srv, o := RunServerWithConfig(confFileName)
|
|
defer srv.Shutdown()
|
|
|
|
if len(o.TLSPinnedCerts) != 1 {
|
|
t.Fatal("expected one pinned cert")
|
|
}
|
|
|
|
opts := []nats.Option{
|
|
nats.RootCAs("configs/certs/ca.pem"),
|
|
nats.ClientCert("./configs/certs/client-cert.pem", "./configs/certs/client-key.pem"),
|
|
}
|
|
|
|
nc, err := nats.Connect(srv.ClientURL(), opts...)
|
|
if err == nil {
|
|
nc.Close()
|
|
t.Fatalf("Expected error trying to connect without a certificate in pinned_certs")
|
|
}
|
|
|
|
os.WriteFile(confFileName, []byte(fmt.Sprintf(tmpl, "bf6f821f09fde09451411ba3b42c0f74727d61a974c69fd3cf5257f39c75f0e9")), 0660)
|
|
if err := srv.Reload(); err != nil {
|
|
t.Fatalf("on Reload got %v", err)
|
|
}
|
|
// reload pinned to the certs used
|
|
nc, err = nats.Connect(srv.ClientURL(), opts...)
|
|
if err != nil {
|
|
t.Fatalf("Expected no error, got: %v", err)
|
|
}
|
|
nc.Close()
|
|
}
|
|
|
|
type captureWarnLogger struct {
|
|
dummyLogger
|
|
receive chan string
|
|
}
|
|
|
|
func newCaptureWarnLogger() *captureWarnLogger {
|
|
return &captureWarnLogger{
|
|
receive: make(chan string, 100),
|
|
}
|
|
}
|
|
|
|
func (l *captureWarnLogger) Warnf(format string, v ...interface{}) {
|
|
l.receive <- fmt.Sprintf(format, v...)
|
|
}
|
|
|
|
func (l *captureWarnLogger) waitFor(expect string, timeout time.Duration) bool {
|
|
for {
|
|
select {
|
|
case msg := <-l.receive:
|
|
if strings.Contains(msg, expect) {
|
|
return true
|
|
}
|
|
case <-time.After(timeout):
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTLSConnectionRate(t *testing.T) {
|
|
config := `
|
|
listen: "127.0.0.1:-1"
|
|
tls {
|
|
cert_file: "./configs/certs/server-cert.pem"
|
|
key_file: "./configs/certs/server-key.pem"
|
|
connection_rate_limit: 3
|
|
}
|
|
`
|
|
|
|
confFileName := createConfFile(t, []byte(config))
|
|
|
|
srv, _ := RunServerWithConfig(confFileName)
|
|
logger := newCaptureWarnLogger()
|
|
srv.SetLogger(logger, false, false)
|
|
defer srv.Shutdown()
|
|
|
|
var err error
|
|
count := 0
|
|
for count < 10 {
|
|
var nc *nats.Conn
|
|
nc, err = nats.Connect(srv.ClientURL(), nats.RootCAs("./configs/certs/ca.pem"))
|
|
|
|
if err != nil {
|
|
break
|
|
}
|
|
nc.Close()
|
|
count++
|
|
}
|
|
|
|
if count != 3 {
|
|
t.Fatalf("Expected 3 connections per second, got %d (%v)", count, err)
|
|
}
|
|
|
|
if !logger.waitFor("connections due to TLS rate limiting", time.Second) {
|
|
t.Fatalf("did not log 'TLS rate limiting' warning")
|
|
}
|
|
}
|
|
|
|
func TestTLSPinnedCertsRoute(t *testing.T) {
|
|
tmplSeed := `
|
|
host: localhost
|
|
port: -1
|
|
cluster {
|
|
port: -1
|
|
pool_size: -1
|
|
tls {
|
|
ca_file: "configs/certs/ca.pem"
|
|
cert_file: "configs/certs/server-cert.pem"
|
|
key_file: "configs/certs/server-key.pem"
|
|
}
|
|
}`
|
|
// this server connects to seed, but is set up to not trust seeds cert
|
|
tmplSrv := `
|
|
host: localhost
|
|
port: -1
|
|
cluster {
|
|
port: -1
|
|
routes = [nats-route://localhost:%d]
|
|
tls {
|
|
ca_file: "configs/certs/ca.pem"
|
|
cert_file: "configs/certs/server-cert.pem"
|
|
key_file: "configs/certs/server-key.pem"
|
|
# Require a client certificate and map user id from certificate
|
|
verify: true
|
|
# expected to fail the seed server
|
|
pinned_certs: ["%s"]
|
|
}
|
|
}`
|
|
|
|
confSeed := createConfFile(t, []byte(tmplSeed))
|
|
srvSeed, o := RunServerWithConfig(confSeed)
|
|
defer srvSeed.Shutdown()
|
|
|
|
confSrv := createConfFile(t, []byte(fmt.Sprintf(tmplSrv, o.Cluster.Port, "89386860ea1222698ea676fc97310bdf2bff6f7e2b0420fac3b3f8f5a08fede5")))
|
|
srv, _ := RunServerWithConfig(confSrv)
|
|
defer srv.Shutdown()
|
|
|
|
checkClusterFormed(t, srvSeed, srv)
|
|
|
|
// this change will result in the server being and remaining disconnected
|
|
os.WriteFile(confSrv, []byte(fmt.Sprintf(tmplSrv, o.Cluster.Port, "aaaaaaaa09fde09451411ba3b42c0f74727d61a974c69fd3cf5257f39c75f0e9")), 0660)
|
|
if err := srv.Reload(); err != nil {
|
|
t.Fatalf("on Reload got %v", err)
|
|
}
|
|
|
|
checkNumRoutes(t, srvSeed, 0)
|
|
checkNumRoutes(t, srv, 0)
|
|
}
|
|
|
|
func TestAllowNonTLSReload(t *testing.T) {
|
|
tmpl := `
|
|
listen: "127.0.0.1:-1"
|
|
ping_interval: "%s"
|
|
tls {
|
|
ca_file: "configs/certs/ca.pem"
|
|
cert_file: "configs/certs/server-cert.pem"
|
|
key_file: "configs/certs/server-key.pem"
|
|
}
|
|
allow_non_tls: true
|
|
`
|
|
conf := createConfFile(t, []byte(fmt.Sprintf(tmpl, "10s")))
|
|
s, o := RunServerWithConfig(conf)
|
|
defer s.Shutdown()
|
|
|
|
check := func() {
|
|
t.Helper()
|
|
nc := createClientConn(t, "127.0.0.1", o.Port)
|
|
defer nc.Close()
|
|
info := checkInfoMsg(t, nc)
|
|
if !info.TLSAvailable {
|
|
t.Fatal("TLSAvailable should be true, was false")
|
|
}
|
|
if info.TLSRequired {
|
|
t.Fatal("TLSRequired should be false, was true")
|
|
}
|
|
}
|
|
check()
|
|
|
|
os.WriteFile(conf, []byte(fmt.Sprintf(tmpl, "20s")), 0660)
|
|
if err := s.Reload(); err != nil {
|
|
t.Fatalf("Error on reload: %v", err)
|
|
}
|
|
check()
|
|
}
|