nginxからApache2へセキュアにリバプロしたときに真のクライアントIPを取得できるようにする

nginxからApache2のバーチャルホストにいい感じにリバプロする方法の続きで、nginxからApache2にリバースプロキシされた時にクライアントのIPを拾う設定の方法について。

確認環境

Env Ver
OS Ubuntu 24.04.3 LTS
nginx 1.26.1
Apache2 2.4.58

やりたいこと

Apache2で動いているCGIのREMOTE_ADDRからクライアントIPを取れるようにする。

nginxからApache2のバーチャルホストにいい感じにリバプロする方法ではApache2側でREMOTE_ADDRを取得しようとするとnginxのIPになってしまうため、これを回避する方法。

前提条件

  1. nginxからApache2へのリバースプロキシをする時に、真のクライアントIPをX-Real-IPヘッダーに入れていること。
    proxy_set_header X-Real-IP $remote_addr;
    
  2. nginxとApacheが動いているマシンが同じ
  3. IPv6を使っている

やること

# remoteipモジュールの有効化
sudo a2enmod remoteip
# ::1からのHTTPリクエストを信頼しX-Real-IPをREMOTE_ADDRとして解釈する
cat <<'EOF' | sudo tee /etc/apache2/conf-available/remoteip.conf
RemoteIPHeader X-Real-IP
RemoteIPInternalProxy ::1
EOF
# 設定の有効化
sudo a2enconf remoteip
# 設定の反映
sudo systemctl restart apache2

意味合いの解説

sudo a2enmod remoteip

remoteipモジュールの有効化をしている。

逆はa2dismodで無効化出来るっぽい。

RemoteIPHeader <header-field>

ここで指定したHTTPヘッダーをクライアントのIPアドレス(useragent IP address)として扱うようにする。

例えばRemoteIPHeader X-Forwarded-ForとすればX-Forwarded-ForをクライアントIPとして扱える。

この設定だけだと問答無用で信頼してしまうため、本来と異なる経路で攻撃を受けた時に偽装されたヘッダをクライアントIPとして解釈するリスクがある。そのため、次項の設定も行う。

参考:mod_remoteip - Apache HTTP Server Version 2.4

RemoteIPInternalProxy <proxy-ip|proxy-ip/subnet|hostname ...>

RemoteIPHeader単品では前段のnginxを無視してApache2へ直にアクセスされたときに偽のX-Real-IPなどが来ていると、偽のIPが取れてしまうので、これを回避する仕組み。

RemoteIPInternalProxy ::1のように指定することで、そのIPから来たRemoteIPHeaderを信頼する。異なるIPからリクエストが来た場合は、RemoteIPHeaderを無視して、そのクライアントのIPが設定される。

RemoteIPTrustedProxyでも同じことができるのだが、違いはよくわかっていない。一応内部用と外部用で分かれているらしいが、RemoteIPTrustedProxy ::1でも期待通り機能したので謎。

公式ドキュメントではIPv4アドレスが指定されているので、恐らくIPv4でも行けると思うが確認していない。

参考:mod_remoteip - Apache HTTP Server Version 2.4

sudo a2enconf remoteip

作成した設定の有効化を行う。これを行っていない場合、設定は反映されない。

やっていることは/etc/apache2/conf-available/にある設定ファイルのシンボリックリンクを/etc/apache2/conf-enabled/に張っているだけだと思われる。

標準では/etc/apache2/apache2.confIncludeOptional conf-enabled/*.confを指定しているため、これで設定が読み込まれるようになる。

逆はa2disconfで無効化できる。

あとがき

ここまで書いたところで/etc/apache2/sites-available//etc/apache2/sites-enabled/の違いに気が付いた。これも多分有効無効を切り替えるもので、sites-enabled側に設定を直書きするのは誤っているのだろう、きっと。