chore(infra): verify + surface two-hop trusted-proxy posture (BUNYIP-476) #472

Merged
longjacksonle merged 1 commit from chore/BUNYIP-476-verify-trusted-proxy into main 2026-08-04 21:48:30 +02:00

Closes BUNYIP-476, the last BUNYIP-437 follow-up: verify the two-hop TRUSTED_PROXY_CIDR config so audited-login IPs resolve, and document the requirement.

What I verified

The "audit records a Docker IP while auto-ban resolves correctly" symptom is a topology/config condition, not a resolver bug. The shared extract_client_ip already serves every surface; audited logins take the two-hop BFF path (Traefik -> bunyip-web -> bunyip-api) and resolve the real browser IP only when bunyip-web re-forwards X-Forwarded-For (peer Traefik in WEB_TRUSTED_PROXY_CIDR) AND bunyip-api trusts bunyip-web's container peer (in TRUSTED_PROXY_CIDR). Auto-ban's single-hop direct-to-API endpoints resolve regardless, hence the disagreement.

The config is already correct by default: compose.yml / compose.dev.yml default both TRUSTED_PROXY_CIDR (api) and WEB_TRUSTED_PROXY_CIDR (web) to 172.16.0.0/12,10.0.0.0/8,192.168.0.0/16, which spans the private ranges Docker assigns container/Traefik addresses, so the two-hop path resolves out of the box. The real residual risk is a silent misconfig: an empty or narrowed CIDR that omits bunyip-web's address drops the forwarded IP with no signal, and the audited actor_ip_address / access-log IP / rate-limit key silently fall back to the bunyip-web peer.

