mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 03:38:42 -07:00
A new option instructs the server to perform the TLS handshake first,
that is prior to sending the INFO protocol to the client.
Only clients that implement equivalent option would be able to
connect if the server runs with this option enabled.
The configuration would look something like this:
```
...
tls {
cert_file: ...
key_file: ...
handshake_first: true
}
```
The same option can be set to "auto" or a Go time duration to fallback
to the old behavior. This is intended for deployments where it is known
that not all clients have been upgraded to a client library providing
the TLS handshake first option.
After the delay has elapsed without receiving the TLS handshake from
the client, the server reverts to sending the INFO protocol so that
older clients can connect. Clients that do connect with the "TLS first"
option will be marked as such in the monitoring's Connz page/result.
It will allow the administrator to keep track of applications still
needing to upgrade.
The configuration would be similar to:
```
...
tls {
cert_file: ...
key_file: ...
handshake_first: auto
}
```
With the above value, the fallback delay used by the server is 50ms.
The duration can be explcitly set, say 300 milliseconds:
```
...
tls {
cert_file: ...
key_file: ...
handshake_first: "300ms"
}
```
It is understood that any configuration other that "true" will result
in the server sending the INFO protocol after the elapsed amount of
time without the client initiating the TLS handshake. Therefore, for
administrators that do not want any data transmitted in plain text,
the value must be set to "true" only. It will require applications
to be updated to a library that provides the option, which may or
may not be readily available.
Signed-off-by: Ivan Kozlovic <ivan@synadia.com>
2025 lines
53 KiB
Go
2025 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)
|
|
}
|
|
defer nc.Close()
|
|
|
|
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()
|
|
}
|