ちょっと話題の記事

[機能アップデート]ついに Lambda@Edge が HTTP リクエストボディにアクセスできるようになった!

ついに Lambda@Edge で HTTP リクエストボディにアクセスできるようになりました!サンプルケースでは Lambda@Edge を Kinesis Firehose のプロキシとして使う方法をご紹介します!
2018.08.16

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

AWS 本家ブログ記事にて、Lambda@Edge で HTTP リクエストの BODY にアクセスできることがアナウンスされました。

Today we announced that Lambda@Edge can now access the HTTP Request Body.

これまで Lambda@Edge ではヘッダのみに限定されていましたので、これは大きなアップデートですね!早速、本家記事を参考に、サンプルケースを試してみましょう!

どんなことが出来るの?

サンプルケースでは、CloudFront + Lambda@Edge で受けたリクエストボディを Kinesis Firehose で S3 に配信する内容となっています。 一般的に、Kinesis にデータを送信する場合、SDK か Kinesis Agent を使用します。もしくは HTTP で POST する場合、API Gateway を Kinesis プロキシとして配置し、 Lambda で流す必要がありました。今回のアップデートにより、Kinesis プロキシを CloudFront + Lambda@Edge だけでも出来るようになったということですね!素敵ですね!

ではさっそくやってみる

CloudFront ディストリビューションの作成

CloudFront の管理画面から、ディストリビューションを作成します。

[Web]の下にある、[Get Started]をクリックします。

ディストリビューションのオプションを選択します。今回は検証目的ですので、必要なところ以外はデフォルトで設定します。

  • POST メソッドを受けますので [Allowed HTTP Methods]は GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE を選択
  • [Cache Based on Selected Request Headers]は ALL を選択
    • POST については元々キャシュされずにパススルーされますが、一旦 ALL にしました

その他はデフォルトのまま [Create Distribution]をクリックし作成します。

[Status]が Deployed、および[State]が Enabled に変わるまで数分待ちますので、その間に次の手順に進みましょう。

Kinesis Firehose 配信ストリームの作成

次に Kinesis Firehose の配信ストリームを作成します。実際の用途であれば、ユーザがいるすべてのリージョンに作成し、CloudFront のエッジロケーションから最も近い配信ストリームに送ることでレイテンシを下げるべきかと思いますが、今回は検証目的ですので東京リージョンのみに作成します。

Kinesis のナビゲート画面から[配信ストリームの作成]をクリックします。

[Step 1:Name and source]では、[Delivery stream name]に任意の名前を割り当て、[Source]では Direct PUT or other sources を選択し、[Next]をクリックします。

[Step 2:Process records]はデフォルトのまま[Next]をクリックします。

[Step 3:Choose destination]では、[Destination]で Amazon S3を選択し、データを保存するためのバケットを選択(または作成)します。必要に応じてプレフィックスの指定も可能です。今回は東京リージョンの S3 バケットを指定しました。設定できましたら[Next]をクリックします。

[Step 4:Configure settings]では、バッファサイズとインターバルの設定が可能ですが、今回はデフォルト設定にしています。また、IAM ロールを作成(または選択)します。検証の場合、この画面から新たに作成した方が、必要な権限(対象バケットへの PUT 権限など)の選択などが不要なので楽でしょう。作成できましたら [Next] をクリックします。

[Step 5:Review]で設定値を確認し、問題なければ[Create delivery stream]をクリックし、配信ストリームを作成します。

各リージョンでも配信ストリームを作成される場合ですが、今回のサンプルコードはすべて同じ配信ストリーム名を想定した形になっていますので、ご注意ください。

Lambda@Edg 関数の作成

次に Lambda@Edge の関数を作成するので、リージョンをバージニア北部に切り替え Lambda の管理画面を開き、[関数の作成]をクリックします。[一から作成]を選択し、関数名は任意の名前を割り当て、ランタイムは [Node.js 6.10]を選択。ロールは以下のポリシーと、信頼関係をセットしたロールを選択(もしくは作成)します。

  • AmazonKinesisFirehoseFullAccess
  • AWSLambdaBasicExecutionRole

信頼関係

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": [
          "edgelambda.amazonaws.com",
          "lambda.amazonaws.com"
        ]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Lambda コードはブログ記事より拝借し、コピペします。今回、単一リージョンにのみ Kinesis Firehose 配信ストリームを作成し利用しますので、4行目のコメントを解除し、5行目が不要になりますのでコメントアウトしています。また、今回は東京リージョンの配信ストリームを使いますので、ap-northeast-1 としました。また、7行目の DeliveryStreamName は、作成された環境にあわせて配信ストリーム名を変更してください。

