代表的な面接トピック

バックエンド面接:マルチテナントAPIでBOLA/IDORをどのように防ぎますか?

バックエンド難しい
Offer.cc 編集チーム公開日 更新日

質問

マルチテナントB2BドキュメントAPIがJWTを検証しているにもかかわらず、一般メンバーがURL、リクエストボディ、または一括エクスポート内のdocument_idを置き換えることで、別テナントのドキュメントを読み取り、変更、またはエクスポートできてしまいます。BOLA/IDORについて説明し、ロールと共有、クエリおよびトランザクション境界、キャッシュ、非同期ジョブ、ダウンロードリンク、PostgreSQL RLS、エラー、監査、セキュリティテストにわたるオブジェクトレベルの認可を設計してください。

問題と適用シナリオ

あるB2Bドキュメントサービスでは共有データベースを使用しています。ユーザーは複数のテナントに所属でき、それぞれのテナントで viewereditor、または admin のロールを持ちます。ドキュメントは同一テナント内のメンバーに直接共有することも可能です。このサービスは単一ドキュメントの読み取り、更新、削除API、一括エクスポート、および非同期アーカイブ生成を公開しています。JWTの署名、有効期限、オーディエンスの検証はすでに機能していますが、レガシーコードはクライアントから提供された document_id によってグローバルにドキュメントを読み込み、その後リクエストが認証されているかのみをチェックしています。

攻撃者は監査ログのエントリ、共有リンク、または別のAPIから有効なUUIDを取得し、自身のドキュメントIDをそれに置き換えます。UUIDの総当たり列挙は困難ですが、サーバーが「現在のアクティブなテナントにおいて、現在のサブジェクトがこのリソースに対してこのアクションを実行できるか」をチェックしない限り、ドキュメントの漏洩や改ざんが発生します。OWASPはこれをBroken Object Level Authorization(BOLA)と呼び、一般的なWebセキュリティの文献ではInsecure Direct Object Reference(IDOR)とも呼ばれます。オブジェクト識別子は、パス、クエリパラメータ、ヘッダー、JSONボディ、GraphQL変数、ファイル名、または一括処理リストに現れる可能性があります。

API面接対策の公開ページ2件で、IDORの説明やテスト方法について候補者に明示的に質問しています。2026年1月の公開ディスカッションでは、オブジェクトIDの暗号化がIDORを解決するかどうかが議論されており、「IDを隠す」というアプローチが候補者が分析すべき実務上の誤解として依然として残っていることが示されています。OWASP API Security Top 10 API1:2023および2026年の公開実証研究は、さらなるリスクと技術的コンテキストを提供しています。この証拠は代表性を裏付けるものであり、特定の企業の出題や面接頻度を確定するものではありません。

これはバックエンドの設問です。中核となる課題は、認証済みアイデンティティ、テナント関係、リソース属性、およびアクションを、API、データアクセス、トランザクション、キャッシュ、ワーカーにまたがる単一の認可境界へと持ち込むことだからです。既存のOAuthに関する記事はアイデンティティと認可コードフローを扱い、SQLインジェクションはクエリ構造を扱い、SSRFは外部への送信先認可を扱っています。いずれもオブジェクトレベルのアクセスには答えていません。

面接官が評価しているポイント

第1のシグナルは正確な用語の理解です。認証は「誰が呼び出しているか?」に答えます。オブジェクトレベル認可は「このサブジェクトは今、このオブジェクトに対してこのアクションを実行してよいか?」に答えます。PATCH /documents/{id} を呼び出す権限はあるものの、別テナントのドキュメントを変更する権限がないユーザーがそれを実行できてしまう場合、BOLAが露呈しています。一般メンバーが管理者専用の一括削除機能にアクセスできてしまう場合は、Broken Function Level Authorizationに近いです。ユーザーが自身のドキュメント上の保護された owner_id プロパティを更新できてしまうのは、オブジェクトプロパティの認可問題です。

第2のシグナルは完全な認可モデルです。document.owner_id == user.id のみを比較すると、テナントロール、チーム共有、読み取り専用権限、権限失効、一時的なサポートアクセスが見落とされます。優れた回答では、単一の決定を明示します:

