Skip to content
CommonLayer
Documentation menu
On this page

ADR-0040: A forwarded client address is honoured only behind a declared proxy

Status: Accepted (WP-70g, WP-70e). Implemented in crates/commonlayer-auth/src/http/middleware.rs (client_ip), apps/web/src/lib/api/forwarding.ts, the Helm chart (config.trustedProxies, web.behindTrustedProxy) and docker-compose.yml (CL_STACK_TRUSTED_PROXIES, CL_STACK_WEB_BEHIND_TRUSTED_PROXY).

Context

Per-IP rate limits (anonymous API, registration, sign-in, registry tokens) and audit records need the client's address. Behind an ingress, the direct peer is the ingress; for server-side rendering, the API's peer is the web server. The first version of the Compose stack trusted the web container and let it forward the visitor's X-Forwarded-For as received: in the publish-drill session a forged X-Forwarded-For: 203.0.113.8 sent to the web container reached the API as the client address through the /api rewrite, so any visitor could choose their rate-limit bucket.

Decision

  • The API takes the client address from X-Forwarded-For only when the direct peer is in server.trusted_proxies, and then uses the right-most entry that is not itself a trusted proxy. Default: no trusted proxies.
  • The web server forwards a visitor's address on its server-side API calls only when it is declared to run behind a trusted proxy (BEHIND_TRUSTED_PROXY=true, chart value web.behindTrustedProxy), that is, when an ingress in front of it appends the real address. Off by default.
  • Compose trusts no proxy by default, so all portal traffic in make stack counts against the web container's address. The e2e workflow's stack trusts the web container and lets it forward, in that stack only, so per-test addresses work.

Consequences

  • A deployment that wants per-visitor limits for portal traffic must run an ingress that appends the client address, list the web pods (or the ingress) in server.trusted_proxies, and set web.behindTrustedProxy. Without an ingress in front of the portal a client could again choose its own bucket, so production must keep one.
  • The behaviour was verified against the Compose stack with forged headers (WP-70g): defaults → the web container's address; e2e settings → the forwarded address. The Helm settings are covered by chart unit tests, not by a cluster install.
  • ADR-0012 said the web server must be listed in server.trusted_proxies; that is now conditional on the above.