diff --git a/docs/gateway/trusted-proxy-auth.md b/docs/gateway/trusted-proxy-auth.md index 6762dba8d3b..30102e1af1a 100644 --- a/docs/gateway/trusted-proxy-auth.md +++ b/docs/gateway/trusted-proxy-auth.md @@ -53,8 +53,8 @@ Implications: ```json5 { gateway: { - // Use loopback for same-host proxy setups; use lan/custom for remote proxy hosts - bind: "loopback", + // Trusted-proxy auth expects requests from a non-loopback trusted proxy source + bind: "lan", // CRITICAL: Only add your proxy's IP(s) here trustedProxies: ["10.0.0.1", "172.17.0.1"], @@ -76,8 +76,12 @@ Implications: } ``` -If `gateway.bind` is `loopback`, include a loopback proxy address in -`gateway.trustedProxies` (`127.0.0.1`, `::1`, or an equivalent loopback CIDR). +Important runtime rule: + +- Trusted-proxy auth rejects loopback-source requests (`127.0.0.1`, `::1`, loopback CIDRs). +- Same-host loopback reverse proxies do **not** satisfy trusted-proxy auth. +- For same-host loopback proxy setups, use token/password auth instead, or route through a non-loopback trusted proxy address that OpenClaw can verify. +- Non-loopback Control UI deployments still need explicit `gateway.controlUi.allowedOrigins`. ### Configuration Reference @@ -179,7 +183,7 @@ Caddy with the `caddy-security` plugin can authenticate users and pass identity { gateway: { bind: "lan", - trustedProxies: ["127.0.0.1"], // Caddy's IP (if on same host) + trustedProxies: ["10.0.0.1"], // Caddy/sidecar proxy IP auth: { mode: "trusted-proxy", trustedProxy: { @@ -282,6 +286,7 @@ Behavior: - When the header is present but empty, the request declares **no** operator scopes. - When the header is absent, normal identity-bearing HTTP APIs fall back to the standard operator default scope set. - Gateway-auth **plugin HTTP routes** are narrower by default: when `x-openclaw-scopes` is absent, their runtime scope falls back to `operator.write`. +- Browser-origin HTTP requests still have to pass `gateway.controlUi.allowedOrigins` (or deliberate Host-header fallback mode) even after trusted-proxy auth succeeds. Practical rule: @@ -295,8 +300,10 @@ Before enabling trusted-proxy auth, verify: - [ ] **Proxy is the only path**: The Gateway port is firewalled from everything except your proxy - [ ] **trustedProxies is minimal**: Only your actual proxy IPs, not entire subnets +- [ ] **No loopback proxy source**: trusted-proxy auth fails closed for loopback-source requests - [ ] **Proxy strips headers**: Your proxy overwrites (not appends) `x-forwarded-*` headers from clients - [ ] **TLS termination**: Your proxy handles TLS; users connect via HTTPS +- [ ] **allowedOrigins is explicit**: Non-loopback Control UI uses explicit `gateway.controlUi.allowedOrigins` - [ ] **allowUsers is set** (recommended): Restrict to known users rather than allowing anyone authenticated - [ ] **No mixed token config**: Do not set both `gateway.auth.token` and `gateway.auth.mode: "trusted-proxy"` @@ -306,9 +313,11 @@ Before enabling trusted-proxy auth, verify: The audit checks for: +- Base `gateway.trusted_proxy_auth` warning/critical reminder - Missing `trustedProxies` configuration - Missing `userHeader` configuration - Empty `allowUsers` (allows any authenticated user) +- Wildcard or missing browser-origin policy on exposed Control UI surfaces ## Troubleshooting @@ -320,6 +329,20 @@ The request didn't come from an IP in `gateway.trustedProxies`. Check: - Is there a load balancer in front of your proxy? - Use `docker inspect` or `kubectl get pods -o wide` to find actual IPs +### "trusted_proxy_loopback_source" + +OpenClaw rejected a loopback-source trusted-proxy request. + +Check: + +- Is the proxy connecting from `127.0.0.1` / `::1`? +- Are you trying to use trusted-proxy auth with a same-host loopback reverse proxy? + +Fix: + +- Use token/password auth for same-host loopback proxy setups, or +- Route through a non-loopback trusted proxy address and keep that IP in `gateway.trustedProxies`. + ### "trusted_proxy_user_missing" The user header was empty or missing. Check: @@ -339,6 +362,16 @@ A required header wasn't present. Check: The user is authenticated but not in `allowUsers`. Either add them or remove the allowlist. +### "trusted_proxy_origin_not_allowed" + +Trusted-proxy auth succeeded, but the browser `Origin` header did not pass Control UI origin checks. + +Check: + +- `gateway.controlUi.allowedOrigins` includes the exact browser origin +- You are not relying on wildcard origins unless you intentionally want allow-all behavior +- If you intentionally use Host-header fallback mode, `gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=true` is set deliberately + ### WebSocket Still Failing Make sure your proxy: