Gateway
信頼済みプロキシ認証
使用する場合
次の場合は trusted-proxy 認証モードを使用します。
- OpenClaw を ID 認識プロキシ(Pomerium、Caddy + OAuth、nginx + oauth2-proxy、Traefik + forward auth)の背後で実行している。
- プロキシがすべての認証を処理し、ヘッダー経由でユーザー ID を渡す。
- Kubernetes またはコンテナ環境で、プロキシが Gateway への唯一の経路である。
- ブラウザーが WS ペイロードでトークンを渡せないため、WebSocket
1008 unauthorizedエラーが発生している。
使用しない場合
- プロキシがユーザーを認証しない場合(単なる TLS ターミネーターやロードバランサー)。
- プロキシを迂回して Gateway に到達できる経路がある場合(ファイアウォールの穴、内部ネットワークアクセス)。
- プロキシが転送ヘッダーを正しく削除または上書きしているか不明な場合。
- 個人の単一ユーザーアクセスだけが必要な場合(より簡単な設定として Tailscale Serve + ループバックを検討してください)。
仕組み
Proxy authenticates the user
リバースプロキシがユーザーを認証します(OAuth、OIDC、SAML など)。
Proxy adds an identity header
プロキシは、認証済みユーザー ID を含むヘッダーを追加します(例: x-forwarded-user: nick@example.com)。
Gateway verifies trusted source
OpenClaw は、リクエストが 信頼済みプロキシ IP(gateway.trustedProxies で設定)から来たことを確認します。
Gateway extracts identity
OpenClaw は、設定されたヘッダーからユーザー ID を抽出します。
Authorize
すべての確認に成功すると、リクエストは認可されます。
Control UI のペアリング動作
gateway.auth.mode = "trusted-proxy" が有効で、リクエストが trusted-proxy チェックに合格した場合、Control UI WebSocket セッションはデバイスペアリング ID なしで接続できます。
スコープへの影響:
- デバイスなしの Control UI WebSocket セッションは接続できますが、デフォルトではオペレータースコープを受け取りません。OpenClaw は要求されたスコープリストを
[]にクリアするため、承認済みのペアリング済みデバイスやトークンに紐づいていないセッションが権限を自己宣言することはできません。 - WebSocket 接続に成功した後にメソッドが
missing scopeで失敗する場合は、ブラウザーがデバイス ID を生成してペアリングを完了できるように HTTPS を使用してください。Control UI の安全でない HTTP を参照してください。 - 緊急時のみ:
gateway.controlUi.dangerouslyDisableDeviceAuth=trueは、デバイス ID がなくても要求されたスコープを保持します。これは深刻なセキュリティ低下です。速やかに元に戻してください。Control UI の安全でない HTTP を参照してください。
リバースプロキシによるスコープ制限:
- プロキシが Control UI WebSocket アップグレードリクエストで
x-openclaw-scopesを送信する場合、OpenClaw はセッションスコープを、要求されたスコープと宣言されたスコープの共通部分に制限します。このヘッダーはスコープを付与しません。セッションが保持できる範囲を狭めるだけです。
影響:
- このモードでは、ペアリングは Control UI アクセスの主要なゲートではなくなります。
- リバースプロキシの認証ポリシーと
allowUsersが実効的なアクセス制御になります。 - Gateway への入口は信頼済みプロキシ IP のみにロックしてください(
gateway.trustedProxies+ ファイアウォール)。
カスタム WebSocket クライアントは Control UI セッションではありません。gateway.controlUi.dangerouslyDisableDeviceAuth は、任意の client.mode: "backend" や CLI 形式のクライアントにスコープを付与しません。カスタム自動化では、デバイス ID/ペアリング、予約済みの直接ローカル client.id: "gateway-client" バックエンドヘルパーパス、または HTTP リクエスト/レスポンスのサーフェスがより適している場合は admin HTTP RPC Plugin を使用してください。
設定
{ gateway: { // Trusted-proxy auth expects requests from a non-loopback trusted proxy source by default 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"], // Optional: allow a same-host loopback proxy after explicit opt-in allowLoopback: false, }, }, },}設定リファレンス
gateway.trustedProxiesstring[]required信頼するプロキシ IP アドレスの配列。他の IP からのリクエストは拒否されます。
gateway.auth.modestringrequired"trusted-proxy" である必要があります。
gateway.auth.trustedProxy.userHeaderstringrequired認証済みユーザー ID を含むヘッダー名。
gateway.auth.trustedProxy.requiredHeadersstring[]リクエストを信頼するために存在している必要がある追加ヘッダー。
gateway.auth.trustedProxy.allowUsersstring[]ユーザー ID の許可リスト。空の場合はすべての認証済みユーザーを許可します。
gateway.auth.trustedProxy.allowLoopbackboolean同一ホストのループバックリバースプロキシに対するオプトインサポート。デフォルトは false です。
TLS 終端と HSTS
TLS 終端点は 1 つにし、そこで HSTS を適用してください。
Proxy TLS termination (recommended)
リバースプロキシが https://control.example.com の HTTPS を処理する場合、そのドメインに対してプロキシで Strict-Transport-Security を設定します。
- インターネットに面したデプロイに適しています。
- 証明書と HTTP 強化ポリシーを 1 か所に保てます。
- OpenClaw はプロキシ背後のループバック HTTP のままにできます。
ヘッダー値の例:
Strict-Transport-Security: max-age=31536000; includeSubDomainsGateway TLS termination
OpenClaw 自体が HTTPS を直接提供する場合(TLS 終端プロキシなし)、次を設定します。
{ gateway: { tls: { enabled: true }, http: { securityHeaders: { strictTransportSecurity: "max-age=31536000; includeSubDomains", }, }, },}strictTransportSecurity は文字列ヘッダー値、または明示的に無効化するための false を受け付けます。
ロールアウトのガイダンス
- トラフィックを検証している間は、まず短い最大期間(例:
max-age=300)から開始します。 - 十分な確信が得られた後にのみ、長期間の値(例:
max-age=31536000)へ増やします。 - すべてのサブドメインが HTTPS 対応済みの場合にのみ、
includeSubDomainsを追加します。 - ドメインセット全体について preload 要件を意図的に満たしている場合にのみ、preload を使用します。
- ループバックのみのローカル開発では、HSTS の利点はありません。
プロキシ設定例
Pomerium
Pomerium は x-pomerium-claim-email(または他のクレームヘッダー)で ID を渡し、x-pomerium-jwt-assertion で JWT を渡します。
{ 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 設定スニペット:
routes: - from: https://openclaw.example.com to: http://openclaw-gateway:18789 policy: - allow: or: - email: is: nick@example.com pass_identity_headers: trueCaddy with OAuth
caddy-security Plugin を備えた Caddy は、ユーザーを認証して ID ヘッダーを渡すことができます。
{ gateway: { bind: "lan", trustedProxies: ["10.0.0.1"], // Caddy/sidecar proxy IP auth: { mode: "trusted-proxy", trustedProxy: { userHeader: "x-forwarded-user", }, }, },}Caddyfile スニペット:
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
oauth2-proxy はユーザーを認証し、x-auth-request-email で ID を渡します。
{ gateway: { bind: "lan", trustedProxies: ["10.0.0.1"], // nginx/oauth2-proxy IP auth: { mode: "trusted-proxy", trustedProxy: { userHeader: "x-auth-request-email", }, }, },}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";}Traefik with forward auth
{ gateway: { bind: "lan", trustedProxies: ["172.17.0.1"], // Traefik container IP auth: { mode: "trusted-proxy", trustedProxy: { userHeader: "x-forwarded-user", }, }, },}混在トークン設定
OpenClaw は、gateway.auth.token(または OPENCLAW_GATEWAY_TOKEN)と trusted-proxy モードの両方が同時に有効な曖昧な設定を拒否します。混在トークン設定では、ループバックリクエストが誤った認証パスで暗黙的に認証される可能性があります。
起動時に mixed_trusted_proxy_token エラーが表示された場合:
- trusted-proxy モードを使用する場合は共有トークンを削除する、または
- トークンベース認証を意図している場合は
gateway.auth.modeを"token"に切り替えます。
Loopback の信頼済みプロキシ identity ヘッダーは引き続き fail closed します。同一ホストの呼び出し元が、プロキシユーザーとして暗黙に認証されることはありません。プロキシをバイパスする内部 OpenClaw 呼び出し元は、代わりに gateway.auth.password / OPENCLAW_GATEWAY_PASSWORD で認証できます。信頼済みプロキシモードでは、トークンフォールバックは意図的にサポートされていません。
オペレータースコープヘッダー
信頼済みプロキシ認証は identity を伴う HTTP モードのため、呼び出し元は HTTP API リクエストで x-openclaw-scopes を使って、オペレータースコープを任意で宣言できます。
注: WebSocket スコープは、Gateway プロトコルのハンドシェイクとデバイス identity バインディングによって決まります。Control UI の WebSocket アップグレードリクエストでは、x-openclaw-scopes はネゴシエートされたセッションスコープの上限にすぎず、付与ではありません。信頼済みプロキシでの WebSocket スコープの挙動については、Control UI ペアリングの挙動を参照してください。
例:
x-openclaw-scopes: operator.readx-openclaw-scopes: operator.read,operator.writex-openclaw-scopes: operator.admin,operator.write
挙動:
- ヘッダーが存在する場合、OpenClaw は宣言されたスコープセットを尊重します。
- ヘッダーが存在するが空の場合、リクエストはオペレータースコープが ない ことを宣言します。
- ヘッダーがない場合、通常の identity を伴う HTTP API は標準のオペレーター既定スコープセットにフォールバックします。
- Gateway 認証のプラグイン HTTP ルートは、既定でより狭くなります。
x-openclaw-scopesがない場合、それらのランタイムスコープはoperator.writeにフォールバックします。 - ブラウザー由来の HTTP リクエストは、信頼済みプロキシ認証が成功した後でも、
gateway.controlUi.allowedOrigins(または意図的な Host ヘッダーフォールバックモード)を通過する必要があります。 - Control UI WebSocket セッションでは、アップグレードリクエストに
x-openclaw-scopesが存在する場合、それはスコープの上限です。空の値はスコープなしになります。
実用上のルール: 信頼済みプロキシリクエストを既定より狭くしたい場合、または Gateway 認証プラグインルートが write スコープより強いものを必要とする場合は、x-openclaw-scopes を明示的に送信してください。
セキュリティチェックリスト
信頼済みプロキシ認証を有効にする前に、次を確認してください。
- [ ] プロキシが唯一の経路: Gateway ポートは、プロキシ以外のすべてからファイアウォールで遮断されています。
- [ ] trustedProxies は最小限: サブネット全体ではなく、実際のプロキシ IP のみです。
- [ ] Loopback プロキシ送信元が意図的: 同一ホストプロキシ用に
gateway.auth.trustedProxy.allowLoopbackが明示的に有効化されていない限り、loopback 送信元リクエストに対する信頼済みプロキシ認証は fail closed します。 - [ ] プロキシがヘッダーを除去する: プロキシはクライアントからの
x-forwarded-*ヘッダーを追記ではなく上書きします。 - [ ] TLS 終端: プロキシが TLS を処理し、ユーザーは HTTPS 経由で接続します。
- [ ] allowedOrigins が明示的: 非 loopback の Control UI は明示的な
gateway.controlUi.allowedOriginsを使います。 - [ ] allowUsers が設定されている(推奨): 認証された誰でも許可するのではなく、既知のユーザーに制限します。
- [ ] トークン設定が混在していない:
gateway.auth.tokenとgateway.auth.mode: "trusted-proxy"の両方を設定しないでください。 - [ ] ローカルパスワードフォールバックがプライベート: 内部の直接呼び出し元向けに
gateway.auth.passwordを設定する場合は、プロキシではないリモートクライアントが Gateway ポートへ直接到達できないよう、ファイアウォールで遮断してください。
セキュリティ監査
openclaw security audit は、信頼済みプロキシ認証を critical 重大度の検出としてフラグします。これは意図的なもので、セキュリティをプロキシ設定に委任していることを思い出させるためです。
監査では次を確認します。
- 基本の
gateway.trusted_proxy_auth警告/critical リマインダー trustedProxies設定の欠落userHeader設定の欠落- 空の
allowUsers(認証済みユーザーなら誰でも許可) - 同一ホストプロキシ送信元向けに有効化された
allowLoopback - 公開された Control UI サーフェスでのワイルドカードまたは欠落したブラウザー origin ポリシー
トラブルシューティング
trusted_proxy_untrusted_source
リクエストは gateway.trustedProxies 内の IP から来ていません。確認してください。
- プロキシ IP は正しいですか?(Docker コンテナー IP は変わることがあります。)
- プロキシの前段にロードバランサーがありますか?
- 実際の IP を見つけるには、
docker inspectまたはkubectl get pods -o wideを使ってください。
trusted_proxy_loopback_source
OpenClaw は loopback 送信元の信頼済みプロキシリクエストを拒否しました。
確認してください。
- プロキシは
127.0.0.1/::1から接続していますか? - 同一ホストの loopback リバースプロキシで信頼済みプロキシ認証を使おうとしていますか?
修正:
- プロキシを通らない内部の同一ホストクライアントには、トークン/パスワード認証を優先する、または
- 非 loopback の信頼済みプロキシアドレス経由でルーティングし、その IP を
gateway.trustedProxiesに保持する、または - 意図的な同一ホストのリバースプロキシでは、
gateway.auth.trustedProxy.allowLoopback = trueを設定し、loopback アドレスをgateway.trustedProxiesに保持し、プロキシが identity ヘッダーを除去または上書きすることを確認してください。
trusted_proxy_user_missing
ユーザーヘッダーが空か欠落していました。確認してください。
- プロキシは identity ヘッダーを渡すように設定されていますか?
- ヘッダー名は正しいですか?(大文字小文字は区別されませんが、スペルは重要です)
- ユーザーは実際にプロキシで認証されていますか?
trusted_proxy_missing_header_*
必須ヘッダーが存在しませんでした。確認してください。
- それら特定のヘッダーに対するプロキシ設定。
- チェーン内のどこかでヘッダーが除去されていないか。
trusted_proxy_user_not_allowed
ユーザーは認証されていますが、allowUsers に含まれていません。そのユーザーを追加するか、許可リストを削除してください。
trusted_proxy_origin_not_allowed
信頼済みプロキシ認証は成功しましたが、ブラウザーの Origin ヘッダーが Control UI の origin チェックを通過しませんでした。
確認してください。
gateway.controlUi.allowedOriginsに正確なブラウザー origin が含まれている。- 意図的に全許可の挙動にしたい場合を除き、ワイルドカード origin に依存していない。
- 意図的に Host ヘッダーフォールバックモードを使う場合、
gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=trueが意図的に設定されている。
Connection succeeds but methods report missing scope
WebSocket は接続されますが、chat.history、sessions.list、または
models.list が missing scope: operator.read で失敗します。
よくある原因:
- デバイスなしの Control UI セッション: 信頼済みプロキシ認証はデバイス identity なしで WebSocket 接続を許可できますが、OpenClaw は設計上、デバイスなしセッションのスコープをクリアします。
- カスタムバックエンドクライアント:
gateway.controlUi.dangerouslyDisableDeviceAuthは Control UI スコープであり、任意のバックエンドまたは CLI 形状の WebSocket クライアントにスコープを付与しません。 - 過度に狭い
x-openclaw-scopes: プロキシが Control UI WebSocket アップグレードリクエストにこのヘッダーを注入する場合、セッションスコープはそのセットに制限されます。空のヘッダー値はスコープなしになります。
修正:
- Control UI では、ブラウザーがデバイス identity を生成してペアリングを完了できるよう HTTPS を使ってください。
- カスタム自動化では、デバイス identity/ペアリング、予約済みの直接ローカル
gateway-clientバックエンドヘルパーパス、または admin HTTP RPC を使ってください。 gateway.controlUi.dangerouslyDisableDeviceAuth: trueは、一時的な Control UI の緊急回避パスとしてのみ使ってください。
WebSocket still failing
プロキシが次を満たしていることを確認してください。
- WebSocket アップグレードをサポートしている(
Upgrade: websocket、Connection: upgrade)。 - WebSocket アップグレードリクエストで identity ヘッダーを渡している(HTTP だけではありません)。
- WebSocket 接続用に別の認証パスを持っていない。
トークン認証からの移行
トークン認証から信頼済みプロキシへ移行する場合:
Configure the proxy
ユーザーを認証してヘッダーを渡すようにプロキシを設定します。
Test the proxy independently
プロキシ設定を単独でテストします(ヘッダー付き curl)。
Update OpenClaw config
信頼済みプロキシ認証で OpenClaw 設定を更新します。
Restart the Gateway
Gateway を再起動します。
Test WebSocket
Control UI からの WebSocket 接続をテストします。
Audit
openclaw security audit を実行し、検出結果を確認します。