コンテンツへスキップ
DedicatedPHP 接触

PHPにおける漏えいのないマルチテナントデータ分離

明示的なコンテキスト、データ・キュー・キャッシュ・テストの制御により、組織間アクセスを防ぐPHP SaaSを設計します。

データベース、キャッシュ、ファイル、キューで組織を分離したPHP SaaSの編集用図解

PHPにおけるマルチテナントデータ分離は、メイン画面にWHERE organization_id = ?という条件を追加するだけでは実現できません。漏えいはAPI、エクスポート、キャッシュ、添付ファイル、キューコンシューマー、スケジュールされたプロセスで発生し得ます。また、正当な管理者が組織を切り替えた際に、システムが以前のコンテキストを保持している場合にも起こり得ます。

アーキテクチャ上の目標は明確でなければなりません。顧客情報を読み取り、変更し、処理し、または提供するいかなる操作も、検証可能な組織スコープなしに実行できてはなりません。そのスコープは明示的に伝播され、アプリケーションの関連する各境界で検証される必要があります。

SaaSアプリケーションで分離すべきもの

SaaSアプリケーションで分離すべきもの — guía visual de DedicatedPHP

トランザクションモデルはリスク対象領域の一部にすぎません。組織に帰属するリソースを棚卸しし、それぞれについて、帰属をどのように識別、保存、取得、削除、監査するかを定義してください。

  • トランザクションデータ:ユーザー、プロジェクト、注文、請求書、設定、エンティティ間の関係。
  • ファイルと添付ファイル:外部ストレージ内のオブジェクト、サムネイル、生成されたドキュメント、およびそれらのメタデータ。
  • キャッシュ:クエリ結果、算出済み権限、セッション、APIレスポンス、設定データ。
  • 検索インデックス:インデックス化されたドキュメント、サジェスト、事前集計されたフィルター。
  • 非同期処理:キュージョブ、リトライ、インポートバッチ、通知。
  • 運用と可観測性:ログ、トレース、メトリクス、サポート用エクスポート、内部ツール。

すべてのリソースに同じ戦略が必要なわけではありません。公開カタログは共有できる一方、請求書、そのPDF、ダウンロードログは組織との明確な関連を維持しなければなりません。新しいエンティティが所有権ルールなしに作られることを防ぐため、この判断を文書化する必要があります。

データ分離モデルの選択

一般的なモデルは3つあります。普遍的に優れたものはなく、選択は規制要件、規模、運用、商用モデル、およびチームがプラットフォームを維持する能力に依存します。

組織キーを用いる共有データベース

すべての組織がテーブルを共有し、分離対象の各レコードにはorganization_idのようなキーが含まれます。製品を進化させ、グローバルな集計クエリを実行するには最も直接的な手法です。その代わり、すべてのクエリ、リレーション、インデックス、キャッシュ、タスクがスコープを遵守するという厳格な規律を要求します。

最低限、適切な場合は外部キー、organization_idで始まる複合インデックス、同じく複合の一意制約を使用してください。たとえば、組織内で一意の注文コードは、それがビジネスルールでない限り、グローバルに一意として宣言すべきではありません。

組織ごとの分離スキーマ

各顧客は同じデータベースサーバー内の別個の論理スキーマで運用します。これにより分離されたテーブルでフィルターを省略するリスクは低減しますが、マイグレーション、接続、分析ツール、グローバルクエリは複雑になります。データベースエンジン、フレームワーク、日常運用がこのパターンを一貫してサポートする場合にのみ適しています。

組織ごとのデータベース

データベースを分離すると、より強い境界が得られ、個々の顧客の復元や移行が容易になる場合があります。一方で、接続、マイグレーション、バックアップ、監視、構造変更のデプロイメントの管理対象が増加します。グローバルレポート、一括変更、障害からの復旧をどのように実行するかを特に評価することが重要です。

物理的な分離は特定の障害クラスを減らしますが、認可、ファイル制御、シークレット管理、共有サービスにおけるコンテキスト検証の代替にはなりません。

参照アーキテクチャ: 境界における明示的コンテキスト

組織コンテキストを、ブラウザーから送信された任意のパラメーターから推測してはなりません。検証済みのサブドメイン、適切なaudienceを持つトークン、ユーザーの所属、または単一の組織に関連付けられた統合認証情報といった、認証・認可されたソースから解決する必要があります。

PHPアプリケーションでは、エントリーレイヤーが組織識別子、アクター、その権限、リクエスト識別子を含む不変のコンテキストオブジェクトを構築できます。コントローラー、コンソールコマンド、キューコンシューマーはこのコンテキストを受け取るか、検証済みデータによって再構築します。長時間稼働プロセスで不適切に残存し得る可変グローバル変数は避けてください。

final class OrganizationContext {
    public function __construct(
        public readonly string $organizationId,
        public readonly string $actorId
    ) {}
}

リポジトリは、分離されたエンティティを照会または変更するためにコンテキストを必須とすべきです。各開発者の記憶に依存する暗黙の規約より、省略を困難にするインターフェースが望まれます。可能な場合は、ドメインレイヤーでもアクセス方針を適用してください。組織に所属していても、その組織内のあらゆる操作が自動的に認可されるわけではありません。

クエリとリレーションでのフィルター漏れを防ぐ

