mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-15 04:18:03 +00:00
Audit review (rounds 1-31): fix confirmed issues, remove dead code, harden tests
Security/correctness fixes: - fail-open token validation preserved with lenient-audience; access token that fails verification and has no ID token to corroborate now fails closed - token_manager blacklist-expiry: fix impossible tautology (x>24h && x<24h) - constant-time OAuth state compare; clear state/nonce/code_verifier on callback token-exchange failure to prevent replay of half-failed flows - StopAllTasks gated on last-instance close - background-task panic recovery; remove task-registry counter leak - circuit breaker half-open: release probe slot; CB + per-session outcome recorded once per refresh operation, not once per waiter - Redis connection pool: handle nil receive when pool closed concurrently - FileStore: atomic temp-write + rename - errors_recovery health-check data race -> atomic Test-isolation / hardening: - reset helpers for Once-pinned global cache manager and transport pool - coverage: utils 70->93% (redirect URL security contract); fuzz targets for bearer parsers; concurrent ServeHTTP race tests for the shared request path Dead-code removal (verified orphaned, build+suite green): internal/providers (30 files, 0 importers), internal/compat, internal/features, input_validation. Verified: govulncheck, go mod tidy, yaegi-validate, cross-compile (darwin+ linux), golangci-lint (new-from-rev clean), 3x full race+shuffle green.
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestDetermineScheme verifies the scheme priority: forceHTTPS > X-Forwarded-Proto > TLS > http.
|
||||
func TestDetermineScheme(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
forceHTTPS bool
|
||||
fwdProto string
|
||||
tls bool
|
||||
want string
|
||||
}{
|
||||
{"force https wins over header", true, "http", false, "https"},
|
||||
{"force https wins over tls absence", true, "", false, "https"},
|
||||
{"forwarded proto used", false, "https", false, "https"},
|
||||
{"forwarded proto http", false, "http", true, "http"},
|
||||
{"tls connection", false, "", true, "https"},
|
||||
{"default http", false, "", false, "http"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
req := &http.Request{}
|
||||
if c.fwdProto != "" {
|
||||
req.Header = http.Header{"X-Forwarded-Proto": {c.fwdProto}}
|
||||
}
|
||||
if c.tls {
|
||||
req.TLS = &tls.ConnectionState{}
|
||||
}
|
||||
if got := DetermineScheme(req, c.forceHTTPS); got != c.want {
|
||||
t.Fatalf("DetermineScheme() = %q, want %q", got, c.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestDetermineHost verifies forwarding-header preference and fallback to req.Host.
|
||||
func TestDetermineHost(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
fwd string
|
||||
host string
|
||||
want string
|
||||
}{
|
||||
{"forwarded host used", "api.example.com", "backend.local", "api.example.com"},
|
||||
{"first of comma list taken", "a.example.com, b.example.com", "backend.local", "a.example.com"},
|
||||
{"empty forwarded falls back to host", "", "backend.local", "backend.local"},
|
||||
{"whitespace forwarded falls back", " ", "backend.local", "backend.local"},
|
||||
{"control-char forwarded rejected", "ok.example.com\r\nInjected: yes", "backend.local", "backend.local"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
req := &http.Request{Host: c.host}
|
||||
if c.fwd != "" {
|
||||
req.Header = http.Header{"X-Forwarded-Host": {c.fwd}}
|
||||
}
|
||||
if got := DetermineHost(req); got != c.want {
|
||||
t.Fatalf("DetermineHost() = %q, want %q", got, c.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestSanitizeForwardedHost verifies CRLF/newline rejection so crafted headers cannot
|
||||
// poison redirect URLs built from the result.
|
||||
func TestSanitizeForwardedHost(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{"empty rejected", "", ""},
|
||||
{"plain accepted", "api.example.com", "api.example.com"},
|
||||
{"first of comma list", "a.example.com,b.example.com", "a.example.com"},
|
||||
{"leading/trailing space trimmed", " api.example.com ", "api.example.com"},
|
||||
{"internal space rejected", "api example.com", ""},
|
||||
{"CRLF rejected", "api.example.com\r\nX-Injected: 1", ""},
|
||||
{"bare LF rejected", "api.example.com\ngo", ""},
|
||||
{"tab rejected", "api.example.com\x09evil", ""},
|
||||
{"DEL rejected", "api.example.com\x7f", ""},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
if got := sanitizeForwardedHost(c.in); got != c.want {
|
||||
t.Fatalf("sanitizeForwardedHost(%q) = %q, want %q", c.in, got, c.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user