こんにちは@mosuke5です。
もともとApach+PHPで動作していたシステムに、リバースプロキシ(Nginx)を前段に挟むことになりました。理由は、もともと社内ネットワークでのみ利用するシステムだったのですが、インターネットの外からも利用することになり、インターネットからの入り口にリバースプロキシを導入したからです。
つまり、Nginx->Apache->PHPという構成になりました。
リバースプロキシを導入した際によく問題となることだが、Apacheからみるとすべてリバースプロキシから通信がきているようにみえます。
接続元のIPアドレスがすべてリバースプロキシのもの変わってしまいます。
そこで、HTTPヘッダーに接続元のIPアドレス追加しアプリ側(PHP)で受け取る方法とその際の注意点を書きます。
リバースプロキシ側でHTTPヘッダー追加
まず、デフォルトのNginxの設定では接続元のIPアドレスをHTTPヘッダーに含みません。そのため、本当のクライアント側のIPアドレスをHTTPヘッダーに追加します。一般的にX-Forwarded-For
というHTTPヘッダーを利用します。
X-Forwarded-Forというヘッダー名にNginxでもっている変数$proxy_add_x_forwarded_forを追加する。
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
リバースプロキシしかり、AWSのELBなどしかり、L7でのプロキシやロードバランサを利用した際に、よくX-Forwarded-Forというヘッダー名がでてきます。このヘッダーはなんなのか?HTTPの標準のヘッダーとして存在するのかと疑問に思い調べてみると、RFCには定義されていないが一般的に利用されているヘッダーであることがわかりました。
X-Forwarded-For (XFF) とは、HTTPヘッダフィールドの一つ。HTTPプロキシサーバまたは負荷分散装置(ロードバランサ)を経由してウェブサーバに接続するクライアントの送信元IPアドレスを特定する際のデファクトスタンダードである。 (略)RFCの標準的なヘッダフィールドではないが、IETFのネットワーク作業部会 (Network Working Group) は2011年10月より同種のHTTPヘッダForwardedの標準化作業を開始した[1]。
PHPでX-Forwarded-Forを受け取る
リバースプロキシで’X-forwarded-For’という名前のHTTPヘッダーを追加したので、そのまま取得しようとするが、実はこのままだとうまくいきません。PHPのサーバ変数に入れる際に、名前を規則に従って変更するからです。これは、<a href=“https://www.ietf.org/rfc/rfc3875" target="_blank>“RFC3875(CGIの仕様) のProtocol-Specific Meta-Variablesに基づいて変換されているようです。
echo $_SERVER['X-Forwarded-For'];
// -> 存在しない
サーバ変数をvar_dumpを使って中身を確認してみると、HTTPが先頭についてて、すべて大文字になってて、ハイフンがアンスコに変わっていることが確認できます。
<?php
var_dump($_SERVER);
# array(x) { ["HTTP_X_FORWARDED_FOR"] => string(12) "192.168.33.1" ...... }
ためしにgetallheadersという関数で生のヘッダーの状態を確認してみると、想定通りでした。 つまりサーバ変数に格納するタイミングで、名前が変わるということです。
<?php
var_dump(getallheaders());
# array(x) { ["X-Forwarded-For"] => string(12) "192.168.33.1" ...... }
tcpdumpで生のパケットを確認してみる
結果は見えているのですが、気になったのでtcpdumpで生パケットの状態を確認してみます。Nginxからプロキシされるときは期待通りとヘッダー名は"X-Forwarded-For “になっていること確認できました。
$ sudo yum install tcpdump
$ tcpdump dst port 80 -X
# ながいんで適当に端折りました
11:04:01.883209 IP 10.0.2.15.43038 > 192.168.0.10.54655: Flags [.], seq 802:1603, ack 1, win 14600, length 1460
(略)
0x0000: 4500 0355 c705 0000 3706 24ca adc2 265f 1.0..X-Forwarded
0x0010: c0a8 000a 0050 d57f 51ad 1e62 e596 78a4 -For:.192.168.33
0x0020: 8018 0137 8dbe 0000 0101 080a d1dc c19e .1..Host:.xxxxxx
任意のHTTPヘッダーつけた場合
それでは、X-Forwarded-For以外の任意のHTTPヘッダーをつけた場合も同様になるのか確認してみます。 Nginxにて、‘my-header’という名前でヘッダーを追加してPHP側で受け取ります。
proxy_set_header my-header 'hogefugafoobar';
my-header
がHTTP_MY_HEADER
に変わっていることが確認できた。
var_dump($_SERVER);
# array(35) { ["HTTP_MY_HEADER"] => string(14) "hogefugafoobar" ...... }
さいごに
PHPでHTTPヘッダーを受け取る際の注意点について見てきました。 本ブログではNginxを用いたリバースプロキシを例にしていますが、その他にもAWS ELBなどのL7のロードバランサを用いた場合も基本的に同様ですので、原理原則をおさえておくといいでしょう。