CloudFront + Lambda + S3 でS3へのアクセスにBasic認証を使用する

# はじめに
S3でファイルを他の人と共有するときに、簡単に認証をつけたいときがあります。そこで、S3へのアクセスにCloudFrontを経由して、LambdaでBasic認証を使用してみます。


# 参考
- [[CloudFront + S3]特定バケットに特定ディストリビューションのみからアクセスできるよう設定する](https://dev.classmethod.jp/cloud/aws/cloudfront-s3-origin-access-identity/)
- [できた！S3 オリジンへの直接アクセス制限と、インデックスドキュメント機能を共存させる方法](https://dev.classmethod.jp/cloud/aws/directory-indexes-in-s3-origin-backed-cloudfront/)
- [Lambda + CloudFront + S3でBasic認証する](https://qiita.com/takahashi-kazuki/items/688f5f736bd76682483c)
- [チュートリアル: シンプルな Lambda@Edge 関数の作成](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-how-it-works-tutorial.html#lambda-edge-how-it-works-tutorial-create-function)

# 環境
- AWSアカウントは作成済とします。

# 手順
## S3にバケット作成する
S3のサービスを開きます。`+バケットを作成する`をクリックしてバケットを作成します。

<img width="1438" alt="スクリーンショット 2019-04-22 23.27.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/e9b56614-6e7b-e435-45fb-21f83747e55e.png">

今回はバケット名を`basic-84uwgk`、リージョンを`東京`としました。
<img width="1097" alt="スクリーンショット 2019-04-22 23.31.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/0f135e8b-7c0f-bfb6-204b-2111f51574c4.png">

バケットにファイルを格納します。今回は`index.html`と`error.html`を格納します。

```html:index.html
This is index.html
```

```html:error.html
Error
```
バケットの画面にファイルをドラッグ&ドロップするとアップロード画面が表示されます。そのまま`アップロード`をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/19d50f75-e445-d1d7-c66c-6c86e1eaac08.png)

バケット内の`index.html`をクリックすると詳細が表示されます。
<img width="670" alt="スクリーンショット 2019-04-22 23.40.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/22da7353-eb02-7992-485c-1fd640e8ad14.png">

`オブジェクトURL`のリンクをクリックするとエラーページに飛びます。これは公開設定をしていないためです。今回は特にアクセスの設定をしていないので、正常な動作になります。
<img width="925" alt="スクリーンショット 2019-04-22 23.45.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/6ce049f0-cd61-96c6-50c7-cc1660ffcefd.png">

## CloudFrontを作成する
CloudFrontサービスを開きます。`Create Distribution`をクリックします。

<img width="1430" alt="スクリーンショット 2019-04-22 23.52.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/de3f90f3-7f70-f9a9-ac0b-c745ebfc9335.png">

Webの`Get Started`をクリックします。

<img width="1430" alt="スクリーンショット 2019-04-22 23.53.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/f683fd16-c9a9-cd34-97dc-8e8b6977f389.png">

### Origin Settings
`Origin Domain Name`のテキストボックスをクリックすると候補を一覧で見ることができます。候補の中から先程作成したS3バケットのドメインを指定します。`Origin ID`は自動で入力されます。
`Restrict Bucket Access`をYesにします。これにより、S3のコンテンツへのアクセスはCloudFront経由に制限されます。
`Origin Access Identity`はCreate a New Identityにします。Commentは自動で入力されます。
`Grant Read Permissions on Bucket`はYes, Update Bucket Policyにします。これにより、S3バケットポリシーに自動的にCLoudFrontのアクセス権限が付与されます。


<img width="1436" alt="スクリーンショット 2019-04-22 23.55.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/2dc1df95-082f-da73-97ee-ca7fff6c8a02.png">


### Default Cache Behavior Settings
`Viewer Protocol Policy`はRedirect HTTP to HTTPSにしました。
`Object Caching`はCustomizeにし、TTLはすべて0としました。

`Create Distribution`をクリックします。

<img width="1436" alt="スクリーンショット 2019-04-23 0.06.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/cb3c4f9e-d3f8-27ee-ced7-dd82be068935.png">
<img width="1436" alt="スクリーンショット 2019-04-23 0.07.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/b38c9e16-70d0-5eb2-25a3-b1461aca9f33.png">

### Distribution Settings
`Default Root Object`にindex.htmlを設定します。これでCloudFrontのルートURLにアクセスしたときにindex.htmlが表示されるようになります。
これ以外すべてデフォルト設定です。

<img width="1436" alt="スクリーンショット 2019-04-23 0.11.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/9a93f5b8-1143-7b26-76fd-d96de4134fba.png">
<img width="1436" alt="スクリーンショット 2019-04-23 0.13.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/9f0d207c-2eef-987b-03e8-b6610206c99f.png">

S3に戻って`アクセス権限`-`バケットポリシー`を見てみるとCloidFrontのアクセスが設定されていることがわかります。
<img width="1436" alt="スクリーンショット 2019-04-23 0.16.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/18aa50e3-3a56-45ec-11a5-a09d3a1c63f8.png">

### エラーページの設定
作成したCloudFrontを開き、`Error Pages`タブを開きます。`Create Custom Error Rsponse`をクリックします。
<img width="1436" alt="スクリーンショット 2019-04-23 1.29.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/20c4472a-3f57-5da7-8247-c5e2f4a756f5.png">

403エラーのTTLを0にします。これにより、エラーページのキャッシュも無効になります。

<img width="1436" alt="スクリーンショット 2019-04-23 1.32.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/03fd0385-aa41-66f8-7a69-a72dcb23c6c7.png">


##ロールの作成
IAMサービスを開き、`ロール`メニューから`ロールの作成`をクリックします。

<img width="1436" alt="スクリーンショット 2019-04-23 0.25.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/3a47b76d-1781-9a99-2b5e-e062f9cb2d45.png">

`AWS サービス`を選び、`Lambda`を選択した状態で`次のステップ：アクセス権限`をクリックします。

<img width="1436" alt="スクリーンショット 2019-04-23 0.27.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/9b93d0b2-8584-a112-b10a-253b857c3bee.png">

`ポリシーの作成`をクリックします。新しいタブで画面が開きます。

<img width="1436" alt="スクリーンショット 2019-04-23 0.31.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/59321840-7e15-10cb-e3e2-26fbe433af5b.png">

JSONタブを開いて下記のポリシーを貼り付けます。`ポリシーの確認`をクリックします。

<img width="1436" alt="スクリーンショット 2019-04-23 0.32.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/5dca0c05-c3e9-ce33-6638-675e02332d39.png">

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "lambda:GetFunction",
                "lambda:EnableReplication*",
                "iam:CreateServiceLinkedRole",
                "cloudfront:CreateDistribution",
                "cloudfront:UpdateDistribution"
            ],
            "Resource": "*"
        }
    ]
}
```

`名前`にポリシー名を入力します。今回はLambdaCloudFrontS3Basicとしました。
`ポリシーの作成`をクリックします。

<img width="1436" alt="スクリーンショット 2019-04-23 0.40.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/54fbb89f-5547-f9ad-4ffa-1a7f4ee67b6a.png">

ロールの作成に戻って、作成したポリシー名でフィルタします。チェックをつけ、`次のステップ：タグ`をクリックします。

<img width="1436" alt="スクリーンショット 2019-04-23 0.41.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/2e9cc90a-7c43-7b05-dcc8-e0e0c197cba8.png">

タグは設定しなくてOKです。`次のステップ：確認`をクリックします。

<img width="1436" alt="スクリーンショット 2019-04-23 0.43.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/5cfaa1b7-956c-7425-288a-9312c00b2788.png">

`ロール名`にロールの名前を入力します。今回はLambdaCloudFrontS3Basicとしました。
`ロールの作成`をクリックします。

<img width="1436" alt="スクリーンショット 2019-04-23 0.44.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/77f1df09-1515-08f8-b684-c353b965608a.png">

作成したロールを開き、`信頼関係`タブから`信頼関係の編集`をクリックします。


<img width="1436" alt="スクリーンショット 2019-04-23 0.53.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/3f881cd6-3819-8006-dab5-24f84f48f8cb.png">

下記のjsonを貼り付け`信頼ポリシーの更新`をクリックします。

<img width="1436" alt="スクリーンショット 2019-04-23 0.54.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/098fdba9-4d0a-c2cf-bbad-41bdf2decdf2.png">


```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": [
          "edgelambda.amazonaws.com",
          "lambda.amazonaws.com"
        ]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
