概要
ECSでNginxのコンテナをプロキシとして立てたところ、APIサーバのアクセスログのクライアントIPがNginxのコンテナIPになっていたのでその修正をしたのがきっかけです。
環境
- Nginx 1.10.2
- Docker1.12.1
構成
Client -> ELB -> Nginx -> API
という構成とします。
ネットでよく見る情報
set_real_ip_from 172.31.0.0/16; real_ip_header X-Forwarded-For;
を追加する、とか
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
を追加する、とかどれがどれだか分かりにくいので1つ1つ説明していきます。
説明
remote_addr
アクセス元のIP。ネットワーク層の情報。
基本的に直前のIPを保持しているので、
Client ↓ ELB( remote_addr はClient) ↓ Nginx( remote_addr はELB) ↓ API( remote_addr はNginx)
となる。
x-forwarded-for
HTTPヘッダの一つ。ロードバランサやプロキシを経由する時に送信元を判別するために利用。アプリケーション層の情報。
一般的なフォーマットは
X-Forwarded-For: client1, proxy1, proxy2
という順に追加していく。
なので送信元を知りたければ1つ目の要素を見れば良い。
x-real-ip
これもHTTPヘッダの一つ。ロードバランサやプロキシを経由する時に送信元を判別するために利用。アプリケーション層の情報。
x-forwared-forと同じような値だけど、複数の可能性があるx-forwarded-forと違って1つ。
real_ip_header
Nginxがremote_addr
を変更するときに使うモジュール。
real_ip_header X-Forwarded-For;
とすればELBが間にあってもちゃんとクライアントのIPをremote_addr
としてくれる。
Nginxのログがデフォルトだとremote_addr
を使うので、ELBを挟んでいるときはこの設定が推奨。
ELB+SSLだとProxyProtocolが必要で、その場合は
real_ip_header proxy_protocol;
と設定する。
set_real_ip_from
x-forwarded-for
は偽装可能なので、信頼できるところ以外からはreal_ip_header
を使わないようにするための設定。
set_real_ip_from 172.31.0.0/16; real_ip_header X-Forwarded-For;
なら172.31.0.0/16
のアクセスのみ書き換える。
ELBの場合、VPCのアドレス空間を指定する。
x-forwarded-for, x-real-ipの注意点
- IPレイヤでなくHTTPレイヤなので書き換え可能(改竄)
- HTTPSだと終端でないとヘッダは暗号化されているので取得できない
最終的にどうすればいい?
Client -> ELB -> Nginx -> API
という構成であれば
server { listen 80; listen [::]:80; set_real_ip_from 10.10.0.0/16; # 信頼できるアドレス空間を指定。 real_ip_header X-Forwarded-For; # remote_addrを書き換え。 location / { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Host $http_host; proxy_set_header Connection ""; proxy_set_header X-Real-IP $remote_addr; # x-real-ipにクライアントIPを設定。APIへ渡す。 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # x-forwarded-forをAPIへ渡す。 } }
でOKです。
API側のチェックは?
x-real-ip ↓ x-forwarded-for ↓ remote_addr
の順にチェックすると良いと思います。存在すればそれを使い、無ければ次の要素をチェックする感じで。