What I changed

  • Boot diagnostic (main.rs + Config::trusts_forwarded_client_ip): bunyip-api logs its posture once at startup - an info line when a CIDR is set, a WARN naming the exact fallback when the list is empty. A WARN on a TLS/BFF deployment is the misconfig, now diagnosable from logs rather than data.
  • docs/client-ip-forwarding.md: a new "audited-login path" section - how actor_ip_address depends on both hops, how to verify (boot log + the newest audit_log row's ip_address), and that the compose defaults already cover it.
  • dev-sso runbook: obstacle 6.10 for the same symptom, pointing at the walkthrough.

Acceptance

  • Audited-login actor_ip_address resolves to the real client on the two-hop path with the default (and any correct) TRUSTED_PROXY_CIDR; a misconfig that would break it now surfaces as a boot WARN.
  • The trusted-proxy requirement is documented (the client-IP doc + the dev-sso runbook).

Verification

just check-container green (fmt, clippy -D warnings, 182 tests incl. a new test for the boot-diagnostic predicate). No resolver/code-path change - the resolver was already correct and shared; this is verification + observability + docs.

🤖 Generated with Claude Code

https://claude.ai/code/session_018TXaT3P192nDsZzbzHETb9

Closes [BUNYIP-476](https://niceguyit.myjetbrains.com/youtrack/issue/BUNYIP-476), the last BUNYIP-437 follow-up: verify the two-hop `TRUSTED_PROXY_CIDR` config so audited-login IPs resolve, and document the requirement. ## What I verified The "audit records a Docker IP while auto-ban resolves correctly" symptom is a topology/config condition, not a resolver bug. The shared `extract_client_ip` already serves every surface; audited logins take the two-hop BFF path (Traefik -> bunyip-web -> bunyip-api) and resolve the real browser IP only when bunyip-web re-forwards `X-Forwarded-For` (peer Traefik in `WEB_TRUSTED_PROXY_CIDR`) AND bunyip-api trusts bunyip-web's container peer (in `TRUSTED_PROXY_CIDR`). Auto-ban's single-hop direct-to-API endpoints resolve regardless, hence the disagreement. The config is **already correct by default**: `compose.yml` / `compose.dev.yml` default both `TRUSTED_PROXY_CIDR` (api) and `WEB_TRUSTED_PROXY_CIDR` (web) to `172.16.0.0/12,10.0.0.0/8,192.168.0.0/16`, which spans the private ranges Docker assigns container/Traefik addresses, so the two-hop path resolves out of the box. The real residual risk is a **silent** misconfig: an empty or narrowed CIDR that omits bunyip-web's address drops the forwarded IP with no signal, and the audited `actor_ip_address` / access-log IP / rate-limit key silently fall back to the bunyip-web peer. ## What I changed - **Boot diagnostic** (`main.rs` + `Config::trusts_forwarded_client_ip`): bunyip-api logs its posture once at startup - an info line when a CIDR is set, a `WARN` naming the exact fallback when the list is empty. A `WARN` on a TLS/BFF deployment is the misconfig, now diagnosable from logs rather than data. - **`docs/client-ip-forwarding.md`**: a new "audited-login path" section - how `actor_ip_address` depends on both hops, how to verify (boot log + the newest `audit_log` row's `ip_address`), and that the compose defaults already cover it. - **dev-sso runbook**: obstacle 6.10 for the same symptom, pointing at the walkthrough. ## Acceptance - Audited-login `actor_ip_address` resolves to the real client on the two-hop path with the default (and any correct) `TRUSTED_PROXY_CIDR`; a misconfig that would break it now surfaces as a boot `WARN`. - The trusted-proxy requirement is documented (the client-IP doc + the dev-sso runbook). ## Verification `just check-container` green (fmt, clippy `-D warnings`, 182 tests incl. a new test for the boot-diagnostic predicate). No resolver/code-path change - the resolver was already correct and shared; this is verification + observability + docs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_018TXaT3P192nDsZzbzHETb9
chore(infra): verify + surface two-hop trusted-proxy posture (BUNYIP-476)
All checks were successful
E2E PR gate / Install + reachability (no deployment secrets) (pull_request) Successful in 28s
Check / fmt + clippy + build + tests (pull_request) Successful in 4m33s
Create release / Create release from merged PR (pull_request) Has been skipped
2596a65b5b
Closes the last BUNYIP-437 follow-up. The reported "audit records a Docker IP while auto-ban resolves correctly" symptom is a topology/config condition, not a resolver bug: audited logins take the two-hop BFF path (Traefik -> bunyip-web -> bunyip-api), and the shared extract_client_ip resolves the real browser IP there only when bunyip-web re-forwards X-Forwarded-For and bunyip-api trusts bunyip-web's container peer via TRUSTED_PROXY_CIDR. Auto-ban's single-hop direct-to-API endpoints resolve regardless, which is why they can disagree.

Verified the config is already correct by default: compose.yml / compose.dev.yml default both TRUSTED_PROXY_CIDR (api) and WEB_TRUSTED_PROXY_CIDR (web) to 172.16.0.0/12,10.0.0.0/8,192.168.0.0/16, which spans the private ranges Docker assigns container and Traefik addresses, so the two-hop path resolves out of the box. The remaining risk is a silent misconfiguration: an empty or narrowed TRUSTED_PROXY_CIDR that omits bunyip-web's address drops the forwarded IP with no signal, and the audited actor_ip_address, access-log IP, and per-IP rate-limit key all fall back to the bunyip-web peer.

Makes that visible and documents it:

- bunyip-api logs its trusted-proxy posture once at boot (Config::trusts_forwarded_client_ip): an info line when a CIDR is configured, a WARN naming the exact fallback (audit actor_ip_address / access log / rate-limit key attributed to the bunyip-web peer) when the list is empty. A WARN on a TLS/BFF deployment is the misconfiguration, diagnosable from the logs rather than the data.
- docs/client-ip-forwarding.md gains an "audited-login path" section: how the actor_ip_address depends on both hops, how to verify it (the boot log and the newest audit_log row's ip_address), and that the compose defaults already cover it.
- The dev-sso runbook gains obstacle 6.10 for the same symptom, pointing at the walkthrough.

A unit test covers the boot-diagnostic predicate (configured CIDR trusts forwarding; empty does not). Verified with just check-container (fmt, clippy -D warnings, 182 tests). No code path or resolver change - the resolver was already correct and shared.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018TXaT3P192nDsZzbzHETb9
longjacksonle scheduled this pull request to auto merge when all checks succeed 2026-08-04 21:46:22 +02:00
longjacksonle deleted branch chore/BUNYIP-476-verify-trusted-proxy 2026-08-04 21:48:30 +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!472
No description provided.