apacheによるリバースプロキシを多段構成にしていて、バックエンドでもクライアントがリクエストしたホスト名を取りたい!なんてことがありました。
ProxyPreserveHostを設定すれば解決するじゃん!という答えに行き着いたのですが、ハマることがあったのでメモしておきます。
参考
- リバースプロキシの多段構成によるRailsアプリへの影響 Ruby - Rack::Request#hostがX-Forwarded-Hostの最後のプロキシホストを返す理由 - Qiita
- ProxyPreserveHostの公式ドキュメント
やりたいこと
以下の構成をとったとき、バックエンドでクライアントがリクエストしたホスト名を取りたい。しかし多段プロキシの影響によりlocalhostが取れてしまう。
cliant -> proxy1(apache) -> proxy2(apache) -> backend
単純に対応すると
上記proxy1, proxy2のコンフィグに以下を追記すればclientがリクエストしたホスト名がそのまま渡ってきます。が、他の設定に影響を与えるため注意が必要です。
ProxyPreserveHost On
ProxyPassReverseにハマる
リバースプロキシを使う際、リダイレクトの対応としてProxyPassReverseを設定することがよくあります。
しかし、以下のような書き方をしているとProxyPassReverseが効かなくなります。
## リバースプロキシの設定
ProxyPass /ja http://localhost:3080/ja
ProxyPassReverse /ja http://localhost:3080/ja
## リダイレクトでこんな設定があるとして
redirect /ja/hoge /ja
期待ではこうなるはず
$ curl -I http://myhost.com/ja/hoge
HTTP/1.0 302 Moved Temporarily
...
Location: http://myhost.com/ja
しかし実際は期待しないポート番号付で返ってくる
$ curl -I http://myhost.com/ja/hoge
HTTP/1.0 302 Moved Temporarily
...
Location: http://myhost.com:3080/ja
これはリクエストヘッダの値がProxyPreserveHostによって変わっているために発生するようです(X-Forwaeded-HostかHostを見てる?)
バックエンドがlocalhost:3080にリダイレクトさせるはずが、myhost:3080で返してくるため、ProxyPassReverseに引っかからなくなっています。
期待通りにリダイレクトのホスト名を書き換えようと思うと以下の対応が必要です
解決案1:ProxyPassReverseを書き換える
クライアントがリクエストするホスト名をベタっと書いてしまう
ProxyPass /ja http://localhost:3080/ja
ProxyPassReverse /ja http://myhost.com:3080/ja
解決案2:バックエンドでServerNameを使う
バックエンド自身にlocalhost、もしくはクライアントがリクエストするホスト名を名乗らせることでも解決します。クライアントがリクエストするホスト名を名乗らせた場合、ProxyPassReverseの設定自体が不要になります
ServerName myhost.com:80 または ServerName localhost:3080 とか
UseCanonicalName on #これをセットで記述しないと有効になりません
SSLProxyにハマる
SSLProxyを利用している場合、プロキシ先の証明書とリクエストのホスト名が不一致となりプロキシに失敗してしまいます。
例えばこんな設定を書いていたとすると、
SSLProxyEngine on
ProxyPass /other https://myhost2.com/other
このようなエラーが出ます。
[ssl:info] [pid xxxx] [remote x.x.x.x:443] AH02005: SSL Proxy: Peer certificate CN mismatch: Certificate CN: myhost2.com Requested hostname: myhost.com
(暫定)解決案 とりあえず証明書の確認をスキップ
プロキシ先のサーバが信頼できる必要がありますが、証明書の確認をスキップすることで回避できます。そうでない場合は真面目に確認が必要ですが、今回はそこまで調べていません・・・。
SSLProxyEngine on
SSLProxyCheckPeerCN off
SSLProxyCheckPeerName off
ProxyPass /other https://myhost2.com/other