43 lines
2.6 KiB
Markdown
43 lines
2.6 KiB
Markdown
# HTTP Client
|
|
|
|
Use `go.gearno.de/kit/httpclient` for every outbound HTTP call. Never use `http.DefaultClient` or a bare `&http.Client{}`.
|
|
|
|
## SSRF protection is the default
|
|
|
|
Every call goes through `httpclient.DefaultClient(...)` / `httpclient.DefaultPooledClient(...)` / `httpclient.DefaultPooledTransport(...)` with `httpclient.WithSSRFProtection()` enabled. This applies whenever the destination URL is:
|
|
|
|
- Customer-supplied (webhook endpoint, OAuth2 token URL, SCIM bridge URL, connector-provided base URL)
|
|
- Reached through a customer-supplied connector (OAuth2/APIKey connection clients)
|
|
- A hardcoded third-party provider host (Slack, Linear, GitHub, Google Workspace, Sentry, …) — defense in depth, and public IPs pass the check unchanged
|
|
|
|
```go
|
|
client := httpclient.DefaultPooledClient(
|
|
httpclient.WithLogger(logger),
|
|
httpclient.WithSSRFProtection(),
|
|
)
|
|
```
|
|
|
|
What the option does:
|
|
|
|
- Rejects dials to loopback, RFC 1918 private, RFC 6598 CGNAT, link-local, multicast, unspecified, ULA, IPv4-mapped IPv6, and IETF-reserved ranges. Check runs on the resolved peer IP at connect time, defeating DNS rebinding.
|
|
- On `DefaultClient` / `DefaultPooledClient`, also refuses redirects whose scheme, host, or port differs from the original.
|
|
|
|
## When to omit SSRF protection
|
|
|
|
Only when the target is an **internal service you actively intend to reach** (sandbox-local service, sidecar, in-cluster endpoint with a known private IP). These cases are rare in this codebase — confirm the intent in code review. Do not disable it "just to make a test pass."
|
|
|
|
For tests that need to hit an `httptest` server on loopback, add `httpclient.WithSSRFAllowLoopback()` on top of `WithSSRFProtection()` (or inject a loopback-friendly client into the component under test). Production callers must not use the loopback exemption.
|
|
|
|
## Connector wiring
|
|
|
|
`OAuth2Connector.HTTPClient` is a required field for the token-exchange request. Set it via `connector.ApplyProviderDefaults`, which wires an SSRF-protected client. Don't re-introduce a nil fallback in `CompleteWithState` — callers must provide the client explicitly.
|
|
|
|
`OAuth2Connection.ClientWithOptions`, `RefreshableClient`, `clientCredentialsClient` and `APIKeyConnection.Client` already append `WithSSRFProtection()` internally; additional caller options layer on top.
|
|
|
|
## Summary
|
|
|
|
- Customer-reachable URL → `WithSSRFProtection()`, always.
|
|
- Third-party SaaS → `WithSSRFProtection()`, always (public IPs pass).
|
|
- Local/internal service you meant to dial → document why and omit.
|
|
- Test against `httptest` → `WithSSRFProtection()` + `WithSSRFAllowLoopback()`.
|