text
allow(subject, tenant, action, resource, context) -> decision + reason

テナントは検証済みのメンバーシップから得られます。アクションは readupdatedeleteshareexport を区別します。リソース属性には、テナント、状態、オーナー、共有関係が含まれます。コンテキストには一時的なサポート付与やポリシーバージョンを含めることができます。一致する許可ルールがない場合、決定は拒否(deny)となります。

第3のシグナルは、データが選択および変更される境界で認可を強制することです。findById(id) でグローバルに読み込み、単一のコントローラーがチェックを追加することに依存すると、一括エンドポイント、ワーカー、キャッシュ、新しいルートを通じてバイパスが発生します。推奨されるアプローチは、認可されたデータセットからクエリを実行することです。変更処理(mutation)は、同一のステートメントまたはトランザクション内にスコープとバージョンの条件を持ち込み、影響を受けた行数(affected rows)を検証します。

第4のシグナルは多層防御の限界を理解していることです。ランダムUUID、404レスポンス、レート制限、PostgreSQLの行レベルセキュリティ(RLS)はいずれも役立ちますが、完全なビジネス認可の代わりにはなりません。PostgreSQL 18のドキュメントにも記載されている通り、スーパーユーザー、BYPASSRLS ロール、そして通常はテーブルオーナーはRLSをバイパスします。ポリシーの有効化、接続ロール、リクエストごとのテナントコンテキストの検証が不可欠です。

最後のシグナルは、あらゆる経路でオブジェクト横断アクセスが拒否されることを証明することです。自身のドキュメントに対する GET の成功は単なる機能テストに過ぎません。セキュリティテストでは、複数のアカウント、テナント、ロール、および既知の有効な外部オブジェクトIDを使用します。読み取り、書き込み、削除、一括操作、エクスポート、ダウンロード、GraphQL、キャッシュヒット、ワーカー実行を網羅し、データベース、オブジェクトストレージ、メッセージングの副作用が一切発生しなかったことをアサートします。

回答前に確認すべき明確化のための質問

  • テナントはどのように選択されるか? ユーザーはアクティブなテナントを切り替えることができますが、クライアントの X-Tenant-ID は単なる選択の表明にすぎません。サーバーはヘッダーを盲信するのではなく、認証されたアイデンティティに対するメンバーシップを再検証しなければなりません。
  • 権限は何によって決定されるか? テナントロールのみか、それともオーナーシップ、チームメンバーシップ、直接共有、ドキュメントの状態、一時的なサポート付与も含まれるか? 動的なルールが増えるほど、中央集権的なポリシーと安定した監査理由が必要になります。
  • どのアクションに個別の権限が必要か? 読み取り権限があっても、ダウンロード、エクスポート、共有、削除の権限が自動的に含まれるわけではありません。一括操作では、すべてのオブジェクトに対して同一のアクションレベルの決定が適用されます。
  • 拒否時は403と404のどちらを返すべきか? 外部オブジェクトIDの存在自体を明かしてはならない場合、存在しないことと権限がないこと(不可視)で404の契約を共有できます。同一テナント内の可視オブジェクトでアクション権限が不足している場合は、プロダクトの契約に基づき403を返すことができます。どちらのレスポンスも機密情報の差異を漏洩してはなりません。
  • ジョブは現在の権限を使用するか、投入時点のスナップショットを使用するか? 本設計ではエンキュー時と実行時の両方でチェックを行うため、キューに入ったエクスポートは権限失効後に停止します。不変のスナップショット権限が必要な場合は、スコープと有効期限を定めた明示的な付与をモデル化します。
  • サポート担当者はテナントにアクセスできるか? 可能な場合は、チケット、理由、承認、有効期限、完全な監査を必要とする独立したJust-In-Time(JIT)パスを使用します。一般的なアプリケーション接続に永続的なグローバル管理者スイッチを持たせてはなりません。
  • PostgreSQL RLSは使用されるか? 本回答ではアプリケーションのテナントスコープを主制御とし、実装漏れを防ぐバックストップとしてRLSを使用します。データベースロール、プールコンテキスト、マイグレーション、ワーカーがその保証を維持できるのであれば、他の組み合わせも有効です。

