わかってればそんなに手間取らないけど、もうちょっと簡単にならないかな、という気持ちがある。
アカウント設定
- まずAWSの組織の画面から、新しいアカウントを追加します
- ルートのメールアドレスが重複していると困る
- Sandbox って名称にした
- IAM IdentityCenter でユーザーに新しく作ったアカウントに対する権限を割り当てる
- ここでユーザーを作ると各AWSアカウントにIAM Userを作らなくてよくて便利。だけど挙動を知らないと絶対行き着けない。仕事で使ってるからわかるけど、初見では絶対わからない自信がある。
- ユーザー、グループ、権限のセットで割り当てを行う。
ドメイン設定と証明書の準備
- もともと管理アカウントでドメイン管理してたので、以下のようにする
- Sandboxアカウントでゾーンを作る。NSレコードをメモしておく。
- 管理アカウントでサブドメインのNSレコードを上でメモしたやつに設定する。
- ACMで証明書を作る。
- us-east-1 で作っておかないとCloudFrontから見えない。
S3 バケットの準備
特別なことはない。
- S3バケットを作る
index.htmlをひとまずおいた。CloudShellを開いて vim で書きます。そのままaws s3 cp index.html s3://<bucket-name>できて便利。- 今どきなのでプライベートにしておく。
CloudFront のディストリビューションを作る
- CloudFront を作る
- デフォルトだと
/にアクセスしても/index.htmlを見せてくれない。これは盲点。見慣れたAccess Denined のXMLが返ってくる。- CloudFrontはあくまでもアクセスされたパスパターンS3へのリクエストに変換している(Webアクセスを転送しているわけではなく)わけだから、この挙動も納得はいく。いくけど不便では?
/ -> /index.html -> 404ってフォールバックしてくれるのをやっぱり期待してしまう。Lambda@Edgeでやるしかないか。/を/index.htmlに書き換えるのは簡単だけど、SPAとかだと/homeとか/searchとかも全部/index.hml返して欲しいなんてことはありそうで、そうするとアプリケーションのURL構造をCloudFrontが知っているということになり... いやいいのかもしれないけど、設定変更の伝播とかどうなんですかねえ奥さん
S3へ転送するビヘイビアはGET, HEAD のみにしておく
/api/とか増やすならそっちはPOSTを許可するといいでしょう。というくらいの話題。
Lamda@Edge で末尾に / があったら /index.html に書き換える
あたりを参考に、Lambda関数を us-east-1 に作成する。
export const handler = async (event, context, callback) => { const request = event.Records[0].cf.request; const paths = request.uri.split("?") paths[0] = paths[0].replace(/\/$/, "/index.html") request.uri = paths.join("?") callback(null, request) }
こういう感じにした。
隠しファイル的なやつは見せないとか、/api/ 以下は触らないとかするなら、たとえばこういう感じにしておくイメージ。
if(paths[0].startsWith("/.")) { // /. で開始されるパスは全部404 paths[0] = "/404.html" } else if(paths[0].startsWith("/api/")) { // /api/ は素通し // do nothing } else{ // 末尾の *.html を index.html に置換 paths[0] = paths[0].replace(/\/[^\/]+\.html$/, "/index.html") // 末尾の / を /index.html に置換 paths[0] = paths[0].replace(/\/$/, "/index.html") }
全てのアクセスにこれ通すとちょっと無駄なので、パスパターンで処理できるやつは優先度高いビヘイビアで処理してしまった方がよさそう。
あとは「Lambda@Edge へのデプロイ」とすると、必要なトリガーとか権限がステップバイステップで作成できるはずである。

これ Terraform とかで一から必要なリソースを組み立てる自信はまるでない。検証環境で作って関連リソースをインポートするとかしないと厳しい。
Lambda@Edgeは us-east-1 に構成され、全てのロケーションにデプロイされ、実行されたリージョンの CloudWatch Logs のストリームにログが出力される。日本からアクセスした時のログは ap-norttheast-1 に /aws/lambda/us-east-1.<関数名> って名前のログストリームができる。ややこしい。
エラーページを用意しておく
S3バックエンドでは存在しないキーに対するリクエストは404にならず403になってしまうので、CloudFrontの側で403を404に変換してあげるようにする。
