Hateburo: kazeburo hatenablog

SRE / 運用系小姑 / Goを書くPerl Monger

GCP HTTP(S) load balancing 配下のnginxでクライアントIPを取得する方法

GCP HTTP(S) load balancing の X-Forwarded-For ヘッダは少し変わっているのでメモ。

X-Forwarded-For とクライアントIP

ELBや他のproxyを使って、その配下のサーバにリクエスト元のIPアドレスを伝える際には、X-Forwarded-For ヘッダが使われます。

X-Forwarded-For: $remote_addr

リクエストにすでにX-F-Fヘッダがあった場合は、後ろに追加します。

X-Forwarded-For: $http_x_forwarded_for, $remote_addr

となります。

X-F-Fを受け取ったサーバでは、アクセス元のIPが信用できるIPアドレスまたはIP帯域の場合に、X-F-Fの最後のIPアドレスを、remote_addrとして利用します。

nginxでは ngx_http_realip_module を使います。

set_real_ip_from  192.168.1.0/24;
real_ip_header    X-Forwarded-For;

アクセス元が 192.168.1.0/24 の場合、X-F-Fの最後のIPを利用するという意味です。

GCP HTTP(S) load balancing の X-F-F

本題のGCP HTTP(S) load balancingの場合、X-F-Fは

X-Forwarded-For: 1.2.3.4, 35.186.x.x

となります。この場合、1.2.3.4 が実際のclientのIPアドレス、35.186.x.xがロードバランサーに割り当てたIPアドレスです。また、アクセス元のIPアドレスは、以下の帯域(2017/2/21現在)となります。

130.211.0.0/22
35.191.0.0/16

この状態で、

まず

set_real_ip_from 130.211.0.0/22;
set_real_ip_from 35.191.0.0/16;
real_ip_header   X-Forwarded-For;

という設定をいれると、nginxはアクセス元を信用してX-F-Fの最後の一つをクライアントIPだとします。この場合は 35.186.x.x になってしまい、クライアントIPではありません。

さらに設定が必要になります。

set_real_ip_from 130.211.0.0/22;
set_real_ip_from 35.191.0.0/16;
set_real_ip_from 35.186.x.x;
real_ip_header   X-Forwarded-For;
real_ip_recursive on;

set_real_ip_from 35.186.x.xreal_ip_recursive を追加しました。real_ip_recursiveを有効にすると信用できるIPである限り再帰的にX-F-Fをたどっていきます。set_real_ip_fromにロードバランサーのIPを追加することで、35.186.x.xを信用し、次のIPアドレスをクライアントのIPとして採用ができるようになります。

この設定でようやく$remote_addrが1.2.3.4となりました。

まとめ

IPアドレスの追加の機会はそれほど回数がないので、IPアドレスをバランサーに追加した際にset_real_ip_from の追加を忘れるということは必ず発生するでしょう(発生した)。

アクセスログで正しくIPアドレスが解決ができているかの監視を行ったり、blogに書くなどしてチームに共有する必要があります。

Googleの皆様におかれましては、X-F-Fだけではなく、Akamai/CloudFlareのように True-Client-IP ヘッダの採用をお願いしたく候。True-Client-IPがあれば、ロードバランサーIPアドレスをnginxのconfに書く必要がなくなり、

set_real_ip_from 130.211.0.0/22;
set_real_ip_from 35.191.0.0/16;
real_ip_header True-Client-IP;

だけとなります。

何卒 :bow: