feat(web): forward end-user client IP to bunyip-api #361

Merged
Claude-Run merged 1 commit from feat/BUNYIP-311-web-forward-client-ip into main 2026-07-11 04:44:39 +02:00
Member

bunyip-web is an SSR BFF: the browser talks only to it (through Traefik) and it calls bunyip-api server-to-server, so bunyip-api saw the BFF process as the peer and lost the end-user IP for logging, rate-limiting, and audit. This adds the missing second hop of the trust chain (Traefik to bunyip-web to bunyip-api).

A per-request middleware (client_ip::forward_client_ip) resolves the end-user IP from the inbound X-Forwarded-For / X-Real-IP, honoured ONLY when bunyip-web's own socket peer is inside its TRUSTED_PROXY_CIDR; otherwise it forwards nothing rather than fabricate an IP. The resolved IP is scoped into a task-local so all three outbound API send paths (JSON send, streaming get_stream, multipart post_form) attach it as X-Forwarded-For at one choke point, instead of threading it through every handler call site. serve now uses into_make_service_with_connect_info so the middleware can read the socket peer.

bunyip-api then reads that header as the external client when bunyip-web's address is in bunyip-api's TRUSTED_PROXY_CIDR. compose.yml wires both hops (WEB_TRUSTED_PROXY_CIDR for the web service, TRUSTED_PROXY_CIDR for the api service) and docs/client-ip-forwarding.md plus .env.example document the two-hop trust chain and the required CIDR entries on each service.

#BUNYIP-311

bunyip-web is an SSR BFF: the browser talks only to it (through Traefik) and it calls bunyip-api server-to-server, so bunyip-api saw the BFF process as the peer and lost the end-user IP for logging, rate-limiting, and audit. This adds the missing second hop of the trust chain (Traefik to bunyip-web to bunyip-api). A per-request middleware (`client_ip::forward_client_ip`) resolves the end-user IP from the inbound X-Forwarded-For / X-Real-IP, honoured ONLY when bunyip-web's own socket peer is inside its `TRUSTED_PROXY_CIDR`; otherwise it forwards nothing rather than fabricate an IP. The resolved IP is scoped into a task-local so all three outbound API send paths (JSON `send`, streaming `get_stream`, multipart `post_form`) attach it as X-Forwarded-For at one choke point, instead of threading it through every handler call site. `serve` now uses `into_make_service_with_connect_info` so the middleware can read the socket peer. bunyip-api then reads that header as the external client when bunyip-web's address is in bunyip-api's `TRUSTED_PROXY_CIDR`. compose.yml wires both hops (WEB_TRUSTED_PROXY_CIDR for the web service, TRUSTED_PROXY_CIDR for the api service) and docs/client-ip-forwarding.md plus .env.example document the two-hop trust chain and the required CIDR entries on each service. #BUNYIP-311
feat(web): forward end-user client IP to bunyip-api
All checks were successful
E2E / Playwright against deployment (pull_request) Successful in 19s
Check / fmt + clippy + build + tests (pull_request) Successful in 18m22s
Create release / Create release from merged PR (pull_request) Has been skipped
7d527a9263
bunyip-web is an SSR BFF: the browser talks only to it (through Traefik) and it calls bunyip-api server-to-server, so bunyip-api saw the BFF process as the peer and lost the end-user IP for logging, rate-limiting, and audit. This adds the missing second hop of the trust chain (Traefik to bunyip-web to bunyip-api).

A per-request middleware (`client_ip::forward_client_ip`) resolves the end-user IP from the inbound X-Forwarded-For / X-Real-IP, honoured ONLY when bunyip-web's own socket peer is inside its `TRUSTED_PROXY_CIDR`; otherwise it forwards nothing rather than fabricate an IP. The resolved IP is scoped into a task-local so all three outbound API send paths (JSON `send`, streaming `get_stream`, multipart `post_form`) attach it as X-Forwarded-For at one choke point, instead of threading it through every handler call site. `serve` now uses `into_make_service_with_connect_info` so the middleware can read the socket peer.

bunyip-api then reads that header as the external client when bunyip-web's address is in bunyip-api's `TRUSTED_PROXY_CIDR`. compose.yml wires both hops (WEB_TRUSTED_PROXY_CIDR for the web service, TRUSTED_PROXY_CIDR for the api service) and docs/client-ip-forwarding.md plus .env.example document the two-hop trust chain and the required CIDR entries on each service.

#BUNYIP-311
Claude-Run deleted branch feat/BUNYIP-311-web-forward-client-ip 2026-07-11 04:44:39 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/bunyip!361
No description provided.