Action Cableのオリジンチェックがリバースプロキシ環境でも正しく動作するよう修正
リバースプロキシ環境でAction CableのオリジンチェックがHTTP_HOSTヘッダーを直接参照していたため、X-Forwarded-Hostが使用される構成では接続が拒否される問題がありました。本PRではrequest.host_with_portとrequest.ssl?を使用するよう修正し、Railsの他の部分と一貫したホスト解決が行われるようになります。
背景
Action Cableの allow_same_origin_as_host チェックは、ブラウザが送信するOriginヘッダーとサーバー側のホスト情報を比較することでCSRF攻撃を防ぐ仕組みです。しかし従来の実装では、比較対象のホストとして生のHTTP_HOSTヘッダーを直接参照していました。
リバースプロキシ構成では、プロキシが内部サーバーへリクエストを転送する際にHTTP_HOSTがプロキシの内部ホスト名になる一方、公開ホストはX-Forwarded-Hostヘッダーで伝達されます。この場合、ブラウザが送信するOrigin(公開ホスト名ベース)とHTTP_HOST(内部ホスト名)が一致せず、正当なリクエストでもRequest origin not allowedエラーが発生します。
Railsの他の部分ではrequest.hostやrequest.host_with_portを通じてX-Forwarded-Hostを考慮したホスト解決が行われており、Action Cableのオリジンチェックだけが生のRackヘッダーを直接参照するという不一致が生じていました。
技術的な変更
actioncable/lib/action_cable/connection/base.rbの allow_request_origin? メソッドで、ホストとプロトコルの解決方法が変更されました。
変更前:
proto = Rack::Request.new(env).ssl? ? "https" : "http"
if server.config.allow_same_origin_as_host && env["HTTP_ORIGIN"] == "#{proto}://#{env['HTTP_HOST']}"
変更後:
proto = request.ssl? ? "https" : "http"
if server.config.allow_same_origin_as_host && env["HTTP_ORIGIN"] == "#{proto}://#{request.host_with_port}"
変更は2点です。まず Rack::Request.new(env).ssl? が request.ssl? に置き換えられました。connection/base.rb内ではすでにrequestメソッドが定義されており、その再利用です。次に env['HTTP_HOST'] が request.host_with_port に置き換えられました。request.host_with_portはActionDispatch::Requestのメソッドであり、X-Forwarded-Hostヘッダーが存在する場合にその値を優先して返します。
テストも大幅に拡充されており、以下のシナリオが追加されています:
-
X-Forwarded-Hostが設定された場合に同一オリジンを許可すること -
X-Forwarded-HostがHTTP_HOSTより優先されること - プロキシチェーン(
proxy1.example.com, proxy2.example.com)では末尾のホストが使用されること - 非標準ポート(
:3000付き)の場合もポートを含めた完全一致が行われること -
X-Forwarded-ProtoによるHTTPS判定が機能すること
設計判断
最小限の変更でRails全体との一貫性を確保する方針 が採用されました。
オリジンチェックのロジック自体は変更されておらず、ホストとプロトコルの取得元をRackレベルの生ヘッダーからAction Dispatchのrequestオブジェクト経由に切り替えるだけの修正です。requestオブジェクトへの委譲によって、ActionDispatch::Requestがすでに実装しているX-Forwarded-*ヘッダーの処理ロジックをそのまま活用できます。PRの説明にも「consistent with how the rest of Rails resolves the host」と明記されており、独自実装を排除してフレームワーク内の既存の仕組みに統一することが意図的な設計判断として示されています。
まとめ
本PRは2行の変更で、Action Cableのオリジンチェックをリバースプロキシ環境に対応させたものです。独自ロジックを排除してrequestオブジェクトに委譲することで、X-Forwarded-*ヘッダーの扱いがRails全体で統一され、プロキシ環境特有の追加設定なしに正常な接続が可能になります。