AttestoPhoenix.ClientIdMetadata.Fetcher.Req (AttestoPhoenix v4.2.1)

Copy Markdown View Source

The default, SSRF-guarded Client ID Metadata Document fetcher - CIMD (draft-ietf-oauth-client-id-metadata-document-02, IETF OAuth WG).

This is the only component of the CIMD feature that makes an outbound request, so it is where the draft's Security Considerations are enforced. Implements AttestoPhoenix.ClientIdMetadata.Fetcher with a one-shot Mint connection. Req remains the optional dependency gate and injectable test transport, but production requests do not create hostname-keyed Finch pools.

SSRF algorithm (draft Security Considerations)

fetch/2 runs the following, erroring closed at the first failure:

  1. Re-validate the URL is https and satisfies the draft §2 grammar via Attesto.ClientIdMetadata.validate_client_id/1 (defense in depth - the caller is never trusted). Failure -> {:error, {:invalid_url, reason}}.
  2. Resolve the host to A/AAAA records through the injectable :resolver (defaults to :inet.getaddrs/2 for both :inet and :inet6). No records -> {:error, :unresolvable}. The resolver seam lets tests inject addresses without real DNS, and is the hook the DNS-rebinding defense below pins against.
  3. Reject special-use IPs (RFC 6890). Every resolved address is checked against special_use_ip?/1; if any is special-use the fetch is refused with {:error, {:blocked_ip, ip}}. Loopback is permitted only when :allow_loopback is true (development).
  4. Pin to a validated IP. The request is dialed at one checked address (Mint's :hostname connect option keeps TLS SNI, certificate hostname verification, and the Host header on the original hostname while the socket targets the pinned IP), closing the DNS-rebinding TOCTOU between the check in step 3 and the connect: the name cannot be re-resolved to an internal address after it was validated.
  5. GET with Accept: application/json, one total deadline covering DNS resolution, connect, send, and receive (:request_timeout_ms, default 5_000), and redirects disabled (draft MUST). Any redirect is surfaced as its 3xx status.
  6. Status must be 200; any other status -> {:error, {:status, n}}.
  7. Content-Type must be application/json or application/<x>+json; otherwise {:error, :bad_content_type}.
  8. Size cap. The body is refused once it exceeds :max_document_bytes (default 5_120) -> {:error, :too_large}.

On success returns {:ok, %{body: body, cache_control: directives}} where directives are the parsed Cache-Control / Expires freshness hints (RFC 9111) for the caller to clamp and store.

Options

  • :resolver - a 2-arity DNS resolver (charlist_host, :inet | :inet6 -> {:ok, [:inet.ip_address()]} | {:error, term()}). Defaults to :inet.getaddrs/2. Injected by tests to exercise the SSRF guard and the DNS-rebinding pin without real DNS.
  • :allow_loopback - when true, loopback addresses (127.0.0.0/8, ::1) are permitted (the draft's "AS runs on loopback" exception). Default false.
  • :max_document_bytes - positive body size cap. Default 5_120 (draft's recommended 5 KB).
  • :request_timeout_ms - positive total DNS and HTTP request timeout. Default 5_000.
  • :req_options - extra options merged into the underlying Req request (e.g. test transport overrides). Escape hatch; not part of the public contract.

Summary

Types

A resolved IP address, as returned by :inet.getaddrs/2.

Functions

Fetch a validated CIMD client_id URL under the SSRF algorithm documented on this module. See AttestoPhoenix.ClientIdMetadata.Fetcher for the contract.

Delegates to the always-available hostname and address policy.

Types

ip()

@type ip() :: :inet.ip_address()

A resolved IP address, as returned by :inet.getaddrs/2.

Functions

fetch(url, opts \\ [])

@spec fetch(String.t(), keyword()) ::
  {:ok, AttestoPhoenix.ClientIdMetadata.Fetcher.result()} | {:error, term()}

Fetch a validated CIMD client_id URL under the SSRF algorithm documented on this module. See AttestoPhoenix.ClientIdMetadata.Fetcher for the contract.

special_use_ip?(ip)

@spec special_use_ip?(ip()) :: boolean()

Delegates to the always-available hostname and address policy.