From 164c2053eecce315c5d516ccddac704e2834ffc2 Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Sun, 25 Jan 2026 05:57:48 +0000 Subject: [PATCH] docs(gateway): add trusted-proxy auth documentation - Explain when to use and when not to use - Configuration reference with examples - Pomerium, Caddy, nginx, and Traefik setup guides - Security checklist - Troubleshooting guide Closes #1560 Co-Authored-By: Claude Sonnet 4.5 --- docs/gateway/trusted-proxy-auth.md | 258 +++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 docs/gateway/trusted-proxy-auth.md diff --git a/docs/gateway/trusted-proxy-auth.md b/docs/gateway/trusted-proxy-auth.md new file mode 100644 index 00000000000..6b490201109 --- /dev/null +++ b/docs/gateway/trusted-proxy-auth.md @@ -0,0 +1,258 @@ +--- +summary: "Delegate gateway authentication to a trusted reverse proxy (Pomerium, Caddy, nginx + OAuth)" +read_when: + - Running Clawdbot behind an identity-aware proxy + - Setting up Pomerium, Caddy, or nginx with OAuth in front of Clawdbot + - Fixing WebSocket 1008 unauthorized errors with reverse proxy setups +--- +# Trusted Proxy Auth + +> ⚠️ **Security-sensitive feature.** This mode delegates authentication entirely to your reverse proxy. Misconfiguration can expose your Gateway to unauthorized access. Read this page carefully before enabling. + +## When to Use + +Use `trusted-proxy` auth mode when: + +- You run Clawdbot behind an **identity-aware proxy** (Pomerium, Caddy + OAuth, nginx + oauth2-proxy, Traefik + forward auth) +- Your proxy handles all authentication and passes user identity via headers +- You're in a Kubernetes or container environment where the proxy is the only path to the Gateway +- You're hitting WebSocket `1008 unauthorized` errors because browsers can't pass tokens in WS payloads + +## When NOT to Use + +- If you can use `gateway.bind: "loopback"` with Tailscale Serve instead (simpler, safer) +- If your proxy doesn't authenticate users (just a TLS terminator) +- If there's any path to the Gateway that bypasses the proxy +- If you're unsure whether your proxy correctly strips/overwrites forwarded headers + +## How It Works + +1. Your reverse proxy authenticates users (OAuth, OIDC, SAML, etc.) +2. Proxy adds a header with the authenticated user identity (e.g., `x-forwarded-user: nick@example.com`) +3. Clawdbot checks that the request came from a **trusted proxy IP** (configured in `gateway.trustedProxies`) +4. Clawdbot extracts the user identity from the configured header +5. If everything checks out, the request is authorized + +## Configuration + +```json5 +{ + gateway: { + // Must bind to network interface (not loopback) + bind: "lan", + + // CRITICAL: Only add your proxy's IP(s) here + trustedProxies: ["10.0.0.1", "172.17.0.1"], + + auth: { + mode: "trusted-proxy", + trustedProxy: { + // Header containing authenticated user identity (required) + userHeader: "x-forwarded-user", + + // Optional: headers that MUST be present (proxy verification) + requiredHeaders: ["x-forwarded-proto", "x-forwarded-host"], + + // Optional: restrict to specific users (empty = allow all) + allowUsers: ["nick@example.com", "admin@company.org"] + } + } + } +} +``` + +### Configuration Reference + +| Field | Required | Description | +|-------|----------|-------------| +| `gateway.trustedProxies` | Yes | Array of proxy IP addresses to trust. Requests from other IPs are rejected. | +| `gateway.auth.mode` | Yes | Must be `"trusted-proxy"` | +| `gateway.auth.trustedProxy.userHeader` | Yes | Header name containing the authenticated user identity | +| `gateway.auth.trustedProxy.requiredHeaders` | No | Additional headers that must be present for the request to be trusted | +| `gateway.auth.trustedProxy.allowUsers` | No | Allowlist of user identities. Empty means allow all authenticated users. | + +## Proxy Setup Examples + +### Pomerium + +Pomerium passes identity in `x-pomerium-claim-email` (or other claim headers) and a JWT in `x-pomerium-jwt-assertion`. + +```json5 +{ + gateway: { + bind: "lan", + trustedProxies: ["10.0.0.1"], // Pomerium's IP + auth: { + mode: "trusted-proxy", + trustedProxy: { + userHeader: "x-pomerium-claim-email", + requiredHeaders: ["x-pomerium-jwt-assertion"] + } + } + } +} +``` + +Pomerium config snippet: +```yaml +routes: + - from: https://clawdbot.example.com + to: http://clawdbot-gateway:18789 + policy: + - allow: + or: + - email: + is: nick@example.com + pass_identity_headers: true +``` + +### Caddy with OAuth + +Caddy with the `caddy-security` plugin can authenticate users and pass identity headers. + +```json5 +{ + gateway: { + bind: "lan", + trustedProxies: ["127.0.0.1"], // Caddy's IP (if on same host) + auth: { + mode: "trusted-proxy", + trustedProxy: { + userHeader: "x-forwarded-user" + } + } + } +} +``` + +Caddyfile snippet: +``` +clawdbot.example.com { + authenticate with oauth2_provider + authorize with policy1 + + reverse_proxy clawdbot:18789 { + header_up X-Forwarded-User {http.auth.user.email} + } +} +``` + +### nginx + oauth2-proxy + +oauth2-proxy authenticates users and passes identity in `x-auth-request-email`. + +```json5 +{ + gateway: { + bind: "lan", + trustedProxies: ["10.0.0.1"], // nginx/oauth2-proxy IP + auth: { + mode: "trusted-proxy", + trustedProxy: { + userHeader: "x-auth-request-email" + } + } + } +} +``` + +nginx config snippet: +```nginx +location / { + auth_request /oauth2/auth; + auth_request_set $user $upstream_http_x_auth_request_email; + + proxy_pass http://clawdbot:18789; + proxy_set_header X-Auth-Request-Email $user; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; +} +``` + +### Traefik with Forward Auth + +```json5 +{ + gateway: { + bind: "lan", + trustedProxies: ["172.17.0.1"], // Traefik container IP + auth: { + mode: "trusted-proxy", + trustedProxy: { + userHeader: "x-forwarded-user" + } + } + } +} +``` + +## Security Checklist + +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 +- [ ] **Proxy strips headers**: Your proxy overwrites (not appends) `x-forwarded-*` headers from clients +- [ ] **TLS termination**: Your proxy handles TLS; users connect via HTTPS +- [ ] **allowUsers is set** (recommended): Restrict to known users rather than allowing anyone authenticated + +## Security Audit + +`clawdbot security audit` will flag trusted-proxy auth with a **critical** severity finding. This is intentional — it's a reminder that you're delegating security to your proxy setup. + +The audit checks for: +- Missing `trustedProxies` configuration +- Missing `userHeader` configuration +- Empty `allowUsers` (allows any authenticated user) + +## Troubleshooting + +### "trusted_proxy_untrusted_source" + +The request didn't come from an IP in `gateway.trustedProxies`. Check: +- Is the proxy IP correct? (Docker container IPs can change) +- 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_user_missing" + +The user header was empty or missing. Check: +- Is your proxy configured to pass identity headers? +- Is the header name correct? (case-insensitive, but spelling matters) +- Is the user actually authenticated at the proxy? + +### "trusted_proxy_missing_header_*" + +A required header wasn't present. Check: +- Your proxy configuration for those specific headers +- Whether headers are being stripped somewhere in the chain + +### "trusted_proxy_user_not_allowed" + +The user is authenticated but not in `allowUsers`. Either add them or remove the allowlist. + +### WebSocket Still Failing + +Make sure your proxy: +- Supports WebSocket upgrades (`Upgrade: websocket`, `Connection: upgrade`) +- Passes the identity headers on WebSocket upgrade requests (not just HTTP) +- Doesn't have a separate auth path for WebSocket connections + +## Migration from Token Auth + +If you're moving from token auth to trusted-proxy: + +1. Configure your proxy to authenticate users and pass headers +2. Test the proxy setup independently (curl with headers) +3. Update Clawdbot config with trusted-proxy auth +4. Restart the Gateway +5. Test WebSocket connections from the Control UI +6. Run `clawdbot security audit` and review findings + +## Related + +- [Security](/gateway/security) — full security guide +- [Configuration](/gateway/configuration) — config reference +- [Remote Access](/gateway/remote) — other remote access patterns +- [Tailscale](/gateway/tailscale) — simpler alternative for tailnet-only access