Files
threshold/internal/tests/cluster/x509_verification_test.go
T
Johan Brandhorst-Satzkorn f89515a19c all: avoid mutating global state in parallel tests (#3687)
The SetupSuiteTargetFilters globally mutates the function used for logic
inside the target handler service. This means that we could get
inconsistent logical behavior in certain tests that were run in parallel,
since the logic can change at runtime. Ensure tests that make use
of this function are never run in parallel with other tests.
2023-09-07 11:52:00 -07:00

247 lines
7.1 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package cluster
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net/http"
"sync"
"testing"
"time"
"github.com/hashicorp/boundary/api/targets"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/cmd/config"
"github.com/hashicorp/boundary/internal/daemon/controller"
tg "github.com/hashicorp/boundary/internal/daemon/controller/handlers/targets"
"github.com/hashicorp/boundary/internal/daemon/worker"
"github.com/hashicorp/boundary/internal/observability/event"
"github.com/hashicorp/boundary/internal/tests/helper"
"github.com/hashicorp/go-hclog"
"github.com/stretchr/testify/require"
"nhooyr.io/websocket"
)
func TestCustomX509Verification_Client(t *testing.T) {
req := require.New(t)
ctx := context.Background()
ec := event.TestEventerConfig(t, "TestWorkerReplay", event.TestWithObservationSink(t), event.TestWithSysSink(t))
testLock := &sync.Mutex{}
// This prevents us from running tests in parallel.
tg.SetupSuiteTargetFilters(t)
logger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
Name: "test",
})
req.NoError(event.InitSysEventer(logger, testLock, "use-TestCustomX509Verification", event.WithEventerConfig(&ec.EventerConfig)))
conf, err := config.DevController()
conf.Eventing = &ec.EventerConfig
req.NoError(err)
c1 := controller.NewTestController(t, &controller.TestControllerOpts{
Config: conf,
InitialResourcesSuffix: "1234567890",
Logger: logger.Named("c1"),
})
conf, err = config.DevWorker()
conf.Eventing = &ec.EventerConfig
req.NoError(err)
w1 := worker.NewTestWorker(t, &worker.TestWorkerOpts{
WorkerAuthKms: c1.Config().WorkerAuthKms,
InitialUpstreams: c1.ClusterAddrs(),
Logger: logger.Named("w1"),
})
// Give time for it to connect
time.Sleep(10 * time.Second)
err = w1.Worker().WaitForNextSuccessfulStatusUpdate()
req.NoError(err)
// Connect target
client := c1.Client()
client.SetToken(c1.Token().Token)
tcl := targets.NewClient(client)
tgt, err := tcl.Read(ctx, "ttcp_1234567890")
req.NoError(err)
req.NotNil(tgt)
// Create test server, update default port on target
ts := helper.NewTestTcpServer(t)
require.NotNil(t, ts)
t.Cleanup(ts.Close)
tgt, err = tcl.Update(ctx, tgt.Item.Id, tgt.Item.Version, targets.WithTcpTargetDefaultPort(ts.Port()), targets.WithSessionConnectionLimit(-1))
req.NoError(err)
req.NotNil(tgt)
tests := []struct {
name string
certPool *x509.CertPool
dnsName *bytes.Buffer
wantErrContains string
}{
{
name: "base",
},
{
name: "modified cert pool",
certPool: x509.NewCertPool(),
wantErrContains: "signed by unknown authority",
},
{
name: "modified dns name",
dnsName: bytes.NewBuffer([]byte("foobar")),
wantErrContains: "not foobar",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)
sess := helper.NewTestSession(ctx, t, tcl, "ttcp_1234567890")
certPool := tc.certPool
if certPool == nil {
parsedCert, err := x509.ParseCertificate(sess.SessionAuthzData.Certificate)
require.NoError(err)
certPool = x509.NewCertPool()
certPool.AddCert(parsedCert)
}
dnsName := sess.SessionId
if tc.dnsName != nil {
dnsName = tc.dnsName.String()
}
verifyOpts := x509.VerifyOptions{
DNSName: dnsName,
Roots: certPool,
KeyUsages: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageServerAuth,
},
}
sess.Transport.TLSClientConfig.VerifyConnection = func(cs tls.ConnectionState) error {
// Go will not run this without at least one peer certificate, but
// doesn't hurt to check
if len(cs.PeerCertificates) == 0 {
return errors.New("no peer certificates provided")
}
_, err := cs.PeerCertificates[0].Verify(verifyOpts)
return err
}
conn, _, err := websocket.Dial(
ctx,
fmt.Sprintf("wss://%s/v1/proxy", sess.WorkerAddr),
&websocket.DialOptions{
HTTPClient: &http.Client{
Transport: sess.Transport,
},
Subprotocols: []string{globals.TcpProxyV1},
},
)
if conn != nil {
defer conn.Close(websocket.StatusNormalClosure, "done")
}
switch tc.wantErrContains != "" {
case true:
require.Error(err)
require.Contains(err.Error(), tc.wantErrContains)
default:
require.NoError(err)
}
})
}
}
func TestCustomX509Verification_Server(t *testing.T) {
ec := event.TestEventerConfig(t, "TestCustomX509Verification_Server", event.TestWithObservationSink(t), event.TestWithSysSink(t))
testLock := &sync.Mutex{}
logger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
Name: "test",
})
require.NoError(t, event.InitSysEventer(logger, testLock, "use-TestCustomX509Verification_Server", event.WithEventerConfig(&ec.EventerConfig)))
t.Run("bad cert pool", testCustomX509Verification_Server(ec, x509.NewCertPool(), "", "bad certificate"))
t.Run("bad dns name", testCustomX509Verification_Server(ec, nil, "foobar", "bad certificate"))
}
func testCustomX509Verification_Server(ec event.TestConfig, certPool *x509.CertPool, dnsName, wantErrContains string) func(t *testing.T) {
return func(t *testing.T) {
req := require.New(t)
ctx := context.Background()
// This prevents us from running tests in parallel.
tg.SetupSuiteTargetFilters(t)
conf, err := config.DevController()
conf.Eventing = &ec.EventerConfig
req.NoError(err)
c1 := controller.NewTestController(t, &controller.TestControllerOpts{
Config: conf,
InitialResourcesSuffix: "1234567890",
})
conf, err = config.DevWorker()
conf.Eventing = &ec.EventerConfig
req.NoError(err)
w1 := worker.NewTestWorker(t, &worker.TestWorkerOpts{
WorkerAuthKms: c1.Config().WorkerAuthKms,
InitialUpstreams: c1.ClusterAddrs(),
})
w1.Worker().TestOverrideX509VerifyCertPool = certPool
w1.Worker().TestOverrideX509VerifyDnsName = dnsName
// Give time for it to connect
time.Sleep(10 * time.Second)
err = w1.Worker().WaitForNextSuccessfulStatusUpdate()
req.NoError(err)
// Connect target
client := c1.Client()
client.SetToken(c1.Token().Token)
tcl := targets.NewClient(client)
tgt, err := tcl.Read(ctx, "ttcp_1234567890")
req.NoError(err)
req.NotNil(tgt)
// Create test server, update default port on target
ts := helper.NewTestTcpServer(t)
require.NotNil(t, ts)
t.Cleanup(ts.Close)
tgt, err = tcl.Update(ctx, tgt.Item.Id, tgt.Item.Version, targets.WithTcpTargetDefaultPort(ts.Port()), targets.WithSessionConnectionLimit(-1))
req.NoError(err)
req.NotNil(tgt)
sess := helper.NewTestSession(ctx, t, tcl, "ttcp_1234567890")
conn, _, err := websocket.Dial(
ctx,
fmt.Sprintf("wss://%s/v1/proxy", sess.WorkerAddr),
&websocket.DialOptions{
HTTPClient: &http.Client{
Transport: sess.Transport,
},
Subprotocols: []string{globals.TcpProxyV1},
},
)
if conn != nil {
defer conn.Close(websocket.StatusNormalClosure, "done")
}
switch wantErrContains != "" {
case true:
req.Error(err)
req.Contains(err.Error(), wantErrContains)
default:
req.NoError(err)
}
}
}