Lambda@Edgeを使いNginxでクライアントIPアドレスを受け取る

2018.07.27

コンニチハ、千葉です。

CloudFront + ELB + Nginxの構成で、NginxのログにクライアントIPアドレスを保存したいという要件がありました。 色々な方法があるので、整理してみました。また、Lambda@Edge使ったらなんとかいけるんじゃないかと思い試してみたらいけたのでお届けします。今回はNginxで試しましたがApacheや他アプリでも応用できるんじゃないかと思います。

いろいろなやり方

案1:X-Forwarded-Forを使ってCloudFrontのIPアドレス帯をメンテする

X-Forwarded-Forの値を使って、remote_addrを書き換えるという内容です。

例えば、以下のようにnginx.confを書き換えます。

set_real_ip_from  VPCのセグメント;
set_real_ip_from  CloudFrontのIPセグメント1;
set_real_ip_from  CloudFrontのIPセグメント2;
・・・
set_real_ip_from  CloudFrontのIPセグメントX;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

ここで、問題になるのがCloudFrontのIPセグメントを指定する必要があり、しかもCloudFrontのセグメントは変わる可能性があるということです。そのため、CloudFrontのIPセグメントをウォッチして定期的にNginxの設定を変更するというのが必要になります。複数のWebサーバーが存在している場合など、自動化は可能ですがあまり現実的ではないですね。

案2:X-Forwarded-Forを使って0.0.0.0/0を信頼するという設定にする

以下のようにnginx.confを書き換えます。

set_real_ip_from  VPCのセグメント;
set_real_ip_from  0.0.0.0/0;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

そもそも、set_real_ip_fromの値を0.0.0.0/0にしちゃいます。案1と挙動が変わるのですが、使えるかは要件次第って感じです。

例えば、X-Forwarded-For: 192.0.2.4(クライアントIP),192.0.2.3(プロキシIP),192.0.2.2(CloudFront IP),192.0.2.1(ELB IP)だったとします。(IP帯が超適当なのは無視してください)

案1の場合は、VPC内のセグメント、CloudFrontのセグメントを信頼しているという設定にしているので、NginxでのクライアントIPは192.0.2.3(プロキシIP)になります。 案2の場合、0.0.0.0/0としているのでX-Forwarded-Forに一番左の値である192.0.2.4(クライアントIP)がクライアントIPとなります。

なので、クライアントIPという定義がどうなっているかっていう要件次第で利用できる、できないっていうのが発生します。

案3:Lambda@Edgeを使ってクライアントIPをカスタムヘッダで送る

CloudFrontで受けたIPを、Lambda@Edgeを使ってカスタムヘッダとしてオリジンへ送ります。

L@Eのコード例です。オリジンリクエストとして設定します。

'use strict';

exports.handler = (event, context, callback) => {
    console.log(JSON.stringify(event));
    const request = event.Records[0].cf.request;
    const clientIp = event.Records[0].cf.request['clientIp'];
    const headerName = 'X-Custom-Client-IP';
    request.headers[headerName.toLowerCase()] = [{ key: headerName, value: clientIp }];
    console.log(JSON.stringify(request));
    callback(null, request);
    return;
};

CloudFrontで受けたIPアドレスをX-Custom-Client-IPというヘッダを付与して、オリジンにリクエストします。これを、Nginxでは以下の設定をすることでクライアントIPとして受け取ることができます。

set_real_ip_from  VPCのセグメント;
real_ip_header X-Custom-Client-IP;
real_ip_recursive on;

これだと運用負荷もなく、CloudFrontの導入前と同じような利用が可能になります。

案4:そもそもNginx以外のところで対応する(Nginxで頑張らない)

せっかくAWSを利用するんだから、Nginxで全部頑張ろうとせずにAWSサービスでカバーできるところはカバーしようぜ!という発想です。

例えばNginxでGEO制限したい場合は、CloudFrontの機能で可能ですし、AWS WAFでブロックしたり、またログ集計したい場合はCloudFrontのログをAthenaで集計したりQuickSightで可視化することが可能です。

Amazon Athena で CloudFront のアクセスログを集計する

ELBのログの例ですがCloudFrontでも応用できます。

ELBのアクセスログを可視化し、超絶手軽に分析する[QuickSight,Athena]

さいごに

CloudFrontを導入してもNginxでクライアントIPを受けたいという構成について考えてみました。既存運用を変更しないのであれば、「Lambda@Edgeを使ってクライアントIPをカスタムヘッダで送る」です。色々理由があってできないとかはあるかもしれませんが、個人的にはせっかくなので「そもそもNginx以外のところで対応する(Nginxで頑張らない)」がおすすめです。

参考

http://nginx.org/en/docs/http/ngx_http_realip_module.html