Ankusa.Net.ClientIP (ankusa v0.7.0)

Copy Markdown View Source

Decide which address a request actually came from.

This is the piece an allowlist cannot afford to get wrong: X-Forwarded-For is client-controlled, so reading it for a peer that is not itself a trusted proxy is the same as letting the caller choose its own IP and walk straight through the route guard.

The rule, in full:

  1. The peer is conn.remote_ip, normalized.
  2. No trusted proxies configured, or the peer matches none of them → the peer is the client. The header is not read at all.
  3. Otherwise the peer is a proxy, so the chain is trustworthy as far as this node can tell: every x-forwarded-for value is joined in the order the request carried them, split on commas and trimmed, and empty entries are dropped. Nothing left → the peer.
  4. The chain is walked right to left, one entry at a time and only as far as needed. Proxies append, so the rightmost entries are the ones this node's own proxies wrote, and the rightmost untrusted entry is the closest client we can vouch for: that entry is the client, and the entries to its left — the prefix a client can forge — are never even parsed. An entry that cannot be read is :error: a chain we cannot fully account for is not one we believe, and the request is denied rather than falling back to the proxy's own address, which is exactly what a forged entry would be trying to achieve. If every entry is trusted, the leftmost one is the client.
  5. An entry is a bare IPv4 or IPv6 address, or one of the host:port forms 1.2.3.4:5678 and [2001:db8::1]:443 (the brackets are how an IPv6 address, which owns colons itself, carries a port). Anything else — garbage, 1.2.3.4:abc, 1.2.3.4:99999, [::1, a non-UTF-8 byte — is an entry that cannot be read.

The result is normalized, so a peer or an entry written as the IPv4-mapped ::ffff:1.2.3.4 resolves to an IPv4 address and matches IPv4 rules.

Summary

Functions

Resolve the client address for conn, given the CIDRs whose peers may set X-Forwarded-For.

Functions

resolve(conn, trusted_proxies)

@spec resolve(Plug.Conn.t(), [Ankusa.Net.cidr()]) ::
  {:ok, :inet.ip_address()} | :error

Resolve the client address for conn, given the CIDRs whose peers may set X-Forwarded-For.

:error when conn.remote_ip is nil — a conn with no peer at all (a test conn with the field cleared) — or when an entry the chain depends on cannot be read. Callers treat both as an IP rejection: there is no address to match against rules, and neither case may quietly become the proxy's own address.