30秒の回答フレームワーク

「認可を subject + tenant + action + resource + context として表現し、デフォルト拒否(deny-by-default)とします。認証ミドルウェアはサブジェクトのみを確定します。アクティブなテナントは検証済みのメンバーシップから取得しなければなりません。データ層はスコープのないドキュメント検索を公開せず、読み取りには tenant_id + document_id を使用し、ロール、オーナー、共有に対するアクションポリシーを適用します。更新と削除はこれらの条件を同一ステートメントまたはトランザクションに含め、影響行数が0の場合は『不可視』として処理します。UUIDは列挙を困難にしますが、アクセス制御の代わりには決してなりません。

一括処理、キャッシュ、ダウンロード、エクスポートの各パスも同じルールを使用します。キャッシュキーにはテナントを含め、ケーパビリティリンクにはリソース、アクション、有効期限をバインドし、ジョブはエンキュー時と実行時の両方でチェックします。PostgreSQL RLSは、アプリケーションロールがそれをバイパスできず、プールのテナント切り替えがテストされている場合に限り、実装ミスのバックストップとして機能します。最後に、2つのテナントと複数ロールのマトリクスにわたってパス、ボディ、一括リスト内のIDを置換し、レスポンス、データ、ファイル、メッセージがテナント境界を越えないことをアサートします。」

ステップごとの詳細解説

ステップ1: 認可タプルとオブジェクトのエントリポイントを棚卸しする

コントローラー全体にロール名を散乱させる前に、ビジネスルールをマトリクスとして記述します:

サブジェクトの関係readupdatedeleteshareexport
テナント viewer、非共有DenyDenyDenyDenyDeny
テナント viewer、直接共有AllowDenyDenyDenyAllow またはプロダクト依存
テナント editorAllowAllowDenyポリシー依存Allow
ドキュメントオーナーAllowAllowポリシー依存AllowAllow
テナント adminAllowAllowAllowAllowAllow

この表はシナリオ上の前提です。プロダクトおよびセキュリティ責任者が実際のマトリクスを承認し、各許可ルールを正当化する必要があります。デフォルト拒否とは、新しいアクション、未知のロール、不足しているテナントコンテキスト、またはポリシーエラーが発生した場合にフェイルクローズ(安全側に倒して拒否)することを意味します。

次に、オブジェクトのエントリポイントを棚卸しします:パスパラメータ、クエリフィルタ、ボディ、一括配列、GraphQLノード、共有トークン、オブジェクトストレージキー、キャッシュキー、メッセージ、ジョブなどです。OWASPの定義では連番IDである必要はありません。UUID、ファイル名、汎用文字列も参照に含まれます。各エントリについて、サブジェクトのソース、テナントのソース、アクション、検索方法、最終的な副作用を記録します。このプロセスによりバイパスが浮き彫りになります。

ステップ2: 認証済みアイデンティティから信頼できるテナントを導出する

検証済みJWTは安定した user_id を提供しますが、長期間有効なロールクレームを常に最新に保つわけではなく、個別に提供されたテナントIDを無条件に信頼できるわけでもありません。ユーザーが tenant_b を選択すると、サーバーは現在のメンバーシップを解決し、リクエストスコープの AuthContext を作成します:

text
AuthContext {
  user_id,
  tenant_id,
  membership_id,
  roles,
  policy_version,
  support_grant_id?
}

ミドルウェアがコンテキストの存在を保証し、リソースアクションの決定はドメインポリシーが行います。コネクションプール、コンシューマー、同時リクエストが可変のグローバルテナントを共有してはなりません。各トランザクション内でデータベースコンテキストを設定し、接続をプールへ返却する前にクリアして、あるリクエストが前のテナントを引き継がないようにします。

ステップ3: クエリとデータ制約にテナントスコープを組み込む

安全でないクエリはグローバルに読み込みます:

sql
SELECT * FROM documents WHERE id = :document_id;

