mcpField
M
multi-tenant-saas-architecture

multi-tenant-saas-architecture

github.com/peterbamuhigire開発29138 回インストール4 日前に更新
リポジトリ

multi-tenant-saas-architecture について

Multi-Tenant SaaS Architecture は、SaaS のテナント境界、権限、パネル、セッション、監査記録を設計・点検するエージェントスキルです。テナント分離と明示的な認可を重視し、設計案や根拠に基づく指針を作成します。

主な機能

  1. 01テナント分離とデータ範囲の整理
  2. 02管理者、テナント、エンドユーザーパネルの定義
  3. 03権限の優先順位と拒否ルールの設計
  4. 04セッション、JWT、テナント ID の計画
  5. 05監査イベントと移行計画の策定

活用シーン

  1. 01新規 SaaS のマルチテナント設計
  2. 02シングルテナントアプリの移行
  3. 03テナント間アクセスリスクの監査
  4. 04ロール上書きと特権操作監査の計画

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/peterbamuhigire/chwezi-dev-engine --skill multi-tenant-saas-architecture

インストール後、Claude Code で/multi-tenant-saas-architectureを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

documentation/
migration.md
references/
database-schema.md
permission-model.md
saas-deployment-models-decision-tree.md
tenant-context-propagation.md

詳細

マルチテナント SaaS アーキテクチャ

謝辞: Peter Bamuhigire、techguypeter.com、+256 784 464178 氏より共有。

必要な入力

入力必須用途
テナント、製品、ライフサイクルのスコープはいSaaS の意思決定を範囲内に収める
現在のアーキテクチャ、計画、ポリシー、制約はい強制可能な動作を保持する
本番データまたは検証済みの証跡条件付きしきい値と移行を検証する

ケイパビリティと権限の契約

デフォルトでは読み取り専用の分析とします。構成、課金、アイデンティティ、テナントデータ、インフラストラクチャ、顧客コミュニケーションを変更するのは、明示的な権限、最小権限の資格情報、テナントスコープ、ロールバック、監査可能な承認がある場合に限ります。シークレットやテナント境界を越えて公開することは決してありません。

縮退モード

本番アクセス、ポリシー、テレメトリ、または権威ある記録が利用できない場合は、ラベル付きの設計またはドライランプランを作成します。デプロイ、照合、削除、配信、または測定された結果を主張してはいけません。不足している証跡と検証を列挙します。

決定ルール

条件アクション停止条件
テナント分離、金銭、アイデンティティ、または削除が影響を受ける承認とロールバックの証跡を要求するスコープまたは権限が曖昧である
証跡が可逆的な変更を支持しているステージング、テスト、および記録を行う受け入れチェックが失敗する
部分的なコンテキストのみが利用可能である前提条件と検証を返す本番の主張を検証できない

ドメインのアンチパターン

  • あるテナントのポリシーやデータを別のテナントに適用する。修正: すべての境界でテナントスコープを強制する。
  • 助言的なリクエストから本番を変更する。修正: 権限が明示されるまで読み取り専用を維持する。
  • 制限、価格、メトリクス、またはコンプライアンスの主張を捏造する。修正: 権威ある記録を使用するか、未解決としてマークする。
  • ロールバックと監査の証跡なしに出荷する。修正: ステージングし、変更前後の証拠を保持する。
  • 欠落している依存関係を成功として扱う。修正: ブロックされた検証を明示する。

使用する場合

  • 新しい SaaS のテナント設計、または既存の SaaS をクロステナント漏洩に対して堅牢化する場合。
  • SaaS を 3 つのパネル (スーパー管理者、テナント/フランチャイズ管理者、エンドユーザー) に分割し、それぞれ異なるスコープを持たせる場合。
  • テナントごとのロールオーバーライド、権限の優先順位付け、および特権アクセスの監査証跡を設計する場合。
  • 下流のデータベース、API、セキュリティ、およびデリバリのスキルが依存するセッション、JWT、および tenant_id の配線を計画する場合。

