CloudFrontとS3で静的なWebサイトを公開するにあたってハマったことがあったのでメモります。
Table of Contents
インデックスドキュメントとは
ブラウザでURLを開くときパスにHTMLファイルを指定しない場合、ApacheやNginxなどのWebサーバではデフォルトでindex.htmlが読み込まれるように設定します。
https://crimsonality.net
https://crimsonality.net/
https://crimsonality.net/index.html
上記のパターンは全てURLルートパス配下のindex.htmlが読み込まれる対象となります。これを インデックスドキュメント と呼びます。
https://crimsonality.net/aws/about-cloudfront-s3-index-document
https://crimsonality.net/aws/about-cloudfront-s3-index-document/
https://crimsonality.net/aws/about-cloudfront-s3-index-document/index.html
上記のように、URL階層のサブディレクトリも同様のルールとなります。
http {
...
index index.html;
...
}
Nginxだとindexディレクティブでインデックスドキュメントとなるファイル名を指定します。もちろん、index.html以外のファイル名をインデックスドキュメントに指定することも可能です(活用シーンがあまり思い浮かびませんが)
CloudFront+S3でハマった点
- 開ける → https://crimsonality.net/
- 開ける → https://crimsonality.net/index.html
- 404エラー → https://crimsonality.net/aws/about-cloudfront-s3-index-document/
- 開ける → https://crimsonality.net/aws/about-cloudfront-s3-index-document/index.html
WebサイトをCloudFrontとS3だけで公開するときに、サブディレクトリのインデックスドキュメントが開けないという問題が起こりました。この問題を捉えるにあたり、 S3の静的ホスティング機能 と S3をCloudFrontのオリジンに指定してホスティング の違いを理解することが重要です。
S3の静的ホスティング機能を利用する場合
S3にはCloudFrontを使わないでもバケットの内容をhttp配信する静的ホスティング機能があります。 AWSコンソールのS3バケットを選択して「プロパティ」タブの下の方に設定があります。バケット単位で公開ドメインが切られます。
静的ホスティングの設定編集画面にいくと、インデックスドキュメントを指定できます。 インデックスドキュメントを設定すると、URLのルートパスだろうが階層パスだろうが、ファイル名が指定されなかった場合は index.html がデフォルトページと解釈してくれます。 これは期待通りの動作です。
S3をCloudFrontのオリジンに設定する場合
S3の静的ホスティングだとhttpsが標準で使えなかったり、S3バケットへのアクセスをCloudFrontだけに絞ってセキュリティを強化したいなど、S3の前にCloudFrontを立てる理由は様々です。単純にCDNで高速配信したいというのも理由になりますね。
以下、S3をオリジンにしてCloudFront経由でのみ読み取り可能にする設定例です。
Origin Domain Name のフォームにフォーカスをあてると、S3のバケットが候補として出てくるので選択します。CloudFrontからのみアクセスを許可するため Restrict Bucket Access を Yes とします。 Origin Access Identity で新しくIdentityを作って、S3側の権限ポリシーにアクセス許可設定をします。
一般設定項目の Default Root Object に index.html を設定しておきます。URLのルートパスにアクセスが来たときに、index.html をオリジンサーバのS3に取得しに行ってくれます。
S3とCloudFrontの連携ができたのでCloudFrontのドメインでサイトにアクセスが可能です。CloudFront Distributionを作ると xxxxxxxxx.cloudfront.net のドメインが割り当てられるので、ブラウザですぐに開けます。
- OK ルートパスは開ける → /
- OK ファイル名指定 → /index.html
- OK 階層パスのファイル名指定 → /aws/about-cloudfront-s3-index-document/index.html
- NG 階層パスファイル名指定なしはエラー → /aws/about-cloudfront-s3-index-document/
階層パスのインデックスドキュメントの取得ができません。S3にファイルの実体はあるが、CloudFrontがS3にリソースを要求するときに、URLのパスをそのままS3に要求してしまうためエラーになってしまいます。 /aws/about-cloudfront-s3-index-document/ という名前のファイルは存在しない という解釈がされます。融通効かせてindex.htmlへのリクエストにフォールバックしてくれれば良いじゃないか・・と思いましたが、CloudFrontの仕様なのでどうしようもありません。
回避策:S3の静的ホスティングURLをCloudFrontのオリジンに設定する
CloudFrontとS3の間に Lambda@Edge を挟んで、S3に要求するリソースパスを書き換えてしまうのが一番まともな方法のようです。しかし、静的なサイトを管理するのにLambda関数を作って管理するのも正直面倒に感じます。
S3のホスティング機能ではインデックスドキュメントが問題なかったのであれば、CloudFrontのオリジンをS3のバケットに直接向けるのではなく、バケットのホスティングURLにしてしまえば良いのです。
Origin Domain Name に、S3のホスティングドメインを指定します。東京リージョンであれば、XXXXXXXXXXX.s3-website-ap-northeast-1.amazonaws.com のようになります。
これで階層パスもCloudFront経由で閲覧できるようになります。
ただし、この方法には制限があります。
サイト閲覧の観点では問題ありませんが、S3のホスティング機能を利用する形になるので、S3へのアクセス制限をCloudFrontに限定することが出来なってしまいます。この制約が許容できないのであれば、オリジンはS3バケットのままにしておいて、前述した Lambda@Edge でインデックスドキュメントのパス解決をするしかなさそうです。運用戦略をよくよく検討してどちらの方法を取るか考えましょう。
まとめ
まとめると以下となります。
- CloudFrontでS3バケットをオリジンとする場合、インデックスドキュメントが閲覧できない。
- オリジンをS3ホスティング機能のURLに変更して回避ができるが、S3のアクセス許可を絞れなくなる。
- Lambda@Edge を利用すればオリジンをS3バケットに維持したまま、インデックスドキュメント問題を解決できる。
それでは、良きAWSライフを!