AWS
S3
CloudFront
Lambda@Edge

S3 + CloudFront + Lambda@Edge でBasic認証

More than 1 year has passed since last update.

1. おさらい

本題の前に前提知識を軽くおさらい.

1.1. S3

Amazon Simple Storage Service.
AWSの分散ストレージサービス.
大量のデータを安く、安全に保管できる.
今回はHTML, CSSといったデータを保管, 配信する用途で利用.

1.2. CloudFront

Amazon CloudFront.
AWS のグローバルなコンテンツ配信サービス.
世界各地のサーバ(エッジロケーション)にサーバの元データ(オリジンサーバ)をキャッシュして, どこからでも高速にコンテンツへアクセスできる.
今回はS3のデータをキャッシュし, HTTPSで安全にコンテンツ配信を行う目的で利用.
また今回は行わないが, Route53でドメインを取得することで, 独自ドメインでの配信も可能になる.

1.3. Lambda@Edge

AWS Lambda@Edge
2017/07/28より正式リリースされたAWS LambdaとCloudFrontの統合サービス.
AWS Lambdaはコンテナ技術を使って、サーバレスに処理を実行するサービスだったが, Lambda@EdgeはそのLambdaFunctionをCloudFrontのエッジロケーションへいっしょにキャッシュ(デプロイ)する.
これにより, Basic認証やCookieの書き換えなど, CloudFront単体では成し得なかったアプリケーション処理を加えることができるようになった.
今回はCloudFrontへのリクエストをインターセプトし, Basic認証を加える.

2. 構成

今回は, S3に配信するWebページ, LambdaにBasic認証を行う関数をそれぞれ配置し, CloudFrontでエッジロケーションに配置してBasic認証を通したWebページ配信を行う.

2.1. Lambda@Edgeのインターセプトタイミング

Lambda@Edgeがインターセプトできるタイミングは4種類ある.

  • ビューワーリクエスト : リクエストがユーザからエッジロケーションに到着したタイミングでインターセプトする.
  • オリジンリクエスト : リクエストがエッジロケーションからオリジンサーバへ送られるタイミングでインターセプトする.
  • オリジンレスポンス : レスポンスがオリジンサーバからエッジロケーションに到着したタイミングでインターセプトする.
  • ビューワーレスポンス : レスポンスがエッジローケーションからユーザに送られるタイミングでインターセプトする.

LambdaEdge.001.png

2.2. CloudFrontはS3にAuthenticationヘッダーを通さない

Basic認証には Authentication ヘッダーが必要になる.

1-Qg-dkUQHomX9HKt1mJTP5A.png

CloudFrontはS3へリクエストを転送する際に Authentication ヘッダーを削除してしまうため, オリジンリクエストをインターセプトした場合 Authentication ヘッダーを検証することができない.
よって, 必然的にLambda@Edgeのインターセプトはビューワーリクエストになる.

2.3. Lambda@Edgeの制約事項

Lambda@EdgeはLambdaの軽量版であるため以下の制約事項が存在する.

  • メモリの使用量は128MBまで.
  • ソースコードは1MBまで.
  • ビューワーリクエスト/レスポンスは実行時間1秒まで, オリジンリクエスト/レスポンスは実行時間3秒まで.
  • Lambdaのバージョン指定があること. $Latest は指定できない.
  • /tmp を利用できない.
  • 環境変数, Dead Letter Queue, Amazon VPCsは利用できない.

最後の方はナンノコッチャだが, メモリとソースコード, 実行時間は大事だ.
これらの制約を超えると, Lambdaのトリガー設定の際にエラーになる.

3. 構築手順

以下に構築手順を示す.

3.1. S3へWebページを配置する

以下のようなWebページを index.html としてS3に配置する.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>My test page</title>
  </head>
  <body>
    <p>Hello World!</p>
  </body>
</html>

3.2. CloudFront経由で配信する

CreateDistribution を押してCloudFrontを構築する.
以下の点に注意.

  • Origin Domain Name は index.html を配置したS3 Bucketを選択する.
  • Viewer Protocol Policy は Redirect HTTP to HTTPS.
  • Default Root Object に index.html を指定する.

設定が終わり, DistributionがDeployedになっていれば, CloudFrontのDomain Name (xxxxxxxxxxxxxx.cloudfront.net) にアクセスすることでHello World!が表示される.

3.3. Lambda@Edgeを作成してデプロイ

以下のようなLambda関数を作成し、トリガーに CloudFront を指定する.
Configure authentication は適宜修正すること.

  • ビューワーリクエスト
  • 実行時間1秒にすること
'use strict';
exports.handler = (event, context, callback) => {

    // Get request and request headers
    const request = event.Records[0].cf.request;
    const headers = request.headers;

    // 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);
    }
    else {
        // Continue request processing if authentication passed
        callback(null, request);
    }
};

トリガーを設定すると, 再びCloudFrontのデプロイが始まる.
デプロイ完了後に再びアクセスするとベーシック認証のダイアログが開く.

4. 参考文献