0xf

日記だよ

久しぶりに個人のAWSアカウントでCloudFrontとS3で静的ページを公開しようとして意外と面倒だった...のでなんとかしたい

わかってればそんなに手間取らないけど、もうちょっと簡単にならないかな、という気持ちがある。

アカウント設定

  • まず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 を作る
    • オリジンをS3にすると、OAC(Origin Access Control)の設定とかしてくれる。便利だ! 便利だけどそれがなんなのか知らないと作れないのは間違いない。
  • デフォルトだと / にアクセスしても /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@Edgeus-east-1 に構成され、全てのロケーションにデプロイされ、実行されたリージョンの CloudWatch Logs のストリームにログが出力される。日本からアクセスした時のログは ap-norttheast-1 に /aws/lambda/us-east-1.<関数名> って名前のログストリームができる。ややこしい。

エラーページを用意しておく

S3バックエンドでは存在しないキーに対するリクエストは404にならず403になってしまうので、CloudFrontの側で403を404に変換してあげるようにする。