Skip to content

Add a configurable trusted client-IP header for CDN-fronted deployments #1041

Description

@jevansnyc

Problem

Trusted Server on Fastly resolves the client IP solely from req.get_client_ip_addr() (crates/trusted-server-adapter-fastly/src/platform.rs:708), which is the immediate downstream peer. Any deployment with a CDN or another Fastly service in front therefore sees the fronting node's IP rather than the reader's, which corrupts geo, EC ID generation, cluster classification, and consent jurisdiction. Measured evidence is in the linked bug.

The Cloudflare and Spin adapters already read a client-IP header. Fastly has no header path at all.

Why this is not a one-line change

The obvious fix, preferring Fastly-Client-IP, is unsafe on its own. Fastly does not protect that header at ingress: "The value is not protected from modification at the edge of the Fastly network, so if a client sets this header themselves, we will use it." It is also absent from SPOOFABLE_FORWARDED_HEADERS (crates/trusted-server-core/src/http_util.rs:40-45), so TS forwards whatever arrives.

Because that value becomes the HMAC input for EC ID generation (crates/trusted-server-core/src/ec/generation.rs:91), trusting it unconditionally would let any caller select their own EC identity, and drive the cluster counter, by setting a request header. That is a worse failure than the bug it fixes.

The Cloudflare adapter's use of cf-connecting-ip is safe only because Cloudflare overwrites that header at ingress. Fastly does not, so mirroring the Cloudflare pattern onto the Fastly adapter would look consistent while being unsound.

Proposal

A configurable trusted client-IP header, opt-in, with the trust boundary established by a shared secret set at the front door rather than by header presence.

  • New optional config. Absent by default, so existing deployments keep current behavior and there is no silent change to EC ID derivation.
  • When configured, the adapter reads the client IP from the named header only if the accompanying secret matches, and otherwise falls back to req.get_client_ip_addr().
  • When not configured, the header name is added to SPOOFABLE_FORWARDED_HEADERS so it is stripped on entry.
  • Values that fail to parse as an IP fall back rather than erroring.

Using a shared secret rather than Fastly-FF or peer detection keeps one mechanism working across every fronting topology TS supports: a Fastly delivery service, Akamai, Cloudflare, or CloudFront. Fastly's own guidance for validating a Fastly hop is fastly.ff.visits_this_service, a VCL variable with no confirmed Compute equivalent, and Fastly-FF can be present on requests originating outside the network.

Known limitation to document

Fastly's no-code request routing provides no place to inject headers. If a deployment fronts TS that way and the client IP is not preserved by the platform, there is no header for TS to read and this mechanism cannot help. Whether request routing preserves the client IP is unverified and worth measuring separately, since it determines whether that topology is viable for EC at all.

Acceptance

  • Config absent: behavior identical to today, header stripped on entry.
  • Config present with a valid secret: geo and EC derive from the forwarded IP.
  • Config present with a missing, wrong, or malformed secret: falls back to the peer IP, no error.
  • Documentation covers the front-door requirement, since a reader without a correctly configured front door is the spoofing hole.

References

Bug and measured evidence: #1040

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions