Amazon CloudFrontとAWS Lambda@EdgeでSPAのBasic認証をやってみる

どうも!大阪オフィスの西村祐二です。

特定のユーザにしかログインできないように、
サイト自体にBasic認証をかけたい時があると思います。

今回はS3でホスティングするSPAを想定して、
Amazon CloudFrontとAWS Lambda@Edgeを使ってBasic認証をかけたいと思います。

構成図

完成画面

完成後下記のような挙動になります。
ブラウザ上にURLを入力してアクセスすると、Basic認証の画面が表示されます。
認証が通ったら、S3に保存されているWEBページが表示されます。

作業手順

  • S3でSPA用のバケット作成
  • CloudFrontのディストリビューションを作成
  • Lambda@Edge用のLambda関数作成
  • 作成したLambda関数でトリガーをCloudFrontに設定

S3での作業

SPAの静的ファイルを設置するためにバケットを作成します。
リージョンは「東京リージョン」で作成しています。
バケット名は今回「test-lambda-edge」としました。
ログ出力用に「test-lambda-edge-log」も一緒に作成しておくとよいかもしれません。

テストファイルを配置

SPAを想定して、今回下記の静的ファイル「index.html」をS3バケットの「test-lambda-edge」に設置しておきます。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Test Basic Authentication</title>
  </head>
  <body>
    <p>Hello World!! Passed Basic Authentication!</p>
  </body>
</html>

webホスティングを有効

webホスティングを有効にしておきます。

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

▼マネージメントコンソールから作成していきます。「Create Distribution」をクリックします。

▼Webを選択します。

▼下記のように設定していきます。

▼TTLは各自適当な設定を行ってください。今回コンテンツの更新をすぐ確認したかったため、無効化しています。

確認

▼作成したディストリビューションが「Deployed」であることを確認します。

▼HTTPでアクセスできないこと

▼HTTPSでアクセスできること

Lambda関数の作成

次にCloudFrontのエッジロケーションに配置するLambda関数を作成していきます。

IAMロールの作成

まず、Lambda@Edge用のIAMロールとポリシーの設定を行います。

▼IAMのマネージメントコンソールから設定していきます。

▼「信頼されたエンティティ」でLambdaをクリックし「次へ」のボタンをクリックします。

▼作成するロールにアタッチするポリシーを作成します。

▼新しいタブが開き、ポリシーを作成する画面に移動します。
JSONのタブをクリックして、下記のポリシーを貼り付けます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "lambda:GetFunction",
                "lambda:EnableReplication*",
                "iam:CreateServiceLinkedRole",
                "cloudfront:CreateDistribution",
                "cloudfront:UpdateDistribution"
            ],
            "Resource": "*"
        }
    ]
}

▼ポリシーの名前を「test-lambda_edge_exection」としてポリシーを作成します。

▼ロール作成のタブに戻り、「更新」ボタンをクリックし、検索窓から検索し、作成したポリシーを選択し、次へ行きます。

▼ロール名を「test-lambda_edge_exection」としてロールを作成します。

▼信頼関係に「edgelambda」を追加します。

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

これで、IAMロールとポリシーの設定は完了です。

Lambda@Edgeの注意点

※2018/1/5時点の情報となります。
通常の作成方法とは異なりいくつかハマりポイントがあります。Lambda関数を作成する前に、Lambda@Edgeの注意点についてまとめておきます。

詳細はドキュメントを参照ください。

  • ランタイム:Node.js 6.10のみ対応?
  • リージョン:米国東部(バージニア北部)で作成する必要あり
  • メモリサイズ:128MBまで
  • 実行時間:
    • ビューワーリクエストおよびビューワーレスポンスイベント:1秒
    • オリジンリクエストおよびオリジンレスポンスイベント:3秒
  • Lambda関数のファイルサイズ:1MBまで
  • バージョニングを使用する必要あり、$Latest は指定できない
  • /tmp は利用不可
  • 環境変数, Dead Letter Queue, Amazon VPCsは利用不可

Lambda@Edgeのイベント

今回行うBasic認証はビューアリクエストのイベントに該当します。

Lambda関数の作成

▼マネージメントコンソールから作成していきます。

▼「一から作成」を選び、名前「S3-basic-authentication」、ランタイム「Node.js 6.10」、作成したIAMロールを設定して関数を作成します。

▼実行時間の設定をビューアリクエストの制限をこえないように「1秒」に設定します。

実際のプログラムはこちらを参考にさせていただきました。

'use strict';
exports.handler = (event, context, callback) => {
    
    // Get request and request headers
    const request = event.Records[0].cf.request;
    const headers = request.headers;
    console.log('request: ' + JSON.stringify(request));
    // Configure authentication
    const authUser = 'user';
    const authPass = 'pass';

    // Construct the Basic Auth string
    const authString = 'Basic ' + new Buffer(authUser + ':' + authPass).toString('base64');

    // Require Basic authentication
    if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
        const body = 'Unauthorized';
        const response = {
            status: '401',
            statusDescription: 'Unauthorized',
            body: body,
            headers: {
                'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
            },
        };
        callback(null, response);
    }

    // Continue request processing if authentication passed
    callback(null, request);
};

ここまで設定ができたら「保存」をクリックしておいてください。

バージョニングの設定

LambdaをCloudFrontのエッジロケーションに配置するためにトリガーをCloudFrontに設定するのですが
CloudFront イベントを $LATEST またはエイリアスと関連付けることはできません。

そのため、バージョン設定を行う必要があります。

▼アクションから「新しいバージョンを発行」をクリックします。

▼バージョンを発行します。

▼発行されたら下記のような表示がされます。

トリガー設定

Lambda@Edgeとして、CloudFrontのエッジロケーションにLambda関数を設定するためには
Lambda関数のトリガーをCloudFrontにする必要があります。

▼作成したディストリビューションのIDを設定し、イベントに「ビューアリクエスト」を設定し追加します。

▼その後「保存」ボタンをクリックすると、CloudFrontのディストリビューションのステータスが「In Progress」になります。

▼ステータスが「Deployed」になったら、エッジロケーションにLambda関数のデプロイが完了したということになります。

Basic認証の動作確認

CloudFrontのURLにアクセスしてみると想定通り Basic認証のログインアラートが表示されています。

さいごに

いかがだったでしょうか。

Amazon CloudFrontとAWS Lambda@EdgeでSPAのBasic認証をやってみました。
Lambda@Edgeはエッジロケーションへのデプロイまで時間がかかったり
作成時に制限などがありますが、いろいろなことに活用できそうだなと思いました。

誰かの参考になれば幸いです。

参考サイト

http://blog.jicoman.info/2017/10/s3-basic-using-cloudfront-lambda-edge/

https://qiita.com/sumikawa@github/items/0344a2920f2438dea3b5

https://gist.github.com/lmakarov/e5984ec16a76548ff2b278c06027f1a4

http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-edge.html#lambda-edge-testing-debugging