ldap-upstream/internal/ldap/serve_test.go
2026-11-01 22:17:59 -06:00

148 lines
4.2 KiB
Go

package ldap
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"os"
"path/filepath"
"testing"
"time"
)
// writeSelfSignedTLSMaterial creates a throwaway certificate and key
// on disk for the TLS serve tests.
func writeSelfSignedTLSMaterial(t *testing.T) (certPath, keyPath string) {
t.Helper()
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatalf("generating key: %s", err)
}
tmpl := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "ldap-bridge-test"},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(time.Hour),
}
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
if err != nil {
t.Fatalf("creating certificate: %s", err)
}
keyDER, err := x509.MarshalECPrivateKey(key)
if err != nil {
t.Fatalf("marshaling key: %s", err)
}
certPath = filepath.Join(t.TempDir(), "cert.pem")
keyPath = filepath.Join(t.TempDir(), "key.pem")
if err := os.WriteFile(certPath, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), 0600); err != nil {
t.Fatalf("writing cert: %s", err)
}
if err := os.WriteFile(keyPath, pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER}), 0600); err != nil {
t.Fatalf("writing key: %s", err)
}
return certPath, keyPath
}
// freeAddr reserves an ephemeral port on loopback and returns its
// address. The listener is closed immediately; the caller is expected
// to hand the address to the server under test.
func freeAddr(t *testing.T) string {
t.Helper()
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("reserving a port: %s", err)
}
addr := ln.Addr().String()
if err := ln.Close(); err != nil {
t.Fatalf("releasing port: %s", err)
}
return addr
}
func TestServeListensAndServeStops(t *testing.T) {
s := New(WithLogger(testLogger()), WithNetAuth(&fakeClient{}))
if err := s.SetDomain("example.com"); err != nil {
t.Fatalf("SetDomain: %s", err)
}
addr := freeAddr(t)
if err := s.Serve(addr); err != nil {
t.Fatalf("Serve: %s", err)
}
// Closing the listener stops the serve loop. The embedded
// server's own Stop cannot be used under -race because it
// closes a done channel that the accept loop reads without a
// synchronising edge.
defer s.Listener.Close()
conn, err := net.Dial("tcp", addr)
if err != nil {
t.Fatalf("dialing server: %s", err)
}
conn.Close()
}
func TestServeReportsBadAddress(t *testing.T) {
s := New(WithLogger(testLogger()), WithNetAuth(&fakeClient{}))
if err := s.Serve("256.256.256.256:0"); err == nil {
t.Fatal("expected an error for an invalid bind address")
}
}
func TestServeTLSRequiresUsableKeyPair(t *testing.T) {
s := New(WithLogger(testLogger()), WithNetAuth(&fakeClient{}))
if err := s.ServeTLS("127.0.0.1:0", "/nope/not-here.key", "/nope/not-here.crt"); err == nil {
t.Fatal("expected an error for a missing key pair")
}
}
// TestServeTLSCapableNegotiation checks that the bridge negotiates a
// modern TLS version and refuses legacy ones, which the vendored
// server made impossible by hard coding an SSL 3.0 floor.
func TestServeTLSNegotiatesModernVersions(t *testing.T) {
certPath, keyPath := writeSelfSignedTLSMaterial(t)
s := New(WithLogger(testLogger()), WithNetAuth(&fakeClient{}))
if err := s.SetDomain("example.com"); err != nil {
t.Fatalf("SetDomain: %s", err)
}
addr := freeAddr(t)
if err := s.ServeTLS(addr, keyPath, certPath); err != nil {
t.Fatalf("ServeTLS: %s", err)
}
defer s.Listener.Close()
conn, err := tls.Dial("tcp", addr, &tls.Config{
InsecureSkipVerify: true,
MinVersion: tls.VersionTLS12,
})
if err != nil {
t.Fatalf("TLS 1.2 handshake: %s", err)
}
if v := conn.ConnectionState().Version; v < tls.VersionTLS12 {
t.Errorf("negotiated TLS version %x, want >= TLS 1.2", v)
}
conn.Close()
for _, ver := range []uint16{tls.VersionSSL30, tls.VersionTLS10, tls.VersionTLS11} {
conn, err := tls.Dial("tcp", addr, &tls.Config{
InsecureSkipVerify: true,
MinVersion: ver,
MaxVersion: ver,
})
if err == nil {
conn.Close()
t.Errorf("legacy version %x was accepted, want rejection", ver)
}
}
}