fix(auth): SEC-001 — gate OIDC discovery through SafeHTTPDialContext + ValidateSafeURL

Sprint 1 unified-master-audit closure. Two OIDC discovery call sites
passed the bare request context to gooidc.NewProvider:

  - internal/auth/oidc/test_discovery.go:65 (dry-run validator)
  - internal/auth/oidc/service.go:1066      (runtime cache load)

gooidc.NewProvider derives its HTTP client from the context via
oidc.ClientContext; with no override it falls through to
http.DefaultClient — no SSRF guard. An admin with auth.oidc.create
could induce server-side HTTPS egress to loopback (127.0.0.1, ::1),
RFC 1918, link-local (169.254.169.254 — cloud-instance metadata),
and IPv6 link-local (fe80::/10). The companion JWKS reachability
probe was already routed through SafeHTTPDialContext via the
Bundle 5 R6 closure; the discovery + claims path bypassed that.

Fix:
  - New internal/auth/oidc/safehttp.go: oidcDiscoveryClient (Transport
    DialContext = validation.SafeHTTPDialContext) + SafeOIDCContext
    helper. Both call sites now wrap ctx through SafeOIDCContext
    before NewProvider runs.
  - Defense-in-depth: OIDCProvider.Validate calls
    validation.ValidateSafeURL on the IssuerURL after the existing
    https/parse checks, refusing reserved-address issuers at
    provider-creation time.
  - TestDiscovery surfaces the SSRF policy error via the result's
    Errors slice up-front (early-fail UX rail) before invoking
    NewProvider.

Test seams:
  - setup_test.go swaps oidcDiscoveryClient + validateIssuerSSRF
    for httptest loopback compatibility, mirroring the existing
    jwksProbeClient pattern.

Regression coverage:
  - internal/auth/oidc/domain/types_test.go: 5-case table pinning
    loopback v4/v6, cloud metadata, link-local v4/v6 rejection.
  - internal/auth/oidc/coverage_fill_test.go: same 5 cases against
    Service.TestDiscovery via temporarily restoring the production
    gate.

Closes SEC-001.
This commit is contained in:
shankar0123
2026-05-16 03:31:42 +00:00
parent 67dbd18fda
commit e6cfd756ac
7 changed files with 232 additions and 2 deletions
+10
View File
@@ -29,4 +29,14 @@ func init() {
Timeout: 10 * time.Second,
Transport: http.DefaultTransport,
}
// SEC-001 closure companion: same SSRF-bypass for the discovery
// fetch's http.Client + the static issuer-URL gate. Tests using
// httptest.NewServer get a loopback URL; the production
// SafeHTTPDialContext + validateIssuerSSRF would reject these.
// Production code never reassigns either var.
oidcDiscoveryClient = &http.Client{
Timeout: 10 * time.Second,
Transport: http.DefaultTransport,
}
validateIssuerSSRF = func(string) error { return nil }
}