基本的な境界には信頼できるテナントが含まれます:

sql
SELECT *
FROM documents
WHERE tenant_id = :auth_tenant_id
  AND id = :document_id;

直接共有が表示可否を決定する場合は、スコープ付きクエリ内で共有関係をJOINするか、同一テナントのリソースのみを読み込んで中央ポリシーエンジンに渡します。リクエストボディの tenant_id をこの述語に決してコピーしてはなりません。外部に対しては、0行の結果に対して一貫して404を返すことで、「他テナントに存在する」と「存在しない」を区別できないようにします。

スキーマ設計によりミスを防ぎやすくなります。共有、バージョン、添付ファイル、エクスポート項目のテーブルにはすべて tenant_id を持たせます。適切な箇所では、一意キーや外部キーに (tenant_id, resource_id) を使用し、あるテナントの子レコードが別のテナントの親レコードを参照できないようにします。アプリケーション認可は引き続き必要ですが、制約によって書き込み時の偶発的なテナント横断関係を拒否します。

ステップ4: 認可とデータ変更を単一の正確性境界に保つ

「読み込み、アプリケーションコードで認可、後で更新」という手順には、Time-of-Check / Time-of-Use(TOCTOU)の競合が存在します。ステップの間に権限やドキュメントの状態が変化する可能性があります。単純なポリシーであれば条件付き更新を使用できます:

sql
UPDATE documents
SET title = :title, version = version + 1
WHERE tenant_id = :auth_tenant_id
  AND id = :document_id
  AND version = :expected_version
  AND (
    owner_id = :user_id
    OR :can_edit_tenant_documents
  );

影響を受けた行数が0の場合は、メッセージの発行、成功監査ログの書き込み、キャッシュの更新を行わないでください。複雑な共有の場合は、関連するメンバーシップとリソースのバージョンを単一トランザクション内でロックするか、ポリシーをデータベースの述語にコンパイルします。認可の根拠と副作用は明示的なトランザクション/バージョン境界を共有しなければならず、古いスナップショットでその後の無条件な書き込みを認可することはできません。

一括操作には原子性(アトミシティ)の契約が必要です。本シナリオでは全件成功か全件失敗(All-or-Nothing)を採用します:すべてのIDを正規化および重複排除し、単一の信頼できるテナントとアクションのもとで認可されたセットをクエリし、件数が一致しない場合はバッチ全体を拒否します。可視なサブセットのみを暗黙的にエクスポートすると、存在確認オラクル(Existence Oracle)になり得ます。項目ごとのプロダクト契約も可能ですが、各オブジェクトを個別に認可し、拒否された項目には情報を漏洩しない結果を使用する必要があります。

ステップ5: キャッシュ、ダウンロード、非同期ジョブを網羅する

キャッシュキーには、少なくともテナントとリソースバージョンを含めます(例: document:{tenant_id}:{document_id}:{version})。キャッシュヒットはデータを取得するだけであり、現在のアクション認可をスキップするものではありません。認可決定自体をキャッシュする場合、キーはサブジェクト、テナント、アクション、リソース、関係またはポリシーのバージョン、および失効状態を網羅する必要があります。複雑なシステムでは、関係データをキャッシュして軽量な決定処理を都度再計算する方が安全な場合が多いです。

事前署名付きダウンロードURL(Presigned URL)は短命なケーパビリティです。一度発行されると、有効期間内であれば保持者はストレージにアクセスできます。発行前に download をチェックし、リソース、アクション、有効期限、Content-Dispositionをバインドします。機密ドキュメントには短い有効期限、ワンタイムまたはプロキシ経由のダウンロードを使用し、必要に応じて失効機能を設けます。署名はサーバーがURLを発行したことを証明するだけであり、権限のない呼び出し元に取得を許可するものではありません。

エクスポートは2つのタイミングでチェックします。APIはエンキュー前に各ドキュメントの export を検証します。実行時、ワーカーはジョブ内のサブジェクトとテナントの参照を使用して、現在のメンバーシップとリソース権限を再取得します。メンバーシップの失効、テナントの変更、サポート付与の期限切れが発生している場合、ダウンロード可能な成果物が生成される前にジョブを停止します。ジョブのペイロードに呼び出し元が指定した管理者フラグを含めたり、ロールの配列を永久に保持したりしてはなりません。

