AWS
S3
CloudFront
lambda
Lambda@Edge

S3 + CloudFront + Lambda@EdgeでBasic認証付き静的サイトホスティング(インデックスドキュメントサポートあり)


はじめに


  • 静的サイトをホスティングしているレガシーなVPS上のWebサーバがあり時折発生する運用作業でつらみが発生していました

  • クラウド上に構築してなるべく運用作業を0に近づけたいと思いAWSのS3をベースに再構築しようと思いました

  • 少しググって簡単に出来そうだなと思ったもののよく見つかる方法では要件を微妙に満たせなく苦戦してしまったのでQiitaに書いときます


    • サブディレクトリでindex.htmlを省略した場合に403となってしまいました

    • インデックスドキュメントがルートでしか効きません



  • 要件としてはBasic認証ありでサブディレクトリでもindex.html省略可能でホスティングしたい感じです


    • 認証なしで良いのであればS3のStatic website hostingを有効にするだけで大丈夫なはずです




構成


  • S3 + CloudFront + Lambda@Edgeで作ります


    • S3は静的ファイル置き場

    • CloudFront経由で公開

    • Lambda@EdgeでCloudFrontで認証処理+αな処理の差し込み


      • ここでサブディレクトリのindex.html対応もやります









S3


  • Static website hostingは無効でOK

  • 特別な設定は不要ですがバージョニングを有効にしています



Lambda


index.js

'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'}]
},
};
return callback(null, response);
}

// Extract the URI from the request
var olduri = request.uri;

// Match any '/' that occurs at the end of a URI. Replace it with a default index
var newuri = olduri.replace(/\/$/, '\/index.html');

// Replace the received URI with the URI that includes the index page
request.uri = newuri;

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



  • よく見つかる方法ではサブディレクトリでindex.htmlを省略したURLを表示できません


    • ここがハマりポイントでした

    • CloudFrontでDefault Root Objectにindex.htmlを設定してもRootでしか有効になりません



  • なのでLambda@EdgeでURIを書き換える方法を採用しました


    • S3のStatic website hostingを有効にしてCloudFrontからのアクセスのみを通すこともできると思いますがIPが変わるたびに対応が必要になると思うのでLambdaで書き換えるほうがお手軽とだと思います



  • authUserとauthPassは任意のものに変えてください

  • Lambda@Edgeにはバージョンの発行が必要になります


    • CloudFrontにバージョン番号付きのARNを設定します





CloudFront


  • Original Domain Nameに公開したいバケットのあるS3を選択

  • Restrict Bucket AccessをYes

  • Origin Access IdentityをCreate a New Identity

  • Grant Read Permissions on BucketをYes, Update Bucket Policy

cloudfront-1.png


  • Object CachingをCustomize


    • 今回の要件ではキャッシュが不要だったのでTTLを0にします



  • CloudFront EventをViewer Request

  • Lambda Function ARNを先程作成したバージョン番号付きのARNを設定

cloudfront-2.png


  • ついでに403もキャッシュしないように設定します
    cloudfront-3.png



おわりに


  • ここまでやればBasic認証付きの静的サイトホスティング環境ができると思います

  • この記事では一旦ここまでで終了ですが発展として以下2つを更に考えていきたいと思います


    • AWS Transfer for SFTPやAWS Storage GatewayでSMBを使えるようにして昔ながらのファイル転送方法を用意する

    • 認証情報がLambdaハードコードになっているのでもう少し管理をちゃんとやる


      • Lambdaの環境変数が良さそうですがLambda@Edgeでは環境変数を利用できません

      • ドメインごとに異なる認証情報を使いたいのでドメインをキーにDynamoDBなどに認証情報を登録しておくのが良い?