nuxt generate+S3+CloudFrontで403エラーを防ぐ方法
本記事はSGG(すごくなりたいがくせいぐるーぷ) Advent Calendar 2020の20日目の記事になります。
はじめに
CloudFrontではデフォルトルートオブジェクトを設定できるため、http://ドメイン名/index.htmlにアクセスしたい場合、デフォルトルートオブジェクトにindex.htmlを設定すればhttp://ドメイン名にアクセスしても該当ページが表示されます。
しかし、デフォルトルートオブジェクトの設定はサブディレクトリには効かないため、http://ドメイン名/hoge/index.htmlへアクセスしたい場合に、http://ドメイン名/hogeとしてしまうと403エラーが発生します。
そのため、nuxt generateで生成したファイルをS3+CloudFrontで静的ホスティングした場合、ルート以外のパスに直接アクセスしたりリロードしたりすると、403エラーが返ってしまいます。
/
├ index.html //←`http://ドメイン名`でアクセス可能
└ hoge/
   └ index.html //←`http://ドメイン名/hoge`ではアクセスできない
このエラーの回避方法はいくつかあり、いろいろな方が記事を書いて下さっています。
- nuxt generate + S3 + CloudFront + Lambda Edge で静的サイト構築&ハマりポイントと解決法
 - CloudFront+S3環境で Vue Nuxt の静的化ページでリロードした時にエラー(access denied)となる時の対処法
 
しかし、前者はLambda@Edgeを実装するのに少し手間がかかり(加えて僕の環境では動作しませんでした。多分僕のやり方が悪い。)、後者はエラーが起きたらトップページにリダイレクトさせるという仕様になってしまいます。
そこで、S3の静的ウェブサイトホスティングと組み合わせるという方法を考えました。
概要
前述の通り、CloudFrontのデフォルトルートオブジェクトはサブディレクトリに適用されません。
しかし、S3の静的ウェブサイトホスティングでインデックスドキュメントにindex.htmlを設定すると、サブディレクトリを指定した場合でもサブディレクトリ配下のindex.htmlを返してくれます。
/
├ index.html //←`http://ドメイン名`でアクセス可能
└ hoge/
   └ index.html //←`http://ドメイン名/hoge`でアクセス可能
また、静的ウェブサイトホスティングの設定をする場合、S3バケットのパブリックアクセスを許可しなければなりませんが、これをするとバケットへ直接アクセスできる様になってしまいます。
これはセキュリティ的によろしくないので、Refererヘッダーを使ってアクセス制限をかけ、CloudFrontディストリビューションからしかアクセスできないようにしていきます。
実装方法
S3バケットを作成し、Webサイトをアップロード
割愛
S3で静的ホスティングの設定を行う
まずはS3でWebサイトを静的ホスティングします。
「プロパティ」→「静的ウェブサイトホスティング」から静的ウェブサイトホスティングを有効にし、インデックスドキュメントにindex.htmlを指定します。
設定が完了すると以下の様にエンドポイントが表示されます。
後ほど使用するため、メモしておきましょう。
CloudFrontを作成
次に、CloudFrontを作成します。
※すでにCloudFrontを作成済みの場合は、該当ディストリビューションを開き、「Origins and Origin Groups」から現存するOriginを以下手順と同じように書き換えてください。
「Create Distribution」からWebを選択します。
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にランダムな文字列(暗号論的擬似乱数など)を設定してください。
残りの項目はユースケースに合わせてよしなに設定してください。
S3バケットポリシーの設定
バケットポリシーを介したパブリックアクセスをブロックしているとアクセスができないため、S3バケットのページに戻り、「アクセス許可」から「ブロックパブリックアクセス」を開き、下2つを解除します。
次に、バケットポリシーを以下のものに変更します。
<バケット名>と<CloudFrontのOrigin Custom Headersで設定したランダム文字列>は該当のものに書き換えてください。
{
    "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ディストリビューション以外からはバケットにアクセスできなくなります。
これで手順はすべて終わりです。