ステップ6: PostgreSQL RLSをテスト可能な多層防御として扱う

共有テーブルでRLSを有効にすることで、ポリシーによって信頼できるトランザクションテナントに基づいて既存行をフィルタリングし、WITH CHECK によって挿入または更新される行を制約できます。ポリシーが適用されない場合にデフォルト拒否となる動作は、望ましいフェイルセーフです。リリース前に以下のすべてを検証してください:

  • アプリケーションの接続がスーパーユーザーではなく、BYPASSRLS を持たず、通常ポリシーをバイパスするテーブルオーナーでもないこと。必要に応じて FORCE ROW LEVEL SECURITY を使用する。
  • すべてのトランザクションがテナントを設定し、プールへの返却前にそれをクリアすること。コンテキストが欠落している場合はデフォルトテナントを選択するのではなく拒否すること。
  • USING が可視な既存行をカバーし、WITH CHECK が挿入または更新される新規行をカバーしていること。
  • マイグレーション、バックアップ、ワーカー、サポートツールが別個のロールと明示的な手順を使用していること。
  • 寛容な(PERMISSIVE)ポリシーはデフォルトで OR 結合されるため、ポリシーを追加したことで誤ってアクセスが拡大しないこと。複雑なサブクエリポリシーについては、同時実行時のスナップショットとコストもレビューすること。

RLSはすべてのプロダクト関係を表現できるわけではなく、データベースをバイパスするオブジェクトストレージや検索インデックスを保護することはできません。アプリケーションポリシーが完全なビジネスセマンティクスを担い、RLSはあるクエリでテナントスコープが欠落した場合の漏洩を防止します。一貫性を保つため、両方の層を同一の権限マトリクスに対してテストします。

ステップ7: エラー、監査、反証可能なテストを設計する

テナント横断オブジェクトまたは不可視のオブジェクトに対して、本シナリオでは統一された404とレスポンス形状を返します。拒否時にタイトル、テナント名、オーナー、バージョン、ファイルサイズ、または明確なタイミングのヒントを返してはなりません。レート制限は列挙攻撃や監査ノイズを低減しますが、セキュリティの保証を「攻撃者がUUIDを推測できないこと」に依存してはなりません。

アクター、信頼できるテナント、アクション、制御された形式のリソースID、決定結果、理由コード、ポリシーバージョン、サポート付与、トレースIDを監査ログに記録します。ドキュメントの内容、ダウンロードケーパビリティ、完全なJWTはログに記録しないでください。有用なシグナルには、テナント横断の拒否、同一サブジェクトによる多数の存在しないオブジェクトの探索、ポリシーエラー、RLSの拒否、権限失効によるジョブ停止、サポート付与の使用が含まれます。

テストマトリクスには少なくとも以下を含めます:

  1. 2つのテナント(それぞれオーナー、閲覧者、編集者、管理者を含む)、失効したユーザー、一時的なサポートオペレーター。
  2. 自身のドキュメント、同一テナントの非共有ドキュメント、直接共有ドキュメント、他テナントの有効なUUID、存在しないUUIDリソース。
  3. 一覧取得、GET、PATCH、DELETE、共有、一括エクスポート、GraphQL、ダウンロード、キャッシュヒット、ワーカー実行。
  4. パス、クエリ、JSON、配列、ネストされたGraphQL変数、ストレージキー、ジョブペイロードにおけるIDの置換。
  5. 読み取り時のレスポンス漏洩がないこと、および拒否時にバージョン、共有、ファイル、メッセージ、検索インデックス、成功監査の変更が発生しないこと。
  6. 同時実行時の失効と更新、1つのプール接続における連続した異なるテナントの使用、RLSコンテキストの欠落、誤設定されたアプリケーションロール、古い認可キャッシュ。

