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対応もやります
          - [クラメソさんの記事](https://dev.classmethod.jp/cloud/aws/directory-indexes-in-s3-origin-backed-cloudfront/)を大いに参考にしています
          - この記事を見つけるまで苦戦していました

---
### S3
- Static website hostingは無効でOK
- 特別な設定は不要ですがバージョニングを有効にしています

---
### Lambda
- 現時点ではLambda@Edgeがus-east-1(米国東部 バージニア北部)でないといけないのでus-east-1でLambdaを作ります
- Lambda@Edgeのポリシーテンプレートを使った新しいロールを作ります
![lambda-1.png](https://qiita-image-store.s3.amazonaws.com/0/3507/d0ba2956-24b5-2126-93b6-c8f3441ad7f1.png)

- Lambdaのコードは以下
    - 以下2つのコード引用して組み合わせています
      - https://gist.github.com/lmakarov/e5984ec16a76548ff2b278c06027f1a4
      - https://dev.classmethod.jp/cloud/aws/directory-indexes-in-s3-origin-backed-cloudfront/

```javascript: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](https://qiita-image-store.s3.amazonaws.com/0/3507/109fe187-89f4-c743-f759-8a0395da1553.png)


- Object CachingをCustomize
  - 今回の要件ではキャッシュが不要だったのでTTLを0にします
- CloudFront EventをViewer Request
- Lambda Function ARNを先程作成したバージョン番号付きのARNを設定

![cloudfront-2.png](https://qiita-image-store.s3.amazonaws.com/0/3507/eaaed2e5-13b4-7b0f-62ed-744c3053a34d.png)


- ついでに403もキャッシュしないように設定します
![cloudfront-3.png](https://qiita-image-store.s3.amazonaws.com/0/3507/2d62f872-066e-bb9e-b728-b3aeb8e6d30c.png)

---

### おわりに
- ここまでやればBasic認証付きの静的サイトホスティング環境ができると思います
- この記事では一旦ここまでで終了ですが発展として以下2つを更に考えていきたいと思います
  - AWS Transfer for SFTPやAWS Storage GatewayでSMBを使えるようにして昔ながらのファイル転送方法を用意する
  - 認証情報がLambdaハードコードになっているのでもう少し管理をちゃんとやる
      - Lambdaの環境変数が良さそうですがLambda@Edgeでは環境変数を利用できません
      - ドメインごとに異なる認証情報を使いたいのでドメインをキーにDynamoDBなどに認証情報を登録しておくのが良い？