```

## Lambdaを作成
Lambdaサービスを開きます。CloudFrontから実行されるLambdaはLambda@Edgeと呼ばれ、サポートされているリージョンはus-east-1（バージニア北部）のみです。（https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-how-it-works-tutorial.html#lambda-edge-how-it-works-tutorial-create-function）

`関数の作成`をクリックします。

<img width="1436" alt="スクリーンショット 2019-04-23 0.15.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/0de2827c-e0a9-56e3-6972-1a21c1aa95f9.png">

`一から作成`を選びます。関数名を入力します。今回はBasicAuthenticationとしました。
ランタイムはNode.js 6.10にします。

<img width="1436" alt="スクリーンショット 2019-04-23 0.21.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/9cf9e356-afd9-27f6-ac21-77636420672c.png">

アクセス権限で、既存のロールを使用するを選び、先程作成したロールを割り当てます。
`関数の作成`をクリックします。

<img width="1436" alt="スクリーンショット 2019-04-23 0.48.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/65590013-552d-5f5b-b151-f6902c5178e4.png">

画面をスクロールすると`関数コード`がありますので、index.jsを書き換えます。
<img width="1436" alt="スクリーンショット 2019-04-23 1.09.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/ac833a64-e825-48a7-3400-a6ffb66e2e4a.png">



```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;
    console.log('request: ' + JSON.stringify(request));
    // Configure authentication
    const authUser = 'User';
    const authPass = 'Password';

    // 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);
    }

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