認可マトリクスからCI用のポジティブケースとネガティブケースを生成し、各新規エンドポイントに対してリソースとアクションの登録を義務付けます。スキャナーは一部の列挙可能なパスを発見できますが、ビジネス上のオーナーシップを理解しているわけではありません。複数のアカウント、既知の有効な外部オブジェクト、そして副作用のアサーションが、ここでの決定的な証拠となります。

高品質な回答例

「私はこの脆弱性をBOLAと分類します。JWTは呼び出し元のアイデンティティを証明しますが、APIには対象ドキュメントに対するアクションレベルの権限が不足しています。UUIDは列挙確率を低減させますが、認可の決定を変えるものではありません。まず subject, tenant, action, resource, context のマトリクスを定義し、デフォルト拒否とします。ユーザーはアクティブなテナントを選択できますが、サーバーは検証済みの現在のメンバーシップからテナントコンテキストを導出します。

データ層は、ビジネスルートに対してグローバルな findById を公開しないようにします。読み取りはまず信頼できる tenant_id + document_id でスコープを絞り、次に readupdatedeleteshare、または export に対するロール、オーナーシップ、共有ルールを適用します。単純な更新は、テナント、オブジェクト、アクションの条件、およびリソースバージョンを単一の条件付きステートメントに含めます。影響行数が0の場合はすべての副作用を停止します。複雑なポリシーは関連バージョンを単一トランザクション内で保持します。テナントを含む複合外部キーにより、テナントを越えた子レコードのリレーションを防ぎます。

あらゆるバイパス経路をこのモデルに含めます。一括エクスポートはすべてのIDを認可し、ここでは全件成功か全件失敗の契約とします。キャッシュキーにはテナントを含め、キャッシュヒット時も再認可を行います。ダウンロードリンクは発行時に download 権限を要求し、短い有効期限をオブジェクトにバインドします。ジョブはエンキュー時と実行時の両方でチェックし、エクスポート前に権限失効が有効になるようにします。RLSはバックストップとなりますが、アプリケーションロールは BYPASSRLS やテーブルオーナー権限を持たないようにし、プールのトランザクションコンテキストでテナント漏洩がないかテストする必要があります。

最後に、2つのテナントにまたがる複数ロールのアカウントを使用し、既知の有効な外部UUIDをパス、JSON、一括リスト、GraphQLに配置してテストします。テストは読み取り、書き込み、削除、エクスポート、ダウンロード、キャッシュ、ワーカーのパスを網羅します。すべての拒否において、情報を漏洩しないレスポンスと、データベースバージョン、ファイル、メッセージ、検索インデックスの変更がないことをアサートします。これにより、ログイン、エラーレスポンス、副作用を網羅しながら、オブジェクトレベルの認可を経路ごとに証明できます。」

よくある間違い

  • UUID、Base64、または暗号化されたIDを解決策とみなす → IDはログ、共有リンク、他のAPIから漏洩する可能性があり、有効な参照は依然として境界を越えてしまう → すべてのリクエストでサブジェクト、テナント、オブジェクト、アクションを認可する。ランダムIDは多層防御としてのみ使用する。
  • JWT検証後に任意のIDを許可してしまう → 認証はサブジェクトを特定するだけであり、リソース権限を付与するわけではない → 検証済みメンバーシップからテナントコンテキストを構築し、その上でオブジェクトの決定を行う。
  • コントローラーでグローバルに読み込み、オーナーチェックを手書きする → 新規ルート、一括処理、キャッシュ、ワーカーでチェックが漏れ、チーム共有が誤って拒否される → テナントスコープ付きのデータアクセスとデフォルト拒否の中央ポリシーを公開する。
  • GETのみをテストする → PATCH、DELETE、エクスポート、GraphQL、ダウンロードで依然として漏洩や変更が発生する可能性がある → エントリポイントとアクションにわたって複数アカウントのネガティブマトリクスを生成する。
  • 成功したバッチから拒否されたIDを除外する(フィルタリングする) → 件数や内容が存在確認オラクルとなり、部分的な成功のセマンティクスが曖昧になる → All-or-Nothingまたは項目ごとのセマンティクスを事前に定義し、すべてのオブジェクトを認可する。
  • RLSを自動的な安全性と過信してしまう → テーブルオーナー、スーパーユーザー、BYPASSRLS、コンテキストの欠落、寛容なポリシーの合成によって分離が破られる可能性がある → ロール、トランザクションコンテキスト、USING/WITH CHECK、障害モードを検証する。
  • 403または404のみをアサートする → 拒否の前にシステムがデータを書き込み、メッセージを送信し、ファイルを生成してしまっている可能性がある → すべての永続的および外部的な副作用が変更されていないことをアサートする。
  • 調査のために完全なオブジェクトやトークンをログに記録する → セキュリティテレメトリが新たなデータ漏洩源となる → 最小限のアイデンティティ、アクション、理由、および制御されたリソース識別子を記録する。

