Z-CMS における Multi-tenancy の実装
1 つの CMS Core で複数 website を安全に分離する architecture
Multi-tenancy はすべての layer に影響する
Multi-tenancy は tenant_id column を追加するだけではありません。Authentication、authorization、database、cache、storage、queue、search、plugin、theme、logging、billing のすべてに影響します。1 layer でも tenant context を失うと cross-tenant data leak が起こり得ます。
Tenant resolution
Z-CMS は custom domain、subdomain、path、X-Tenant-Code のような trusted header から tenant を resolve できます。Tenant を特定できない request は business service に入る前に拒否します。User が request body に指定した tenantId は信頼しません。
Request-scoped tenant context
Resolution 後、tenant context を request scope に設定し、service call 全体に伝搬させます。Application code は domain を再解析したり任意の tenantId を受け取るのではなく、tenantContext.getTenantId() を利用します。
Request
→ Authentication
→ Tenant Resolution
→ Authorization
→ Database / Cache / Storage
→ Queue / Search
→ Plugin / Theme
→ LoggingDatabase isolation strategy
小規模 tenant には shared database/shared schema、より高い isolation には separate schema、enterprise には dedicated database を選択できます。Application は共通 repository contract を利用し、hybrid model に対応します。
Tenant-aware repository と Row-Level Security
Repository は現在の tenant に bind され、developer が各 query に WHERE tenant_id を書き忘れる risk を減らします。PostgreSQL Row-Level Security を defense-in-depth として使えば、application filter が欠けても database policy が row access を制限できます。
tenant-a:post:123
tenants/tenant-a/media/image.jpg
tenant-a:plugin-seo:settingsCache、storage、queue、search isolation
Cache key には必ず tenant prefix を含めます。Storage API は tenants/{tenantId}/... path を自動追加し、plugin に arbitrary path を許可しません。Background job は tenant context を保持し、search query は tenant filter または dedicated index を使用します。
Authentication、membership、permission
Login 済み user がすべての tenant に access できるわけではありません。まず tenant membership、次に role と content.publish などの permission を確認します。同じ user が tenant A では Owner、tenant B では Editor、tenant C では Viewer になることがあります。
Tenant ごとの plugin と theme
各 tenant は異なる plugin、version、settings、active theme を利用できます。Plugin settings は tenant + plugin ID で scope されます。Runtime は Core から immutable tenant context を受け取り、別 tenant を選択できません。
Logging、rate limit、maintenance
Structured log には tenantId、requestId、userId、pluginId を含めます。Rate limit は IP、user、tenant、API key、plugin ごとに適用できます。Reindex や migration は tenant 単位の job に分割し、1 tenant が platform 全体を block しないようにします。
Multi-tenancy の test
Test では常に 2 つ以上の tenant を作り、別 tenant の resource ID を意図的に使用します。Repository、cache、storage、queue、search、plugin bridge が cross-tenant access を拒否することを確認します。
結論
Z-CMS は trusted boundary で tenant context を決定し、immutable な context を Request → Authorization → Database → Cache → Storage → Queue → Search → Plugin → Theme → Logging 全体で enforce します。
Key takeaways
- Security boundary は plugin の善意ではなく Core 側で enforce する必要があります。
- Lifecycle、permission、tenant context、observability は同じ platform contract の一部です。
- 優れた developer experience は、安全な runtime と supply chain の設計によって持続可能になります。
GitHub: https://github.com/zscontributor/z-cms
Website: https://z-cms.org