S3+CloudFrontなど、サーバーレスでBasic認証をかける方法を何年も検討していたのですが、CloudFrontにLambdaを割り当てられるようになり、試したところ実装することがで来ました。
まだPreview版ですが、フォームから申請することで、数日で許可が得られます。
イベントの種類
まず仕組みとしては、以下4つのイベントに、機能が絞られたLambda@Edgeを割り当てることがで来ます。
Viewer レスポンス
レスポンス時に呼ばれるイベント
Origin レスポンス
オリジナルデータからのレスポンス時に呼ばれるイベント
リクエストとレスポンスの違い
レスポンスの場合
- レスポンスヘッダーの参照
- レスポンスヘッダーの書き換え
- レスポンスを返す
こちらはあまり使うことを検討していないので、もしかしたら他の機能もあるかもしれません。
リクエストの場合
status が callback に渡した連想配列に含まれているかどうかで、Originへアクセスするかどうかの挙動が変わるようです。
今回は Viewer リクエストを使います。
Basic 認証の仕様に合わせてヘッダーを返すことで、Basic認証を実装することがで来ました。
exports.handler = (event, context, callback) => { const Allows = { "users": "users", }; const request = event.Records[0].cf.request; const headers = request.headers; const authorization = headers.authorization || headers.Authorization; if (authorization) { const enc = authorization[0].split(" ")[1]; const userPassword = new Buffer(enc, 'base64').toString(); for (var key in Allows) { var val = Allows[key]; if (`${key}:${val}` === userPassword) { callback(null, request); return } } } const response = { status: '401', statusDescription: 'Authorization Required', httpVersion: request.httpVersion, headers: { "WWW-Authenticate": ['Basic realm="Enter username and password."'], "Content-Type": ["text/plain; charset=utf-8"], }, body: "401 Authorization Required", }; callback(null, response); };
event のサンプル
※ event に入ってくるデータは、以下のように小文字でくることもあれば、大文字始まりで受け取る場合もあるので、どちらにも対応しなければならないようです。
{ "host":["dummy.dayo"], "authorization":["Basic dXNlcnM6dXNlcnM="], "cache-control":["no-cache"], "user-agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"], "accept":["*/*"], "accept-encoding":["gzip, deflate, sdch, br"], "accept-language":["ja,en;q=0.8,en-US;q=0.6"] }
ログがでないと言うことと、Lambda@Edgeを割り当てたときだけでなく、コードを書き換えたあとも、数分待ち時間があることで、確認が大変でした。
ElasticBeanstalk+CloudFrontのときにも、このBasic認証の設定が活躍するのでうれしいです。