この対策として 3 パターンほど挙げます。
- HttpServletRequest#getHeader("x-forwaded-for") を利用
- (Tomcat6以降のみ)RemoteIpValve を利用
- Byteman を利用
プロキシの想定
今回はリバースプロキシとして、CentOS 6.4 に yum でインストールした Nginx を利用していると想定します。
インストールしていない場合は以下を実施ください。
Nginx がインストールできたら /etc/nginx/conf.d/proxy.conf にプロキシ設定を記載します。
※ proxy-host, app-host の部分は環境に合わせて読み替えてください。
次節からリモートホストの IP 取得方法を記述していきます。
インストールしていない場合は以下を実施ください。
# 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 の場合ですが)。
@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 という名前で以下の内容で作成します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
RULE x-forwarded-for | |
INTERFACE javax.servlet.http.HttpServletRequest | |
METHOD getRemoteAddr | |
AT EXIT | |
IF $0.getHeader("x-forwarded-for") != null | |
DO | |
RETURN $0.getHeader("x-forwarded-for") | |
ENDRULE |
リクエストヘッダに x-forwarded-for が存在する場合は、HttpServletRequest#getRemoteAddr の値を HttpServletRequest#getHeader("x-forwarded-for") の値に書き換えます。存在しない場合は何もしません。
3. Tomcat/JBoss 起動時に Byteman がアタッチされるように設定
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 アドレスの確認)を行っていない