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種類ある.
- ビューワーリクエスト : リクエストがユーザからエッジロケーションに到着したタイミングでインターセプトする.
- オリジンリクエスト : リクエストがエッジロケーションからオリジンサーバへ送られるタイミングでインターセプトする.
- オリジンレスポンス : レスポンスがオリジンサーバからエッジロケーションに到着したタイミングでインターセプトする.
- ビューワーレスポンス : レスポンスがエッジローケーションからユーザに送られるタイミングでインターセプトする.
2.2. CloudFrontはS3にAuthenticationヘッダーを通さない
Basic認証には Authentication
ヘッダーが必要になる.
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. 参考文献
- ウェブ開発を学ぶ | MDN : HTML, CSSなど
- Serverless: password protecting a static website in an AWS S3 bucket : Basic認証の仕組みとLambda@Edgeについて