mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 21:11:30 +00:00
e6cfd756ac
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.
43 lines
1.6 KiB
Go
43 lines
1.6 KiB
Go
// Test-only setup for the internal/auth/oidc package.
|
|
//
|
|
// Bundle 5 closure (audit R6) wrapped the package's jwks reachability
|
|
// probe in validation.SafeHTTPDialContext so production OIDC config
|
|
// dry-runs can't be pivoted into reserved-address ranges via DNS
|
|
// rebinding. The test suite uses httptest.NewServer which binds to
|
|
// 127.0.0.1 — that's exactly the reserved-address case the production
|
|
// guard refuses to dial, so the package-level jwksProbeClient is
|
|
// replaced here with an SSRF-guard-bypassed http.Client for the
|
|
// duration of every test in this package.
|
|
//
|
|
// Mirrors the internal/connector/notifier/webhook + slack + teams
|
|
// test-seam pattern (newForTest constructor). The production code
|
|
// never reassigns jwksProbeClient — only this _test.go file does, so
|
|
// the test seam can't leak into a real deployment.
|
|
|
|
package oidc
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
func init() {
|
|
// Replace the SSRF-safe transport with one that has no
|
|
// DialContext override. http.DefaultTransport handles 127.0.0.1
|
|
// without complaint, which is what httptest.NewServer needs.
|
|
jwksProbeClient = &http.Client{
|
|
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 }
|
|
}
|