Pulse/pkg/tlsutil/tlsutil_test.go
rcourtman f7aa9ee922 Remove unreachable exported functions from pkg/
Dead-code audit batch 1: five functions with zero references from main,
tests, or pulse-enterprise (deadcode -test, grep-verified per symbol,
enterprise compile-checked). Also renames the tlsutil test that
exercised the deleted compat alias's target to name the Unverified
function it actually calls.

Skipped from the audit list after re-verification: IsNilAlertPayload
(live pulse-enterprise consumer in internal/aialertanalysis).
2026-07-10 00:27:35 +01:00

381 lines
12 KiB
Go

package tlsutil
import (
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"net/http"
"net/url"
"strings"
"testing"
"time"
)
func TestFingerprintVerifier_NormalizesFingerprint(t *testing.T) {
// Fingerprint with colons
fp1 := "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99"
// Same fingerprint without colons, lowercase
fp2 := "aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899"
config1 := FingerprintVerifier(fp1)
config2 := FingerprintVerifier(fp2)
// Both should have InsecureSkipVerify set (we do our own verification)
if !config1.InsecureSkipVerify {
t.Error("FingerprintVerifier should set InsecureSkipVerify to true")
}
if !config2.InsecureSkipVerify {
t.Error("FingerprintVerifier should set InsecureSkipVerify to true")
}
// Both should have VerifyPeerCertificate function set
if config1.VerifyPeerCertificate == nil {
t.Error("FingerprintVerifier should set VerifyPeerCertificate")
}
if config2.VerifyPeerCertificate == nil {
t.Error("FingerprintVerifier should set VerifyPeerCertificate")
}
if config1.MinVersion != minimumTLSVersion {
t.Errorf("FingerprintVerifier MinVersion = %v, want %v", config1.MinVersion, minimumTLSVersion)
}
if config2.MinVersion != minimumTLSVersion {
t.Errorf("FingerprintVerifier MinVersion = %v, want %v", config2.MinVersion, minimumTLSVersion)
}
}
func TestUnverifiedPeerCertificateCaptureTLSConfigRequiresPeerCertificate(t *testing.T) {
config := UnverifiedPeerCertificateCaptureTLSConfig()
if !config.InsecureSkipVerify {
t.Fatal("UnverifiedPeerCertificateCaptureTLSConfig should enable custom verification mode")
}
if config.VerifyPeerCertificate == nil {
t.Fatal("UnverifiedPeerCertificateCaptureTLSConfig should install a peer-certificate verifier")
}
if config.MinVersion != minimumTLSVersion {
t.Fatalf("UnverifiedPeerCertificateCaptureTLSConfig MinVersion = %v, want %v", config.MinVersion, minimumTLSVersion)
}
err := config.VerifyPeerCertificate(nil, nil)
if err == nil || !strings.Contains(err.Error(), "no certificates") {
t.Fatalf("expected missing-certificate error, got %v", err)
}
}
func TestFingerprintVerifier_NoCertificates(t *testing.T) {
config := FingerprintVerifier("aabbccdd")
// Should fail when no certificates presented
err := config.VerifyPeerCertificate([][]byte{}, nil)
if err == nil {
t.Error("Should fail when no certificates presented")
}
if !strings.Contains(err.Error(), "no certificates") {
t.Errorf("Error message should mention no certificates, got: %v", err)
}
}
func TestFingerprintVerifier_MatchingFingerprint(t *testing.T) {
// Create a mock certificate (just random bytes for testing)
mockCert := []byte("mock certificate data for testing purposes")
// Calculate its fingerprint
fingerprint := sha256.Sum256(mockCert)
expectedFP := hex.EncodeToString(fingerprint[:])
config := FingerprintVerifier(expectedFP)
// Should succeed with matching fingerprint
err := config.VerifyPeerCertificate([][]byte{mockCert}, nil)
if err != nil {
t.Errorf("Should succeed with matching fingerprint, got: %v", err)
}
}
func TestFingerprintVerifier_MismatchedFingerprint(t *testing.T) {
mockCert := []byte("mock certificate data")
// Use a different fingerprint
wrongFP := "0000000000000000000000000000000000000000000000000000000000000000"
config := FingerprintVerifier(wrongFP)
// Should fail with mismatched fingerprint
err := config.VerifyPeerCertificate([][]byte{mockCert}, nil)
if err == nil {
t.Error("Should fail with mismatched fingerprint")
}
if !strings.Contains(err.Error(), "mismatch") {
t.Errorf("Error message should mention mismatch, got: %v", err)
}
}
func TestCreateHTTPClient_InsecureMode(t *testing.T) {
client := CreateHTTPClient(false, "")
if client == nil {
t.Fatal("CreateHTTPClient returned nil")
}
transport, ok := client.Transport.(*http.Transport)
if !ok {
t.Fatal("Transport is not *http.Transport")
}
if transport.TLSClientConfig == nil {
t.Fatal("TLSClientConfig should be set")
}
if !transport.TLSClientConfig.InsecureSkipVerify {
t.Error("InsecureSkipVerify should be true in insecure mode")
}
if transport.TLSClientConfig.VerifyPeerCertificate == nil {
t.Fatal("insecure mode should still validate peer certificate structure")
}
if transport.TLSClientConfig.MinVersion != minimumTLSVersion {
t.Fatalf("TLSClientConfig.MinVersion = %v, want %v", transport.TLSClientConfig.MinVersion, minimumTLSVersion)
}
}
func TestCreateHTTPClient_FingerprintMode(t *testing.T) {
fingerprint := "aabbccdd"
client := CreateHTTPClient(false, fingerprint)
if client == nil {
t.Fatal("CreateHTTPClient returned nil")
}
transport, ok := client.Transport.(*http.Transport)
if !ok {
t.Fatal("Transport is not *http.Transport")
}
if transport.TLSClientConfig == nil {
t.Fatal("TLSClientConfig should be set")
}
// Should have custom verification function
if transport.TLSClientConfig.VerifyPeerCertificate == nil {
t.Error("VerifyPeerCertificate should be set in fingerprint mode")
}
if transport.TLSClientConfig.MinVersion != minimumTLSVersion {
t.Fatalf("TLSClientConfig.MinVersion = %v, want %v", transport.TLSClientConfig.MinVersion, minimumTLSVersion)
}
}
func TestCreateHTTPClient_SecureMode(t *testing.T) {
client := CreateHTTPClient(true, "")
if client == nil {
t.Fatal("CreateHTTPClient returned nil")
}
transport, ok := client.Transport.(*http.Transport)
if !ok {
t.Fatal("Transport is not *http.Transport")
}
if transport.TLSClientConfig == nil {
t.Fatal("secure mode should install an explicit TLS client config")
}
if transport.TLSClientConfig.InsecureSkipVerify {
t.Error("InsecureSkipVerify should not be true in secure mode")
}
if transport.TLSClientConfig.MinVersion != minimumTLSVersion {
t.Fatalf("TLSClientConfig.MinVersion = %v, want %v", transport.TLSClientConfig.MinVersion, minimumTLSVersion)
}
}
func TestCreateHTTPClientWithTimeout_DefaultTimeout(t *testing.T) {
client := CreateHTTPClientWithTimeout(true, "", 0)
if client == nil {
t.Fatal("CreateHTTPClientWithTimeout returned nil")
}
// Should use default timeout when 0 is passed
if client.Timeout != 60*time.Second {
t.Errorf("Timeout = %v, want %v", client.Timeout, 60*time.Second)
}
}
func TestCreateHTTPClientWithTimeout_CustomTimeout(t *testing.T) {
customTimeout := 30 * time.Second
client := CreateHTTPClientWithTimeout(true, "", customTimeout)
if client == nil {
t.Fatal("CreateHTTPClientWithTimeout returned nil")
}
if client.Timeout != customTimeout {
t.Errorf("Timeout = %v, want %v", client.Timeout, customTimeout)
}
}
func TestCreateHTTPClientWithTimeout_NegativeTimeout(t *testing.T) {
client := CreateHTTPClientWithTimeout(true, "", -10*time.Second)
if client == nil {
t.Fatal("CreateHTTPClientWithTimeout returned nil")
}
// Should use default timeout when negative is passed
if client.Timeout != 60*time.Second {
t.Errorf("Timeout = %v, want %v (default)", client.Timeout, 60*time.Second)
}
}
func TestCreateHTTPClient_TransportSettings(t *testing.T) {
client := CreateHTTPClient(true, "")
transport, ok := client.Transport.(*http.Transport)
if !ok {
t.Fatal("Transport is not *http.Transport")
}
// Verify transport settings
if transport.MaxIdleConns != 100 {
t.Errorf("MaxIdleConns = %v, want 100", transport.MaxIdleConns)
}
if transport.MaxIdleConnsPerHost != 20 {
t.Errorf("MaxIdleConnsPerHost = %v, want 20", transport.MaxIdleConnsPerHost)
}
if transport.MaxConnsPerHost != 20 {
t.Errorf("MaxConnsPerHost = %v, want 20", transport.MaxConnsPerHost)
}
if transport.IdleConnTimeout != 90*time.Second {
t.Errorf("IdleConnTimeout = %v, want 90s", transport.IdleConnTimeout)
}
if !transport.DisableCompression {
t.Error("DisableCompression should be true")
}
if transport.TLSHandshakeTimeout != 10*time.Second {
t.Errorf("TLSHandshakeTimeout = %v, want 10s", transport.TLSHandshakeTimeout)
}
}
func TestShouldBypassProxyForTargetHost(t *testing.T) {
tests := []struct {
name string
host string
want bool
}{
{name: "single label Proxmox host", host: "delly", want: true},
{name: "localhost", host: "localhost", want: true},
{name: "mDNS local", host: "pve.local", want: true},
{name: "loopback IPv4", host: "127.0.0.1", want: true},
{name: "loopback IPv6", host: "::1", want: true},
{name: "RFC1918 IPv4", host: "192.168.0.5", want: true},
{name: "Tailscale CGNAT IPv4", host: "100.127.165.127", want: true},
{name: "public IPv4", host: "203.0.113.10", want: false},
{name: "public DNS name", host: "pve.example.com", want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := shouldBypassProxyForTargetHost(tt.host); got != tt.want {
t.Fatalf("shouldBypassProxyForTargetHost(%q) = %v, want %v", tt.host, got, tt.want)
}
})
}
}
func TestProxyFromEnvironmentBypassingLocalInfrastructure(t *testing.T) {
t.Setenv("HTTP_PROXY", "http://proxy.example:8080")
t.Setenv("HTTPS_PROXY", "http://proxy.example:8080")
t.Setenv("NO_PROXY", "")
localReq := &http.Request{URL: &url.URL{Scheme: "https", Host: "192.168.0.5:8006"}}
if proxyURL, err := proxyFromEnvironmentBypassingLocalInfrastructure(localReq); err != nil || proxyURL != nil {
t.Fatalf("local proxy = %v, err = %v; want direct", proxyURL, err)
}
publicReq := &http.Request{URL: &url.URL{Scheme: "https", Host: "pve.example.com:8006"}}
proxyURL, err := proxyFromEnvironmentBypassingLocalInfrastructure(publicReq)
if err != nil {
t.Fatalf("public proxy lookup failed: %v", err)
}
if proxyURL == nil || proxyURL.String() != "http://proxy.example:8080" {
t.Fatalf("public proxy = %v, want http://proxy.example:8080", proxyURL)
}
}
func TestFingerprintVerifier_IgnoresVerifiedChains(t *testing.T) {
mockCert := []byte("test certificate")
fingerprint := sha256.Sum256(mockCert)
expectedFP := hex.EncodeToString(fingerprint[:])
config := FingerprintVerifier(expectedFP)
// verifiedChains parameter should be ignored
mockChains := [][]*x509.Certificate{{&x509.Certificate{}}}
err := config.VerifyPeerCertificate([][]byte{mockCert}, mockChains)
if err != nil {
t.Errorf("Should ignore verifiedChains, got error: %v", err)
}
}
func TestGetDNSResolver(t *testing.T) {
resolver := GetDNSResolver()
if resolver == nil {
t.Fatal("GetDNSResolver returned nil")
}
// Call again - should return same instance (singleton)
resolver2 := GetDNSResolver()
if resolver != resolver2 {
t.Error("GetDNSResolver should return same instance")
}
}
func TestFingerprintVerifier_ColonSeparatedFingerprint(t *testing.T) {
mockCert := []byte("test cert with colons")
fingerprint := sha256.Sum256(mockCert)
// Format with colons (common format from openssl)
fpBytes := fingerprint[:]
var parts []string
for _, b := range fpBytes {
parts = append(parts, hex.EncodeToString([]byte{b}))
}
colonSeparated := strings.ToUpper(strings.Join(parts, ":"))
config := FingerprintVerifier(colonSeparated)
err := config.VerifyPeerCertificate([][]byte{mockCert}, nil)
if err != nil {
t.Errorf("Should handle colon-separated fingerprint, got: %v", err)
}
}
func TestCreateHTTPClient_FingerprintTakesPrecedence(t *testing.T) {
// Even if verifySSL is true, fingerprint mode should be used if fingerprint is provided
fingerprint := "aabbccdd"
client := CreateHTTPClient(true, fingerprint)
transport, ok := client.Transport.(*http.Transport)
if !ok {
t.Fatal("Transport is not *http.Transport")
}
// Should use fingerprint verification
if transport.TLSClientConfig == nil {
t.Fatal("TLSClientConfig should be set")
}
if transport.TLSClientConfig.VerifyPeerCertificate == nil {
t.Error("Should use fingerprint verification when fingerprint is provided")
}
}
func TestFingerprintVerifier_TLSVersion(t *testing.T) {
config := FingerprintVerifier("aabbccdd")
// Check that config is a valid TLS config
if config == nil {
t.Fatal("FingerprintVerifier returned nil config")
}
if config.MinVersion != minimumTLSVersion {
t.Errorf("MinVersion should be %v, got %v", minimumTLSVersion, config.MinVersion)
}
}