216 lines
5.7 KiB
Go
216 lines
5.7 KiB
Go
package main
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"crypto/tls"
|
||
|
|
"io"
|
||
|
|
"net/http"
|
||
|
|
"net/http/httptest"
|
||
|
|
"net/url"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestParseTLSVersion(t *testing.T) {
|
||
|
|
tests := []struct {
|
||
|
|
input string
|
||
|
|
want uint16
|
||
|
|
wantErr bool
|
||
|
|
}{
|
||
|
|
{"1.0", tls.VersionTLS10, false},
|
||
|
|
{"TLS1.0", tls.VersionTLS10, false},
|
||
|
|
{"1.1", tls.VersionTLS11, false},
|
||
|
|
{"1.2", tls.VersionTLS12, false},
|
||
|
|
{"tls1.2", tls.VersionTLS12, false},
|
||
|
|
{"1.3", tls.VersionTLS13, false},
|
||
|
|
{"2.0", 0, true},
|
||
|
|
{"invalid", 0, true},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, tt := range tests {
|
||
|
|
got, err := parseTLSVersion(tt.input)
|
||
|
|
if (err != nil) != tt.wantErr {
|
||
|
|
t.Errorf("parseTLSVersion(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if got != tt.want {
|
||
|
|
t.Errorf("parseTLSVersion(%q) = %x, want %x", tt.input, got, tt.want)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestParseTargetURL(t *testing.T) {
|
||
|
|
tests := []struct {
|
||
|
|
input string
|
||
|
|
wantHost string
|
||
|
|
wantSch string
|
||
|
|
wantErr bool
|
||
|
|
}{
|
||
|
|
{"https://example.com", "example.com", "https", false},
|
||
|
|
{"http://localhost:9000", "localhost:9000", "http", false},
|
||
|
|
{"example.com:8443", "example.com:8443", "https", false},
|
||
|
|
{"", "", "", true},
|
||
|
|
{"ftp://example.com", "", "", true},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, tt := range tests {
|
||
|
|
u, err := ParseTargetURL(tt.input)
|
||
|
|
if (err != nil) != tt.wantErr {
|
||
|
|
t.Errorf("ParseTargetURL(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if !tt.wantErr {
|
||
|
|
if u.Host != tt.wantHost || u.Scheme != tt.wantSch {
|
||
|
|
t.Errorf("ParseTargetURL(%q) = %s://%s, want %s://%s", tt.input, u.Scheme, u.Host, tt.wantSch, tt.wantHost)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGenerateSelfSignedCert(t *testing.T) {
|
||
|
|
cert, err := generateSelfSignedCert([]string{"custom.local", "192.168.1.50"})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("generateSelfSignedCert() failed: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if len(cert.Certificate) == 0 {
|
||
|
|
t.Fatal("generateSelfSignedCert() returned empty certificate chain")
|
||
|
|
}
|
||
|
|
if cert.PrivateKey == nil {
|
||
|
|
t.Fatal("generateSelfSignedCert() returned nil private key")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestProxy_TLS12_To_TLS13_Backend(t *testing.T) {
|
||
|
|
// 1. Setup backend server that ONLY accepts TLS 1.3
|
||
|
|
backendHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
if r.URL.Path == "/test-endpoint" {
|
||
|
|
w.Header().Set("X-Backend-Received-Proto", r.Proto)
|
||
|
|
w.Header().Set("X-Backend-Received-Host", r.Host)
|
||
|
|
w.WriteHeader(http.StatusOK)
|
||
|
|
body, _ := io.ReadAll(r.Body)
|
||
|
|
w.Write([]byte("backend-response: " + string(body)))
|
||
|
|
return
|
||
|
|
}
|
||
|
|
http.NotFound(w, r)
|
||
|
|
})
|
||
|
|
|
||
|
|
backendServer := httptest.NewUnstartedServer(backendHandler)
|
||
|
|
backendServer.TLS = &tls.Config{
|
||
|
|
MinVersion: tls.VersionTLS13,
|
||
|
|
MaxVersion: tls.VersionTLS13,
|
||
|
|
}
|
||
|
|
backendServer.StartTLS()
|
||
|
|
defer backendServer.Close()
|
||
|
|
|
||
|
|
backendURL, err := url.Parse(backendServer.URL)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("failed to parse backend URL: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2. Setup smallprox handler pointing to the TLS 1.3 backend
|
||
|
|
proxyHandler := NewProxyHandler(ProxyConfig{
|
||
|
|
TargetURL: backendURL,
|
||
|
|
PreserveHost: false,
|
||
|
|
})
|
||
|
|
|
||
|
|
proxyCert, err := generateSelfSignedCert(nil)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("failed to generate proxy cert: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
proxyServer := httptest.NewUnstartedServer(proxyHandler)
|
||
|
|
// Proxy frontend only accepts TLS 1.2
|
||
|
|
proxyServer.TLS = &tls.Config{
|
||
|
|
Certificates: []tls.Certificate{proxyCert},
|
||
|
|
MinVersion: tls.VersionTLS12,
|
||
|
|
MaxVersion: tls.VersionTLS12,
|
||
|
|
}
|
||
|
|
proxyServer.StartTLS()
|
||
|
|
defer proxyServer.Close()
|
||
|
|
|
||
|
|
// 3. Client configured strictly for TLS 1.2
|
||
|
|
client := &http.Client{
|
||
|
|
Transport: &http.Transport{
|
||
|
|
TLSClientConfig: &tls.Config{
|
||
|
|
InsecureSkipVerify: true,
|
||
|
|
MinVersion: tls.VersionTLS12,
|
||
|
|
MaxVersion: tls.VersionTLS12,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
// Make request through proxy
|
||
|
|
req, err := http.NewRequest(http.MethodPost, proxyServer.URL+"/test-endpoint", strings.NewReader("hello-from-tls12-client"))
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("failed to create request: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
resp, err := client.Do(req)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("client request through proxy failed: %v", err)
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
|
||
|
|
if resp.StatusCode != http.StatusOK {
|
||
|
|
t.Fatalf("expected status 200 OK, got %d", resp.StatusCode)
|
||
|
|
}
|
||
|
|
|
||
|
|
if resp.TLS == nil || resp.TLS.Version != tls.VersionTLS12 {
|
||
|
|
t.Errorf("expected client TLS version to be TLS 1.2 (0x0303), got %x", resp.TLS.Version)
|
||
|
|
}
|
||
|
|
|
||
|
|
body, err := io.ReadAll(resp.Body)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("failed to read response body: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
expectedBody := "backend-response: hello-from-tls12-client"
|
||
|
|
if string(body) != expectedBody {
|
||
|
|
t.Errorf("expected body %q, got %q", expectedBody, string(body))
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verify Host was rewritten to backend host
|
||
|
|
if receivedHost := resp.Header.Get("X-Backend-Received-Host"); receivedHost != backendURL.Host {
|
||
|
|
t.Errorf("expected backend to receive host %q, got %q", backendURL.Host, receivedHost)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestProxy_PreserveHost(t *testing.T) {
|
||
|
|
backendHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
w.Header().Set("X-Received-Host", r.Host)
|
||
|
|
w.WriteHeader(http.StatusOK)
|
||
|
|
})
|
||
|
|
|
||
|
|
backendServer := httptest.NewServer(backendHandler)
|
||
|
|
defer backendServer.Close()
|
||
|
|
|
||
|
|
backendURL, err := url.Parse(backendServer.URL)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("failed to parse backend URL: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
proxyHandler := NewProxyHandler(ProxyConfig{
|
||
|
|
TargetURL: backendURL,
|
||
|
|
PreserveHost: true,
|
||
|
|
})
|
||
|
|
|
||
|
|
proxyServer := httptest.NewServer(proxyHandler)
|
||
|
|
defer proxyServer.Close()
|
||
|
|
|
||
|
|
req, err := http.NewRequest(http.MethodGet, proxyServer.URL+"/test", nil)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("failed to create request: %v", err)
|
||
|
|
}
|
||
|
|
req.Host = "custom-domain.net"
|
||
|
|
|
||
|
|
resp, err := http.DefaultClient.Do(req)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("request failed: %v", err)
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
|
||
|
|
if got := resp.Header.Get("X-Received-Host"); got != "custom-domain.net" {
|
||
|
|
t.Errorf("expected preserved host %q, got %q", "custom-domain.net", got)
|
||
|
|
}
|
||
|
|
}
|