分離されたクエリは、ビジネス識別子で検索する前に組織でフィルタリングしなければなりません。まずidでレコードを取得し、その後に所有者を確認すると、拒否する前に結果がシリアライズ、記録、使用された場合に情報露出が生じる可能性があります。

  • コンテキストを受け取るメソッドを持つリポジトリまたは読み取りサービスにクエリを集約します。
  • コントローラー、テンプレート、イベントコンシューマーから分離対象モデルへ直接アクセスすることを禁止します。
  • リレーションを確認します。遅延ロードされたリレーションは、主エンティティに適用されたフィルターを回避する可能性があります。
  • モデルが許す場合、異なる組織の行どうしのリレーションを防ぐデータベース制約を使用します。
  • マイグレーション、テストシード、分析クエリの規約を定義します。

行レベルセキュリティポリシーを提供するデータベースエンジンでは、それらが追加の防御となる場合があります。しかし、その採用には接続テスト、ロール管理、管理プロセスのレビューを含める必要があります。データベースポリシーがファイル、キャッシュ、外部インデックスを自動的に保護すると想定すべきではありません。

メインWebフロー外のリスク

不透明な識別子は列挙を減らしますが、アクセスを認可するものではありません。UUIDまたはランダム識別子も、アクティブな組織の範囲内で解決されなければなりません。同様に、署名付きダウンロードURLには、正しいスコープに属するオブジェクト、適切な有効期限、権限変更時の失効ルールが必要です。

キャッシュキーには組織識別子を含め、コンテンツが権限に依存する場合は、追加のロールまたは認可バージョンの次元も含める必要があります。dashboard:summaryのようなキーはマルチテナント環境では安全ではありません。明示的なスコープを持つキーは、より正確な無効化も可能にします。

エクスポートは元のリクエスト外で実行されることが多いため、特に機密性が高いものです。誰が要求したか、どの組織向けか、どのフィルターが承認されたか、結果をどこへ配信するかを保存してください。未検証データから算出された宛先に添付ファイルやリンクを送信してはなりません。

API、Webhook、キューでコンテキストを伝播する

APIは認証情報から組織を導出するか、要求されたリソースがその認証情報に関連付けられた組織に属することを確認しなければなりません。X-Organization-Idヘッダーを許可することは、明示的な委任を持つオペレーターには有効な場合がありますが、特別な認可、監査、およびスコープ変更を可視化するインターフェースが必要です。

受信Webhookは、署名、送信者、統合の事前関連付けを検証せずに、本文に含まれる組織識別子を信頼してはなりません。送信Webhookについては、すでに境界設定されたデータからイベントを生成し、宛先を検証せずに共有キューのペイロードを再利用することを避けてください。

各非同期ジョブは、リソース識別子とともに組織識別子を運び、照会前にコンテキストを再構築しなければなりません。ジョブが内部で作成された場合でも、コンシューマーは両方の値を確認する必要があります。リトライ、遅延ジョブ、スケジュールされたタスクにも同じルールが必要です。安全に利用できる暗黙のリクエストコンテキストは存在しません。

検証可能なテストと診断シグナル

最も重要なテストは、ある組織が自身のデータを閲覧できることではなく、別の組織のデータを読み取りも変更もできないことです。意図的に似たデータを持つ2つの組織を作成し、Webインターフェース、API、コマンド、エクスポート、ダウンロード、キューコンシューマーという各エントリーポイントに対して統合テストを実行してください。

  • 組織Aのセッションまたは認証情報を使用して組織Bのリソースを要求し、情報を明かさないレスポンスを期待します。
  • クロス組織リソースを参照するだけでなく、更新、削除、ダウンロード、エクスポートを試みます。
  • AとBのキャッシュキーが独立した結果を生成することを確認します。
  • 別組織のリソースを含むキュージョブを実行し、制御された形で失敗することを検証します。
  • 複数組織のデータを用いて、復元、インポート、夜間タスクをテストします。
  • 不要な個人データをログに含めず、アクター、組織、リソース、結果とともに機密操作を記録します。

プロパティベーステストは手動ケースを補完できます。ある組織の下で作成されたあらゆるリソースについて、有効な所属を持たないアクターは、公開された経路を通じてそれを観測または変更できてはなりません。このプロパティは、今後のエンドポイントとリポジトリの変更にも適用される必要があります。

既存アプリケーションの導入計画

データがすでに混在している場合、アプリケーション全体を書き直すことから始めないでください。まずエンティティ、フロー、統合、管理アクセスを棚卸しします。次に、各レコードの所有権を定義し、曖昧なケースをレビュー可能なビジネスルールで解決します。

  1. 組織エンティティと所属キーを対象テーブルに追加します。
  2. 制御されたマイグレーションによりそのキーを設定し、信頼して割り当てられないケースの証跡を保持します。
  3. 最も機密性の高い経路に、スコープ付きリポジトリとクロス組織アクセスのテストを導入します。
  4. 新しいキャッシュ、ファイル、検索、ジョブにスコープを含めます。
  5. 古いフローを段階的に移行し、コードレビューでコンテキストなしの新規クエリを禁止します。
  6. メトリクスとテストが十分なカバレッジを示した時点で、より厳格な制御を有効化します。

成長前に文書化すべき判断

成長前に文書化すべき判断 — guía visual de DedicatedPHP

次の組織を追加する前に、選択したモデル、コンテキストの信頼できる情報源、管理アクセスの例外、識別子戦略、キャッシュ境界、ファイルの所有権、データ復旧、ログ保持、およびクロス組織アクセスの疑いがある場合の手順を文書化してください。

また、誰が別の組織の代理で操作できるか、その委任をどのように承認し、どのように取り消すかも決定してください。PHPにおけるマルチテナントデータ分離は、明示的な判断、再現可能な技術的制約、アーキテクチャ上の約束を検証可能な振る舞いに変えるテストによって維持されます。

これらのアイデアをあなたのプロジェクトに活用してみませんか?あなたのPHPプラットフォームについて話し合いましょう。
関連サービスを見る