前提
現状
SPAのフロントページをホスティングしたS3「通称バケットA」にCloudFront経由でのみアクセスできるようにしている。
やりたいこと
特定パス /signage/{userName}
にアクセスされた時のみ、別のS3「通称バケットB」にホスティングした静的Webページを表示したい。
その他
- {userName}はユーザーによって可変
- バケットA、バケットB共にパブリックブロックは全てOFF
- S3の静的ウェブサイトホスティング機能はCloudFrontを通してアクセスするので無効状態
手順
◆ バケットB直下にindex.html等の成果物を格納
◆ CloudFrontのオリジンにバケットBを追加
◆ CloudFrontのポリシーをバケットBのバケットポリシーに設定
◆ CloudFrontのビヘイビアにて/signage/*
へのアクセスはバケットBを向くように設定
発生した問題1
ここまで行った手順で、/signage/mori-dev
へアクセスした時はバケットBの静的ページを表示してくれると思ったが、バケットAのページを表示してしまう。
原因
CloudFrontのビヘイビア設定/signage/*
が効いていないのか大分疑ったが、原因は別にあった。
/signage/mori-dev
へアクセスするとバケットB自体にはアクセスしていたが、バケットB/signage/mori-dev/
を探しに行ってしまう。
index.htmlはバケットB直下に置いており、バケットB/signage/mori-dev/
は存在しないオブジェクトなので403エラーが発生していた。
CloudFrontのエラーページ設定で403エラーが発生した場合は200と見做してバケットAのindex.htmlを表示する、というカスタマイズをしていたので、バケットBにはアクセスできていないように見えてハマった。
対応
試しにバケットBの階層化をキチンとしてバケットB/signage/mori-dev/index.html
を生成したところ、/signage/mori-dev/index.html
で表示することが可能になった。
しかしmori-dev
の部分は可変であり、ユーザー分階層を用意するわけにはいかない。
S3のプレフィックスにはワイルドカードは使えない。
ということでCloudFront Functionを使用して対応することに。
CloudFront -> 関数 -> 新規作成
function handler(event) {
var request = event.request;
var uri = request.uri;
if (uri.startsWith('/signage/')) {
// signageパスへのアクセスは全てバケット直下のindex.htmlに
request.uri = '/index.html';
}
return request;
}
これによって/signage/mori-dev
のようなアクセスは全てバケットBのindex.htmlを見るようになりました。
起きた問題その2
/signage/mori-dev
へのアクセスでバケットBのindex.htmlを表示するようになったものの、index.htmlからバケットBの同階層にあるbundle.jsが読み込めずに403エラー。
原因
bundle.jsはビヘイビア設定の対象ではないので、バケットA/bundle.jsへアクセスしようとして403エラーが発生していた。
ビルドはWebpackで行っていたので、webpack.config.js
を設定して、ビルド成果物の接頭辞にsignage-resoure-
をつけるようにした。
その上で、CloudFrontのビヘイビア設定を追加し、/signage-resoure-*
へのアクセスはバケットBを向くように追加
これにて、無事/signage/{userName}
へのアクセスでバケットBの静的Webページを正しく表示できるようになりました。
まとめ
S3へのWebホスティングはバケット内での構造化をちゃんとしていないと、存在しないオブジェクトへのアクセスになり403エラーが発生する。
上記が発生した場合に、CloudFrontで403エラーのカスタムエラーレスポンスを行なっていると、原因発見が遠くなってしまう。
CloudFront周りの調査は一時的にキャッシュを無効化して調査することが解決への近道。