共有サブドメイン、二つの入口:誰がどの記録を見るのか
サブドメインはブランドを選び、検証済みのメンバーシップはデータを選ぶ。オフィスのアドレスからでも VPN 経由でも、同じテナント検査を実行する。
クリニック A とクリニック B は、一つのコードベース、二つのサブドメイン、そしてオフィスからの訪問者と VPN 経由の訪問者の混在を共有している。ネットワークチームは二つの入口を別々に設定しがちだが、アプリケーションがそうしない限り問題ない。以下の検査はアプリケーションに属し、リクエストがどちらの扉を使ったかに関係なく同じ動作をすべきだ。
第一:ホスト名はテナントを名指しするだけだ
要求されたホスト名を管理者が管理する対応表に照らして解決し、未知のものはすべて拒否する。生のヘッダーではなくフレームワークが検証済みのホスト値を使い、細工された Host が検索キーを注入できないようにする。一致すれば、どのテナント設定とブランドを読み込むかをコードに伝える。だが呼び出し元がそのテナントのデータを見てよいかは何も語らない。その二つ目の問いは、別の入力を持つ別の関数に置く。
一つのホスト名検索から文脈と許可の両方を返す共有ヘルパー関数は、バグの始まりだ。
第二:メンバーシップはセッションから来る
認証済みユーザーの有効なメンバーシップを検索し、最初の検査で得たテナントと比較する。両方のクリニックに属するユーザーは切り替えてよいが、その切り替えはリクエストの文脈に記録された明示的な操作であるべきで、URL からの推測であってはならない。一方のクリニックに属するユーザーが他方を要求した場合は拒否し、ダウンロードや API 呼び出しも含める。
記録の識別子とテナントのパラメーターを編集して、否定ケースを試す。どちらかの変更でデータが返るなら、スコープは検証済みのメンバーシップではなくリクエストから読まれている。
Cookie のスコープとシングルサインオンは、アカウントをどこで提示できるかを決める。そのアカウントが何を読めるかは決めない。
第三:ネットワークは人について何も証明しない
VPN ゲートウェイはトンネルを認証し、公開の許可リストはアドレスの範囲を認証する。どちらも人を特定しない。NAT は多くの利用者を一つの送信元アドレスにまとめ、オフィスの範囲も建物内の全員に対して同じことをする。どちらも輸送の事実として扱い、認可はアプリケーションの中、または身元を明示的に表明し、そのようにレビューされる連携の中に保つ。
DNS は三つ目の輸送の事実だ。プライベートな応答にはプライベートな経路が必要で、公開のフロントエンドには対応するオリジン経路が必要だ。どちらも許可を運ばない。
壁に貼る一文:ネットワークはリクエストがどう届くかを決め、アプリケーションは何を読めるかを決める。
第四:両方の入口でマトリックスを回す
管理下のアカウントを使って、A を A で、B を B で、A を B で、未知のホスト名、そしてメンバーシップのない認証済みアカウントを試す。四番目と五番目は閉じた状態で失敗し、決して既定のテナントに落ちてはならない。同じマトリックスを VPN の入口でも回し、アカウントごとに一つのセッションの瓶を使う:
curl -s -o /dev/null -w '%{http_code}' -b jar.txt https://clinic-b.example.com/records/42
curl -s -o /dev/null -w '%{http_code}' -b jar.txt https://clinic-a.example.com/records/42
次に許可リストの項目を一つ変えて、もう一度回す。結果が動けば、ネットワーク制御が認可の判断をしているということだ。
フロントエンドを変更するたびにマトリックスを繰り返すのは、検査を飛ばす二つ目のコード経路がこの境界の侵食で最も一般的な道だからだ。期待される結果は退屈だ:どの入口も同じ答えを返す。