コンテンツにスキップ

ADR-0031: サブドメインに分ける規則を決め、1 つのアプリを 1 つの Worker とホスト名に割り当てる

Status

承認

Context

fukuemon.dev のドメインの下に、サイトのほかに、複数のアプリを置く。
どれを fukuemon.dev に置き、どれをサブドメインに分けるかを決める必要がある。

  • 配信は Cloudflare Workers Static Assets で行う。custom domain は Terraform が、deploy は Wrangler が管理する。
  • カタログは、design.fukuemon.dev に置いている。
  • 訪問者のアカウントを持たないので、サイトが持つ訪問者のデータは、訪問者のブラウザに保存したデータだけである。
  • プレイグラウンドは、訪問者がブラウザの中で使う複数のツール(コードと SQL の実行環境、キャンバス、スライドのエディタ)を提供する。最初のツールは、コードと SQL の実行環境である。キャンバスとスライドのエディタを、後から追加する。訪問者が作ったファイル、図、スライドは、訪問者のブラウザに保存する。
  • 実行環境の方式は、実行環境の要求の prototype で決める。候補の WebContainers は、cross-origin isolation(COOP: same-origin と COEP: require-corp)を求める。
  • 訪問者のコードが、実行環境のページと同じ origin で動くかは、実行環境の方式で変わる。
  • 実行環境の要求は、訪問者が作ったファイルを URL で共有することを、範囲の外に置いている。
  • cross-origin isolation は、サイト全体に適用せず、/playground/* だけに適用すると決めている。
  • COEP: require-corp のページでは、CORP を返さないクロスオリジンのリソースがブロックされる。
  • isolation のヘッダは、ページのレスポンスごとに返す。同じ origin の中で、返す URL と返さない URL を分けられる。
  • キャンバスの候補の draw.io の Embed Mode は、draw.io のエディタを、別の origin の iframe で読み込む。そのレスポンスは、COEP と CORP のヘッダを返さない(curl -I で確認した)。
  • isolation が要る実行環境を、別の origin の iframe で記事に埋め込むときは、iframe の側がヘッダを返しても、親の記事のページがヘッダを返さなければ、iframe の中は isolation の状態にならない。実行環境の要求の prototype で確認した。
  • サイトを作り直すあいだ、本番は切り替える前のサイトを配信し続ける。開発中のサイトを、実機と E2E で確認する URL が要る。

Decision

1. サブドメインに分ける規則

次のどれかに該当するものを、サブドメインに分ける。該当しないものは、fukuemon.dev に置く。

  1. 訪問者が書いたコードを動かす。origin を分けて、サイトが保存したデータにアクセスできないようにする。
  2. サイトと違うレスポンスヘッダか、アクセスの認証が要る。
  3. サイトと別に作って別にデプロイする、独立したアプリである。

1 つのアプリは、1 つの Worker と 1 つのホスト名に割り当てる。
1 つの Worker に、パスで複数のアプリを配置しない。アプリごとに、デプロイのタイミングとヘッダを分けるためである。

2. 割り当て

ホスト名 置くもの 該当する規則
fukuemon.dev トップ、記事、シリーズ、プレイグラウンドの一覧のページ 無い
プレイグラウンドのサブドメイン プレイグラウンドの全てのツール 1、2、3
design.fukuemon.dev カタログ 3
docs.fukuemon.dev ドキュメントサイト 2 と 3
  • プレイグラウンドのサブドメインの名前は、実行環境の方式を決める ADR が決める。
  • ゲームのような独立したアプリは、追加するときに、規則 3 でサブドメインを 1 つ割り当てる。

3. プレイグラウンドのツールの配置先

  • プレイグラウンドは、1 つのアプリとして扱う。全てのツールを、1 つのサブドメインの、1 つの Worker に置く。訪問者から見て 1 つの場所にし、ツールの間で、訪問者が保存したデータを使えるようにするためである。
  • ツールごとに、パスとページを分ける。1 つのページに、複数のツールを置かない。
  • cross-origin isolation のヘッダは、isolation を求めるツールのパスにだけ返す。サブドメインの全体には返さない。別の origin のページを iframe で読み込むツールを、同じサブドメインに置くためである。
  • この Worker は、パスごとにヘッダを書き分ける。規則の「アプリごとにヘッダを分ける」の例外になる。
  • どのパスにヘッダを返すかは、実行環境の方式を決める ADR が決める。

4. プレイグラウンドに保存したデータ

  • プレイグラウンドのコードは、fukuemon.dev が保存したデータにアクセスできない。origin が違うためである。
  • プレイグラウンドの中では、ツールの間で origin を分けない。訪問者が動かしたコードが、同じ origin に保存したファイル、図、スライドにアクセスできることを受け入れる。理由は次の 3 つ。
    • データは、訪問者の端末にだけある。訪問者のアカウントも、サーバーに置くデータも無い。
    • 動くコードは、訪問者が書いたコードか、訪問者が自分の操作で動かしたコードである。
    • 作ったファイルを URL で共有しないので、他人が用意したコードが、訪問者の操作なしに動くことが無い。
  • 次の 3 つを条件にする。
    • プレイグラウンドの origin に、シークレット(トークン、認証情報)を保存しない。
    • 他人が用意したコードを、訪問者の操作なしに動かさない。運営者が記事とプレイグラウンドに用意した初期ファイルは、この条件の対象にしない。
    • URL での共有のように、他人が用意したコードを読み込む機能を追加するときは、この決定を決め直す。

5. cross-origin isolation

fukuemon.dev の全体には、cross-origin isolation のヘッダを返さない。
サイト全体に適用すると、CORP を返さない外部のリソースが、全てのページでブロックされるためである。

例外は、isolation を求める実行環境を埋め込む記事の URL である。
実行環境を iframe で埋め込むときは、親の記事のページもヘッダを返さないと、iframe の中が isolation の状態にならないためである。
どの記事の URL に返すかは、実行環境の方式を決める ADR が決める。

6. 切り替えまでのプレビュー

  • 新しいサイトを、本番と別の名前の Worker(fukuemon-dev-preview)にデプロイする。custom domain を付けず、workers.dev の URL で開く。
  • develop への push で check が通ったときにデプロイする。
  • 誰でも開けるままにし、全てのレスポンスに X-Robots-Tag: noindex を返す。E2E と Lighthouse を、認証なしで実行するためである。
  • 本番の Worker(fukuemon-dev)への deploy は、運用者が手動で実行したときだけ行う。
    • 通常の deploy workflow が push で動くとした ADR-0005 の 5 の、切り替えまでの例外である。例外は本番の Worker だけで、credential の 2 系統は変えない。
  • 本番を新しいサイトに切り替えるときに、プレビューの Worker を消し、本番の Worker へ develop への push でデプロイする設定に変更する。

承認の条件

実装の中で、次の 3 つを確認する。1 つ目と 3 つ目は実行環境の要求の prototype で、2 つ目はキャンバスの要求の prototype で確認する。

  • isolation のヘッダを返すパスのページと、返さないパスのページが、同じ origin で両方動くこと。
  • キャンバスの候補が、isolation のヘッダを返さないパスのページで動くこと。
  • 実行環境の方式が、訪問者のコードを、プレイグラウンドのページと同じ origin で動かすか。同じ origin で動かす方式を採るときは、Decision 4 の受け入れを、その方式の ADR でもう一度判断する。

Considered Options

選択肢 強み 弱み
採用: プレイグラウンドの全てのツールを 1 つのサブドメインに置き、isolation のヘッダを、求めるツールのパスにだけ返す 訪問者から見て、プレイグラウンドが 1 つの場所になる。ツールの間で、保存したデータを使える。訪問者のコードが、サイトが保存したデータにアクセスできない。Worker が 1 つで済む ヘッダを、パスごとに書き分ける。訪問者が動かしたコードが、ほかのツールの保存したデータにアクセスできることがある。isolation を求めるツールと、別の origin のページを iframe で読み込むツールを、同じ文書に並べられない
コードを動かすツールだけをサブドメインに置き、動かさないツールを fukuemon.dev に置く 訪問者のコードが、図とスライドにアクセスできない。ヘッダを書き分けない プレイグラウンドが 2 つの origin に分かれる。ツールの間で、保存したデータを使えない
ツールごとに、サブドメインを 1 つ割り当てる ツールごとに、ヘッダとデプロイのタイミングを分けられる。保存したデータが、ツールごとに分かれる Worker とホスト名が、ツールの数だけ増える。ツールの間で、保存したデータを使えない
プレイグラウンドのサブドメインの全体に、isolation のヘッダを返す ヘッダを書き分けない COEP を返さない別の origin のページを iframe で読み込むツールを、置けない
サイトの全てを fukuemon.dev の下のパスに置く Worker が少ない。同じ origin なので、埋め込みが単純になる 訪問者のコードが、サイトと同じ origin で動く。isolation のヘッダを、サイトのパスごとに書き分ける
ツールの配置先を決めず、実行環境の方式と一緒に決める 方式が決まる前に、origin を固定しない キャンバスとスライドのエディタの要求を、配置先が決まらないまま書くことになる
プレビューを、本番の Worker のバージョンごとの URL で公開する Worker が増えない 上げるたびに URL が変わる。本番の Worker への操作なので、コマンドを誤ると本番が替わる
プレビューを用意せず、手元だけで確認する 基盤が増えない 実機で確認できない。E2E と Lighthouse を、配信した状態で実行できない

Consequences

Positive

  • 新しいアプリを追加するときに、配置先を規則で決められる。
  • プレイグラウンドにツールを追加するときに、Worker とホスト名が増えない。
  • 開発中のサイトを、本番の Worker に触れずに、変わらない URL で確認できる。

Negative

  • Worker が、サイト、プレイグラウンド、カタログ、ドキュメントサイト、プレビューの 5 つになる。
  • プレイグラウンドの Worker は、パスごとに違うヘッダを返す。ヘッダの設定を誤ると、isolation を求めるツールが起動しないか、iframe を読み込むツールが動かない。
  • isolation を求めるツールと、別の origin のページを iframe で読み込むツールは、同じ画面に並べられない。ツールを移るときは、ページを読み込み直す。
  • isolation を求める実行環境を埋め込む記事では、CORP を返さない外部のリソースがブロックされる。
  • 訪問者が、他人の書いたコードを貼って動かすと、そのコードが、同じ端末のプレイグラウンドに保存した図、スライド、ファイルを読むか、消すことがある。
  • プレビューの Worker は custom domain を持たないので、Worker の名前を Terraform の変数と比べるチェックの対象の外になる。
  • プレビューの URL を知っている人は、開発中のサイトを開ける。