多段nginxでもX-Forwarded-ForできちんとバックエンドにクライアントIPアドレスを伝える2019/08/17nginx主にバックエンドにRailsを利用する環境向け。LBやリバースプロキシの背後にあるバックエンドサーバにクライアントIPアドレスを伝える方法の一つとして、`X-Forwarded-For`というHTTPリクエストヘッダがあります。このリクエストヘッダを利用してきちんとバックエンドにクライアントIPアドレスを伝える方法をメモします。X-Forwarded-For(以下XFF)とはLBによるL4以上でのD-NATや、リバースプロキシによる代理アクセスなど、IPヘッダのソースIPアドレスがクライアントのIPアドレスからその中継機器のIPアドレスに置き換えられてしまう場合に、クライアントのIPアドレスをバックエンドサーバが取得できるよう、中継機器が設定するリクエストヘッダです。クライアントのIPアドレスと、中継した機器のIPアドレスを配列として格納します。内部ネットワーク構造隠蔽のため、プライベートIPアドレス空間からインターネットへ出るタイミングではこのヘッダを削除するのが普通です。...proxy1はリクエストを受け付けて、背後のproxy2に代理リクエストを送る際に、クライアントのIPアドレスをこのヘッダに設定します。proxy2以下は、上流プロキシから送られてきたXFFの右端に、上流プロキシのIPアドレスを追加して、下流プロキシ、もしくはバックエンドにリクエストを送付します。クライアントのIPアドレスを取得することが目的のヘッダなので、究極を言えばclientのIPアドレスさえ付与されていれば良いですが、きちんと中継機器のIPアドレスも追加しておけば、バックエンドサーバがリクエストを受け取った際、どういう経路を辿ってリクエストが到着したのかがわかります。リクエストヘッダの改ざんとXFF肝要なのは、このXFFは改ざんが容易であることです。本来このリクエストヘッダは、中継機器が受け取ったリクエストをバックエンドに流す際に付与されるヘッダですが、リクエストヘッダなのでクライアント自身もこのヘッダを付与したリクエストを送ることが可能です。ローカルIPアドレスをX-Forwarded-Forに記載しています。proxy1.example.com以下の条件がそろうと、外部からプライベートIPアドレスのアクセス制限を破ることができます。clientのIPアドレスは仮に8.8.8.8だとします。XFF以外のクライアントIPアドレスをバックエンドに伝えるリクエストヘッダ他にもあります。バックエンドのアプリケーションが何のリクエストヘッダを使ってクライアントIPアドレスを判断するかによって使い分けます(全部付けといてもいいです)。今回はXFFに焦点を絞ります。以下のような環境を想定します。proxy1はインターネットに露出しているという想定の検証環境です。proxy1とproxy2、backendは信頼できる同一ネットワーク内にいると想定します。インターネットに露出しているproxy1は、仮にリクエストヘッダにXFFがあったとしても信頼できないため無視(破棄)する。クライアントのIPアドレスを値にとってXFFヘッダを作り、proxy2にリクエストを送るproxy2は信頼できるネットワーク内にいるproxy1を信用し、proxy1から受け取ったリクエストのXFFにproxy1のIPアドレスを追加し、backendにリクエストを送るbackendは信頼できるネットワーク内にいるproxy1とproxy2を信用し、受け取ったXFFの左端の値からクライアントIPアドレスを取得するnginxをインターネットに直接露出させることはあまりないと思いますが、proxy2の設定と対比させる例として記載します。インターネットに露出しているnginxの設定インターネットに露出するなら普通SSL/TLS対応するいうのは今回なしで......。}}12345678910111213141516171819ポイントはXFFを$remote_addrで置き換えてしまうこと。仮にXFFに値が入った状態でクライアントがアクセスしてきても、それを無視してクライアントのIPアドレスをXFFに格納します。set_real_ip_fromディレクティブproxy1を信頼できる上流プロキシとして登録します。ngx_http_realip_moduleを有効化し、real_ip_headerディレクティブやreal_ip_recursiveディレクティブを有効化します。real_ip_headerディレクティブクライアントIPアドレスを判別するリクエストヘッダをXFFに変更します。バックエンドのアプリケーションサーバによっては、X-Real-IPを取得することができず、XFFを使わねばならないことがあります。X-Real-IPを使ってクライアントIPアドレスを取得している、となると気持ち悪いので、XFFに統一します。real_ip_recursiveディレクティブreal_ip_headerをXFFに設定している場合にこのディレクティブの設定が有効になります。XFFからクライアントIPアドレスを判断する方法を変更します。XFFからクライアントIPアドレスをどう判断するかの判断方法を変更します。デフォルトはoffで、XFFの右端にあるIPアドレスをクライアントのIPアドレスとします。右端が信頼できるプロキシであったとしても問答無用でそれをクライアントのIPアドレスとします。onにすると、XFFを右から左に見ていき、一番最初に見つかった信頼できないIPアドレスをクライアントのIPアドレスだと判断します。proxy2はproxy1しか経由しておらず、XFFには一つのアドレス(クライアントIPアドレス)しか入っていないはずなので、onでもoffでも取れるアドレスは変わりません。あとでもう一段リバースプロキシを追加したい、などとなった場合でも、設定変更なしできちんとクライアントのIPアドレスが取れるよう、onにしておきます。offの方がXFF改ざんに対してセキュアですが、proxy1でXFFを初期化しているため問題ありません。ちなみにreal_ip_headerをproxy_protocolに変更している場合も、real_ip_recursiveの設定でクライアントIPアドレスを判断する方法を変更できますが、ここでは割愛します。上流のプロキシがリクエストで送ってきたXFFを$http_x_forwarded_forで取得し、それにスペース+カンマで上流のプロキシのIPアドレスを追加したものをXFFに設定します。real_ip_headerやreal_ip_recursiveを変更した際の$remote_addrの注意点クライアントIPアドレスの取得の方法を変更するため、nginx変数の$remote_addrの中身も書き換えられます。有効化されており、かつX-Real-IPヘッダがあればその値を、そうでなければIPヘッダのソースIPアドレスを利用しますが、今回設定を変更した内容より、XFFから$remote_addrが判別され、$remote_addrには上流プロキシではなくクライアントのIPアドレスが入っています。この状況で上流プロキシのIPアドレスを取得したい場合は、代わりに$realip_remote_addrを利用してください。この変数は、リクエストを送ってきたホストのIPアドレスをそのまま記憶しています。バックエンドnginxの設定バックエンドをnginxにする場合の例です。実際はアプリケーションサーバであることが多いと思いますが、ログを確認する目的だけなのでngnixで。}}123456789101112131415161718192021バックエンド側でもクライアントIPアドレスを判断するために、信頼できるプロキシと、クライアントIPアドレスを判断するリクエストヘッダ名、それを判断する方法を指定する必要があります。railsならtrusted_proxiesで信頼できるプロキシを登録しておけばよしなに判断してくれます。先ほど既述したように必ずreal_ip_recursiveをonにしないといけません。クライアントIPや上流プロキシのIPアドレスをどうやって取得しているかクライアントIPアドレスはremote_addrで取得できています。これは設定を行ったようにXFFで判断しています。XFFの推移は以下の通り。172.21.0.1このXFFの値によって、remote_addrは全て元々のクライアントのIPアドレス192.168.100.101が取得できているのでOK。proxy)相手はrealip_remote_addrで取得できています。以下のコマンドでXFFを詐称してみます。172.21.0.1proxy1でいかなるXFFも無視しているので、下流には詐称されたヘッダが伝わっていませんのでOK。remote_addrもOKですね。