2013年7月24日水曜日

Java におけるプロキシ経由のリモートIP 取得あれこれ

Servlet プログラミングで、リモートホストの IP の取得に HttpServletRequest#getRemoteAddr をしたところ、プロキシ経由からのアクセスだったためにプロキシサーバの IP が取得されてしまうという、さみしい事態になることがあります。

この対策として 3 パターンほど挙げます。
  1. HttpServletRequest#getHeader("x-forwaded-for") を利用
  2. (Tomcat6以降のみ)RemoteIpValve を利用
  3. Byteman を利用

プロキシの想定

今回はリバースプロキシとして、CentOS 6.4 に yum でインストールした Nginx を利用していると想定します。

インストールしていない場合は以下を実施ください。
# rpm -ivh http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm
# yum install nginx

Nginx がインストールできたら /etc/nginx/conf.d/proxy.conf にプロキシ設定を記載します。
※ proxy-host, app-host の部分は環境に合わせて読み替えてください。
# cat /etc/nginx/conf.d/proxy.conf
server {
  listen 80;
  server_name proxy-host;

  location / {
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass http://app-host:8080;
  }
}

次節からリモートホストの IP 取得方法を記述していきます。

HttpServletRequest#getHeader("x-forwaded-for") を利用

Web で一番多く見かける方法です。HttpServletRequest#getRemoteAddr の代りに、 HttpServletRequest#getHeader("x-forwaded-for") の値を利用します。実際は以下のブログのようにフィルタにすると楽ですね(こちらは X-Real-IP の場合ですが)。

Keeping real user IP in Java web apps behind Nginx proxy

(Tomcat6以降のみ)RemoteIpValve を利用

@msfm さんに教えていただきました。ありがとうございます!

Tomcat 6 以降、または JBoss AS7/EAP6のみとなりますが、
org.apache.catalina.valves.RemoteIpValve を利用する方法です。

Tomcat であれば上記 API リファレンスを参考に、server.xml に Valve 要素を追加します。

JBoss AS7/EAP6 では、残念ながらCLI で設定できなさそうです。war アプリケーションの WEB-INF 配下に以下のような jboss-web.mxl を追加し、再ビルドします。

<jboss-web>
  <valve>
    <class-name>org.apache.catalina.valves.RemoteIpValve</class-name>
    <param>
      <param-name>remoteIpHeader</param-name>
      <param-value>x-forwarded-for</param-value>
    </param>
  </valve>
</jboss-web>

どちらの方法でも、ソースコードを変更することなく実施できます。

なお、WildFly の Web/Servlet コンテナであるところの Undertow では、まだこの機構はないようです。

Byteman を利用

Byteman を使って、アプリケーションのソースコードを変更せずに、
HttpServletRequest#getRemoteAddr の実装を書き換える方法です。
ザ・黒魔術という感じがしますね。

以下、手順です。

1. Byteman のインストール


2013/07/24 時点での最新版である 2.1.3 をインストールします。
# cd /opt
# wget -qO- -O tmp.zip http://downloads.jboss.org/byteman/2.1.3/byteman-download-2.1.3-bin.zip && unzip tmp.zip && rm -f tmp.zip && ln -s byteman-download-2.1.3 byteman

2. Byteman のルールを作成


x-forwarded-for.btm という名前で以下の内容で作成します。

今回作成した Byteman のルールですが、発想としては一番最初の HttpServletRequest#getHeader("x-forwaded-for")  を利用する方法と同じです。

リクエストヘッダに x-forwarded-for が存在する場合は、HttpServletRequest#getRemoteAddr の値を HttpServletRequest#getHeader("x-forwarded-for") の値に書き換えます。存在しない場合は何もしません。

3. Tomcat/JBoss 起動時に Byteman がアタッチされるように設定


Tomcat: $CATALINA_HOME/bin/setenv.sh
JBoss AS7/EAP6: $JBOSS_HOME/bin/standalone.conf

に以下を新規作成(または追記)します。
BYTEMAN_RULE 変数に代入する x-forwaded-for.btm のパスは適宜変更ください。

BYTEMAN_HOME=/opt/byteman
BYTEMAN_RULE=/path/to/x-forwarded-for.btm
BYTEMAN_OPTS="-javaagent:$BYTEMAN_HOME/lib/byteman.jar=listener:true,boot:$BYTEMAN_HOME/lib/byteman.jar"
if [ "x$BYTEMAN_RULE" != "x" ]; then
BYTEMAN_OPTS="${BYTEMAN_OPTS},script:$BYTEMAN_RULE"
fi
BYTEMAN_OPTS="$BYTEMAN_OPTS -Dorg.jboss.byteman.transform.all
-Dorg.jboss.byteman.debug"
JAVA_OPTS="$BYTEMAN_OPTS $JAVA_OPTS"

※ Tomcat の場合は、JAVA_OPTS を CATALINA_OPTS に変更してください。

4. Tomcat/JBoss AS7/EAP6 の起動


起動してみて、getRemoteAddr の返り値がクライアントのリモートホストの IP であれば成功です。

参考


課題

  • 多段プロキシに対応できない
  • 現状の Byteman ルールでは、X-Forwarded-For ヘッダを送ってくるプロキシが信頼できるプロキシかどうかチェック(IP アドレスの確認)を行っていない

0 件のコメント:

コメントを投稿