mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 03:38:42 -07:00
Also had to change all references from `path.` to `filepath.` when dealing with files, so that it works properly on Windows. Fixed also lots of tests to defer the shutdown of the server after the removal of the storage, and fixed some config files directories to use the single quote `'` to surround the file path, again to work on Windows. Signed-off-by: Ivan Kozlovic <ivan@synadia.com>
2936 lines
76 KiB
Go
2936 lines
76 KiB
Go
// Copyright 2021 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/rsa"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nats-io/nats-server/v2/server"
|
|
"github.com/nats-io/nats.go"
|
|
"golang.org/x/crypto/ocsp"
|
|
)
|
|
|
|
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)
|
|
}
|
|
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)
|
|
}
|
|
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))
|
|
defer removeFile(t, conf)
|
|
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))
|
|
defer removeFile(t, conf)
|
|
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))
|
|
defer removeFile(t, conf)
|
|
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)
|
|
}
|
|
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 := ioutil.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 := createDir(t, "_ocsp")
|
|
defer removeDir(t, storeDir)
|
|
|
|
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))
|
|
defer removeFile(t, conf)
|
|
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)
|
|
}
|
|
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 := ioutil.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 := ioutil.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 := ioutil.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 := ioutil.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))
|
|
defer removeFile(t, conf)
|
|
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)
|
|
}
|
|
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 := ioutil.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 := createDir(t, "_ocspA")
|
|
defer removeDir(t, storeDirA)
|
|
storeDirB := createDir(t, "_ocspB")
|
|
defer removeDir(t, storeDirB)
|
|
storeDirC := createDir(t, "_ocspC")
|
|
defer removeDir(t, storeDirC)
|
|
|
|
// 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
|
|
|
|
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))
|
|
defer removeFile(t, sconfA)
|
|
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
|
|
|
|
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))
|
|
defer removeFile(t, conf)
|
|
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
|
|
|
|
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))
|
|
defer removeFile(t, conf)
|
|
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
|
|
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 := ioutil.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)
|
|
|
|
// Store Dirs
|
|
storeDirA := createDir(t, "_ocspA")
|
|
defer removeDir(t, storeDirA)
|
|
storeDirB := createDir(t, "_ocspB")
|
|
defer removeDir(t, storeDirB)
|
|
storeDirC := createDir(t, "_ocspC")
|
|
defer removeDir(t, storeDirC)
|
|
|
|
// 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
|
|
}
|
|
}
|
|
`
|
|
srvConfA = fmt.Sprintf(srvConfA, storeDirA)
|
|
sconfA := createConfFile(t, []byte(srvConfA))
|
|
defer removeFile(t, sconfA)
|
|
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'
|
|
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))
|
|
defer removeFile(t, conf)
|
|
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,
|
|
// so that new leafnodes 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'
|
|
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))
|
|
defer removeFile(t, conf)
|
|
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()
|
|
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 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
|
|
}
|
|
}
|
|
`
|
|
srvConfA = fmt.Sprintf(srvConfA, storeDirA)
|
|
if err := ioutil.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 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 := createDir(t, "_ocspA")
|
|
defer removeDir(t, storeDirA)
|
|
storeDirB := createDir(t, "_ocspB")
|
|
defer removeDir(t, storeDirB)
|
|
storeDirC := createDir(t, "_ocspC")
|
|
defer removeDir(t, storeDirC)
|
|
|
|
// 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))
|
|
defer removeFile(t, sconfA)
|
|
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))
|
|
defer removeFile(t, conf)
|
|
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))
|
|
defer removeFile(t, conf)
|
|
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 := ioutil.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) {
|
|
const (
|
|
caCert = "configs/certs/ocsp/desgsign/ca-cert.pem"
|
|
caIntermCert = "configs/certs/ocsp/desgsign/ca-interm-cert.pem"
|
|
caIntermKey = "configs/certs/ocsp/desgsign/ca-interm-key.pem"
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
ocspr := newOCSPResponderDesignated(t, caCert, caIntermCert, caIntermKey, true)
|
|
defer ocspr.Shutdown(ctx)
|
|
|
|
addr := fmt.Sprintf("http://%s", ocspr.Addr)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/desgsign/server-01-cert.pem", ocsp.Good)
|
|
setOCSPStatus(t, addr, "configs/certs/ocsp/desgsign/server-02-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/desgsign/server-01-cert.pem"
|
|
key_file: "configs/certs/ocsp/desgsign/server-01-key.pem"
|
|
ca_file: "configs/certs/ocsp/desgsign/ca-chain-cert.pem"
|
|
timeout: 5
|
|
}
|
|
}
|
|
`
|
|
srvConfA = fmt.Sprintf(srvConfA, addr)
|
|
sconfA := createConfFile(t, []byte(srvConfA))
|
|
defer removeFile(t, sconfA)
|
|
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/desgsign/server-02-cert.pem"
|
|
key_file: "configs/certs/ocsp/desgsign/server-02-key.pem"
|
|
ca_file: "configs/certs/ocsp/desgsign/ca-chain-cert.pem"
|
|
timeout: 5
|
|
}
|
|
}
|
|
`
|
|
srvConfB = fmt.Sprintf(srvConfB, addr, optsA.Gateway.Port)
|
|
conf := createConfFile(t, []byte(srvConfB))
|
|
defer removeFile(t, conf)
|
|
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))
|
|
defer removeFile(t, conf)
|
|
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))
|
|
defer removeFile(t, conf)
|
|
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)
|
|
}
|
|
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 := ioutil.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))
|
|
defer removeFile(t, conf)
|
|
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)
|
|
}
|
|
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 := ioutil.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 newOCSPResponder(t *testing.T, issuerCertPEM, issuerKeyPEM string) *http.Server {
|
|
t.Helper()
|
|
return newOCSPResponderDesignated(t, issuerCertPEM, issuerCertPEM, issuerKeyPEM, false)
|
|
}
|
|
|
|
func newOCSPResponderDesignated(t *testing.T, issuerCertPEM, respCertPEM, respKeyPEM string, embed bool) *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 := ioutil.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(),
|
|
NextUpdate: time.Now().Add(4 * time.Second),
|
|
}
|
|
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: "127.0.0.1:8888",
|
|
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 := ioutil.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) *rsa.PrivateKey {
|
|
t.Helper()
|
|
block := parsePEM(t, keyPEM)
|
|
|
|
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
if err != nil {
|
|
t.Fatalf("failed to parse ikey %s: %s", keyPEM, err)
|
|
}
|
|
return key
|
|
}
|
|
|
|
func parsePEM(t *testing.T, pemPath string) *pem.Block {
|
|
t.Helper()
|
|
data, err := ioutil.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 := createDir(t, "_ocspA")
|
|
defer removeDir(t, storeDirA)
|
|
storeDirB := createDir(t, "_ocspB")
|
|
defer removeDir(t, storeDirB)
|
|
storeDirC := createDir(t, "_ocspC")
|
|
defer removeDir(t, storeDirC)
|
|
storeDirD := createDir(t, "_ocspD")
|
|
defer removeDir(t, storeDirD)
|
|
|
|
// 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))
|
|
defer removeFile(t, sconfA)
|
|
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))
|
|
defer removeFile(t, conf)
|
|
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))
|
|
defer removeFile(t, conf)
|
|
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))
|
|
defer removeFile(t, conf)
|
|
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)
|
|
}
|
|
}
|