フォローアップの質問と回答

フォローアップ1: UUIDv4が実質的に推測不可能である場合、それでもオブジェクト認可は必要ですか?

はい、必要です。参照情報は共有リンク、ブラウザ履歴、ログ、通知、アナリティクス、他のAPI、あるいは誤送信されたメッセージから漏洩します。UUIDは盲目的な総当たり列挙を低減させるだけであり、オーナー、テナント、アクション、有効期限を表現するものではありません。テストにおいて攻撃者アカウントに既知の有効な外部UUIDを1つ渡したとき、アクセスが成功してしまえば、認可が欠落していることが即座に証明されます。

フォローアップ2: すべてのテナント横断オブジェクトに対して404を返すと、デバッグが難しくなりすぎませんか?

外部レスポンスを統一する一方で、内部監査ログには RESOURCE_NOT_VISIBLEACTION_DENIEDTENANT_CONTEXT_INVALID などの安定した理由コードを保持できます。運用担当者は制御されたログとトレースIDを通じて調査し、呼び出し元にはオブジェクトが存在するかどうかを知らせません。同一テナント内のコラボレーションで「編集権限なし」を伝える必要がある場合は、一貫したAPI契約のもと、オブジェクトが可視であることを確認した後にのみ403を返します。

フォローアップ3: ユーザーがテナントから削除された場合、キューに入っているエクスポートはどうなりますか?

送信の成功は永続的な読み取り権限を作成しません。実行前に、すべてのリソースについてメンバーシップと export 権限を再取得します。権限失効後はジョブを拒否し、一時ファイルを削除し、ダウンロードURLを発行しません。コンプライアンス要件等で送信時点の権利を固定化する必要がある場合は、古いJWTロールを暗黙的に保持するのではなく、スコープ、承認者、有効期限を持つ明示的で短命な認可スナップショットを作成します。

フォローアップ4: すべてのアプリケーションクエリにすでに tenant_id が含まれている場合、RLSは何を追加しますか?

述語を省略した新しいクエリや直接的なデータベースアクセス経路を阻止でき、単一の実装ミスの影響範囲(ブラスト半径)を抑えることができます。一方で、プールコンテキスト、ロール、マイグレーション、ポリシーの保守コストが追加され、検索、キャッシュ、オブジェクトストレージを保護することはできません。機密性の高い共有テーブルは両方の層を適用する良い候補です。まず、アプリケーションロールがRLSをバイパスできないこと、テナントコンテキストの欠落時に拒否されること、プールコンテキストが漏洩しないこと、そして両層が同一のマトリクスに一致することを証明してください。

フォローアップ5: サポートエンジニアが顧客ドキュメントに一時的にアクセスする必要があります。永続的なバックドアを防ぐにはどうすればよいですか?

チケット、対象テナント、許可されたアクション、承認者、短い有効期限、理由に紐づく、独立したJust-In-Time(JIT)サポート認可を作成します。機密性の高いアクションには2人承認を要求できます。サポート用エンドポイントと通常のエンドポイントを分離し、セッション状態を目立つように表示し、一括ダウンロードを禁止し、すべてのオブジェクトアクセスを監査します。期限切れによってアクセスは即座に失効し、使用状況は定期的にレビューされます。一般的なアプリケーションロールやサービストークンにはテナント横断の権限を一切付与しません。

公開情報ソース

関連する質問