mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 11:48:43 -07:00
3978 lines
104 KiB
Go
3978 lines
104 KiB
Go
// Copyright 2021-2023 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 (
|
|
"bytes"
|
|
"context"
|
|
"crypto"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/ocsp"
|
|
|
|
"github.com/nats-io/nats-server/v2/server"
|
|
"github.com/nats-io/nats.go"
|
|
)
|
|
|
|
const (
|
|
defaultResponseTTL = 4 * time.Second
|
|
defaultAddress = "127.0.0.1:8888"
|
|
)
|
|
|
|
func TestOCSPAlwaysMustStapleAndShutdown(t *testing.T) {
|
|
// Certs that have must staple will auto shutdown the server.
|
|
const (
|
|
caCert = "configs/certs/ocsp/ca-cert.pem"
|
|
caKey = "configs/certs/ocsp/ca-key.pem"
|
|
serverCert = "configs/certs/ocsp/server-cert.pem"
|
|
serverKey = "configs/certs/ocsp/server-key.pem"
|
|
)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
ocspr := newOCSPResponder(t, caCert, caKey)
|
|
defer ocspr.Shutdown(ctx)
|
|
addr := fmt.Sprintf("http://%s", ocspr.Addr)
|
|
setOCSPStatus(t, addr, serverCert, ocsp.Good)
|
|
|
|
opts := server.Options{}
|
|
opts.Host = "127.0.0.1"
|
|
opts.NoLog = true
|
|
opts.NoSigs = true
|
|
opts.MaxControlLine = 4096
|
|
opts.Port = -1
|
|
opts.TLSCert = serverCert
|
|
opts.TLSKey = serverKey
|
|
opts.TLSCaCert = caCert
|
|
opts.TLSTimeout = 5
|
|
tcOpts := &server.TLSConfigOpts{
|
|
CertFile: opts.TLSCert,
|
|
KeyFile: opts.TLSKey,
|
|
CaFile: opts.TLSCaCert,
|
|
Timeout: opts.TLSTimeout,
|
|
}
|
|
|
|
tlsConf, err := server.GenTLSConfig(tcOpts)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
opts.TLSConfig = tlsConf
|
|
|
|
opts.OCSPConfig = &server.OCSPConfig{
|
|
Mode: server.OCSPModeAlways,
|
|
OverrideURLs: []string{addr},
|
|
}
|
|
srv := RunServer(&opts)
|
|
defer srv.Shutdown()
|
|
|
|
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
resp, err := getOCSPStatus(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if resp.Status != ocsp.Good {
|
|
return fmt.Errorf("invalid staple")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer nc.Close()
|
|
sub, err := nc.SubscribeSync("foo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Publish("foo", []byte("hello world"))
|
|
nc.Flush()
|
|
|
|
_, err = sub.NextMsg(1 * time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Close()
|
|
|
|
// The server will shutdown because the server becomes revoked
|
|
// and the policy is to always must-staple. The OCSP Responder
|
|
// instructs the NATS Server to fetch OCSP Staples every 2 seconds.
|
|
time.Sleep(2 * time.Second)
|
|
setOCSPStatus(t, addr, serverCert, ocsp.Revoked)
|
|
time.Sleep(2 * time.Second)
|
|
|
|
// Should be connection refused since server will abort now.
|
|
_, err = nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nats.ErrNoServers {
|
|
t.Errorf("Expected connection refused")
|
|
}
|
|
// Verify that the server finishes shutdown
|
|
srv.WaitForShutdown()
|
|
}
|
|
|
|
func TestOCSPMustStapleShutdown(t *testing.T) {
|
|
const (
|
|
caCert = "configs/certs/ocsp/ca-cert.pem"
|
|
caKey = "configs/certs/ocsp/ca-key.pem"
|
|
serverCert = "configs/certs/ocsp/server-status-request-cert.pem"
|
|
serverKey = "configs/certs/ocsp/server-status-request-key.pem"
|
|
)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
ocspr := newOCSPResponder(t, caCert, caKey)
|
|
defer ocspr.Shutdown(ctx)
|
|
addr := fmt.Sprintf("http://%s", ocspr.Addr)
|
|
setOCSPStatus(t, addr, serverCert, ocsp.Good)
|
|
|
|
opts := server.Options{}
|
|
opts.Host = "127.0.0.1"
|
|
opts.NoLog = true
|
|
opts.NoSigs = true
|
|
opts.MaxControlLine = 4096
|
|
opts.Port = -1
|
|
opts.TLSCert = serverCert
|
|
opts.TLSKey = serverKey
|
|
opts.TLSCaCert = caCert
|
|
opts.TLSTimeout = 5
|
|
tlsConfigOpts := &server.TLSConfigOpts{
|
|
CertFile: opts.TLSCert,
|
|
KeyFile: opts.TLSKey,
|
|
CaFile: opts.TLSCaCert,
|
|
Timeout: opts.TLSTimeout,
|
|
}
|
|
|
|
tlsConf, err := server.GenTLSConfig(tlsConfigOpts)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
opts.TLSConfig = tlsConf
|
|
|
|
opts.OCSPConfig = &server.OCSPConfig{
|
|
Mode: server.OCSPModeMust,
|
|
OverrideURLs: []string{addr},
|
|
}
|
|
|
|
srv := RunServer(&opts)
|
|
defer srv.Shutdown()
|
|
|
|
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
resp, err := getOCSPStatus(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if resp.Status != ocsp.Good {
|
|
return fmt.Errorf("invalid staple")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer nc.Close()
|
|
sub, err := nc.SubscribeSync("foo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Publish("foo", []byte("hello world"))
|
|
nc.Flush()
|
|
|
|
_, err = sub.NextMsg(1 * time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Close()
|
|
|
|
// The server will shutdown because the server becomes revoked
|
|
// and the policy is to always must-staple. The OCSP Responder
|
|
// instructs the NATS Server to fetch OCSP Staples every 2 seconds.
|
|
time.Sleep(2 * time.Second)
|
|
setOCSPStatus(t, addr, serverCert, ocsp.Revoked)
|
|
time.Sleep(2 * time.Second)
|
|
|
|
// Should be connection refused since server will abort now.
|
|
_, err = nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nats.ErrNoServers {
|
|
t.Errorf("Expected connection refused")
|
|
}
|
|
}
|
|
|
|
func TestOCSPMustStapleAutoDoesNotShutdown(t *testing.T) {
|
|
const (
|
|
caCert = "configs/certs/ocsp/ca-cert.pem"
|
|
caKey = "configs/certs/ocsp/ca-key.pem"
|
|
serverCert = "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
ocspr := newOCSPResponder(t, caCert, caKey)
|
|
defer ocspr.Shutdown(ctx)
|
|
addr := fmt.Sprintf("http://%s", ocspr.Addr)
|
|
setOCSPStatus(t, addr, serverCert, ocsp.Good)
|
|
|
|
content := `
|
|
port: -1
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-01-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
`
|
|
conf := createConfFile(t, []byte(content))
|
|
s, opts := RunServerWithConfig(conf)
|
|
defer s.Shutdown()
|
|
|
|
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
resp, err := getOCSPStatus(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if resp.Status != ocsp.Good {
|
|
t.Errorf("Expected valid OCSP staple status")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer nc.Close()
|
|
sub, err := nc.SubscribeSync("foo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Publish("foo", []byte("hello world"))
|
|
nc.Flush()
|
|
|
|
_, err = sub.NextMsg(1 * time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Close()
|
|
|
|
// The server will shutdown because the server becomes revoked
|
|
// and the policy is to always must-staple. The OCSP Responder
|
|
// instructs the NATS Server to fetch OCSP Staples every 2 seconds.
|
|
time.Sleep(2 * time.Second)
|
|
setOCSPStatus(t, addr, serverCert, ocsp.Revoked)
|
|
time.Sleep(2 * time.Second)
|
|
|
|
// Should not be connection refused, the client will continue running and
|
|
// be served the stale OCSP staple instead.
|
|
nc, err = nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
resp, err := getOCSPStatus(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if resp.Status != ocsp.Revoked {
|
|
t.Errorf("Expected revoked status")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Close()
|
|
}
|
|
|
|
func TestOCSPAutoWithoutMustStapleDoesNotShutdownOnRevoke(t *testing.T) {
|
|
const (
|
|
caCert = "configs/certs/ocsp/ca-cert.pem"
|
|
caKey = "configs/certs/ocsp/ca-key.pem"
|
|
serverCert = "configs/certs/ocsp/server-cert.pem"
|
|
serverKey = "configs/certs/ocsp/server-key.pem"
|
|
)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
ocspr := newOCSPResponder(t, caCert, caKey)
|
|
defer ocspr.Shutdown(ctx)
|
|
addr := fmt.Sprintf("http://%s", ocspr.Addr)
|
|
setOCSPStatus(t, addr, serverCert, ocsp.Good)
|
|
|
|
opts := server.Options{}
|
|
opts.Host = "127.0.0.1"
|
|
opts.NoLog = true
|
|
opts.NoSigs = true
|
|
opts.MaxControlLine = 4096
|
|
opts.Port = -1
|
|
opts.TLSCert = serverCert
|
|
opts.TLSKey = serverKey
|
|
opts.TLSCaCert = caCert
|
|
opts.TLSTimeout = 5
|
|
tlsConfigOpts := &server.TLSConfigOpts{
|
|
CertFile: opts.TLSCert,
|
|
KeyFile: opts.TLSKey,
|
|
CaFile: opts.TLSCaCert,
|
|
Timeout: opts.TLSTimeout,
|
|
}
|
|
tlsConf, err := server.GenTLSConfig(tlsConfigOpts)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
opts.TLSConfig = tlsConf
|
|
|
|
opts.OCSPConfig = &server.OCSPConfig{
|
|
Mode: server.OCSPModeAuto,
|
|
OverrideURLs: []string{addr},
|
|
}
|
|
|
|
srv := RunServer(&opts)
|
|
defer srv.Shutdown()
|
|
|
|
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse != nil {
|
|
return fmt.Errorf("Unexpected OCSP staple for certificate")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer nc.Close()
|
|
sub, err := nc.SubscribeSync("foo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Publish("foo", []byte("hello world"))
|
|
nc.Flush()
|
|
|
|
_, err = sub.NextMsg(1 * time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Close()
|
|
|
|
// Revoke the client certificate, nothing will happens since does
|
|
// not have MustStaple.
|
|
time.Sleep(2 * time.Second)
|
|
setOCSPStatus(t, addr, serverCert, ocsp.Revoked)
|
|
time.Sleep(2 * time.Second)
|
|
|
|
// Should not be connection refused since server will continue running.
|
|
nc, err = nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %s", err)
|
|
}
|
|
nc.Close()
|
|
}
|
|
|
|
func TestOCSPClient(t *testing.T) {
|
|
const (
|
|
caCert = "configs/certs/ocsp/ca-cert.pem"
|
|
caKey = "configs/certs/ocsp/ca-key.pem"
|
|
serverCert = "configs/certs/ocsp/server-cert.pem"
|
|
serverKey = "configs/certs/ocsp/server-key.pem"
|
|
)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
ocspr := newOCSPResponder(t, caCert, caKey)
|
|
ocspURL := fmt.Sprintf("http://%s", ocspr.Addr)
|
|
defer ocspr.Shutdown(ctx)
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
config string
|
|
opts []nats.Option
|
|
err error
|
|
rerr error
|
|
configure func()
|
|
}{
|
|
{
|
|
"OCSP Stapling makes server fail to boot if status is unknown",
|
|
`
|
|
port: -1
|
|
|
|
# Enable OCSP stapling with policy to honor must staple if present.
|
|
ocsp: true
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
`,
|
|
[]nats.Option{
|
|
nats.ClientCert("./configs/certs/ocsp/client-cert.pem", "./configs/certs/ocsp/client-key.pem"),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
},
|
|
nil,
|
|
nil,
|
|
func() {},
|
|
},
|
|
{
|
|
"OCSP Stapling ignored by default if server without must staple status",
|
|
`
|
|
port: -1
|
|
|
|
ocsp: true
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
`,
|
|
[]nats.Option{
|
|
nats.ClientCert("./configs/certs/ocsp/client-cert.pem", "./configs/certs/ocsp/client-key.pem"),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
},
|
|
nil,
|
|
nil,
|
|
func() { setOCSPStatus(t, ocspURL, serverCert, ocsp.Good) },
|
|
},
|
|
{
|
|
"OCSP Stapling honored by default if server has must staple status",
|
|
`
|
|
port: -1
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-01-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
`,
|
|
[]nats.Option{
|
|
nats.ClientCert("./configs/certs/ocsp/client-cert.pem", "./configs/certs/ocsp/client-key.pem"),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
},
|
|
nil,
|
|
nil,
|
|
func() {
|
|
setOCSPStatus(t, ocspURL, "configs/certs/ocsp/server-status-request-url-01-cert.pem", ocsp.Good)
|
|
},
|
|
},
|
|
{
|
|
"OCSP Stapling can be disabled even if server has must staple status",
|
|
`
|
|
port: -1
|
|
|
|
ocsp: false
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-01-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
`,
|
|
[]nats.Option{
|
|
nats.ClientCert("./configs/certs/ocsp/client-cert.pem", "./configs/certs/ocsp/client-key.pem"),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
},
|
|
nil,
|
|
nil,
|
|
func() {
|
|
setOCSPStatus(t, ocspURL, "configs/certs/ocsp/server-status-request-url-01-cert.pem", ocsp.Revoked)
|
|
},
|
|
},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
test.configure()
|
|
content := test.config
|
|
conf := createConfFile(t, []byte(content))
|
|
s, opts := RunServerWithConfig(conf)
|
|
defer s.Shutdown()
|
|
|
|
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts...)
|
|
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 TestOCSPReloadRotateTLSCertWithNoURL(t *testing.T) {
|
|
const (
|
|
caCert = "configs/certs/ocsp/ca-cert.pem"
|
|
caKey = "configs/certs/ocsp/ca-key.pem"
|
|
serverCert = "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
updatedServerCert = "configs/certs/ocsp/server-status-request-cert.pem"
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
ocspr := newOCSPResponder(t, caCert, caKey)
|
|
defer ocspr.Shutdown(ctx)
|
|
addr := fmt.Sprintf("http://%s", ocspr.Addr)
|
|
setOCSPStatus(t, addr, serverCert, ocsp.Good)
|
|
|
|
content := `
|
|
port: -1
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-01-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
`
|
|
conf := createConfFile(t, []byte(content))
|
|
s, opts := RunServerWithConfig(conf)
|
|
defer s.Shutdown()
|
|
|
|
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
resp, err := getOCSPStatus(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if resp.Status != ocsp.Good {
|
|
t.Errorf("Expected valid OCSP staple status")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer nc.Close()
|
|
sub, err := nc.SubscribeSync("foo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Publish("foo", []byte("hello world"))
|
|
nc.Flush()
|
|
|
|
_, err = sub.NextMsg(1 * time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Close()
|
|
|
|
// Change the contents with another that will fail to get a staple
|
|
// since it does not have an URL.
|
|
content = `
|
|
port: -1
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
`
|
|
if err := os.WriteFile(conf, []byte(content), 0666); err != nil {
|
|
t.Fatalf("Error writing config: %v", err)
|
|
}
|
|
// Reload show warning because of cert missing OCSP Url so cannot be used
|
|
// with OCSP stapling.
|
|
if err := s.Reload(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
expectedErr := fmt.Errorf("missing OCSP response")
|
|
// The server will not shutdown because the reload will fail.
|
|
_, err = nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
// The new certificate does not have OCSP Staples since
|
|
// it could not fetch one from a OCSP server.
|
|
if s.OCSPResponse == nil {
|
|
return expectedErr
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != expectedErr {
|
|
t.Fatalf("Unexpected error: %s", expectedErr)
|
|
}
|
|
}
|
|
|
|
func TestOCSPReloadRotateTLSCertDisableMustStaple(t *testing.T) {
|
|
const (
|
|
caCert = "configs/certs/ocsp/ca-cert.pem"
|
|
caKey = "configs/certs/ocsp/ca-key.pem"
|
|
serverCert = "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
updatedServerCert = "configs/certs/ocsp/server-status-request-cert.pem"
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
ocspr := newOCSPResponder(t, caCert, caKey)
|
|
defer ocspr.Shutdown(ctx)
|
|
addr := fmt.Sprintf("http://%s", ocspr.Addr)
|
|
setOCSPStatus(t, addr, serverCert, ocsp.Good)
|
|
|
|
storeDir := t.TempDir()
|
|
|
|
originalContent := `
|
|
port: -1
|
|
|
|
store_dir: '%s'
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-01-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
`
|
|
|
|
content := fmt.Sprintf(originalContent, storeDir)
|
|
conf := createConfFile(t, []byte(content))
|
|
s, opts := RunServerWithConfig(conf)
|
|
defer s.Shutdown()
|
|
|
|
var staple []byte
|
|
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
staple = s.OCSPResponse
|
|
resp, err := getOCSPStatus(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if resp.Status != ocsp.Good {
|
|
t.Errorf("Expected valid OCSP staple status")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer nc.Close()
|
|
sub, err := nc.SubscribeSync("foo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Publish("foo", []byte("hello world"))
|
|
nc.Flush()
|
|
|
|
_, err = sub.NextMsg(1 * time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Close()
|
|
|
|
files := []string{}
|
|
err = filepath.Walk(storeDir+"/ocsp/", func(path string, info os.FileInfo, err error) error {
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
files = append(files, path)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
found := false
|
|
for _, file := range files {
|
|
data, err := os.ReadFile(file)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if bytes.Equal(staple, data) {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("Could not find OCSP Staple")
|
|
}
|
|
|
|
// Change the contents with another that has OCSP Stapling disabled.
|
|
updatedContent := `
|
|
port: -1
|
|
|
|
store_dir: '%s'
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
`
|
|
content = fmt.Sprintf(updatedContent, storeDir)
|
|
if err := os.WriteFile(conf, []byte(content), 0666); err != nil {
|
|
t.Fatalf("Error writing config: %v", err)
|
|
}
|
|
if err := s.Reload(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// The new certificate does not have must staple so they will be missing.
|
|
time.Sleep(4 * time.Second)
|
|
|
|
nc, err = nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse != nil {
|
|
return fmt.Errorf("unexpected OCSP Staple!")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Close()
|
|
|
|
// Re-enable OCSP Stapling
|
|
content = fmt.Sprintf(originalContent, storeDir)
|
|
if err := os.WriteFile(conf, []byte(content), 0666); err != nil {
|
|
t.Fatalf("Error writing config: %v", err)
|
|
}
|
|
if err := s.Reload(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var newStaple []byte
|
|
nc, err = nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
newStaple = s.OCSPResponse
|
|
resp, err := getOCSPStatus(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if resp.Status != ocsp.Good {
|
|
t.Errorf("Expected valid OCSP staple status")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Close()
|
|
|
|
// Confirm that it got a new staple.
|
|
files = []string{}
|
|
err = filepath.Walk(storeDir+"/ocsp/", func(path string, info os.FileInfo, err error) error {
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
files = append(files, path)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
found = false
|
|
for _, file := range files {
|
|
data, err := os.ReadFile(file)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if bytes.Equal(newStaple, data) {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("Could not find OCSP Staple")
|
|
}
|
|
if bytes.Equal(staple, newStaple) {
|
|
t.Error("Expected new OCSP Staple")
|
|
}
|
|
}
|
|
|
|
func TestOCSPReloadRotateTLSCertEnableMustStaple(t *testing.T) {
|
|
const (
|
|
caCert = "configs/certs/ocsp/ca-cert.pem"
|
|
caKey = "configs/certs/ocsp/ca-key.pem"
|
|
serverCert = "configs/certs/ocsp/server-cert.pem"
|
|
updatedServerCert = "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
ocspr := newOCSPResponder(t, caCert, caKey)
|
|
defer ocspr.Shutdown(ctx)
|
|
addr := fmt.Sprintf("http://%s", ocspr.Addr)
|
|
setOCSPStatus(t, addr, serverCert, ocsp.Good)
|
|
setOCSPStatus(t, addr, updatedServerCert, ocsp.Good)
|
|
|
|
// Start without OCSP Stapling MustStaple
|
|
content := `
|
|
port: -1
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
`
|
|
conf := createConfFile(t, []byte(content))
|
|
s, opts := RunServerWithConfig(conf)
|
|
defer s.Shutdown()
|
|
|
|
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse != nil {
|
|
return fmt.Errorf("unexpected OCSP Staple!")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer nc.Close()
|
|
sub, err := nc.SubscribeSync("foo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Publish("foo", []byte("hello world"))
|
|
nc.Flush()
|
|
|
|
_, err = sub.NextMsg(1 * time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Close()
|
|
|
|
// Change the contents with another that has OCSP Stapling enabled.
|
|
content = `
|
|
port: -1
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-01-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
`
|
|
if err := os.WriteFile(conf, []byte(content), 0666); err != nil {
|
|
t.Fatalf("Error writing config: %v", err)
|
|
}
|
|
if err := s.Reload(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// The new certificate does not have must staple so they will be missing.
|
|
time.Sleep(2 * time.Second)
|
|
|
|
nc, err = nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
resp, err := getOCSPStatus(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if resp.Status != ocsp.Good {
|
|
t.Errorf("Expected valid OCSP staple status")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Close()
|
|
}
|
|
|
|
func TestOCSPCluster(t *testing.T) {
|
|
const (
|
|
caCert = "configs/certs/ocsp/ca-cert.pem"
|
|
caKey = "configs/certs/ocsp/ca-key.pem"
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
ocspr := newOCSPResponder(t, caCert, caKey)
|
|
defer ocspr.Shutdown(ctx)
|
|
addr := fmt.Sprintf("http://%s", ocspr.Addr)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-01-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-02-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-03-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-04-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-05-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-06-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-07-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-08-cert.pem", ocsp.Good)
|
|
|
|
// Store Dirs
|
|
storeDirA := t.TempDir()
|
|
storeDirB := t.TempDir()
|
|
storeDirC := t.TempDir()
|
|
|
|
// Seed server configuration
|
|
srvConfA := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "AAA"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-01-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
cluster {
|
|
name: AB
|
|
host: "127.0.0.1"
|
|
advertise: 127.0.0.1
|
|
port: -1
|
|
pool_size: -1
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-02-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-02-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
}
|
|
`
|
|
srvConfA = fmt.Sprintf(srvConfA, storeDirA)
|
|
sconfA := createConfFile(t, []byte(srvConfA))
|
|
srvA, optsA := RunServerWithConfig(sconfA)
|
|
defer srvA.Shutdown()
|
|
|
|
// The rest
|
|
srvConfB := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "BBB"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-03-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-03-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
cluster {
|
|
name: AB
|
|
host: "127.0.0.1"
|
|
advertise: 127.0.0.1
|
|
port: -1
|
|
pool_size: -1
|
|
|
|
routes: [ nats://127.0.0.1:%d ]
|
|
connect_retries: 30
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-04-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-04-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
}
|
|
`
|
|
srvConfB = fmt.Sprintf(srvConfB, storeDirB, optsA.Cluster.Port)
|
|
conf := createConfFile(t, []byte(srvConfB))
|
|
srvB, optsB := RunServerWithConfig(conf)
|
|
defer srvB.Shutdown()
|
|
|
|
// Client connects to server A.
|
|
cA, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", optsA.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return fmt.Errorf("missing OCSP Staple from server")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cA.Close()
|
|
checkClusterFormed(t, srvA, srvB)
|
|
|
|
// Revoke the seed server cluster certificate, following servers will not be able to verify connection.
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-02-cert.pem", ocsp.Revoked)
|
|
|
|
// Original set of servers still can communicate to each other, even though the cert has been revoked.
|
|
// NOTE: Should we unplug from the cluster in case our server is revoke and OCSP policy is always or must?
|
|
checkClusterFormed(t, srvA, srvB)
|
|
|
|
// Wait for seed server to notice that its certificate has been revoked,
|
|
// so that new routes can't connect to it.
|
|
time.Sleep(6 * time.Second)
|
|
|
|
// Start another server against the seed server that has an invalid OCSP Staple
|
|
srvConfC := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "CCC"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-05-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-05-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
cluster {
|
|
name: AB
|
|
host: "127.0.0.1"
|
|
advertise: 127.0.0.1
|
|
port: -1
|
|
pool_size: -1
|
|
|
|
routes: [ nats://127.0.0.1:%d ]
|
|
connect_retries: 30
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-06-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-06-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
}
|
|
`
|
|
srvConfC = fmt.Sprintf(srvConfC, storeDirC, optsA.Cluster.Port)
|
|
conf = createConfFile(t, []byte(srvConfC))
|
|
srvC, optsC := RunServerWithConfig(conf)
|
|
defer srvC.Shutdown()
|
|
|
|
cB, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", optsB.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return fmt.Errorf("missing OCSP Staple from server")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cB.Close()
|
|
cC, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", optsC.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return fmt.Errorf("missing OCSP Staple from server")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cC.Close()
|
|
|
|
// There should be no connectivity between the clients due to the revoked staple.
|
|
_, err = cA.Subscribe("foo", func(m *nats.Msg) {
|
|
m.Respond(nil)
|
|
})
|
|
if err != nil {
|
|
t.Errorf("%v", err)
|
|
}
|
|
cA.Flush()
|
|
_, err = cB.Subscribe("bar", func(m *nats.Msg) {
|
|
m.Respond(nil)
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cB.Flush()
|
|
resp, err := cC.Request("foo", nil, 2*time.Second)
|
|
if err == nil {
|
|
t.Errorf("Unexpected success, response: %+v", resp)
|
|
}
|
|
resp, err = cC.Request("bar", nil, 2*time.Second)
|
|
if err == nil {
|
|
t.Errorf("Unexpected success, response: %+v", resp)
|
|
}
|
|
|
|
// Switch the certs from the seed server to new ones that are not revoked,
|
|
// this should restart OCSP Stapling for the cluster routes.
|
|
srvConfA = `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "AAA"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-07-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-07-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
cluster {
|
|
port: -1
|
|
pool_size: -1
|
|
compression: "disabled"
|
|
name: AB
|
|
host: "127.0.0.1"
|
|
advertise: 127.0.0.1
|
|
connect_retries: 30
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-08-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-08-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
}
|
|
`
|
|
srvConfA = fmt.Sprintf(srvConfA, storeDirA)
|
|
if err := os.WriteFile(sconfA, []byte(srvConfA), 0666); err != nil {
|
|
t.Fatalf("Error writing config: %v", err)
|
|
}
|
|
if err := srvA.Reload(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Wait to get a new OCSP Staple.
|
|
time.Sleep(10 * time.Second)
|
|
checkClusterFormed(t, srvA, srvB, srvC)
|
|
|
|
// Now clients connect to C can communicate with B and A.
|
|
_, err = cC.Request("foo", nil, 2*time.Second)
|
|
if err != nil {
|
|
t.Errorf("%v", err)
|
|
}
|
|
_, err = cC.Request("bar", nil, 2*time.Second)
|
|
if err != nil {
|
|
t.Errorf("%v", err)
|
|
}
|
|
}
|
|
|
|
func TestOCSPLeaf(t *testing.T) {
|
|
const (
|
|
caCert = "configs/certs/ocsp/ca-cert.pem"
|
|
caKey = "configs/certs/ocsp/ca-key.pem"
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
ocspr := newOCSPResponder(t, caCert, caKey)
|
|
defer ocspr.Shutdown(ctx)
|
|
addr := fmt.Sprintf("http://%s", ocspr.Addr)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-01-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-02-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-03-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-04-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-05-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-06-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-07-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-08-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/client-cert.pem", ocsp.Good)
|
|
|
|
// Store Dirs
|
|
storeDirA := t.TempDir()
|
|
storeDirB := t.TempDir()
|
|
storeDirC := t.TempDir()
|
|
|
|
// LeafNode server configuration
|
|
srvConfA := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "AAA"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-01-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
|
|
leafnodes {
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
advertise: "127.0.0.1"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-02-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-02-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
# Leaf connection must present certs.
|
|
verify: true
|
|
}
|
|
}
|
|
`
|
|
srvConfA = fmt.Sprintf(srvConfA, storeDirA)
|
|
sconfA := createConfFile(t, []byte(srvConfA))
|
|
srvA, optsA := RunServerWithConfig(sconfA)
|
|
defer srvA.Shutdown()
|
|
|
|
// LeafNode that has the original as a remote and running
|
|
// without OCSP Stapling for the leaf remote.
|
|
srvConfB := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "BBB"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-03-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-03-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
|
|
leafnodes {
|
|
remotes: [ {
|
|
url: "tls://127.0.0.1:%d"
|
|
tls {
|
|
# Cert without OCSP Stapling enabled is able to connect.
|
|
cert_file: "configs/certs/ocsp/client-cert.pem"
|
|
key_file: "configs/certs/ocsp/client-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
} ]
|
|
}
|
|
`
|
|
srvConfB = fmt.Sprintf(srvConfB, storeDirB, optsA.LeafNode.Port)
|
|
conf := createConfFile(t, []byte(srvConfB))
|
|
srvB, optsB := RunServerWithConfig(conf)
|
|
defer srvB.Shutdown()
|
|
|
|
// Client connects to server A.
|
|
cA, err := nats.Connect(fmt.Sprintf("tls://127.0.0.1:%d", optsA.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return fmt.Errorf("missing OCSP Staple from server")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cA.Close()
|
|
checkLeafNodeConnected(t, srvA)
|
|
|
|
// Revoke the seed server cluster certificate, following servers will not be able to verify connection.
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-02-cert.pem", ocsp.Revoked)
|
|
|
|
// Original set of servers still can communicate to each other via leafnode, even though the staple
|
|
// for the leaf server has been revoked.
|
|
checkLeafNodeConnected(t, srvA)
|
|
|
|
// Wait for seed server to notice that its certificate has been revoked.
|
|
time.Sleep(6 * time.Second)
|
|
|
|
// Start another server against the seed server that has an revoked OCSP Staple.
|
|
srvConfC := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "CCC"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-05-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-05-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
leafnodes {
|
|
remotes: [ {
|
|
url: "tls://127.0.0.1:%d"
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-06-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-06-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
} ]
|
|
}
|
|
`
|
|
srvConfC = fmt.Sprintf(srvConfC, storeDirC, optsA.LeafNode.Port)
|
|
conf = createConfFile(t, []byte(srvConfC))
|
|
srvC, optsC := RunServerWithConfig(conf)
|
|
defer srvC.Shutdown()
|
|
|
|
cB, err := nats.Connect(fmt.Sprintf("tls://127.0.0.1:%d", optsB.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return fmt.Errorf("missing OCSP Staple from server")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cB.Close()
|
|
cC, err := nats.Connect(fmt.Sprintf("tls://127.0.0.1:%d", optsC.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return fmt.Errorf("missing OCSP Staple from server")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cC.Close()
|
|
|
|
// There should be connectivity between the clients even if there is a revoked staple
|
|
// from a leafnode connection.
|
|
_, err = cA.Subscribe("foo", func(m *nats.Msg) {
|
|
m.Respond(nil)
|
|
})
|
|
if err != nil {
|
|
t.Errorf("%v", err)
|
|
}
|
|
cA.Flush()
|
|
_, err = cB.Subscribe("bar", func(m *nats.Msg) {
|
|
m.Respond(nil)
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cB.Flush()
|
|
_, err = cC.Request("foo", nil, 2*time.Second)
|
|
if err != nil {
|
|
t.Errorf("Expected success, got: %+v", err)
|
|
}
|
|
_, err = cC.Request("bar", nil, 2*time.Second)
|
|
if err != nil {
|
|
t.Errorf("Expected success, got: %+v", err)
|
|
}
|
|
|
|
// Switch the certs from the leafnode server to new ones that are not revoked,
|
|
// this should restart OCSP Stapling for the leafnode server.
|
|
srvConfA = `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "AAA"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-07-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-07-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
leafnodes {
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
advertise: "127.0.0.1"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-08-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-08-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
verify: true
|
|
}
|
|
}
|
|
`
|
|
srvConfA = fmt.Sprintf(srvConfA, storeDirA)
|
|
if err := os.WriteFile(sconfA, []byte(srvConfA), 0666); err != nil {
|
|
t.Fatalf("Error writing config: %v", err)
|
|
}
|
|
if err := srvA.Reload(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
time.Sleep(4 * time.Second)
|
|
|
|
// A <-> A
|
|
_, err = cA.Request("foo", nil, 2*time.Second)
|
|
if err != nil {
|
|
t.Errorf("%v", err)
|
|
}
|
|
|
|
// B <-> A
|
|
_, err = cB.Request("foo", nil, 2*time.Second)
|
|
if err != nil {
|
|
t.Errorf("%v", err)
|
|
}
|
|
|
|
// C <-> A
|
|
_, err = cC.Request("foo", nil, 2*time.Second)
|
|
if err != nil {
|
|
t.Errorf("%v", err)
|
|
}
|
|
// C <-> B via leafnode A
|
|
_, err = cC.Request("bar", nil, 2*time.Second)
|
|
if err != nil {
|
|
t.Errorf("%v", err)
|
|
}
|
|
}
|
|
|
|
func TestOCSPLeafNoVerify(t *testing.T) {
|
|
const (
|
|
caCert = "configs/certs/ocsp/ca-cert.pem"
|
|
caKey = "configs/certs/ocsp/ca-key.pem"
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
ocspr := newOCSPResponder(t, caCert, caKey)
|
|
defer ocspr.Shutdown(ctx)
|
|
addr := fmt.Sprintf("http://%s", ocspr.Addr)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-01-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-02-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-03-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-04-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-05-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-06-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-07-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-08-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/client-cert.pem", ocsp.Good)
|
|
|
|
// Store Dirs
|
|
storeDirA := t.TempDir()
|
|
storeDirB := t.TempDir()
|
|
storeDirC := t.TempDir()
|
|
|
|
// LeafNode server configuration
|
|
srvConfA := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "AAA"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-01-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
|
|
leafnodes {
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
advertise: "127.0.0.1"
|
|
# for this test, explicitly disable compression because we do it
|
|
# in RunServer but here we do a config reload...
|
|
compression: off
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-02-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-02-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
# Leaf server does not require certs for clients.
|
|
verify: false
|
|
}
|
|
}
|
|
`
|
|
srvConfA = fmt.Sprintf(srvConfA, storeDirA)
|
|
sconfA := createConfFile(t, []byte(srvConfA))
|
|
srvA, optsA := RunServerWithConfig(sconfA)
|
|
defer srvA.Shutdown()
|
|
|
|
// LeafNode remote that will connect to A and will not present certs.
|
|
srvConfB := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "BBB"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-03-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-03-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
|
|
leafnodes {
|
|
remotes: [ {
|
|
url: "tls://127.0.0.1:%d"
|
|
tls {
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
} ]
|
|
}
|
|
`
|
|
srvConfB = fmt.Sprintf(srvConfB, storeDirB, optsA.LeafNode.Port)
|
|
conf := createConfFile(t, []byte(srvConfB))
|
|
srvB, optsB := RunServerWithConfig(conf)
|
|
defer srvB.Shutdown()
|
|
|
|
// Client connects to server A.
|
|
cA, err := nats.Connect(fmt.Sprintf("tls://127.0.0.1:%d", optsA.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return fmt.Errorf("missing OCSP Staple from server")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cA.Close()
|
|
checkLeafNodeConnected(t, srvA)
|
|
|
|
// Revoke the seed server cluster certificate, following servers will not be able to verify connection.
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-02-cert.pem", ocsp.Revoked)
|
|
|
|
// Original set of servers still can communicate to each other, even though the cert has been revoked.
|
|
checkLeafNodeConnected(t, srvA)
|
|
|
|
// Wait for seed server to notice that its certificate has been revoked.
|
|
time.Sleep(6 * time.Second)
|
|
|
|
// Start another server against the seed server that has an revoked OCSP Staple.
|
|
srvConfC := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "CCC"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-05-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-05-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
leafnodes {
|
|
remotes: [ {
|
|
url: "tls://127.0.0.1:%d"
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-06-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-06-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
timeout: 5
|
|
}
|
|
} ]
|
|
}
|
|
`
|
|
srvConfC = fmt.Sprintf(srvConfC, storeDirC, optsA.LeafNode.Port)
|
|
conf = createConfFile(t, []byte(srvConfC))
|
|
srvC, optsC := RunServerWithConfig(conf)
|
|
defer srvC.Shutdown()
|
|
|
|
cB, err := nats.Connect(fmt.Sprintf("tls://127.0.0.1:%d", optsB.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return fmt.Errorf("missing OCSP Staple from server")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cB.Close()
|
|
cC, err := nats.Connect(fmt.Sprintf("tls://127.0.0.1:%d", optsC.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return fmt.Errorf("missing OCSP Staple from server")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cC.Close()
|
|
|
|
// There should be connectivity between the clients even if there is a revoked staple
|
|
// from a leafnode connection.
|
|
_, err = cA.Subscribe("foo", func(m *nats.Msg) {
|
|
m.Respond(nil)
|
|
})
|
|
if err != nil {
|
|
t.Errorf("%v", err)
|
|
}
|
|
cA.Flush()
|
|
_, err = cB.Subscribe("bar", func(m *nats.Msg) {
|
|
m.Respond(nil)
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cB.Flush()
|
|
_, err = cC.Request("foo", nil, 2*time.Second)
|
|
if err != nil {
|
|
t.Errorf("Expected success, got: %+v", err)
|
|
}
|
|
_, err = cC.Request("bar", nil, 2*time.Second)
|
|
if err != nil {
|
|
t.Errorf("Expected success, got: %+v", err)
|
|
}
|
|
|
|
// Switch the certs from the leafnode server to new ones that are not revoked,
|
|
// this should restart OCSP Stapling for the leafnode server.
|
|
srvConfA = `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "AAA"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-07-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-07-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
leafnodes {
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
advertise: "127.0.0.1"
|
|
compression: off
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-08-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-08-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
verify: true
|
|
}
|
|
}
|
|
`
|
|
srvConfA = fmt.Sprintf(srvConfA, storeDirA)
|
|
if err := os.WriteFile(sconfA, []byte(srvConfA), 0666); err != nil {
|
|
t.Fatalf("Error writing config: %v", err)
|
|
}
|
|
if err := srvA.Reload(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
time.Sleep(4 * time.Second)
|
|
|
|
// A <-> A
|
|
_, err = cA.Request("foo", nil, 2*time.Second)
|
|
if err != nil {
|
|
t.Errorf("%v", err)
|
|
}
|
|
|
|
// B <-> A
|
|
_, err = cB.Request("foo", nil, 2*time.Second)
|
|
if err != nil {
|
|
t.Errorf("%v", err)
|
|
}
|
|
|
|
// C <-> A
|
|
_, err = cC.Request("foo", nil, 2*time.Second)
|
|
if err != nil {
|
|
t.Errorf("%v", err)
|
|
}
|
|
// C <-> B via leafnode A
|
|
_, err = cC.Request("bar", nil, 2*time.Second)
|
|
if err != nil {
|
|
t.Errorf("%v", err)
|
|
}
|
|
}
|
|
|
|
func TestOCSPLeafVerifyLeafRemote(t *testing.T) {
|
|
const (
|
|
caCert = "configs/certs/ocsp/ca-cert.pem"
|
|
caKey = "configs/certs/ocsp/ca-key.pem"
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
ocspr := newOCSPResponder(t, caCert, caKey)
|
|
defer ocspr.Shutdown(ctx)
|
|
addr := fmt.Sprintf("http://%s", ocspr.Addr)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-01-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-02-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-03-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-04-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-05-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-06-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-07-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-08-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/client-cert.pem", ocsp.Good)
|
|
|
|
// Store Dirs
|
|
storeDirA := t.TempDir()
|
|
storeDirB := t.TempDir()
|
|
|
|
// LeafNode server configuration
|
|
srvConfA := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "AAA"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-01-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
|
|
leafnodes {
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
advertise: "127.0.0.1"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-02-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-02-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
verify: true
|
|
}
|
|
}
|
|
`
|
|
srvConfA = fmt.Sprintf(srvConfA, storeDirA)
|
|
sconfA := createConfFile(t, []byte(srvConfA))
|
|
srvA, optsA := RunServerWithConfig(sconfA)
|
|
defer srvA.Shutdown()
|
|
|
|
// LeafNode remote that will connect to A and will not present certs.
|
|
srvConfB := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "BBB"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-03-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-03-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
|
|
leafnodes {
|
|
remotes: [ {
|
|
url: "tls://127.0.0.1:%d"
|
|
tls {
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
} ]
|
|
}
|
|
`
|
|
srvConfB = fmt.Sprintf(srvConfB, storeDirB, optsA.LeafNode.Port)
|
|
conf := createConfFile(t, []byte(srvConfB))
|
|
srvB, _ := RunServerWithConfig(conf)
|
|
defer srvB.Shutdown()
|
|
|
|
// Client connects to server A.
|
|
cA, err := nats.Connect(fmt.Sprintf("tls://127.0.0.1:%d", optsA.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return fmt.Errorf("missing OCSP Staple from server")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cA.Close()
|
|
|
|
// Should not have been able to connect.
|
|
checkLeafNodeConnections(t, srvA, 0)
|
|
}
|
|
|
|
func TestOCSPLeafVerifyAndMapLeafRemote(t *testing.T) {
|
|
const (
|
|
caCert = "configs/certs/ocsp/ca-cert.pem"
|
|
caKey = "configs/certs/ocsp/ca-key.pem"
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
ocspr := newOCSPResponder(t, caCert, caKey)
|
|
defer ocspr.Shutdown(ctx)
|
|
addr := fmt.Sprintf("http://%s", ocspr.Addr)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-01-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-02-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-03-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-04-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-05-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-06-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-07-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-08-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/client-cert.pem", ocsp.Good)
|
|
|
|
// Store Dirs
|
|
storeDirA := t.TempDir()
|
|
storeDirB := t.TempDir()
|
|
|
|
// LeafNode server configuration
|
|
srvConfA := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "AAA"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-01-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
verify_and_map: true
|
|
}
|
|
store_dir: '%s'
|
|
|
|
leafnodes {
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
advertise: "127.0.0.1"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-02-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-02-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
verify_and_map: true
|
|
}
|
|
}
|
|
|
|
accounts: {
|
|
leaf: {
|
|
users: [ {user: "C=US, ST=CA, L=San Francisco, O=Synadia, OU=nats.io, CN=localhost server-status-request-url-04"} ]
|
|
}
|
|
client: {
|
|
users: [ {user: "C=US, ST=CA, L=San Francisco, O=Synadia, OU=nats.io, CN=localhost client"} ]
|
|
}
|
|
}
|
|
|
|
`
|
|
srvConfA = fmt.Sprintf(srvConfA, storeDirA)
|
|
sconfA := createConfFile(t, []byte(srvConfA))
|
|
srvA, optsA := RunServerWithConfig(sconfA)
|
|
defer srvA.Shutdown()
|
|
|
|
// LeafNode remote that will connect to A and will not present certs.
|
|
srvConfB := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "BBB"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-03-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-03-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
|
|
leafnodes {
|
|
remotes: [ {
|
|
url: "tls://127.0.0.1:%d"
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-04-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-04-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
} ]
|
|
}
|
|
`
|
|
srvConfB = fmt.Sprintf(srvConfB, storeDirB, optsA.LeafNode.Port)
|
|
conf := createConfFile(t, []byte(srvConfB))
|
|
srvB, _ := RunServerWithConfig(conf)
|
|
defer srvB.Shutdown()
|
|
|
|
// Client connects to server A.
|
|
cA, err := nats.Connect(fmt.Sprintf("tls://127.0.0.1:%d", optsA.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return fmt.Errorf("missing OCSP Staple from server")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.ClientCert("./configs/certs/ocsp/client-cert.pem", "./configs/certs/ocsp/client-key.pem"),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cA.Close()
|
|
checkLeafNodeConnections(t, srvA, 1)
|
|
}
|
|
|
|
func TestOCSPGateway(t *testing.T) {
|
|
const (
|
|
caCert = "configs/certs/ocsp/ca-cert.pem"
|
|
caKey = "configs/certs/ocsp/ca-key.pem"
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
ocspr := newOCSPResponder(t, caCert, caKey)
|
|
defer ocspr.Shutdown(ctx)
|
|
addr := fmt.Sprintf("http://%s", ocspr.Addr)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-01-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-02-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-03-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-04-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-05-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-06-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-07-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-08-cert.pem", ocsp.Good)
|
|
|
|
// Store Dirs
|
|
storeDirA := t.TempDir()
|
|
storeDirB := t.TempDir()
|
|
storeDirC := t.TempDir()
|
|
|
|
// Gateway server configuration
|
|
srvConfA := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "AAA"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-01-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
gateway {
|
|
name: A
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
advertise: "127.0.0.1"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-02-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-02-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
}
|
|
`
|
|
srvConfA = fmt.Sprintf(srvConfA, storeDirA)
|
|
sconfA := createConfFile(t, []byte(srvConfA))
|
|
srvA, optsA := RunServerWithConfig(sconfA)
|
|
defer srvA.Shutdown()
|
|
|
|
// LeafNode that has the original as a remote.
|
|
srvConfB := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "BBB"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-03-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-03-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
gateway {
|
|
name: B
|
|
host: "127.0.0.1"
|
|
advertise: "127.0.0.1"
|
|
port: -1
|
|
gateways: [{
|
|
name: "A"
|
|
url: "nats://127.0.0.1:%d"
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-04-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-04-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
}]
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-04-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-04-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
}
|
|
`
|
|
srvConfB = fmt.Sprintf(srvConfB, storeDirB, optsA.Gateway.Port)
|
|
conf := createConfFile(t, []byte(srvConfB))
|
|
srvB, optsB := RunServerWithConfig(conf)
|
|
defer srvB.Shutdown()
|
|
|
|
// Client connects to server A.
|
|
cA, err := nats.Connect(fmt.Sprintf("tls://127.0.0.1:%d", optsA.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return fmt.Errorf("missing OCSP Staple from server")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cA.Close()
|
|
waitForOutboundGateways(t, srvB, 1, 5*time.Second)
|
|
|
|
// Revoke the seed server cluster certificate, following servers will not be able to verify connection.
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-02-cert.pem", ocsp.Revoked)
|
|
|
|
// Original set of servers still can communicate to each other, even though the cert has been revoked.
|
|
waitForOutboundGateways(t, srvA, 1, 5*time.Second)
|
|
waitForOutboundGateways(t, srvB, 1, 5*time.Second)
|
|
|
|
// Wait for gateway A to notice that its certificate has been revoked,
|
|
// so that new gateways can't connect to it.
|
|
time.Sleep(6 * time.Second)
|
|
|
|
// Start another server against the seed server that has an invalid OCSP Staple
|
|
srvConfC := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "CCC"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-05-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-05-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
gateway {
|
|
name: C
|
|
host: "127.0.0.1"
|
|
advertise: "127.0.0.1"
|
|
port: -1
|
|
gateways: [{name: "A", url: "nats://127.0.0.1:%d" }]
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-06-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-06-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
}
|
|
`
|
|
srvConfC = fmt.Sprintf(srvConfC, storeDirC, optsA.Gateway.Port)
|
|
conf = createConfFile(t, []byte(srvConfC))
|
|
srvC, optsC := RunServerWithConfig(conf)
|
|
defer srvC.Shutdown()
|
|
|
|
cB, err := nats.Connect(fmt.Sprintf("tls://127.0.0.1:%d", optsB.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return fmt.Errorf("missing OCSP Staple from server")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cB.Close()
|
|
cC, err := nats.Connect(fmt.Sprintf("tls://127.0.0.1:%d", optsC.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return fmt.Errorf("missing OCSP Staple from server")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cC.Close()
|
|
|
|
// There should be no connectivity between the clients due to the revoked staple.
|
|
_, err = cA.Subscribe("foo", func(m *nats.Msg) {
|
|
m.Respond(nil)
|
|
})
|
|
if err != nil {
|
|
t.Errorf("%v", err)
|
|
}
|
|
cA.Flush()
|
|
_, err = cB.Subscribe("bar", func(m *nats.Msg) {
|
|
m.Respond(nil)
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cB.Flush()
|
|
|
|
// Gateway C was not able to mesh with Gateway A because of the revoked OCSP staple
|
|
// so these requests to A and B should fail.
|
|
resp, err := cC.Request("foo", nil, 2*time.Second)
|
|
if err == nil {
|
|
t.Errorf("Unexpected success, response: %+v", resp)
|
|
}
|
|
// Make request to B
|
|
resp, err = cC.Request("bar", nil, 2*time.Second)
|
|
if err == nil {
|
|
t.Errorf("Unexpected success, response: %+v", resp)
|
|
}
|
|
|
|
// Switch the certs from the seed server to new ones that are not revoked,
|
|
// this should restart OCSP Stapling for the cluster routes.
|
|
srvConfA = `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "AAA"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-07-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-07-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
gateway {
|
|
name: A
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
advertise: "127.0.0.1"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-08-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-08-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
}
|
|
`
|
|
|
|
srvConfA = fmt.Sprintf(srvConfA, storeDirA)
|
|
if err := os.WriteFile(sconfA, []byte(srvConfA), 0666); err != nil {
|
|
t.Fatalf("Error writing config: %v", err)
|
|
}
|
|
if err := srvA.Reload(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
time.Sleep(4 * time.Second)
|
|
waitForOutboundGateways(t, srvA, 2, 5*time.Second)
|
|
waitForOutboundGateways(t, srvB, 2, 5*time.Second)
|
|
waitForOutboundGateways(t, srvC, 2, 5*time.Second)
|
|
|
|
// Now clients connect to C can communicate with B and A.
|
|
_, err = cC.Request("foo", nil, 2*time.Second)
|
|
if err != nil {
|
|
t.Errorf("%v", err)
|
|
}
|
|
_, err = cC.Request("bar", nil, 2*time.Second)
|
|
if err != nil {
|
|
t.Errorf("%v", err)
|
|
}
|
|
}
|
|
|
|
func TestOCSPGatewayIntermediate(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
intermediateCA1Responder := newOCSPResponderIntermediateCA1(t)
|
|
intermediateCA1ResponderURL := fmt.Sprintf("http://%s", intermediateCA1Responder.Addr)
|
|
defer intermediateCA1Responder.Shutdown(ctx)
|
|
setOCSPStatus(t, intermediateCA1ResponderURL, "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, intermediateCA1ResponderURL, "configs/certs/ocsp_peer/mini-ca/server1/TestServer2_cert.pem", ocsp.Good)
|
|
|
|
// Gateway server configuration
|
|
srvConfA := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "AAA"
|
|
|
|
ocsp: {
|
|
mode: always
|
|
url: %s
|
|
}
|
|
|
|
gateway {
|
|
name: A
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
advertise: "127.0.0.1"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
|
|
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
|
|
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
|
|
timeout: 5
|
|
}
|
|
}
|
|
`
|
|
srvConfA = fmt.Sprintf(srvConfA, intermediateCA1ResponderURL)
|
|
sconfA := createConfFile(t, []byte(srvConfA))
|
|
srvA, optsA := RunServerWithConfig(sconfA)
|
|
defer srvA.Shutdown()
|
|
|
|
srvConfB := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "BBB"
|
|
|
|
ocsp: {
|
|
mode: always
|
|
url: %s
|
|
}
|
|
|
|
gateway {
|
|
name: B
|
|
host: "127.0.0.1"
|
|
advertise: "127.0.0.1"
|
|
port: -1
|
|
gateways: [{
|
|
name: "A"
|
|
url: "nats://127.0.0.1:%d"
|
|
}]
|
|
tls {
|
|
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer2_bundle.pem"
|
|
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer2_keypair.pem"
|
|
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
|
|
timeout: 5
|
|
}
|
|
}
|
|
`
|
|
srvConfB = fmt.Sprintf(srvConfB, intermediateCA1ResponderURL, optsA.Gateway.Port)
|
|
conf := createConfFile(t, []byte(srvConfB))
|
|
srvB, optsB := RunServerWithConfig(conf)
|
|
defer srvB.Shutdown()
|
|
|
|
// Client connects to server A.
|
|
cA, err := nats.Connect(fmt.Sprintf("nats://127.0.0.1:%d", optsA.Port),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cA.Close()
|
|
waitForOutboundGateways(t, srvB, 1, 5*time.Second)
|
|
|
|
cB, err := nats.Connect(fmt.Sprintf("nats://127.0.0.1:%d", optsB.Port),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cB.Close()
|
|
}
|
|
|
|
func TestOCSPCustomConfig(t *testing.T) {
|
|
const (
|
|
caCert = "configs/certs/ocsp/ca-cert.pem"
|
|
caKey = "configs/certs/ocsp/ca-key.pem"
|
|
serverCert = "configs/certs/ocsp/server-cert.pem"
|
|
serverKey = "configs/certs/ocsp/server-key.pem"
|
|
)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
ocspr := newOCSPResponder(t, caCert, caKey)
|
|
ocspURL := fmt.Sprintf("http://%s", ocspr.Addr)
|
|
defer ocspr.Shutdown(ctx)
|
|
|
|
var (
|
|
errExpectedNoStaple = fmt.Errorf("expected no staple")
|
|
errMissingStaple = fmt.Errorf("missing OCSP Staple from server")
|
|
)
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
config string
|
|
opts []nats.Option
|
|
err error
|
|
rerr error
|
|
configure func()
|
|
}{
|
|
{
|
|
"OCSP Stapling in auto mode makes server fail to boot if status is revoked",
|
|
`
|
|
port: -1
|
|
|
|
ocsp {
|
|
mode: auto
|
|
}
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
`,
|
|
[]nats.Option{
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse != nil {
|
|
return errExpectedNoStaple
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.ClientCert("./configs/certs/ocsp/client-cert.pem", "./configs/certs/ocsp/client-key.pem"),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
},
|
|
nil,
|
|
nil,
|
|
func() { setOCSPStatus(t, ocspURL, serverCert, ocsp.Revoked) },
|
|
},
|
|
{
|
|
"OCSP Stapling must staple ignored if disabled with ocsp: false",
|
|
`
|
|
port: -1
|
|
|
|
ocsp: false
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-01-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
`,
|
|
[]nats.Option{
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse != nil {
|
|
return errExpectedNoStaple
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.ClientCert("./configs/certs/ocsp/client-cert.pem", "./configs/certs/ocsp/client-key.pem"),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
},
|
|
nil,
|
|
nil,
|
|
func() {
|
|
setOCSPStatus(t, ocspURL, "configs/certs/ocsp/server-status-request-url-01-cert.pem", ocsp.Good)
|
|
},
|
|
},
|
|
{
|
|
"OCSP Stapling must staple ignored if disabled with ocsp mode never",
|
|
`
|
|
port: -1
|
|
|
|
ocsp: { mode: never }
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-01-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
`,
|
|
[]nats.Option{
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse != nil {
|
|
return errExpectedNoStaple
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.ClientCert("./configs/certs/ocsp/client-cert.pem", "./configs/certs/ocsp/client-key.pem"),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
},
|
|
nil,
|
|
nil,
|
|
func() {
|
|
setOCSPStatus(t, ocspURL, "configs/certs/ocsp/server-status-request-url-01-cert.pem", ocsp.Good)
|
|
},
|
|
},
|
|
{
|
|
"OCSP Stapling in always mode fetches a staple even if cert does not have one",
|
|
`
|
|
port: -1
|
|
|
|
ocsp {
|
|
mode: always
|
|
url: "http://127.0.0.1:8888"
|
|
}
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
`,
|
|
[]nats.Option{
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return errMissingStaple
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.ClientCert("./configs/certs/ocsp/client-cert.pem", "./configs/certs/ocsp/client-key.pem"),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
},
|
|
nil,
|
|
nil,
|
|
func() { setOCSPStatus(t, ocspURL, serverCert, ocsp.Good) },
|
|
},
|
|
{
|
|
"OCSP Stapling in must staple mode does not fetch staple if there is no must staple flag",
|
|
`
|
|
port: -1
|
|
|
|
ocsp {
|
|
mode: must
|
|
url: "http://127.0.0.1:8888"
|
|
}
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
`,
|
|
[]nats.Option{
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse != nil {
|
|
return errExpectedNoStaple
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.ClientCert("./configs/certs/ocsp/client-cert.pem", "./configs/certs/ocsp/client-key.pem"),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
},
|
|
nil,
|
|
nil,
|
|
func() { setOCSPStatus(t, ocspURL, serverCert, ocsp.Good) },
|
|
},
|
|
{
|
|
"OCSP Stapling in must staple mode fetches staple if there is a must staple flag",
|
|
`
|
|
port: -1
|
|
|
|
ocsp {
|
|
mode: must
|
|
url: "http://127.0.0.1:8888"
|
|
}
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-01-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
`,
|
|
[]nats.Option{
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return errMissingStaple
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.ClientCert("./configs/certs/ocsp/client-cert.pem", "./configs/certs/ocsp/client-key.pem"),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
},
|
|
nil,
|
|
nil,
|
|
func() {
|
|
setOCSPStatus(t, ocspURL, "configs/certs/ocsp/server-status-request-url-01-cert.pem", ocsp.Good)
|
|
},
|
|
},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
test.configure()
|
|
content := test.config
|
|
conf := createConfFile(t, []byte(content))
|
|
s, opts := RunServerWithConfig(conf)
|
|
defer s.Shutdown()
|
|
|
|
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts...)
|
|
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 TestOCSPCustomConfigReloadDisable(t *testing.T) {
|
|
const (
|
|
caCert = "configs/certs/ocsp/ca-cert.pem"
|
|
caKey = "configs/certs/ocsp/ca-key.pem"
|
|
serverCert = "configs/certs/ocsp/server-cert.pem"
|
|
updatedServerCert = "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
ocspr := newOCSPResponder(t, caCert, caKey)
|
|
defer ocspr.Shutdown(ctx)
|
|
addr := fmt.Sprintf("http://%s", ocspr.Addr)
|
|
setOCSPStatus(t, addr, serverCert, ocsp.Good)
|
|
setOCSPStatus(t, addr, updatedServerCert, ocsp.Good)
|
|
|
|
// Start with server without OCSP Stapling MustStaple
|
|
content := `
|
|
port: -1
|
|
|
|
ocsp: { mode: always, url: "http://127.0.0.1:8888" }
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
`
|
|
conf := createConfFile(t, []byte(content))
|
|
s, opts := RunServerWithConfig(conf)
|
|
defer s.Shutdown()
|
|
|
|
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return fmt.Errorf("missing OCSP Staple!")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer nc.Close()
|
|
sub, err := nc.SubscribeSync("foo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Publish("foo", []byte("hello world"))
|
|
nc.Flush()
|
|
|
|
_, err = sub.NextMsg(1 * time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Close()
|
|
|
|
// Change and disable OCSP Stapling.
|
|
content = `
|
|
port: -1
|
|
|
|
ocsp: { mode: never }
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-01-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
`
|
|
if err := os.WriteFile(conf, []byte(content), 0666); err != nil {
|
|
t.Fatalf("Error writing config: %v", err)
|
|
}
|
|
if err := s.Reload(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// The new certificate has must staple but OCSP Stapling is disabled.
|
|
time.Sleep(2 * time.Second)
|
|
|
|
nc, err = nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse != nil {
|
|
return fmt.Errorf("unexpected OCSP Staple!")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Close()
|
|
}
|
|
|
|
func TestOCSPCustomConfigReloadEnable(t *testing.T) {
|
|
const (
|
|
caCert = "configs/certs/ocsp/ca-cert.pem"
|
|
caKey = "configs/certs/ocsp/ca-key.pem"
|
|
serverCert = "configs/certs/ocsp/server-cert.pem"
|
|
updatedServerCert = "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
ocspr := newOCSPResponder(t, caCert, caKey)
|
|
defer ocspr.Shutdown(ctx)
|
|
addr := fmt.Sprintf("http://%s", ocspr.Addr)
|
|
setOCSPStatus(t, addr, serverCert, ocsp.Good)
|
|
setOCSPStatus(t, addr, updatedServerCert, ocsp.Good)
|
|
|
|
// Start with server without OCSP Stapling MustStaple
|
|
content := `
|
|
port: -1
|
|
|
|
ocsp: { mode: never, url: "http://127.0.0.1:8888" }
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-01-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
`
|
|
conf := createConfFile(t, []byte(content))
|
|
s, opts := RunServerWithConfig(conf)
|
|
defer s.Shutdown()
|
|
|
|
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse != nil {
|
|
return fmt.Errorf("unexpected OCSP Staple!")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer nc.Close()
|
|
sub, err := nc.SubscribeSync("foo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Publish("foo", []byte("hello world"))
|
|
nc.Flush()
|
|
|
|
_, err = sub.NextMsg(1 * time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Close()
|
|
|
|
// Change and disable OCSP Stapling.
|
|
content = `
|
|
port: -1
|
|
|
|
ocsp: { mode: always, url: "http://127.0.0.1:8888" }
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-01-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
`
|
|
if err := os.WriteFile(conf, []byte(content), 0666); err != nil {
|
|
t.Fatalf("Error writing config: %v", err)
|
|
}
|
|
if err := s.Reload(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
time.Sleep(2 * time.Second)
|
|
|
|
nc, err = nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return fmt.Errorf("missing OCSP Staple!")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nc.Close()
|
|
}
|
|
|
|
func newOCSPResponderCustomAddress(t *testing.T, issuerCertPEM, issuerKeyPEM string, addr string) *http.Server {
|
|
t.Helper()
|
|
return newOCSPResponderBase(t, issuerCertPEM, issuerCertPEM, issuerKeyPEM, false, addr, defaultResponseTTL)
|
|
}
|
|
|
|
func newOCSPResponder(t *testing.T, issuerCertPEM, issuerKeyPEM string) *http.Server {
|
|
t.Helper()
|
|
return newOCSPResponderBase(t, issuerCertPEM, issuerCertPEM, issuerKeyPEM, false, defaultAddress, defaultResponseTTL)
|
|
}
|
|
|
|
func newOCSPResponderDesignatedCustomAddress(t *testing.T, issuerCertPEM, respCertPEM, respKeyPEM string, addr string) *http.Server {
|
|
t.Helper()
|
|
return newOCSPResponderBase(t, issuerCertPEM, respCertPEM, respKeyPEM, true, addr, defaultResponseTTL)
|
|
}
|
|
|
|
func newOCSPResponderBase(t *testing.T, issuerCertPEM, respCertPEM, respKeyPEM string, embed bool, addr string, responseTTL time.Duration) *http.Server {
|
|
t.Helper()
|
|
var mu sync.Mutex
|
|
status := make(map[string]int)
|
|
|
|
issuerCert := parseCertPEM(t, issuerCertPEM)
|
|
respCert := parseCertPEM(t, respCertPEM)
|
|
respKey := parseKeyPEM(t, respKeyPEM)
|
|
|
|
mux := http.NewServeMux()
|
|
// The "/statuses/" endpoint is for directly setting a key-value pair in
|
|
// the CA's status database.
|
|
mux.HandleFunc("/statuses/", func(rw http.ResponseWriter, r *http.Request) {
|
|
defer r.Body.Close()
|
|
|
|
key := r.URL.Path[len("/statuses/"):]
|
|
switch r.Method {
|
|
case "GET":
|
|
mu.Lock()
|
|
n, ok := status[key]
|
|
if !ok {
|
|
n = ocsp.Unknown
|
|
}
|
|
mu.Unlock()
|
|
|
|
fmt.Fprintf(rw, "%s %d", key, n)
|
|
case "POST":
|
|
data, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
n, err := strconv.Atoi(string(data))
|
|
if err != nil {
|
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
mu.Lock()
|
|
status[key] = n
|
|
mu.Unlock()
|
|
|
|
fmt.Fprintf(rw, "%s %d", key, n)
|
|
default:
|
|
http.Error(rw, "Method Not Allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
})
|
|
// The "/" endpoint is for normal OCSP requests. This actually parses an
|
|
// OCSP status request and signs a response with a CA. Lightly based off:
|
|
// https://www.ietf.org/rfc/rfc2560.txt
|
|
mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "GET" {
|
|
http.Error(rw, "Method Not Allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
reqData, err := base64.StdEncoding.DecodeString(r.URL.Path[1:])
|
|
if err != nil {
|
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
ocspReq, err := ocsp.ParseRequest(reqData)
|
|
if err != nil {
|
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
mu.Lock()
|
|
n, ok := status[ocspReq.SerialNumber.String()]
|
|
if !ok {
|
|
n = ocsp.Unknown
|
|
}
|
|
mu.Unlock()
|
|
|
|
tmpl := ocsp.Response{
|
|
Status: n,
|
|
SerialNumber: ocspReq.SerialNumber,
|
|
ThisUpdate: time.Now(),
|
|
}
|
|
if responseTTL != 0 {
|
|
tmpl.NextUpdate = tmpl.ThisUpdate.Add(responseTTL)
|
|
}
|
|
if embed {
|
|
tmpl.Certificate = respCert
|
|
}
|
|
respData, err := ocsp.CreateResponse(issuerCert, respCert, tmpl, respKey)
|
|
if err != nil {
|
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
rw.Header().Set("Content-Type", "application/ocsp-response")
|
|
rw.Header().Set("Content-Length", fmt.Sprint(len(respData)))
|
|
|
|
fmt.Fprint(rw, string(respData))
|
|
})
|
|
|
|
srv := &http.Server{
|
|
Addr: addr,
|
|
Handler: mux,
|
|
}
|
|
go srv.ListenAndServe()
|
|
time.Sleep(1 * time.Second)
|
|
return srv
|
|
}
|
|
|
|
func setOCSPStatus(t *testing.T, ocspURL, certPEM string, status int) {
|
|
t.Helper()
|
|
|
|
cert := parseCertPEM(t, certPEM)
|
|
|
|
hc := &http.Client{Timeout: 10 * time.Second}
|
|
resp, err := hc.Post(
|
|
fmt.Sprintf("%s/statuses/%s", ocspURL, cert.SerialNumber),
|
|
"",
|
|
strings.NewReader(fmt.Sprint(status)),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
data, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
t.Fatalf("failed to read OCSP HTTP response body: %s", err)
|
|
}
|
|
|
|
if got, want := resp.Status, "200 OK"; got != want {
|
|
t.Error(strings.TrimSpace(string(data)))
|
|
t.Fatalf("unexpected OCSP HTTP set status, got %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func parseCertPEM(t *testing.T, certPEM string) *x509.Certificate {
|
|
t.Helper()
|
|
block := parsePEM(t, certPEM)
|
|
|
|
cert, err := x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
t.Fatalf("failed to parse cert '%s': %s", certPEM, err)
|
|
}
|
|
return cert
|
|
}
|
|
|
|
func parseKeyPEM(t *testing.T, keyPEM string) crypto.Signer {
|
|
t.Helper()
|
|
block := parsePEM(t, keyPEM)
|
|
|
|
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
|
if err != nil {
|
|
key, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
if err != nil {
|
|
t.Fatalf("failed to parse ikey %s: %s", keyPEM, err)
|
|
}
|
|
}
|
|
keyc := key.(crypto.Signer)
|
|
return keyc
|
|
}
|
|
|
|
func parsePEM(t *testing.T, pemPath string) *pem.Block {
|
|
t.Helper()
|
|
data, err := os.ReadFile(pemPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
block, _ := pem.Decode(data)
|
|
if block == nil {
|
|
t.Fatalf("failed to decode PEM %s", pemPath)
|
|
}
|
|
return block
|
|
}
|
|
|
|
func getOCSPStatus(s tls.ConnectionState) (*ocsp.Response, error) {
|
|
if len(s.VerifiedChains) == 0 {
|
|
return nil, fmt.Errorf("missing TLS verified chains")
|
|
}
|
|
chain := s.VerifiedChains[0]
|
|
|
|
if got, want := len(chain), 2; got < want {
|
|
return nil, fmt.Errorf("incomplete cert chain, got %d, want at least %d", got, want)
|
|
}
|
|
leaf, issuer := chain[0], chain[1]
|
|
|
|
resp, err := ocsp.ParseResponseForCert(s.OCSPResponse, leaf, issuer)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse OCSP response: %w", err)
|
|
}
|
|
if err := resp.CheckSignatureFrom(issuer); err != nil {
|
|
return resp, err
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
func TestOCSPTLSConfigNoLeafSet(t *testing.T) {
|
|
o := DefaultTestOptions
|
|
o.HTTPHost = "127.0.0.1"
|
|
o.HTTPSPort = -1
|
|
o.TLSConfig = &tls.Config{ServerName: "localhost"}
|
|
cert, err := tls.LoadX509KeyPair("configs/certs/server-cert.pem", "configs/certs/server-key.pem")
|
|
if err != nil {
|
|
t.Fatalf("Got error reading certificates: %s", err)
|
|
}
|
|
o.TLSConfig.Certificates = []tls.Certificate{cert}
|
|
s := RunServer(&o)
|
|
s.Shutdown()
|
|
}
|
|
|
|
func TestOCSPSuperCluster(t *testing.T) {
|
|
const (
|
|
caCert = "configs/certs/ocsp/ca-cert.pem"
|
|
caKey = "configs/certs/ocsp/ca-key.pem"
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
ocspr := newOCSPResponder(t, caCert, caKey)
|
|
defer ocspr.Shutdown(ctx)
|
|
addr := fmt.Sprintf("http://%s", ocspr.Addr)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-01-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-02-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-03-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-04-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-05-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-06-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-07-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-status-request-url-08-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/server-cert.pem", ocsp.Good)
|
|
|
|
// Store Dirs
|
|
storeDirA := t.TempDir()
|
|
storeDirB := t.TempDir()
|
|
storeDirC := t.TempDir()
|
|
storeDirD := t.TempDir()
|
|
|
|
// Gateway server configuration
|
|
srvConfA := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "A"
|
|
|
|
ocsp { mode: "always" }
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-01-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
|
|
cluster {
|
|
name: A
|
|
host: "127.0.0.1"
|
|
advertise: 127.0.0.1
|
|
port: -1
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-02-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-02-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
}
|
|
|
|
gateway {
|
|
name: A
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
advertise: "127.0.0.1"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-03-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-03-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
verify: true
|
|
}
|
|
}
|
|
`
|
|
srvConfA = fmt.Sprintf(srvConfA, storeDirA)
|
|
sconfA := createConfFile(t, []byte(srvConfA))
|
|
srvA, optsA := RunServerWithConfig(sconfA)
|
|
defer srvA.Shutdown()
|
|
|
|
// Server that has the original as a cluster.
|
|
srvConfB := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "B"
|
|
|
|
ocsp { mode: "always" }
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-01-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-01-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
|
|
cluster {
|
|
name: A
|
|
host: "127.0.0.1"
|
|
advertise: 127.0.0.1
|
|
port: -1
|
|
|
|
routes: [ nats://127.0.0.1:%d ]
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-02-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-02-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
}
|
|
|
|
gateway {
|
|
name: A
|
|
host: "127.0.0.1"
|
|
advertise: "127.0.0.1"
|
|
port: -1
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-03-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-03-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
verify: true
|
|
}
|
|
}
|
|
`
|
|
srvConfB = fmt.Sprintf(srvConfB, storeDirB, optsA.Cluster.Port)
|
|
conf := createConfFile(t, []byte(srvConfB))
|
|
srvB, optsB := RunServerWithConfig(conf)
|
|
defer srvB.Shutdown()
|
|
|
|
// Client connects to server A.
|
|
cA, err := nats.Connect(fmt.Sprintf("tls://127.0.0.1:%d", optsA.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return fmt.Errorf("missing OCSP Staple from server")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
|
|
}
|
|
defer cA.Close()
|
|
|
|
// Start another server that will make connect as a gateway to cluster A.
|
|
srvConfC := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "C"
|
|
|
|
ocsp { mode: "always" }
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-05-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-05-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
gateway {
|
|
name: C
|
|
host: "127.0.0.1"
|
|
advertise: "127.0.0.1"
|
|
port: -1
|
|
gateways: [{
|
|
name: "A",
|
|
urls: ["nats://127.0.0.1:%d"]
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-06-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-06-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
}]
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-06-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-06-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
verify: true
|
|
}
|
|
}
|
|
`
|
|
srvConfC = fmt.Sprintf(srvConfC, storeDirC, optsA.Gateway.Port)
|
|
conf = createConfFile(t, []byte(srvConfC))
|
|
srvC, optsC := RunServerWithConfig(conf)
|
|
defer srvC.Shutdown()
|
|
|
|
// Check that server is connected to any server from the other cluster.
|
|
checkClusterFormed(t, srvA, srvB)
|
|
waitForOutboundGateways(t, srvC, 1, 5*time.Second)
|
|
|
|
// Start one more server that will become another gateway.
|
|
srvConfD := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "D"
|
|
|
|
ocsp { mode: "auto", url: "%s" }
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-07-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-07-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
gateway {
|
|
name: D
|
|
host: "127.0.0.1"
|
|
advertise: "127.0.0.1"
|
|
port: -1
|
|
gateways: [{
|
|
name: "A",
|
|
urls: ["nats://127.0.0.1:%d"]
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-08-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-08-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
}},
|
|
{
|
|
name: "C",
|
|
urls: ["nats://127.0.0.1:%d"]
|
|
|
|
####################################################################
|
|
## TEST NOTE: This cert does not have an OCSP Staple intentionally##
|
|
####################################################################
|
|
tls {
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
cert_file: "configs/certs/ocsp/server-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-key.pem"
|
|
timeout: 5
|
|
}}
|
|
]
|
|
tls {
|
|
cert_file: "configs/certs/ocsp/server-status-request-url-08-cert.pem"
|
|
key_file: "configs/certs/ocsp/server-status-request-url-08-key.pem"
|
|
ca_file: "configs/certs/ocsp/ca-cert.pem"
|
|
timeout: 5
|
|
verify: true
|
|
}
|
|
}
|
|
`
|
|
srvConfD = fmt.Sprintf(srvConfD, addr, storeDirD, optsA.Gateway.Port, optsC.Gateway.Port)
|
|
conf = createConfFile(t, []byte(srvConfD))
|
|
srvD, _ := RunServerWithConfig(conf)
|
|
defer srvD.Shutdown()
|
|
|
|
// There should be a single gateway here because one of the gateway connections does not have a OCSP staple.
|
|
waitForOutboundGateways(t, srvD, 1, 10*time.Second)
|
|
|
|
// Connect to cluster A using server B.
|
|
cB, err := nats.Connect(fmt.Sprintf("tls://127.0.0.1:%d", optsB.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return fmt.Errorf("missing OCSP Staple from server")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cB.Close()
|
|
|
|
// Connects to cluster C using server C.
|
|
cC, err := nats.Connect(fmt.Sprintf("tls://127.0.0.1:%d", optsC.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return fmt.Errorf("missing OCSP Staple from server")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cC.Close()
|
|
|
|
_, err = cA.Subscribe("foo", func(m *nats.Msg) {
|
|
m.Respond([]byte("From Server A"))
|
|
})
|
|
if err != nil {
|
|
t.Errorf("%v", err)
|
|
}
|
|
cA.Flush()
|
|
|
|
_, err = cB.Subscribe("bar", func(m *nats.Msg) {
|
|
m.Respond([]byte("From Server B"))
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cB.Flush()
|
|
|
|
// Confirm that a message from server C can flow back to server A via gateway..
|
|
var (
|
|
resp *nats.Msg
|
|
lerr error
|
|
)
|
|
for i := 0; i < 10; i++ {
|
|
resp, lerr = cC.Request("foo", nil, 500*time.Millisecond)
|
|
if lerr != nil {
|
|
continue
|
|
}
|
|
got := string(resp.Data)
|
|
expected := "From Server A"
|
|
if got != expected {
|
|
t.Fatalf("Expected %v, got: %v", expected, got)
|
|
}
|
|
|
|
// Make request to B
|
|
resp, lerr = cC.Request("bar", nil, 500*time.Millisecond)
|
|
if lerr != nil {
|
|
continue
|
|
}
|
|
got = string(resp.Data)
|
|
expected = "From Server B"
|
|
if got != expected {
|
|
t.Errorf("Expected %v, got: %v", expected, got)
|
|
}
|
|
lerr = nil
|
|
break
|
|
}
|
|
if lerr != nil {
|
|
t.Errorf("Unexpected error: %v", lerr)
|
|
}
|
|
if n := srvD.NumOutboundGateways(); n > 1 {
|
|
t.Errorf("Expected single gateway, got: %v", n)
|
|
}
|
|
}
|
|
|
|
func TestOCSPLocalIssuerDetermination(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
intermediateCA1Responder := newOCSPResponderIntermediateCA1(t)
|
|
intermediateCA1ResponderURL := fmt.Sprintf("http://%s", intermediateCA1Responder.Addr)
|
|
defer intermediateCA1Responder.Shutdown(ctx)
|
|
|
|
// Test constants
|
|
ocspURL := intermediateCA1ResponderURL
|
|
clientTrustBundle := "configs/certs/ocsp_peer/mini-ca/misc/trust_config1_bundle.pem"
|
|
serverCert := "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_cert.pem"
|
|
|
|
var (
|
|
errMissingStaple = fmt.Errorf("missing OCSP Staple from server")
|
|
)
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
config string
|
|
opts []nats.Option
|
|
err error
|
|
rerr error
|
|
serverStart bool
|
|
configure func()
|
|
}{
|
|
{
|
|
"Correct issuer configured in cert bundle",
|
|
`
|
|
port: -1
|
|
|
|
ocsp {
|
|
mode: always
|
|
}
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
|
|
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
|
|
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
|
|
timeout: 5
|
|
}
|
|
`,
|
|
[]nats.Option{
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return errMissingStaple
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.ClientCert("./configs/certs/ocsp/client-cert.pem", "./configs/certs/ocsp/client-key.pem"),
|
|
nats.RootCAs(clientTrustBundle),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
},
|
|
nil,
|
|
nil,
|
|
true,
|
|
func() {
|
|
setOCSPStatus(t, ocspURL, serverCert, ocsp.Good)
|
|
},
|
|
},
|
|
{
|
|
"Wrong issuer configured in cert bundle, server no start",
|
|
`
|
|
port: -1
|
|
|
|
ocsp {
|
|
mode: always
|
|
}
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp_peer/mini-ca/misc/misconfig_TestServer1_bundle.pem"
|
|
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
|
|
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
|
|
timeout: 5
|
|
}
|
|
`,
|
|
[]nats.Option{
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return errMissingStaple
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.ClientCert("./configs/certs/ocsp/client-cert.pem", "./configs/certs/ocsp/client-key.pem"),
|
|
nats.RootCAs(clientTrustBundle),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
},
|
|
nil,
|
|
nil,
|
|
false,
|
|
func() {
|
|
setOCSPStatus(t, ocspURL, serverCert, ocsp.Good)
|
|
},
|
|
},
|
|
{
|
|
"Issuer configured in CA bundle only, configuration 1",
|
|
`
|
|
port: -1
|
|
|
|
ocsp {
|
|
mode: always
|
|
}
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_cert.pem"
|
|
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
|
|
ca_file: "configs/certs/ocsp_peer/mini-ca/misc/trust_config1_bundle.pem"
|
|
timeout: 5
|
|
}
|
|
`,
|
|
[]nats.Option{
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return errMissingStaple
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.ClientCert("./configs/certs/ocsp/client-cert.pem", "./configs/certs/ocsp/client-key.pem"),
|
|
nats.RootCAs(clientTrustBundle),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
},
|
|
nil,
|
|
nil,
|
|
true,
|
|
func() {
|
|
setOCSPStatus(t, ocspURL, serverCert, ocsp.Good)
|
|
},
|
|
},
|
|
{
|
|
"Issuer configured in CA bundle only, configuration 2",
|
|
`
|
|
port: -1
|
|
|
|
ocsp {
|
|
mode: always
|
|
}
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_cert.pem"
|
|
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
|
|
ca_file: "configs/certs/ocsp_peer/mini-ca/misc/trust_config2_bundle.pem"
|
|
timeout: 5
|
|
}
|
|
`,
|
|
[]nats.Option{
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return errMissingStaple
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.ClientCert("./configs/certs/ocsp/client-cert.pem", "./configs/certs/ocsp/client-key.pem"),
|
|
nats.RootCAs(clientTrustBundle),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
},
|
|
nil,
|
|
nil,
|
|
true,
|
|
func() {
|
|
setOCSPStatus(t, ocspURL, serverCert, ocsp.Good)
|
|
},
|
|
},
|
|
{
|
|
"Issuer configured in CA bundle only, configuration 3",
|
|
`
|
|
port: -1
|
|
|
|
ocsp {
|
|
mode: always
|
|
}
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_cert.pem"
|
|
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
|
|
ca_file: "configs/certs/ocsp_peer/mini-ca/misc/trust_config3_bundle.pem"
|
|
timeout: 5
|
|
}
|
|
`,
|
|
[]nats.Option{
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return errMissingStaple
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.ClientCert("./configs/certs/ocsp/client-cert.pem", "./configs/certs/ocsp/client-key.pem"),
|
|
nats.RootCAs(clientTrustBundle),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
},
|
|
nil,
|
|
nil,
|
|
true,
|
|
func() {
|
|
setOCSPStatus(t, ocspURL, serverCert, ocsp.Good)
|
|
},
|
|
},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
defer func() {
|
|
r := recover()
|
|
if r != nil && test.serverStart {
|
|
t.Fatalf("Expected server start, unexpected panic: %v", r)
|
|
}
|
|
if r == nil && !test.serverStart {
|
|
t.Fatalf("Expected server to not start and panic thrown")
|
|
}
|
|
}()
|
|
test.configure()
|
|
content := test.config
|
|
conf := createConfFile(t, []byte(content))
|
|
s, opts := RunServerWithConfig(conf)
|
|
// server may not start for some tests
|
|
if s != nil {
|
|
defer s.Shutdown()
|
|
}
|
|
|
|
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts...)
|
|
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 TestMixedCAOCSPSuperCluster(t *testing.T) {
|
|
const (
|
|
caCert = "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
|
|
caKey = "configs/certs/ocsp/ca-key.pem"
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
intermediateCA1Responder := newOCSPResponderIntermediateCA1(t)
|
|
intermediateCA1ResponderURL := fmt.Sprintf("http://%s", intermediateCA1Responder.Addr)
|
|
defer intermediateCA1Responder.Shutdown(ctx)
|
|
setOCSPStatus(t, intermediateCA1ResponderURL, "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_cert.pem", ocsp.Good)
|
|
|
|
intermediateCA2Responder := newOCSPResponderIntermediateCA2(t)
|
|
intermediateCA2ResponderURL := fmt.Sprintf("http://%s", intermediateCA2Responder.Addr)
|
|
defer intermediateCA2Responder.Shutdown(ctx)
|
|
setOCSPStatus(t, intermediateCA2ResponderURL, "configs/certs/ocsp_peer/mini-ca/server2/TestServer3_cert.pem", ocsp.Good)
|
|
|
|
// Store Dirs
|
|
storeDirA := t.TempDir()
|
|
storeDirB := t.TempDir()
|
|
storeDirC := t.TempDir()
|
|
|
|
// Gateway server configuration
|
|
srvConfA := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "A"
|
|
|
|
ocsp { mode: "always" }
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
|
|
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
|
|
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
|
|
cluster {
|
|
name: A
|
|
host: "127.0.0.1"
|
|
advertise: 127.0.0.1
|
|
port: -1
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
|
|
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
|
|
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
|
|
timeout: 5
|
|
}
|
|
}
|
|
|
|
gateway {
|
|
name: A
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
advertise: "127.0.0.1"
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
|
|
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
|
|
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
|
|
timeout: 5
|
|
verify: true
|
|
}
|
|
}
|
|
`
|
|
srvConfA = fmt.Sprintf(srvConfA, storeDirA)
|
|
sconfA := createConfFile(t, []byte(srvConfA))
|
|
srvA, optsA := RunServerWithConfig(sconfA)
|
|
defer srvA.Shutdown()
|
|
|
|
// Server that has the original as a cluster.
|
|
srvConfB := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "B"
|
|
|
|
ocsp { mode: "always" }
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
|
|
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
|
|
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
|
|
cluster {
|
|
name: A
|
|
host: "127.0.0.1"
|
|
advertise: 127.0.0.1
|
|
port: -1
|
|
|
|
routes: [ nats://127.0.0.1:%d ]
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
|
|
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
|
|
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
|
|
timeout: 5
|
|
}
|
|
}
|
|
|
|
gateway {
|
|
name: A
|
|
host: "127.0.0.1"
|
|
advertise: "127.0.0.1"
|
|
port: -1
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
|
|
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
|
|
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
|
|
timeout: 5
|
|
verify: true
|
|
}
|
|
}
|
|
`
|
|
srvConfB = fmt.Sprintf(srvConfB, storeDirB, optsA.Cluster.Port)
|
|
conf := createConfFile(t, []byte(srvConfB))
|
|
srvB, optsB := RunServerWithConfig(conf)
|
|
defer srvB.Shutdown()
|
|
|
|
// Client connects to server A.
|
|
cA, err := nats.Connect(fmt.Sprintf("tls://127.0.0.1:%d", optsA.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return fmt.Errorf("missing OCSP Staple from server")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
|
|
}
|
|
defer cA.Close()
|
|
|
|
// Start another server that will make connect as a gateway to cluster A but with different CA issuer.
|
|
srvConfC := `
|
|
host: "127.0.0.1"
|
|
port: -1
|
|
|
|
server_name: "C"
|
|
|
|
ocsp { mode: "always" }
|
|
|
|
tls {
|
|
cert_file: "configs/certs/ocsp_peer/mini-ca/server2/TestServer3_bundle.pem"
|
|
key_file: "configs/certs/ocsp_peer/mini-ca/server2/private/TestServer3_keypair.pem"
|
|
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
|
|
timeout: 5
|
|
}
|
|
store_dir: '%s'
|
|
gateway {
|
|
name: C
|
|
host: "127.0.0.1"
|
|
advertise: "127.0.0.1"
|
|
port: -1
|
|
gateways: [{
|
|
name: "A",
|
|
urls: ["nats://127.0.0.1:%d"]
|
|
tls {
|
|
cert_file: "configs/certs/ocsp_peer/mini-ca/server2/TestServer3_bundle.pem"
|
|
key_file: "configs/certs/ocsp_peer/mini-ca/server2/private/TestServer3_keypair.pem"
|
|
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
|
|
timeout: 5
|
|
}
|
|
}]
|
|
tls {
|
|
cert_file: "configs/certs/ocsp_peer/mini-ca/server2/TestServer3_bundle.pem"
|
|
key_file: "configs/certs/ocsp_peer/mini-ca/server2/private/TestServer3_keypair.pem"
|
|
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
|
|
timeout: 5
|
|
verify: true
|
|
}
|
|
}
|
|
`
|
|
srvConfC = fmt.Sprintf(srvConfC, storeDirC, optsA.Gateway.Port)
|
|
conf = createConfFile(t, []byte(srvConfC))
|
|
srvC, optsC := RunServerWithConfig(conf)
|
|
defer srvC.Shutdown()
|
|
|
|
// Check that server is connected to any server from the other cluster.
|
|
checkClusterFormed(t, srvA, srvB)
|
|
waitForOutboundGateways(t, srvC, 1, 5*time.Second)
|
|
|
|
// Connect to cluster A using server B.
|
|
cB, err := nats.Connect(fmt.Sprintf("tls://127.0.0.1:%d", optsB.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return fmt.Errorf("missing OCSP Staple from server")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cB.Close()
|
|
|
|
// Connects to cluster C using server C.
|
|
cC, err := nats.Connect(fmt.Sprintf("tls://127.0.0.1:%d", optsC.Port),
|
|
nats.Secure(&tls.Config{
|
|
VerifyConnection: func(s tls.ConnectionState) error {
|
|
if s.OCSPResponse == nil {
|
|
return fmt.Errorf("missing OCSP Staple from server")
|
|
}
|
|
return nil
|
|
},
|
|
}),
|
|
nats.RootCAs(caCert),
|
|
nats.ErrorHandler(noOpErrHandler),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cC.Close()
|
|
|
|
_, err = cA.Subscribe("foo", func(m *nats.Msg) {
|
|
m.Respond([]byte("From Server A"))
|
|
})
|
|
if err != nil {
|
|
t.Errorf("%v", err)
|
|
}
|
|
cA.Flush()
|
|
|
|
_, err = cB.Subscribe("bar", func(m *nats.Msg) {
|
|
m.Respond([]byte("From Server B"))
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cB.Flush()
|
|
|
|
// Confirm that a message from server C can flow back to server A via gateway..
|
|
var (
|
|
resp *nats.Msg
|
|
lerr error
|
|
)
|
|
for i := 0; i < 10; i++ {
|
|
resp, lerr = cC.Request("foo", nil, 500*time.Millisecond)
|
|
if lerr != nil {
|
|
continue
|
|
}
|
|
got := string(resp.Data)
|
|
expected := "From Server A"
|
|
if got != expected {
|
|
t.Fatalf("Expected %v, got: %v", expected, got)
|
|
}
|
|
|
|
// Make request to B
|
|
resp, lerr = cC.Request("bar", nil, 500*time.Millisecond)
|
|
if lerr != nil {
|
|
continue
|
|
}
|
|
got = string(resp.Data)
|
|
expected = "From Server B"
|
|
if got != expected {
|
|
t.Errorf("Expected %v, got: %v", expected, got)
|
|
}
|
|
lerr = nil
|
|
break
|
|
}
|
|
if lerr != nil {
|
|
t.Errorf("Unexpected error: %v", lerr)
|
|
}
|
|
}
|