使用しない場合

  • タスクがテナント化の計画がないシングルテナントアプリである場合 — このスキルの制約は利益なしにオーバーヘッドを追加します。
  • 作業がテナント境界ではなくビジネスモジュールの構成に関するものである場合 — modular-saas-architecture を使用してください。
  • 作業がすでに定義されたテナントモデル内の純粋なスキーマ整形である場合 — database-design-engineering と mysql-best-practices を使用してください。

必要な入力

  • system-architecture-design からのコンテキストマップとクリティカルフローテーブル。
  • database-design-engineering からのアクセスパターンリスト (分離モデルが実際のクエリと一致するように)。
  • vibe-security-skill からの脅威モデル / 不正利用ケース。
  • 製品要件からのパネルとユーザータイプのリスト (スーパー管理者、テナント管理者、エンドユーザー、およびそれらのサブタイプ)。

ワークフロー

  • 出力で指定された成果物として、テナント分離マップ、パネルレイアウト、権限モデル、および監査計画を作成します。

出力

  • テナント分離マップ、パネル定義、権限優先順位モデル、および監査計画 (以下の出力テーブルを参照)。

生成される証跡

カテゴリ成果物形式例
データ安全性テナント分離ノート行レベル vs スキーマレベルの分離、RLS ポリシー、およびテナントスコープのインデックスを扱う Markdown ドキュメントdocs/saas/tenant-isolation-note.md
セキュリティテナント認可監査スーパー管理者 vs テナント管理者 vs ユーザー特権境界および認可テストを扱う Markdown ドキュメントdocs/saas/tenant-authz-audit.md

リファレンス

  • 以下のコアワークフローを読んだ後、詳細が必要な場合は references/ ディレクトリを使用してください。
  • 実装の補足詳細や移行に関するメモについては、documentation/ ディレクトリを使用してください。

3 パネル分離、ゼロトラストの徹底、厳格なテナント分離、および包括的な監査証跡を備えた本番グレードのマルチテナント SaaS アーキテクチャです。このスキルは、下流のあらゆるモジュール、API、データクエリが継承するテナント境界と認証境界を定義します。

前提条件

まず、以下の順番で読み込んでください。

  1. world-class-engineering — リポジトリ全体の品質基準。
  2. system-architecture-design — このスキルが利用するコンテキストマップとクリティカルフローを生成します。
  3. database-design-engineering — 分離モデルを形作るアクセスパターン一覧を生成します。
  4. vibe-security-skill — このスキルが組み込む脅威モデルと認証ルールを生成します。

このスキルが適用される場面

  • 新しい SaaS のテナント設計をゼロから行う場合。
  • シングルテナントアプリをマルチテナントに変換する場合(documentation/migration.md を参照)。
  • 既存の SaaS について、テナント間のリーク、tenant_id フィルターの欠落、監査されていない管理者操作を監査する場合。
  • 3 パネルのファイルおよびルートレイアウト(/、/adminpanel/、/memberpanel/)を計画する場合。
  • 権限の優先順位ラダー(ユーザー拒否 > ユーザー許可 > テナント上書き > ロール > デフォルト拒否)を設計する場合。
  • どの操作を監査対象とすべきか、および監査レコードをどのくらい保持するかを定義する場合。

入力

成果物生成元必須?理由
コンテキストマップsystem-architecture-design必須サービス、パネル、所有権を定義します
クリティカルフロー表system-architecture-design必須認証とセッションの境界を形作ります
アクセスパターン一覧database-design-engineering必須tenant_id を用いた共有 DB かテナントごとの DB かを選択します
脅威モデルvibe-security-skill必須監査ルールと不正利用ケースに反映されます
パネルおよびユーザータイプ一覧製品要件必須パネルごとのロールとスコープを決定します

出力

