読者です 読者をやめる 読者になる 読者になる

Carpe Diem

備忘録。https://github.com/jun06t

remote_addrとかx-forwarded-forとかx-real-ipとか

Nginx Docker

概要

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

の順にチェックすると良いと思います。存在すればそれを使い、無ければ次の要素をチェックする感じで。

ソース