要点: テナント間の情報漏えいは、高度な攻撃よりも一つの条件漏れから起きることが少なくありません。
この記事の用語: RLS:DBが行単位でアクセスを制限する仕組み。Fail closed:情報不足なら拒否する方針。
現場の課題
ログイン済みであることは、すべてのデータを見られるという意味ではありません。クエリからテナントIDが一つ抜けるだけで、別の顧客データを返す可能性があります。
認証は誰かを示しますが、どの資源へ何をしてよいかは別の判断です。APIだけでなく、ジョブ、キャッシュ、データ出力、ファイル保存でも同じテナント境界を再確認します。
設計と実装
- 業務処理の前に、セッション内の利用者、サイト、テナントの関係を検証します。
- DB トランザクションへテナントコンテキストを設定し、コンテキストがなければクエリを拒否します。
- ファイルパスはサーバー側で生成し、短時間だけ有効なアップロード・ダウンロード URLを発行します。
実装例
BEGIN
SET LOCAL app.tenant_id = :tenantId
SET LOCAL app.actor_id = :actorId
SELECT * FROM documents
WHERE site_id = :siteId AND id = :documentId
-- RLS policyでも必須:
tenant_id = current_setting('app.tenant_id')
COMMIT本番導入の進め方
新しい制約で拒否されるクエリを先に記録し、まだ遮断しません。呼び出し元を修正し、二つのテナントを使う越境テストを追加してから、低トラフィックのテーブルからRLSを有効化します。
まとめ
テナント分離は連続した防御です。一か所でもテナントIDを忘れれば、重大な情報漏えいにつながります。