成果物利用先テンプレート
テナント分離マップ(分離モデル + テーブルごとの tenant_id ルール)database-design-engineering、api-design-firstインライン(このスキル)
パネル定義(スーパー管理者、テナント管理者、エンドユーザー)api-design-first、kubernetes-saas-deliveryインライン
権限優先順位モデル(ユーザー / テナント上書き / ロール / デフォルト拒否)vibe-security-skill、api-design-firstインライン + references/permission-model.md
セッションおよび JWT 計画(プレフィックス、有効期間、ローテーション)api-design-first、vibe-security-skillインライン
監査イベントセットと保持ポリシーobservability-monitoring、reliability-engineeringインライン
移行計画(シングルテナントからマルチテナントへ)database-design-engineering、deployment-release-engineeringdocumentation/migration.md

譲れない原則

  • フランチャイズ / テナントスコープのすべてのテーブルは、tenant_id(または franchise_id)を NOT NULL とし、tenants への外部キーを持ちます。
  • テナントスコープのテーブルに対するすべてのクエリは、WHERE 句に tenant_id を含めます。
  • tenant_id は常にセッションまたは JWT から取得します — クライアント入力(ボディ、クエリ、ヘッダー)から取得してはいけません。
  • デフォルトの認可判断は拒否です。すべての操作には明示的な権限チェックが必要です。
  • テナントデータに触れるすべてのスーパー管理者操作は、実行者、対象テナント、正当性の理由とともに監査されます。
  • 存在すべきでないクロステナントアクセスは 404 Not Found を返し、403 Forbidden は決して返しません(403 はリソースが存在することを確認してしまうため)。

決定ルール

分離モデル
Tenants share data schema AND tenants < ~10k AND regulated data not tenant-partitioned
    -> Shared DB, row-level tenant_id (default choice for this skill)

Strong regulatory boundary (HIPAA/PCI) per tenant
    -> Schema-per-tenant OR DB-per-tenant; app injects connection by tenant

Single very large tenant needs isolation from the rest
    -> Hybrid: shared DB for small tenants, dedicated DB for large tenant

Wrong choice failure modes:
- Shared DB without tenant_id discipline -> data leakage, impossible to audit
- DB-per-tenant at small scale -> migration and operational cost explodes
- Schema-per-tenant without tooling -> migrations drift across schemas
新機能のパネル配置
Feature controls platform config, billing, tenant lifecycle, or impersonation
    -> /adminpanel/ (super_admin only, audited)

Feature is tenant-admin workspace (manage own tenant, staff, catalogue)
    -> /public/ root (tenant owners/staff, scoped by tenant_id)

Feature is self-service for end user (customer/member/student/patient)
    -> /memberpanel/ (own records only, scoped by tenant_id AND user_id)

Wrong choice failure modes:
- Admin feature in /public/ -> tenant users see platform levers
- Tenant feature in /memberpanel/ -> end users escalate to admin-only data
- End-user feature in /public/ -> collides with tenant admin routes
権限解決の優先順位
1. user_permissions.denied match   -> DENY for tenant-scoped actions
2. actor is super_admin            -> ALLOW only for an explicitly scoped administrative permission (always audit)
3. user_permissions.granted match  -> ALLOW
4. tenant_role_overrides match     -> ALLOW/DENY per is_enabled
5. role -> permission via template -> ALLOW
6. no match                        -> DENY

Wrong ordering failure mode: if role is checked before user deny, an
explicit revocation never takes effect. Always deny-before-grant at each tier.
セッションプレフィックスの有無
One SaaS codebase hosts multiple apps on the same origin  -> prefix required
Only one app on origin AND no shared subdomain sessions   -> prefix optional
SSO / impersonation across apps planned                   -> prefix required

Without a prefix, two SaaS apps on the same host collide in $_SESSION and the
bug only appears in production when both apps are logged in.

3 パネルアーキテクチャ

+-------------------------------------------------------------+
|                Shared Infrastructure Layer                  |
|  Data (tenant isolated) | Business Logic | Session system   |
+-------------------------------------------------------------+
         |                       |                    |