`基本設定`の`タイムアウト`を1秒に設定します。
右上の`保存`をクリックして保存します。

<img width="1436" alt="スクリーンショット 2019-04-23 1.10.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/863f34aa-61a4-c853-9dfd-744dadd08fa2.png">

画面上部の`アクション`から`新しいバージョンを発行`をクリックします。そのまま`発行`をクリックします。
<img width="1436" alt="スクリーンショット 2019-04-23 1.10.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/6bdba080-e3a5-758f-4401-a43b0f44b604.png">

バージョン1が選択された状態で左の`トリガーの追加`からCloudFrontをクリックします。

<img width="1436" alt="スクリーンショット 2019-04-23 1.11.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/7f2afd33-4235-2e19-75f4-8833c4f3eb71.png">

CloudFrontを選択した状態でスクロールすると、`トリガーの設定`があります。
`CloudFront イベント`をビューアーリクエストにし、`Lambda@Edge へのデプロイを確認`にチェックします。
右下の`追加`をクリックします。
その後、右上の`保存`をクリックします。
<img width="1436" alt="スクリーンショット 2019-04-23 1.15.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/445d0628-f8c0-e01d-087e-80eec23f57e4.png">

これでCloudFrontからLambdaが呼び出され、Basic認証を行い、S3へフォワードされます。

## 確認
`Status`がDeployedになっていることを確認し、`Domain Name`に表示されているドメインにアクセスします。
<img width="1436" alt="スクリーンショット 2019-04-23 1.49.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/8e3f6d68-ddb5-fc8b-6432-f4c967667d70.png">

Lambdaのindex.jsにべた書きしたユーザ名とパスワードを入力します。ここではUser/Passwordです。

<img width="1436" alt="スクリーンショット 2019-04-23 1.51.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/6d87e9aa-6de3-b297-7ee9-8766f301919e.png">

ページが表示されました。
<img width="284" alt="スクリーンショット 2019-04-23 1.52.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/a70157b6-8a83-bd50-bb6a-aabe62976237.png">

他のオブジェクトはURLを指定すれば表示されます。
https://d1eioc8flbgy69.cloudfront.net/error.html

<img width="284" alt="スクリーンショット 2019-04-23 1.54.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211712/54601a6d-cbda-bf67-e565-c928f18c6ce0.png">

# おわりに
CloudFront+LambdaL+S3でBasic認証のファイルアクセスができました。
ちょっとしたファイル配布で便利？だと思います。