Files
lixen bf410e1225
Test / test (push) Canceled after 0s
Test / e2e (push) Canceled after 0s
v0.23.0 color, presets, certificates made at startup, cli.go, sink flush
2026-10-04 03:44:12 -04:00

164 lines
4.8 KiB
Go

// Package testutil provides standard-library fixtures shared by logwisp tests.
// Application packages must not import it.
package testutil
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
// ClearEnvPrefix isolates overrides while preserving the caller's environment.
// Like testing.Setenv, it must not be used by parallel tests.
func ClearEnvPrefix(t testing.TB, prefix string) {
t.Helper()
for _, entry := range os.Environ() {
key, value, _ := strings.Cut(entry, "=")
if strings.HasPrefix(key, prefix) {
t.Setenv(key, value) // Register restoration before unsetting it.
if err := os.Unsetenv(key); err != nil {
t.Fatalf("unset fixture variable %s: %v", key, err)
}
}
}
}
func WriteFile(t testing.TB, path, contents string) {
t.Helper()
if err := os.WriteFile(path, []byte(contents), 0600); err != nil {
t.Fatalf("write fixture %q: %v", path, err)
}
}
// RequireIPv6 skips a test on a host without an IPv6 loopback
func RequireIPv6(t testing.TB) {
t.Helper()
ln, err := net.Listen("tcp6", "[::1]:0")
if err != nil {
t.Skipf("no IPv6 loopback: %v", err)
}
ln.Close()
}
// PKI is a throwaway CA with a server certificate for 127.0.0.1 and ::1 and
// one client certificate, written as PEM files for plugins that load TLS from
// disk.
type PKI struct {
CA, ServerCert, ServerKey, ClientCert, ClientKey string
dir string
ca *x509.Certificate
caKey *ecdsa.PrivateKey
serial int64
}
// NewPKI issues the PKI into a test directory; clientCN names the client.
func NewPKI(t testing.TB, clientCN string) *PKI {
t.Helper()
now := time.Now()
caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
caTmpl := &x509.Certificate{
SerialNumber: big.NewInt(1), Subject: pkix.Name{CommonName: "LogWisp Test CA"},
NotBefore: now.Add(-time.Hour), NotAfter: now.Add(24 * time.Hour),
IsCA: true, BasicConstraintsValid: true, KeyUsage: x509.KeyUsageCertSign,
}
caDER, err := x509.CreateCertificate(rand.Reader, caTmpl, caTmpl, &caKey.PublicKey, caKey)
if err != nil {
t.Fatal(err)
}
ca, err := x509.ParseCertificate(caDER)
if err != nil {
t.Fatal(err)
}
pki := &PKI{dir: t.TempDir(), ca: ca, caKey: caKey, serial: 1}
pki.CA = writePEM(t, pki.dir, "ca.crt", "CERTIFICATE", caDER)
pki.ServerCert, pki.ServerKey = pki.Leaf(t, "server", "relay.internal", false)
pki.ClientCert, pki.ClientKey = pki.Leaf(t, "client", clientCN, true)
return pki
}
// Leaf issues another certificate from the same CA: a client certificate, or
// a server certificate valid for 127.0.0.1 and ::1.
func (p *PKI) Leaf(t testing.TB, name, cn string, client bool) (certFile, keyFile string) {
t.Helper()
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
p.serial++
now := time.Now()
tmpl := &x509.Certificate{
SerialNumber: big.NewInt(p.serial), Subject: pkix.Name{CommonName: cn},
NotBefore: now.Add(-time.Hour), NotAfter: now.Add(24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback},
}
if client {
tmpl.ExtKeyUsage, tmpl.IPAddresses = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, nil
}
der, err := x509.CreateCertificate(rand.Reader, tmpl, p.ca, &key.PublicKey, p.caKey)
if err != nil {
t.Fatal(err)
}
keyDER, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
t.Fatal(err)
}
return writePEM(t, p.dir, name+".crt", "CERTIFICATE", der), writePEM(t, p.dir, name+".key", "PRIVATE KEY", keyDER)
}
// Handshake runs a TLS handshake between the two configurations over
// loopback TCP, whose buffers let a failing side's alert go out without a
// reader, and returns the client's error, else the server's.
func Handshake(t testing.TB, server, client *tls.Config) error {
t.Helper()
ln, err := net.Listen("tcp4", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer ln.Close()
serverErr := make(chan error, 1)
go func() {
conn, err := ln.Accept()
if err != nil {
serverErr <- err
return
}
defer conn.Close()
conn.SetDeadline(time.Now().Add(5 * time.Second))
serverErr <- tls.Server(conn, server).Handshake()
}()
conn, err := net.Dial("tcp4", ln.Addr().String())
if err != nil {
t.Fatal(err)
}
conn.SetDeadline(time.Now().Add(5 * time.Second))
err = tls.Client(conn, client).Handshake()
conn.Close()
if serr := <-serverErr; err == nil {
err = serr
}
return err
}
func writePEM(t testing.TB, dir, name, blockType string, der []byte) string {
t.Helper()
path := filepath.Join(dir, name)
WriteFile(t, path, string(pem.EncodeToMemory(&pem.Block{Type: blockType, Bytes: der})))
return path
}