+--------v--------+    +---------v------+   +---------v-------+
|   /public/      |    |  /adminpanel/  |   |  /memberpanel/  |
| (ROOT)          |    |                |   |                 |
| Tenant Admin    |    |  Super Admin   |   |  End User       |
|  Workspace      |    |  System        |   |  Portal         |
| owner, staff    |    |  super_admin   |   |  member/student |
+-----------------+    +----------------+   +-----------------+

重要: /public/ ルートはテナント管理者のワークスペースであり、エンドユーザーパネルではありません。この 2 つを混同することが最もよくある間違いです。

ファイルレイアウト:

public/
  index.php           # Landing
  sign-in.php         # Login
  dashboard.php       # Tenant admin dashboard
  adminpanel/         # Super admin panel
    includes/
  memberpanel/        # End user portal
    includes/
  includes/           # Shared includes
  assets/
パネル定義
パネルパスユーザースコープ備考
テナント管理者/public/owner、staff単一テナントすべてのクエリに tenant_id = ? が含まれます。プラットフォーム設定には触れられません
スーパー管理者/public/adminpanel/super_adminクロステナントtenant_id は NULL の場合があります。すべての操作が監査されます
エンドユーザー/public/memberpanel/member、student、customer、patientテナント内の自身のレコードクエリは tenant_id AND user_id でスコープされます

テナント分離モデル

