9d11cd9bf4
Intention de-duplication in previously merged PR actualy failed some tests that were not caught be me or CI. I ran the test files for state changes but they happened not to trigger this case so I made sure they did first and then fixed. That fixed some upstream intention endpoint tests that I'd not run as part of testing the previous fix.
189 lines
5.1 KiB
Go
189 lines
5.1 KiB
Go
package connect
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"sync/atomic"
|
|
|
|
"github.com/hashicorp/consul/agent/connect"
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
"github.com/hashicorp/consul/lib/freeport"
|
|
testing "github.com/mitchellh/go-testing-interface"
|
|
)
|
|
|
|
// TestService returns a Service instance based on a static TLS Config.
|
|
func TestService(t testing.T, service string, ca *structs.CARoot) *Service {
|
|
t.Helper()
|
|
|
|
// Don't need to talk to client since we are setting TLSConfig locally
|
|
svc, err := NewService(service, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
svc.serverTLSCfg = newReloadableTLSConfig(
|
|
TestTLSConfigWithVerifier(t, service, ca, serverVerifyCerts))
|
|
svc.clientTLSCfg = newReloadableTLSConfig(
|
|
TestTLSConfigWithVerifier(t, service, ca, clientVerifyCerts))
|
|
|
|
return svc
|
|
}
|
|
|
|
// TestTLSConfig returns a *tls.Config suitable for use during tests.
|
|
func TestTLSConfig(t testing.T, service string, ca *structs.CARoot) *tls.Config {
|
|
t.Helper()
|
|
|
|
// Insecure default (nil verifier)
|
|
return TestTLSConfigWithVerifier(t, service, ca, nil)
|
|
}
|
|
|
|
// TestTLSConfigWithVerifier returns a *tls.Config suitable for use during
|
|
// tests, it will use the given verifyFunc to verify tls certificates.
|
|
func TestTLSConfigWithVerifier(t testing.T, service string, ca *structs.CARoot,
|
|
verifier verifyFunc) *tls.Config {
|
|
t.Helper()
|
|
|
|
cfg := defaultTLSConfig(verifier)
|
|
cfg.Certificates = []tls.Certificate{TestSvcKeyPair(t, service, ca)}
|
|
cfg.RootCAs = TestCAPool(t, ca)
|
|
cfg.ClientCAs = TestCAPool(t, ca)
|
|
return cfg
|
|
}
|
|
|
|
// TestCAPool returns an *x509.CertPool containing the passed CA's root(s)
|
|
func TestCAPool(t testing.T, cas ...*structs.CARoot) *x509.CertPool {
|
|
t.Helper()
|
|
pool := x509.NewCertPool()
|
|
for _, ca := range cas {
|
|
pool.AppendCertsFromPEM([]byte(ca.RootCert))
|
|
}
|
|
return pool
|
|
}
|
|
|
|
// TestSvcKeyPair returns an tls.Certificate containing both cert and private
|
|
// key for a given service under a given CA from the testdata dir.
|
|
func TestSvcKeyPair(t testing.T, service string, ca *structs.CARoot) tls.Certificate {
|
|
t.Helper()
|
|
certPEM, keyPEM := connect.TestLeaf(t, service, ca)
|
|
cert, err := tls.X509KeyPair([]byte(certPEM), []byte(keyPEM))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return cert
|
|
}
|
|
|
|
// TestPeerCertificates returns a []*x509.Certificate as you'd get from
|
|
// tls.Conn.ConnectionState().PeerCertificates including the named certificate.
|
|
func TestPeerCertificates(t testing.T, service string, ca *structs.CARoot) []*x509.Certificate {
|
|
t.Helper()
|
|
certPEM, _ := connect.TestLeaf(t, service, ca)
|
|
cert, err := connect.ParseCert(certPEM)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return []*x509.Certificate{cert}
|
|
}
|
|
|
|
// TestServer runs a service listener that can be used to test clients. It's
|
|
// behaviour can be controlled by the struct members.
|
|
type TestServer struct {
|
|
// The service name to serve.
|
|
Service string
|
|
// The (test) CA to use for generating certs.
|
|
CA *structs.CARoot
|
|
// TimeoutHandshake controls whether the listening server will complete a TLS
|
|
// handshake quickly enough.
|
|
TimeoutHandshake bool
|
|
// TLSCfg is the tls.Config that will be used. By default it's set up from the
|
|
// service and ca set.
|
|
TLSCfg *tls.Config
|
|
// Addr is the listen address. It is set to a random free port on `localhost`
|
|
// by default.
|
|
Addr string
|
|
|
|
l net.Listener
|
|
stopFlag int32
|
|
stopChan chan struct{}
|
|
}
|
|
|
|
// NewTestServer returns a TestServer. It should be closed when test is
|
|
// complete.
|
|
func NewTestServer(t testing.T, service string, ca *structs.CARoot) *TestServer {
|
|
ports := freeport.GetT(t, 1)
|
|
return &TestServer{
|
|
Service: service,
|
|
CA: ca,
|
|
stopChan: make(chan struct{}),
|
|
TLSCfg: TestTLSConfig(t, service, ca),
|
|
Addr: fmt.Sprintf("localhost:%d", ports[0]),
|
|
}
|
|
}
|
|
|
|
// Serve runs a tcp echo server and blocks until it is closed or errors. If
|
|
// TimeoutHandshake is set it won't start TLS handshake on new connections.
|
|
func (s *TestServer) Serve() error {
|
|
// Just accept TCP conn but so we can control timing of accept/handshake
|
|
l, err := net.Listen("tcp", s.Addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.l = l
|
|
log.Printf("test connect service listening on %s", s.Addr)
|
|
|
|
for {
|
|
conn, err := s.l.Accept()
|
|
if err != nil {
|
|
if atomic.LoadInt32(&s.stopFlag) == 1 {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Ignore the conn if we are not actively handshaking
|
|
if !s.TimeoutHandshake {
|
|
// Upgrade conn to TLS
|
|
conn = tls.Server(conn, s.TLSCfg)
|
|
|
|
// Run an echo service
|
|
log.Printf("test connect service accepted conn from %s, "+
|
|
" running echo service", conn.RemoteAddr())
|
|
go io.Copy(conn, conn)
|
|
}
|
|
|
|
// Close this conn when we stop
|
|
go func(c net.Conn) {
|
|
<-s.stopChan
|
|
c.Close()
|
|
}(conn)
|
|
}
|
|
}
|
|
|
|
// ServeHTTPS runs an HTTPS server with the given config. It invokes the passed
|
|
// Handler for all requests.
|
|
func (s *TestServer) ServeHTTPS(h http.Handler) error {
|
|
srv := http.Server{
|
|
Addr: s.Addr,
|
|
TLSConfig: s.TLSCfg,
|
|
Handler: h,
|
|
}
|
|
log.Printf("starting test connect HTTPS server on %s", s.Addr)
|
|
return srv.ListenAndServeTLS("", "")
|
|
}
|
|
|
|
// Close stops a TestServer
|
|
func (s *TestServer) Close() error {
|
|
old := atomic.SwapInt32(&s.stopFlag, 1)
|
|
if old == 0 {
|
|
if s.l != nil {
|
|
s.l.Close()
|
|
}
|
|
close(s.stopChan)
|
|
}
|
|
return nil
|
|
}
|