exports.handler = (event, context, callback) => {
    var bodyData = new Buffer(event.Records[0].cf.request.body.data, 'base64').toString("utf-8");
    var AWS = require('aws-sdk');
    var kinesisfh = new AWS.Firehose({region: 'ap-northeast-1'});  //uncomment if you want a specific-region of Firehose
    //var kinesisfh = new AWS.Firehose();  //or provision a firehose in every region where Lambda may run
    var params = {
        DeliveryStreamName: 'edgetest', /* required */
        Record: { /* required */
        Data: JSON.stringify({bodyData})
        }
        
    };
    var responseBody = "Successfully Submitted Record";
        responseBody +=  bodyData;
        responseBody += context.invokedFunctionArn;
        responseBody += "Invoke Id: ";
        responseBody += context.invokeid;
    kinesisfh.putRecord(params, function(err, data) {
        if (err) console.log(err, err.stack); // an error occurred
        else console.log(responseBody); // successful response
        });
    var headers = [];
    headers['strict-transport-security'] = [{
        key: 'Strict-Transport-Security',
        value: "max-age=31536000; includeSubdomains; preload"
        
    }];
    headers['content-security-policy'] = [{
        key: 'Content-Security-Policy',
        value: "default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'"
        
    }];
    headers['x-content-type-options'] = [{
        key: 'X-Content-Type-Options',
        value: "nosniff"
        
    }];
    const response = {
        body: responseBody,
        bodyEncoding: 'text',
        headers,
        status: '200',
        statusDescription: 'OK'
    };
    callback(null, response);
    return response;
};

Lambda のリソース設定としては、タイムアウトをデフォルト3秒から5秒に変更し、画面右上の[保存]をクリックします。(処理の内容次第で、適宜変更してください)

次に[アクション]-[新しいバージョンを発行]をクリックし、バージョン名は任意に入力ください。(省略の場合、自動で割当たります)

トリガーの追加欄から[CloudFront]をクリックします。

[トリガーの設定]では、先程作成したディストリビューションを選択、キャッシュ動作は今回はデフォルトのままです。[CloudFront イベント]ではビューアーリクエストを選択します。そして、新しく追加された項目[ボディを含める](include body)にチェックをいれ、[トリガーとレプリケートの有効化]にもチェックを入れて、[追加]をクリックします。最後に画面右上にもどり[保存]をクリックします。

CloudFront ディストリビューションの[Status]が Deployed、[State]が Enabled に変わるまで数分待ちます。

では、動作を確認してみましょう!

動作を確認してみる!

それでは curl コマンドで POST してみましょう!

$ curl http://XXXXXXXX.cloudfront.net --request POST --data '{"name":"test http"}'

Successfully Submitted Record{"name":"test http"}arn:aws:lambda:ap-northeast-1:XXXXXXXXXXX:function:us-east-1.edge-kinesis-producer:3Invoke Id: 24803517-a0ef-11e8-86c6-e779169f30aa

指定した S3 バケットを確認してみると、ファイルが作成されていますので内容を確認してみます。

{"bodyData":"{\"name\":\"test http\"}"}

問題なく HTTP リクエストボディの内容が Lambda@Edge で Firehose → S3 に配信できてますね! HTTPS のアクセスも確認しておきましょう。

$ curl https://XXXXXXXX.cloudfront.net --request POST --data '{"name":"test https"}'

Successfully Submitted Record{"name":"test https"}arn:aws:lambda:ap-northeast-1:XXXXXXXXXXX:function:us-east-1.edge-kinesis-producer:3Invoke Id: 76f28370-a0f0-11e8-b1d6-5f2dd9f8ef19

HTTPS の場合でも CloudFront で終端されて Lambda@Edge で処理されてるので問題なさそうですね!

{"bodyData":"{\"name\":\"test https\"}"}

制限

HTTP リクエストボディにアクセスするにあたって、いくつか制限があるので確認しておきましょう。

  • ボディは常に Lambda@Edge によって base64 でエンコードされています
    • 人が読める状態でボディを処理するには、base64 デコードの処理が必要です。(サンプルコード2行目がデコード処理になっています)
  • ボディのサイズが大きすぎる場合、Lambda@Edge はボディを切り捨てます
    • ビューアーリクエストは、 40KB で切り捨てられます
    • オリジンリクエストは、 1MB で切り捨てられます
  • リクエストボディを読み取り専用でアクセスすると、元の状態のリクエストボディがオリジンに戻されます。ただし、リクエストボディに変更を加えた場合、Lambda 関数から戻されるとき次のボディサイズの制限が適用されます。
エンコーディングのタイプ ビューアーリクエストのボディサイズ制限 オリジンリクエストのボディサイズ制限
text 40KB 1MB
base64 53.2KB 1.33MB

さいごに

サンプルケースのように Kinesis プロキシとして利用する場合、送信元の IP 制限なども CloudFront + WAF によって容易に構成することが出来ますね!サイズ制限や、base64 でエンコードされているなど、いくつかの制限はありますが、Lambda@Edge がリクエストボディにアクセス出来るようになったことで、またまたアーキテクチャの幅が広がりますね!どんどん試して使っていきましょう!

以上!大阪オフィスの丸毛(@marumo1981)でした!