mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 14:30:45 +00:00
docs(trusted-proxy-auth): rewrite with Steps for handshake, Tabs for TLS, AccordionGroup for proxy examples and troubleshooting
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
---
|
||||
summary: "Delegate gateway authentication to a trusted reverse proxy (Pomerium, Caddy, nginx + OAuth)"
|
||||
title: "Trusted proxy auth"
|
||||
sidebarTitle: "Trusted proxy auth"
|
||||
read_when:
|
||||
- Running OpenClaw behind an identity-aware proxy
|
||||
- Setting up Pomerium, Caddy, or nginx with OAuth in front of OpenClaw
|
||||
@@ -8,37 +9,49 @@ read_when:
|
||||
- Deciding where to set HSTS and other HTTP hardening headers
|
||||
---
|
||||
|
||||
> ⚠️ **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.
|
||||
<Warning>
|
||||
**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.
|
||||
</Warning>
|
||||
|
||||
## When to Use
|
||||
## When to use
|
||||
|
||||
Use `trusted-proxy` auth mode when:
|
||||
|
||||
- You run OpenClaw 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
|
||||
- You run OpenClaw 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
|
||||
## When NOT to use
|
||||
|
||||
- If your proxy doesn't authenticate users (just a TLS terminator or load balancer)
|
||||
- If there's any path to the Gateway that bypasses the proxy (firewall holes, internal network access)
|
||||
- If you're unsure whether your proxy correctly strips/overwrites forwarded headers
|
||||
- If you only need personal single-user access (consider Tailscale Serve + loopback for simpler setup)
|
||||
- If your proxy doesn't authenticate users (just a TLS terminator or load balancer).
|
||||
- If there's any path to the Gateway that bypasses the proxy (firewall holes, internal network access).
|
||||
- If you're unsure whether your proxy correctly strips/overwrites forwarded headers.
|
||||
- If you only need personal single-user access (consider Tailscale Serve + loopback for simpler setup).
|
||||
|
||||
## How It Works
|
||||
## 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. OpenClaw checks that the request came from a **trusted proxy IP** (configured in `gateway.trustedProxies`)
|
||||
4. OpenClaw extracts the user identity from the configured header
|
||||
5. If everything checks out, the request is authorized
|
||||
<Steps>
|
||||
<Step title="Proxy authenticates the user">
|
||||
Your reverse proxy authenticates users (OAuth, OIDC, SAML, etc.).
|
||||
</Step>
|
||||
<Step title="Proxy adds an identity header">
|
||||
Proxy adds a header with the authenticated user identity (e.g., `x-forwarded-user: nick@example.com`).
|
||||
</Step>
|
||||
<Step title="Gateway verifies trusted source">
|
||||
OpenClaw checks that the request came from a **trusted proxy IP** (configured in `gateway.trustedProxies`).
|
||||
</Step>
|
||||
<Step title="Gateway extracts identity">
|
||||
OpenClaw extracts the user identity from the configured header.
|
||||
</Step>
|
||||
<Step title="Authorize">
|
||||
If everything checks out, the request is authorized.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Control UI Pairing Behavior
|
||||
## Control UI pairing behavior
|
||||
|
||||
When `gateway.auth.mode = "trusted-proxy"` is active and the request passes
|
||||
trusted-proxy checks, Control UI WebSocket sessions can connect without device
|
||||
pairing identity.
|
||||
When `gateway.auth.mode = "trusted-proxy"` is active and the request passes trusted-proxy checks, Control UI WebSocket sessions can connect without device pairing identity.
|
||||
|
||||
Implications:
|
||||
|
||||
@@ -74,61 +87,73 @@ Implications:
|
||||
}
|
||||
```
|
||||
|
||||
Important runtime rule:
|
||||
<Warning>
|
||||
**Important runtime rules**
|
||||
|
||||
- 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`.
|
||||
- **Forwarded-header evidence overrides loopback locality.** If a request arrives on loopback but carries `X-Forwarded-For` / `X-Forwarded-Host` / `X-Forwarded-Proto` headers pointing at a non-local origin, that evidence disqualifies the loopback locality claim. The request is treated as remote for pairing, trusted-proxy auth, and Control UI device-identity gating. This prevents a same-host loopback proxy from laundering forwarded-header identity into trusted-proxy auth.
|
||||
</Warning>
|
||||
|
||||
### Configuration Reference
|
||||
### 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. |
|
||||
<ParamField path="gateway.trustedProxies" type="string[]" required>
|
||||
Array of proxy IP addresses to trust. Requests from other IPs are rejected.
|
||||
</ParamField>
|
||||
<ParamField path="gateway.auth.mode" type="string" required>
|
||||
Must be `"trusted-proxy"`.
|
||||
</ParamField>
|
||||
<ParamField path="gateway.auth.trustedProxy.userHeader" type="string" required>
|
||||
Header name containing the authenticated user identity.
|
||||
</ParamField>
|
||||
<ParamField path="gateway.auth.trustedProxy.requiredHeaders" type="string[]">
|
||||
Additional headers that must be present for the request to be trusted.
|
||||
</ParamField>
|
||||
<ParamField path="gateway.auth.trustedProxy.allowUsers" type="string[]">
|
||||
Allowlist of user identities. Empty means allow all authenticated users.
|
||||
</ParamField>
|
||||
|
||||
## TLS termination and HSTS
|
||||
|
||||
Use one TLS termination point and apply HSTS there.
|
||||
|
||||
### Recommended pattern: proxy TLS termination
|
||||
<Tabs>
|
||||
<Tab title="Proxy TLS termination (recommended)">
|
||||
When your reverse proxy handles HTTPS for `https://control.example.com`, set `Strict-Transport-Security` at the proxy for that domain.
|
||||
|
||||
When your reverse proxy handles HTTPS for `https://control.example.com`, set
|
||||
`Strict-Transport-Security` at the proxy for that domain.
|
||||
- Good fit for internet-facing deployments.
|
||||
- Keeps certificate + HTTP hardening policy in one place.
|
||||
- OpenClaw can stay on loopback HTTP behind the proxy.
|
||||
|
||||
- Good fit for internet-facing deployments.
|
||||
- Keeps certificate + HTTP hardening policy in one place.
|
||||
- OpenClaw can stay on loopback HTTP behind the proxy.
|
||||
Example header value:
|
||||
|
||||
Example header value:
|
||||
```text
|
||||
Strict-Transport-Security: max-age=31536000; includeSubDomains
|
||||
```
|
||||
|
||||
```text
|
||||
Strict-Transport-Security: max-age=31536000; includeSubDomains
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Gateway TLS termination">
|
||||
If OpenClaw itself serves HTTPS directly (no TLS-terminating proxy), set:
|
||||
|
||||
### Gateway TLS termination
|
||||
|
||||
If OpenClaw itself serves HTTPS directly (no TLS-terminating proxy), set:
|
||||
|
||||
```json5
|
||||
{
|
||||
gateway: {
|
||||
tls: { enabled: true },
|
||||
http: {
|
||||
securityHeaders: {
|
||||
strictTransportSecurity: "max-age=31536000; includeSubDomains",
|
||||
```json5
|
||||
{
|
||||
gateway: {
|
||||
tls: { enabled: true },
|
||||
http: {
|
||||
securityHeaders: {
|
||||
strictTransportSecurity: "max-age=31536000; includeSubDomains",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
}
|
||||
```
|
||||
|
||||
`strictTransportSecurity` accepts a string header value, or `false` to disable explicitly.
|
||||
`strictTransportSecurity` accepts a string header value, or `false` to disable explicitly.
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Rollout guidance
|
||||
|
||||
@@ -138,124 +163,126 @@ If OpenClaw itself serves HTTPS directly (no TLS-terminating proxy), set:
|
||||
- Use preload only if you intentionally meet preload requirements for your full domain set.
|
||||
- Loopback-only local development does not benefit from HSTS.
|
||||
|
||||
## Proxy Setup Examples
|
||||
## Proxy setup examples
|
||||
|
||||
### Pomerium
|
||||
<AccordionGroup>
|
||||
<Accordion title="Pomerium">
|
||||
Pomerium passes identity in `x-pomerium-claim-email` (or other claim headers) and a JWT in `x-pomerium-jwt-assertion`.
|
||||
|
||||
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"],
|
||||
```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://openclaw.example.com
|
||||
to: http://openclaw-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: ["10.0.0.1"], // Caddy/sidecar proxy IP
|
||||
auth: {
|
||||
mode: "trusted-proxy",
|
||||
trustedProxy: {
|
||||
userHeader: "x-forwarded-user",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Caddyfile snippet:
|
||||
|
||||
```
|
||||
openclaw.example.com {
|
||||
authenticate with oauth2_provider
|
||||
authorize with policy1
|
||||
|
||||
reverse_proxy openclaw:18789 {
|
||||
header_up X-Forwarded-User {http.auth.user.email}
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
### nginx + oauth2-proxy
|
||||
Pomerium config snippet:
|
||||
|
||||
oauth2-proxy authenticates users and passes identity in `x-auth-request-email`.
|
||||
```yaml
|
||||
routes:
|
||||
- from: https://openclaw.example.com
|
||||
to: http://openclaw-gateway:18789
|
||||
policy:
|
||||
- allow:
|
||||
or:
|
||||
- email:
|
||||
is: nick@example.com
|
||||
pass_identity_headers: true
|
||||
```
|
||||
|
||||
```json5
|
||||
{
|
||||
gateway: {
|
||||
bind: "lan",
|
||||
trustedProxies: ["10.0.0.1"], // nginx/oauth2-proxy IP
|
||||
auth: {
|
||||
mode: "trusted-proxy",
|
||||
trustedProxy: {
|
||||
userHeader: "x-auth-request-email",
|
||||
</Accordion>
|
||||
<Accordion title="Caddy with OAuth">
|
||||
Caddy with the `caddy-security` plugin can authenticate users and pass identity headers.
|
||||
|
||||
```json5
|
||||
{
|
||||
gateway: {
|
||||
bind: "lan",
|
||||
trustedProxies: ["10.0.0.1"], // Caddy/sidecar proxy IP
|
||||
auth: {
|
||||
mode: "trusted-proxy",
|
||||
trustedProxy: {
|
||||
userHeader: "x-forwarded-user",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
}
|
||||
```
|
||||
|
||||
nginx config snippet:
|
||||
Caddyfile snippet:
|
||||
|
||||
```nginx
|
||||
location / {
|
||||
auth_request /oauth2/auth;
|
||||
auth_request_set $user $upstream_http_x_auth_request_email;
|
||||
```
|
||||
openclaw.example.com {
|
||||
authenticate with oauth2_provider
|
||||
authorize with policy1
|
||||
|
||||
proxy_pass http://openclaw: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";
|
||||
}
|
||||
```
|
||||
reverse_proxy openclaw:18789 {
|
||||
header_up X-Forwarded-User {http.auth.user.email}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Traefik with Forward Auth
|
||||
</Accordion>
|
||||
<Accordion title="nginx + oauth2-proxy">
|
||||
oauth2-proxy authenticates users and passes identity in `x-auth-request-email`.
|
||||
|
||||
```json5
|
||||
{
|
||||
gateway: {
|
||||
bind: "lan",
|
||||
trustedProxies: ["172.17.0.1"], // Traefik container IP
|
||||
auth: {
|
||||
mode: "trusted-proxy",
|
||||
trustedProxy: {
|
||||
userHeader: "x-forwarded-user",
|
||||
```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://openclaw: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";
|
||||
}
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="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",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Mixed token configuration
|
||||
|
||||
@@ -270,8 +297,7 @@ Loopback trusted-proxy auth also fails closed: same-host callers must supply the
|
||||
|
||||
## Operator scopes header
|
||||
|
||||
Trusted-proxy auth is an **identity-bearing** HTTP mode, so callers may
|
||||
optionally declare operator scopes with `x-openclaw-scopes`.
|
||||
Trusted-proxy auth is an **identity-bearing** HTTP mode, so callers may optionally declare operator scopes with `x-openclaw-scopes`.
|
||||
|
||||
Examples:
|
||||
|
||||
@@ -287,26 +313,22 @@ Behavior:
|
||||
- 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:
|
||||
Practical rule: send `x-openclaw-scopes` explicitly when you want a trusted-proxy request to be narrower than the defaults, or when a gateway-auth plugin route needs something stronger than write scope.
|
||||
|
||||
- Send `x-openclaw-scopes` explicitly when you want a trusted-proxy request to
|
||||
be narrower than the defaults, or when a gateway-auth plugin route needs
|
||||
something stronger than write scope.
|
||||
|
||||
## Security Checklist
|
||||
## 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
|
||||
- [ ] **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"`
|
||||
- [ ] **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"`.
|
||||
|
||||
## Security Audit
|
||||
## Security audit
|
||||
|
||||
`openclaw 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.
|
||||
|
||||
@@ -320,79 +342,95 @@ The audit checks for:
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "trusted_proxy_untrusted_source"
|
||||
<AccordionGroup>
|
||||
<Accordion title="trusted_proxy_untrusted_source">
|
||||
The request didn't come from an IP in `gateway.trustedProxies`. Check:
|
||||
|
||||
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.
|
||||
|
||||
- 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
|
||||
</Accordion>
|
||||
<Accordion title="trusted_proxy_loopback_source">
|
||||
OpenClaw rejected a loopback-source trusted-proxy request.
|
||||
|
||||
### "trusted_proxy_loopback_source"
|
||||
Check:
|
||||
|
||||
OpenClaw rejected a loopback-source trusted-proxy request.
|
||||
- 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?
|
||||
|
||||
Check:
|
||||
Fix:
|
||||
|
||||
- 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?
|
||||
- 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`.
|
||||
|
||||
Fix:
|
||||
</Accordion>
|
||||
<Accordion title="trusted_proxy_user_missing">
|
||||
The user header was empty or missing. Check:
|
||||
|
||||
- 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`.
|
||||
- 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_user_missing"
|
||||
</Accordion>
|
||||
<Accordion title="trusted_proxy_missing_header_*">
|
||||
A required header wasn't present. Check:
|
||||
|
||||
The user header was empty or missing. Check:
|
||||
- Your proxy configuration for those specific headers.
|
||||
- Whether headers are being stripped somewhere in the chain.
|
||||
|
||||
- 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?
|
||||
</Accordion>
|
||||
<Accordion title="trusted_proxy_user_not_allowed">
|
||||
The user is authenticated but not in `allowUsers`. Either add them or remove the allowlist.
|
||||
</Accordion>
|
||||
<Accordion title="trusted_proxy_origin_not_allowed">
|
||||
Trusted-proxy auth succeeded, but the browser `Origin` header did not pass Control UI origin checks.
|
||||
|
||||
### "trusted*proxy_missing_header*\*"
|
||||
Check:
|
||||
|
||||
A required header wasn't present. 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.
|
||||
|
||||
- Your proxy configuration for those specific headers
|
||||
- Whether headers are being stripped somewhere in the chain
|
||||
</Accordion>
|
||||
<Accordion title="WebSocket still failing">
|
||||
Make sure your proxy:
|
||||
|
||||
### "trusted_proxy_user_not_allowed"
|
||||
- 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.
|
||||
|
||||
The user is authenticated but not in `allowUsers`. Either add them or remove the allowlist.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
### "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:
|
||||
|
||||
- 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
|
||||
## 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 OpenClaw config with trusted-proxy auth
|
||||
4. Restart the Gateway
|
||||
5. Test WebSocket connections from the Control UI
|
||||
6. Run `openclaw security audit` and review findings
|
||||
<Steps>
|
||||
<Step title="Configure the proxy">
|
||||
Configure your proxy to authenticate users and pass headers.
|
||||
</Step>
|
||||
<Step title="Test the proxy independently">
|
||||
Test the proxy setup independently (curl with headers).
|
||||
</Step>
|
||||
<Step title="Update OpenClaw config">
|
||||
Update OpenClaw config with trusted-proxy auth.
|
||||
</Step>
|
||||
<Step title="Restart the Gateway">
|
||||
Restart the Gateway.
|
||||
</Step>
|
||||
<Step title="Test WebSocket">
|
||||
Test WebSocket connections from the Control UI.
|
||||
</Step>
|
||||
<Step title="Audit">
|
||||
Run `openclaw security audit` and review findings.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Related
|
||||
|
||||
- [Security](/gateway/security) — full security guide
|
||||
- [Configuration](/gateway/configuration) — config reference
|
||||
- [Remote Access](/gateway/remote) — other remote access patterns
|
||||
- [Remote access](/gateway/remote) — other remote access patterns
|
||||
- [Security](/gateway/security) — full security guide
|
||||
- [Tailscale](/gateway/tailscale) — simpler alternative for tailnet-only access
|
||||
|
||||
Reference in New Issue
Block a user