mod_extract_forwardedと多段 Proxy

Apache HTTP Server

リバース Proxyの奧に居るWebサーバで、Proxyされる前のリモート(クライアント)IPアドレスを元に処理の振り分けやログの記録をしたい場合がある。普通だとProxy経由のアクセス元は全て ProxyサーバのIPアドレスになってしまうので、Proxyサーバによって送出される特別なリクエスト HTTPヘッダからオリジナルのIPアドレスを取り出し、それでリモートIPアドレスを上書きしてくれるモジュールを使用することになる。

それを実現するモジュールとしては mod_rpafと mod_extract_forwarded、そして Apache 2.4から標準で利用可能になった mod_remoteip が知られている。mod_rpafは Apache の Allow fromディレクティブなどを使ってアクセス制限には効かないのと、mod_remoteipは Apache 2.4からしか提供されていないので mod_extract_forwardedがいまだ多く使われていると思われる。

IPアドレスの詐称による不正アクセスを防ぐため、IPアドレスの上書きには注意が必要である。具体的には、信頼できる Proxyサーバが送出してくる情報のみを用いるよう設定しなければならないということだ。mod_extract_forwardedの場合、

MEForder refuse,accept
MEFrefuse all
MEFaccept 192.168.x.y

のようにして信頼出来る ProxyサーバのIPアドレス(複数可)を記述しておくことでそれ以外の Proxyサーバが送出してくるリモートIPアドレスを無視するように設定するのが普通である。mod_rpafや mod_remoteipにも同じ用途のディレクティブが存在する。

ところが mod_extract_forwardedは(ネットワーク長やサブネットマスクを使った)「範囲」によるIPアドレス指定を受け付けることができないため、リバース Proxyのプライベートセグメント側IPアドレスが動的に割り当てられていて特定できないような場合に困ったことになる。もしターゲットの Webサーバがプライベートネットワーク内にあり、そもそも信頼できないホストから少なくとも直接リクエストの中継を受けることはないのであればIPアドレスの代わりに all と指定をすることでIPアドレスによる制限を外すことができる。

MEFaccept all

ここでひとつ問題がある。Proxy元のIPアドレスを通知するためによく使われる X-Forwarded-Forヘッダには、経路に複数の Proxyサーバが挟まっている場合にその数だけのIPアドレスがセットされることがあるのだ。

このフィールドの一般的なフォーマットは次の通りである。
X-Forwarded-For: client1, proxy1, proxy2
X-Forwarded-Forフィールドをでっち上げるのは簡単なので、与えられた情報は慎重に取り扱うべきである。最終IPアドレスは常に最後のプロキシに接続するIPアドレスであるが、すなわちこの情報ソースが最も信頼できることを意味する。
出典: Wikipedia

直近だけではなく、通信経路のどこかにある信頼出来ない(自分の管理下にない)Proxyサーバがセットした IPアドレスもそこに記載されて来るとすれば全ての Proxyサーバを信頼する設定は危険だ。mod_extract_forwardedのソースを確認したところ、このモジュールは当該ヘッダに複数のIPアドレスが記載されている場合、右側から順に信頼可能な範囲まで辿るように実装されていることがわかった。そして MEFaccept allの状態では一番左側の(一番信頼できない)IPアドレスまで信頼することになってしまう。

/*
 * Scan back from the end of the list of proxies until we
 * find one that isn't trusted, typically one that isn't one of our
 * proxy servers. This allows us to back out any sequence of trusted
 * proxy servers and find the first IP that isn't, which is the IP
 * we're interested in. What we want is the IP number of the machine
 * that made the connection to the first of, potentially a sequence
 * of, our trusted proxies. We don't care about any external
 * proxies that may precede our trusted proxies because we cannot
 * trust what they say.
 *
 * Do not search back beyond the 2nd forwarded-for IP number. Even
 * if the first is from a trusted proxy's IP number it must have
 * been acting as a client not a proxy if it appears in that
 * position.
 */    
for (ctr = ary->nelts - 1; ctr >= 1; ctr--)
{
    if (!acceptable_proxy(conf, ((char**)ary->elts)[ctr]))
    {
        break;
    }
}
client_ip = ((char**)ary->elts)[ctr];

仕方が無いので、自分の管理下にあるリバース Proxyサーバで、そこに到達する以前にセットされた X-Forwarded-For(など、mod_extract_forwardedが注目する)ヘッダを削除するように設定した。リバース ProxyがApacheの場合下記のような設定となる。

RequestHeader unset X-Forwarded-For
RequestHeader unset Forwarded-For

こうすることで、途中経路の Proxyサーバがセットした信頼できないリモートIPアドレス情報が削除され、直近のリモートIPアドレスだけが記載されるようになるため MEFAccept allで mod_extract_forwardedを使っても(信頼できる Proxyサーバ経由でのみ対象サーバにアクセスされることが保証されている限り)IPアドレスによるアクセス制御を安全に行えるようになった。

ちなみに mod_remoteipではどのような処理になっているのか確認しようと http://svn.apache.org/repos/asf/httpd/httpd/trunk/modules/metadata/mod_remoteip.c の remoteip_modify_request 関数を読もうとしてみたが、まるでアセンブラのようなコードで読みにくかったので深追いしなかった。

Apache 2.4なら範囲でのIPアドレス指定も可能な mod_remoteipを使えば問題ないが、デフォルトのログ設定だと書き換え前のIPアドレスが記録されてしまうので注意すること。

参考記事: Apacheでmod_remoteipを使うときに気をつけておいた方がよいこと

名前の逆引きみたいな遅い処理をWebサーバにやらせるなんて今時そんな運用する奴いないよねえと思ってたら webalizerみたいなログ解析でどうしても必要っていう人がまだいるのを検索で見つけて南無。

同じカテゴリの記事

静的コンテンツとAPIそれぞれへのHTTPリクエストを Apacheと Javaアプリケーションサーバに振り分ける簡単な設定 2014年1月21日
DebianのApache2.2で DocumentRootにWebDAVアクセスするための最短手順 2010年8月6日
Sambaのパスワードデータベースを利用してApacheのBASIC認証を行う 2010年6月5日
ApacheとWebDAVとPHP 2010年1月17日

お勧めカテゴリ

英語でアニメ観ようず
なじみ深い日本製アニメの英語版DVDで、字幕と音声から英語を学びましょうという趣旨のシリーズ記事です。
ScalaのようでJavaだけど少しScalaなJSON API
Scalaと Spring Frameworkを使って REST的なJSON APIを実装してみましょう。
ドクジリアン柔術少女 すから☆ぱいそん
代表 嶋田大貴のブログです。写真は神仏に見せ金をはたらく罰当たりの図