ユーザータイプごとの tenant_id ルール
ユーザータイプtenant_id カラム
super_adminnullable
テナント owner必須、NOT NULL
テナント staff必須、NOT NULL
member / student / customer / patient必須、NOT NULL
テーブルパターン
CREATE TABLE students (
    id            BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    tenant_id     BIGINT UNSIGNED NOT NULL,
    first_name    VARCHAR(100) NOT NULL,
    last_name     VARCHAR(100) NOT NULL,
    email         VARCHAR(100),
    FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
    INDEX idx_tenant (tenant_id),
    INDEX idx_tenant_email (tenant_id, email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

複合インデックスは tenant_id を先頭にします。すべての箇所で utf8mb4_unicode_ci を使用します。ファイルの大文字小文字を正確に一致させてください(Linux のファイルシステムは大文字小文字を区別します)。

クエリの強制
// Extract tenant from session/JWT. Never from client input.
$tenantId = getSession('tenant_id');

// Regular users: filter by tenant_id
$stmt = $db->prepare('SELECT * FROM students WHERE tenant_id = ? AND id = ?');
$stmt->execute([$tenantId, $studentId]);

// Super admin: cross-tenant access is allowed but always audited
if (getSession('user_type') === 'super_admin') {
    auditLog('CROSS_TENANT_ACCESS', [
        'admin_user_id'      => getSession('user_id'),
        'target_tenant_id'   => $requestedTenantId,
        'action'             => 'VIEW_STUDENTS',
        'justification'      => $request['justification'] ?? null,
    ]);
}

セッションと JWT の計画

セッションプレフィックスシステム
define('SESSION_PREFIX', 'saas_app_');  // e.g. 'school_', 'restaurant_'

setSession('user_id', $userId);      // writes $_SESSION['saas_app_user_id']
$userId = getSession('user_id');     // reads $_SESSION['saas_app_user_id']
セッション Cookie 設定
設定値
HttpOnlytrue
SecureHTTPS を自動検出(ポート 443 または $_SERVER['HTTPS'])
SameSiteStrict
有効期間アイドル 30 分
再生成ログイン時および権限変更時
$isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
    || (int) $_SERVER['SERVER_PORT'] === 443;
ini_set('session.cookie_secure', $isHttps ? '1' : '0');
モバイルと API 用の JWT
設定値
アクセストークンの有効期間15 分
リフレッシュトークンの有効期間30 日
ローテーションリフレッシュのたびに実施
失効jti をキーとする永続化テーブル

権限モデル

優先順位(高い順):ユーザー拒否 > ユーザー許可 > テナント上書き > ロール権限 > デフォルト拒否。完全なスキーマ、解決アルゴリズム、キャッシュ戦略、シードデータについては references/permission-model.md を参照してください。

function hasPermission(userId, tenantId, permission) {
  if (userPermissions.denied(userId, tenantId, permission)) return false;
  if (user.type === 'super_admin' && isScopedAdministrativePermission(permission)) {
    auditLog('PERMISSION_BYPASS', { userId, permission });
    return true;
  }
  if (userPermissions.granted(userId, tenantId, permission)) return true;
  for (const role of getUserRoles(userId, tenantId)) {
    const override = tenantRoleOverride(tenantId, role.id, permission);
    if (override !== null) return override.isEnabled;
    if (roleHasPermission(role, permission)) return true;
  }
  return false; // default deny
}

ゼロトラストチェックリスト

  • テナントスコープのテーブルに対するすべてのクエリに tenant_id を含める(監査付きの super_admin を除く)。
  • プリペアドステートメントのみを使用する — SQL への文字列連結は行わない。
  • すべての変更操作の前に権限チェックを行う。
  • スーパー管理者アクセスには MFA を必須とする。
  • パスワードはユーザーごとのソルト(32 文字)とサーバー全体のペッパー(64 文字以上)を用いた Argon2ID として保存する。
  • 5 回の失敗試行後にアカウントをロックアウトし、指数バックオフを適用する。
  • レート制限:テナント 1000 リクエスト/分、ユーザー 100 リクエスト/分。
  • すべてのパネルで HTTPS + HSTS を適用する。
  • スーパー管理者の操作は正当性の理由とともに監査する。
  • クロステナントアクセスは 403 ではなく 404 を返す。

API 設計

# Tenant-scoped (default)
GET    /api/v1/orders          # list within tenant
POST   /api/v1/orders          # create within tenant
GET    /api/v1/orders/{id}     # show; 404 if wrong tenant
DELETE /api/v1/orders/{id}     # delete; 404 if wrong tenant

# Super admin (cross-tenant, audited)
GET    /api/v1/admin/tenants
POST   /api/v1/admin/impersonate

レスポンスエンベロープ:

{ "success": true, "data": {}, "meta": { "page": 1, "total": 100 } }

エラーエンベロープ:

{ "success": false, "error": { "code": "PERMISSION_DENIED", "message": "..." } }

監査とコンプライアンス

常に監査する対象:すべてのスーパー管理者操作、なりすまし、権限変更、テナントの作成/停止、データエクスポート、認証失敗、クロステナントアクセスの試行。

{
  "id": "uuid",
  "timestamp": "2026-04-07T10:30:00Z",
  "actor_user_id": 123,
  "actor_type": "super_admin",
  "action": "IMPERSONATE_USER",
  "target_tenant_id": 456,
  "justification": "Support request #12345",
  "ip_address": "203.0.113.1",
  "changes": { "before": {}, "after": {} }
}

保持期間:セキュリティログ 1 年、監査証跡 7 年、運用ログ 90 日。

監視アラート

  • クロステナントアクセスの試行(目標値 = ゼロ)。
  • 新しい IP からのスーパー管理者ログイン。
  • 認証失敗の急増 > 100/分。
  • テナントスコープのテーブルに対する tenant_id を含まないデータベースクエリ。
  • API エラー率 > 5%。

テナントのライフサイクル

PENDING -> ACTIVE -> SUSPENDED -> ARCHIVED

アーカイブは、設定された保持期限まで可逆です。その後、アーカイブされたテナントはカスケードによって完全に削除されます。

アンチパターン

  • クライアントが指定するテナント識別子。 $franchiseId = $_POST['franchise_id']; — どのユーザーでもこれを別のテナントに設定できてしまいます。修正: 常に getSession('tenant_id') を使うか、検証済みの JWT クレームから読み取ります。
  • クエリにテナントスコープがない。 SELECT * FROM students WHERE id = ? — どのテナントの生徒でも返してしまいます。修正: SELECT * FROM students WHERE tenant_id = ? AND id = ? とし、行が見つからない場合は 404 として扱います。
  • 監査なしのスーパー管理者による変更。 deleteStudent($studentId); がスーパー管理者パネルからログなしで実行されます。修正: すべてのスーパー管理者による変更を auditLog('ADMIN_DELETE_STUDENT', [...]) でラップし、実行者、対象テナント、正当性を記録します。
  • クロステナントアクセスに対して 403 を返す。 リソースが存在することを確認できてしまい、テナント ID の列挙を可能にします。修正: クロステナントのミスは 404 にマッピングし、リソースの存在を隠します。
  • ビジネステーブルの tenant_id が NULL 許容。 tenant_id BIGINT NULL — 所有者のいない行を許容し、すべてのテナントクエリがそれを黙って除外してしまいます。修正: 外部キー付きの tenant_id BIGINT UNSIGNED NOT NULL とし、NOT NULL に切り替える前にバックフィルします。
  • マルチアプリホストでプレフィックスのないセッションキー。 同一オリジン上の 2 つのアプリが互いの $_SESSION['user_id'] を上書きしてしまいます。修正: アプリごとに SESSION_PREFIX を設定し、常に setSession() / getSession() を経由します。
  • インデックス順序が (tenant_id, status) ではなく (status, tenant_id)。 クエリプランナーが複合インデックスを使って 1 テナントの範囲を切り出せません。修正: すべての複合インデックスの先頭を tenant_id にします。
  • 全テーブルを一度にビッグバンマイグレーション。 ロールバックのリスクを高め、数時間のダウンタイムを生じます。修正: documentation/migration.md にある 6 ステップのパターンで 1 ドメインずつマイグレーションし、カラムを NOT NULL に切り替える前に NULL の tenant_id がないことを検証します。

次に読む

  • database-design-engineering — 分離モデルを選んだ後のスキーマ、インデックス、マイグレーションの規律。
  • api-design-first — このスキルのパネルが依存する REST 規約、認証モデル、エラーエンベロープ。
  • vibe-security-skill — このスキルに供給される脅威モデリング、濫用ケース、認証/認可マトリクス。
  • kubernetes-saas-delivery — このモデルに基づくテナントごとのデプロイ、名前空間分離、段階的ロールアウト。
  • modular-saas-architecture — ここで定義されたテナント内で、プラットフォームがプラグイン可能なビジネスモジュールも組み合わせる場合。
  • mysql-best-practices — ここにあるスキーマパターンの MySQL エンジン固有の実行方法。

参考資料

  • references/database-schema.md — テナント、ユーザー、監査、テナントスコープのテーブルスキーマと、インデックスおよびパーティショニング。
  • references/permission-model.md — RBAC スキーマ、権限解決アルゴリズム、キャッシュ、ミドルウェア、階層的および条件付き権限。
  • documentation/migration.md — tenant_id を安全に追加する方法、ゼロダウンタイムマイグレーション、シングルテナントからマルチテナントへのフェーズ、ロールバック。

AI サービス分離の補足

AI が関与する場合、テナント分離パターンはさらに 1 段階深く適用されます。このスキルのトランザクションパターン(RLS / テナントごとのスキーマ / テナントごとの DB)は、AI の資産クラス — ベクトルストア、プロンプト、ファインチューニング、評価データセット、会話ログ、検索キャッシュ、監査ログのペイロード — に拡張されます。専用のスキルは ai-tenant-isolation-patterns です。

クロスリファレンス:

  • ai-on-saas-architecture — AI+SaaS アーキテクチャの統合。
  • ai-tenant-isolation-patterns — ベクトルストアのパーティショニング、多層防御、BYOK、データブリードテストスイート。
  • ai-rag-multi-tenant — RAG 固有の分離。
  • ai-model-gateway — リクエスト境界でテナントスコープを強制します。
  • ai-prompt-injection-and-tenant-safety — プロンプトレイヤーの敵対的要素を補完します。

コメント