API Gatewayでもアクセス元IPを取得したい #アドカレ2015

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

ウィスキー、シガー、パイプをこよなく愛する大栗です。
今回は非エンジニア視点でのAWSモバイルアドベントカレンダー19日目の記事になります。AWSのインフラチームに属しているので、アプリケーションの要素が多いモバイル関連のサービスには触れる機会が少ないのですが何故かモバイルのアドベントカレンダーにエントリーすることになり、なかなか困っています。。。

API Gatewayを使う時にアクセス元のIPアドレスを取得するのに戸惑ったことがあったので、ご紹介してみます。

アクセス元のIPアドレスの取得?

普通にWebサーバで直接受ける場合とロードバランサ(ELB等)を経由する場合では、アクセス元IPアドレスの取得方法が変わります。

Webサーバで直接受ける場合

一般的にWebサーバで直接リクエストを受ける場合は、以下のようにWebサーバ内で定義されています。

  • Apache:REMOTE_ADDR(mod_setenvifの場合)、%{REMOTE_ADDR}(mod_rewriteの場合)
  • nginx:$remote_addr

REMOTE_ADDRは、RFC 3875(The Common Gateway Interface (CGI) Version 1.1)で規定されているようです。REMOTE_ADDRはIPレイヤの情報を元にするため確実な情報と言えます。

ロードバランサを経由する場合

ELB等のような一般的なロードバランサでは、HTTPのX-Forwarded-Forヘッダにアクセス元IPアドレスを記載します。

X-Forwarded-Forは、RFC 7239(Forwarded HTTP Extension)で規定されています。X-Forwarded-ForはHTTPレイヤであるため、ヘッダ情報を追加することで偽装が可能です。1

API Gatewayでアクセス元IPアドレスを取得する場合

API Gatewayを使用する場合はTCPパケットを直接受けるわけではないので、「ロードバランサを経由する場合」だと思うでしょう。しかし、API GatewayではELBの様に「X-Forwarded-For」を付与してくれません。バックエンドにLambdaを置く場合は、そもそもHTTPヘッダを参照できません。そのため、アクセス元のIPアドレスを取得する時に困ってしまいます。

実は、API Gatewayにはアクセス元のIPアドレスを取得する方法が用意されています。 API Gatewayのマッピングテンプレートで事前に $context.identity.sourceIp として変数が定義されており、それを使用することでアクセス元IPアドレスを取得できます。マッピングテンプレートで定義するので、バックエンドではリクエストボディの中身として使用します。
定義されている変数の詳細は、 API Gateway のマッピングテンプレートリファレンス を参照しましょう。

Integration Request で Mapping Templates を以下のように定義します。sourceIp にアクセス元IPアドレスを格納しています、

{
  "sourceIp" : "$context.identity.sourceIp",
  "input" : $input.path('$')
}

API Gatewayのバックエンドとして入力データをログに出力するLambda Functionを用意しておきます。

console.log('Loading function');

exports.handler = function(event, context) {
    console.log('Received event:', JSON.stringify(event, null, 2));
    context.succeed(event);
};

以下のようにcurlでアクセスしてみます。

$ curl -X POST -H 'Content-type: application/json' \
    -d '{ "key1": "value1", "key2": "value2", "key3": "value3" }' \
    https://w84pwiefp3.execute-api.ap-northeast-1.amazonaws.com/prod/sample

CloudWatch Logsでは以下のように sourceIp として出力されます。これでアクセス元IPアドレスが取得できました。

2015-12-19T14:47:48.199Z	78adad8f-a65f-11e5-8b0b-2dd2763df46e	Received event:
{
    "sourceIp": "192.0.2.1",
    "input": {
        "key1": "value1",
        "key2": "value2",
        "key3": "value3"
    }
}

なお、Method Request の中で、HTTP Request HeadersX-Forwarded-For を定義するとMapping Templates で$input.params('X-Forwarded-For')としてアクセス元IPアドレスを取得することも可能です。しかし、内容としてクライアントのIPアドレスと、CLoudFrontのIPアドレスの両方が入っているため、使用しにくい内容です。

さいごに

API Gatewayはインフラ担当ではあまり使用しないサービスなので、まだまだ勉強中ですが使ってみると奥が深いサービスなので使い込んでいきたいですね。 明日(12月20日)は、田宮の Amazon Device Farm (実装) についての記事です。お楽しみに!


  1. 通過されるロードバランサはX-Forwarded-Forの最後に自分のIPアドレスを追加します。そのため、最後のIPアドレスを信用すべきです。