nuxt generate+S3+CloudFrontで403エラーを防ぐ方法

# nuxt generate+S3+CloudFrontで403エラーを防ぐ方法

本記事は[SGG(すごくなりたいがくせいぐるーぷ) Advent Calendar 2020](https://qiita.com/advent-calendar/2020/sgg)の20日目の記事になります。

## はじめに

CloudFrontではデフォルトルートオブジェクトを設定できるため、`http://ドメイン名/index.html`にアクセスしたい場合、デフォルトルートオブジェクトに`index.html`を設定すれば`http://ドメイン名`にアクセスしても該当ページが表示されます。

しかし、デフォルトルートオブジェクトの設定はサブディレクトリには効かないため、`http://ドメイン名/hoge/index.html`へアクセスしたい場合に、`http://ドメイン名/hoge`としてしまうと403エラーが発生します。

そのため、nuxt generateで生成したファイルをS3+CloudFrontで静的ホスティングした場合、ルート以外のパスに直接アクセスしたりリロードしたりすると、403エラーが返ってしまいます。

```js
/
├ index.html //←`http://ドメイン名`でアクセス可能
└ hoge/
   └ index.html //←`http://ドメイン名/hoge`ではアクセスできない
```

このエラーの回避方法はいくつかあり、いろいろな方が記事を書いて下さっています。

- [nuxt generate + S3 + CloudFront + Lambda Edge で静的サイト構築＆ハマりポイントと解決法](https://qiita.com/GussieTech/items/3ae77dbcb1e79222a9bc)
- [CloudFront+S3環境で Vue Nuxt の静的化ページでリロードした時にエラー（access denied)となる時の対処法](https://qiita.com/gakumac/items/b948eb1a13ab6a0375f5)

しかし、前者はLambda@Edgeを実装するのに少し手間がかかり（加えて僕の環境では動作しませんでした。多分僕のやり方が悪い。）、後者はエラーが起きたらトップページにリダイレクトさせるという仕様になってしまいます。

そこで、S3の静的ウェブサイトホスティングと組み合わせるという方法を考えました。

## 概要

前述の通り、CloudFrontのデフォルトルートオブジェクトはサブディレクトリに適用されません。
しかし、S3の静的ウェブサイトホスティングでインデックスドキュメントに`index.html`を設定すると、サブディレクトリを指定した場合でもサブディレクトリ配下の`index.html`を返してくれます。

```js
/
├ index.html //←`http://ドメイン名`でアクセス可能
└ hoge/
   └ index.html //←`http://ドメイン名/hoge`でアクセス可能
```

また、静的ウェブサイトホスティングの設定をする場合、S3バケットのパブリックアクセスを許可しなければなりませんが、これをするとバケットへ直接アクセスできる様になってしまいます。

これはセキュリティ的によろしくないので、Refererヘッダーを使ってアクセス制限をかけ、CloudFrontディストリビューションからしかアクセスできないようにしていきます。

## 実装方法

### S3バケットを作成し、Webサイトをアップロード

割愛

### S3で静的ホスティングの設定を行う

まずはS3でWebサイトを静的ホスティングします。

「プロパティ」→「静的ウェブサイトホスティング」から静的ウェブサイトホスティングを有効にし、インデックスドキュメントに`index.html`を指定します。

![スクリーンショット 2020-12-20 14.41.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260292/63b89f10-c14e-abe8-ffc7-d243b5491cfa.png)

設定が完了すると以下の様にエンドポイントが表示されます。
後ほど使用するため、メモしておきましょう。

![スクリーンショット 2020-12-20 14.49.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260292/ae66796e-229c-36b7-f33f-771c49172208.png)

### CloudFrontを作成

次に、CloudFrontを作成します。

※すでにCloudFrontを作成済みの場合は、該当ディストリビューションを開き、「Origins and Origin Groups」から現存するOriginを以下手順と同じように書き換えてください。

「Create Distribution」からWebを選択します。

![スクリーンショット 2020-12-20 14.51.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260292/f9efb8c5-2673-a090-50ae-c9a535772324.png)

Origin Domain Nameに、「静的ウェブサイトホスティングのエンドポイントから`http://`を除いたものを設定します。

例: `http://XXXXX.s3-website-ap-northeast-1.amazonaws.com`→`XXXXX.s3-website-ap-northeast-1.amazonaws.com`

また、Origin Custom Headersの、`Header Name`に`Referer`、`Value`にランダムな文字列（暗号論的擬似乱数など）を設定してください。

![スクリーンショット 2020-12-20 15.00.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260292/62276141-6d8c-645a-eebf-3a9b5135717a.png)

残りの項目はユースケースに合わせてよしなに設定してください。

### S3バケットポリシーの設定

バケットポリシーを介したパブリックアクセスをブロックしているとアクセスができないため、S3バケットのページに戻り、「アクセス許可」から「ブロックパブリックアクセス」を開き、下2つを解除します。

![スクリーンショット 2020-12-20 13.47.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260292/2fd7a578-96da-b7cf-bdb8-8d852842664a.png)

次に、バケットポリシーを以下のものに変更します。

\<バケット名\>と\<CloudFrontのOrigin Custom Headersで設定したランダム文字列\>は該当のものに書き換えてください。

```json
{
    "Version": "2008-10-17",
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Sid": "1",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::<バケット名>/*",
            "Condition": {
                "StringLike": {
                    "aws:Referer": "<CloudFrontのOrigin Custom Headersで設定したランダム文字列>"
                }
            }
        }
    ]
}
```

上記のバケットポリシーを適用することで、`Referer`ヘッダーに正規の文字列を含んでいないリクエストをブロックすることができるため、当該CloudFrontディストリビューション以外からはバケットにアクセスできなくなります。

これで手順はすべて終わりです。

## 参考文献

- [CloudFrontのデフォルトルートオブジェクトとS3の静的ウェブサイトホスティングのインデックスドキュメントの動作の違い](https://dev.classmethod.jp/articles/cloudfront_s3_difference/)

- [CloudFront を使用して、Amazon S3 でホストされた静的ウェブサイトを公開するにはどうすればよいですか?](https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudfront-serve-static-website/)