S3 + CloudFront + Lambda@Edge でBasic認証

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

## 1.1. S3
[Amazon Simple Storage Service.](https://aws.amazon.com/jp/s3) 
AWSの分散ストレージサービス.
大量のデータを安く、安全に保管できる.
今回はHTML, CSSといったデータを保管, 配信する用途で利用.

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

## 1.3. Lambda@Edge
[AWS Lambda@Edge](http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-edge.html)
2017/07/28より[正式リリース](https://aws.amazon.com/jp/blogs/news/lambdaedge-intelligent-processing-of-http-requests-at-the-edge/)された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](https://qiita-image-store.s3.amazonaws.com/0/129363/bac42487-e1f1-ce0a-f4e3-60c2488eedf5.png)

## 2.2. CloudFrontはS3にAuthenticationヘッダーを通さない
Basic認証には `Authentication` ヘッダーが必要になる.

![1-Qg-dkUQHomX9HKt1mJTP5A.png](https://qiita-image-store.s3.amazonaws.com/0/129363/ee074828-6b4c-c9b6-e44e-5462bf7fef85.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に配置する.

``` HTML
<!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秒にすること

``` javascript
'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. 参考文献

* [ウェブ開発を学ぶ | MDN](https://developer.mozilla.org/ja/docs/Learn) : HTML, CSSなど
* [Serverless: password protecting a static website in an AWS S3 bucket](https://medium.com/@lmakarov/serverless-password-protecting-a-static-website-in-an-aws-s3-bucket-bfaaa01b8666) : Basic認証の仕組みとLambda@Edgeについて