Skip to content

Commit eb3ff36

Browse files
author
MPCoreDeveloper
committed
Add multitenant SaaS sample docs and threat model
1 parent 1a99fe6 commit eb3ff36

File tree

12 files changed

+442
-19
lines changed

12 files changed

+442
-19
lines changed

.github/issue-drafts/multitenancy-v1.6.0/00-epic-multitenant-saas-hardening-v1.6.0.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ Implemented in workspace:
66
- [x] `09-tenant-encryption-key-management.md`
77
- [x] `10-per-tenant-quotas-and-limits.md`
88
- [x] `11-tenant-audit-and-security-events.md`
9+
- [x] `12-saas-sample-docs-and-threat-model.md`
910

1011
Next planned work:
11-
- [ ] `12-saas-sample-docs-and-threat-model.md`
12+
- [ ] `13-tenant-ops-backup-restore-migrate.md`
1213

1314
Current baseline in codebase:
1415
- Multi-database hosting in one server instance is available (`DatabaseRegistry`, `ServerConfiguration.Databases`).
Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,18 @@
11
## Summary
22
Publish a complete SaaS reference sample and documentation set for multi-tenant deployments.
33

4-
## Why
5-
Feature completeness must be paired with clear adoption guidance.
6-
7-
## Scope
8-
- End-to-end sample: tenant onboarding, routing, secure connection, isolation checks.
9-
- Docs: recommended patterns (DB-per-tenant default, shared DB optional), migration path, ops runbook.
10-
- Threat model: cross-tenant access risks and mitigations.
4+
## Status
5+
Completed in workspace.
116

12-
## Implementation Plan
13-
1. Build reference sample with provisioning and scoped access.
14-
2. Add validation scripts for isolation tests.
15-
3. Publish docs under `docs/server` and link from package readmes.
16-
4. Add architecture and threat model diagrams.
7+
## Completed Implementation Notes
8+
- Added a multi-tenant SaaS reference sample under `Examples/Server/SharpCoreDB.MultiTenantSaaSSample`.
9+
- Added sample configuration, REST onboarding flow, and PowerShell isolation validation script.
10+
- Published docs under `docs/server` for reference architecture, operations runbook, and threat model.
11+
- Linked new multitenancy docs/sample from package and repository readmes.
1712

18-
## Acceptance Criteria
19-
- New users can implement multi-tenant setup using docs/sample.
20-
- Threat model and mitigations are explicit.
21-
- Docs are versioned and aligned with released behavior.
13+
## Validation
14+
- Documentation and sample assets added for onboarding and isolation validation.
15+
- Workspace build passed.
2216

23-
## Dependencies
24-
- Finalization issue; depends on most technical tracks.
17+
## Why
18+
Feature completeness must be paired with clear adoption guidance.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# SharpCoreDB Multi-Tenant SaaS Reference Sample v1.6.0
2+
3+
This reference sample demonstrates a database-per-tenant SaaS deployment with runtime tenant provisioning, scoped JWT access, tenant quotas, tenant encryption keys, and security audit inspection.
4+
5+
## Goals
6+
- onboard a tenant with the tenant management API
7+
- connect with tenant-scoped users
8+
- validate same-tenant access succeeds
9+
- validate cross-tenant access is denied
10+
- inspect tenant audit/security traces
11+
12+
## Sample Layout
13+
- `appsettings.multitenant.sample.json` - reference server configuration
14+
- `validate-tenant-isolation.ps1` - PowerShell validation script
15+
- `tenant-onboarding.http` - manual REST flow for provisioning and validation
16+
17+
## Recommended Topology
18+
```mermaid
19+
flowchart LR
20+
Admin[Platform Admin] -->|HTTPS REST| Server[SharpCoreDB.Server]
21+
TenantA[Tenant A User] -->|JWT + HTTPS/gRPC| Server
22+
TenantB[Tenant B User] -->|JWT + HTTPS/gRPC| Server
23+
Server --> Master[(master)]
24+
Server --> TenantADB[(tenant_tenant_a)]
25+
Server --> TenantBDB[(tenant_tenant_b)]
26+
Master --> Catalog[(tenant catalog + quotas + audits)]
27+
```
28+
29+
## Quick Start
30+
1. Copy `appsettings.multitenant.sample.json` into your server deployment as a starting point.
31+
2. Start `SharpCoreDB.Server` with TLS enabled.
32+
3. Run `pwsh ./validate-tenant-isolation.ps1 -BaseUrl https://localhost:8443`.
33+
4. Review the output and the audit endpoints.
34+
35+
## Expected Validation Outcomes
36+
- admin login succeeds
37+
- tenant provisioning succeeds
38+
- tenant A can query `tenant_tenant_a`
39+
- tenant A is denied against `tenant_tenant_b`
40+
- security audit endpoints show login/connect/denied events
41+
42+
## Notes
43+
- The sample uses REST for readability. The same tenant model applies to gRPC and WebSocket/Binary paths.
44+
- Replace example secrets and certificate paths before any non-local use.
45+
- Keep database-per-tenant as the default recommendation. Shared-database mode remains optional.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
{
2+
"Server": {
3+
"ServerName": "SharpCoreDB Multi-Tenant SaaS Sample v1.6.0",
4+
"BindAddress": "0.0.0.0",
5+
"GrpcPort": 5001,
6+
"HttpsApiPort": 8443,
7+
"DefaultDatabase": "master",
8+
"Databases": [
9+
{
10+
"Name": "master",
11+
"DatabasePath": "./data/master.db",
12+
"StorageMode": "SingleFile",
13+
"IsSystemDatabase": true,
14+
"ConnectionPoolSize": 20
15+
}
16+
],
17+
"Security": {
18+
"TlsEnabled": true,
19+
"TlsCertificatePath": "./certs/server.pfx",
20+
"TlsPrivateKeyPath": null,
21+
"JwtSecretKey": "replace-with-a-real-32-char-secret-minimum",
22+
"JwtExpirationHours": 24,
23+
"Users": [
24+
{
25+
"Username": "admin",
26+
"PasswordHash": "240be518fabd2724ddb6f04eeb1da5967448d7e831c08c8fa822809f74c720a9",
27+
"Role": "admin",
28+
"TenantId": "platform",
29+
"AllowedDatabases": [ "master", "*" ],
30+
"ScopeVersion": "1"
31+
},
32+
{
33+
"Username": "tenant-a-reader",
34+
"PasswordHash": "240be518fabd2724ddb6f04eeb1da5967448d7e831c08c8fa822809f74c720a9",
35+
"Role": "reader",
36+
"TenantId": "tenant-a",
37+
"AllowedDatabases": [ "tenant_tenant_a" ],
38+
"ScopeVersion": "1"
39+
},
40+
{
41+
"Username": "tenant-b-reader",
42+
"PasswordHash": "240be518fabd2724ddb6f04eeb1da5967448d7e831c08c8fa822809f74c720a9",
43+
"Role": "reader",
44+
"TenantId": "tenant-b",
45+
"AllowedDatabases": [ "tenant_tenant_b" ],
46+
"ScopeVersion": "1"
47+
}
48+
],
49+
"TenantEncryption": {
50+
"Enabled": true,
51+
"RequireDedicatedTenantKey": true,
52+
"DefaultProvider": "configuration",
53+
"KeyMaterialByReference": {
54+
"tenant-a-key": "tenant-a-master-password",
55+
"tenant-b-key": "tenant-b-master-password"
56+
},
57+
"DefaultKeyReferenceByTenant": {
58+
"tenant-a": "tenant-a-key",
59+
"tenant-b": "tenant-b-key"
60+
}
61+
}
62+
},
63+
"TenantQuotas": {
64+
"Enabled": true,
65+
"DefaultMaxActiveSessions": 10,
66+
"DefaultMaxRequestsPerSecond": 100,
67+
"DefaultMaxStorageMb": 512,
68+
"DefaultMaxBatchSize": 200,
69+
"PlanDefaults": {
70+
"starter": {
71+
"MaxActiveSessions": 5,
72+
"MaxRequestsPerSecond": 50,
73+
"MaxStorageMb": 256,
74+
"MaxBatchSize": 100
75+
},
76+
"pro": {
77+
"MaxActiveSessions": 25,
78+
"MaxRequestsPerSecond": 250,
79+
"MaxStorageMb": 2048,
80+
"MaxBatchSize": 500
81+
}
82+
}
83+
}
84+
}
85+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
@baseUrl = https://localhost:8443
2+
@adminUser = admin
3+
@adminPassword = admin123
4+
5+
### Login as platform admin
6+
POST {{baseUrl}}/api/v1/auth/login
7+
Content-Type: application/json
8+
9+
{
10+
"username": "{{adminUser}}",
11+
"password": "{{adminPassword}}"
12+
}
13+
14+
> {% client.global.set("adminToken", response.body.token); %}
15+
16+
### Create tenant A
17+
POST {{baseUrl}}/api/v1/tenants
18+
Authorization: Bearer {{adminToken}}
19+
Content-Type: application/json
20+
21+
{
22+
"tenantKey": "tenant-a",
23+
"displayName": "Tenant A",
24+
"databasePath": "./data/tenant-a.db",
25+
"planTier": "starter",
26+
"encryptionKeyReference": "tenant-a-key"
27+
}
28+
29+
### Create tenant B
30+
POST {{baseUrl}}/api/v1/tenants
31+
Authorization: Bearer {{adminToken}}
32+
Content-Type: application/json
33+
34+
{
35+
"tenantKey": "tenant-b",
36+
"displayName": "Tenant B",
37+
"databasePath": "./data/tenant-b.db",
38+
"planTier": "starter",
39+
"encryptionKeyReference": "tenant-b-key"
40+
}
41+
42+
### Review tenant security audit
43+
GET {{baseUrl}}/api/v1/tenant-security/audit?maxCount=50
44+
Authorization: Bearer {{adminToken}}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
param(
2+
[string]$BaseUrl = "https://localhost:8443",
3+
[string]$AdminUser = "admin",
4+
[string]$AdminPassword = "admin123",
5+
[string]$TenantAUser = "tenant-a-reader",
6+
[string]$TenantAPassword = "admin123",
7+
[string]$TenantBUser = "tenant-b-reader",
8+
[string]$TenantBPassword = "admin123"
9+
)
10+
11+
$ErrorActionPreference = 'Stop'
12+
13+
function Invoke-JsonPost {
14+
param(
15+
[string]$Url,
16+
[object]$Body,
17+
[string]$Token
18+
)
19+
20+
$headers = @{}
21+
if ($Token) {
22+
$headers['Authorization'] = "Bearer $Token"
23+
}
24+
25+
Invoke-RestMethod -Method Post -Uri $Url -Headers $headers -ContentType 'application/json' -Body ($Body | ConvertTo-Json -Depth 10) -SkipCertificateCheck
26+
}
27+
28+
function Get-Token {
29+
param([string]$Username, [string]$Password)
30+
31+
(Invoke-JsonPost -Url "$BaseUrl/api/v1/auth/login" -Body @{
32+
username = $Username
33+
password = $Password
34+
}).token
35+
}
36+
37+
Write-Host "[1/5] Admin login"
38+
$adminToken = Get-Token -Username $AdminUser -Password $AdminPassword
39+
40+
Write-Host "[2/5] Provision tenant A and B"
41+
Invoke-JsonPost -Url "$BaseUrl/api/v1/tenants" -Token $adminToken -Body @{
42+
tenantKey = 'tenant-a'
43+
displayName = 'Tenant A'
44+
databasePath = './data/tenant-a.db'
45+
planTier = 'starter'
46+
encryptionKeyReference = 'tenant-a-key'
47+
} | Out-Null
48+
49+
Invoke-JsonPost -Url "$BaseUrl/api/v1/tenants" -Token $adminToken -Body @{
50+
tenantKey = 'tenant-b'
51+
displayName = 'Tenant B'
52+
databasePath = './data/tenant-b.db'
53+
planTier = 'starter'
54+
encryptionKeyReference = 'tenant-b-key'
55+
} | Out-Null
56+
57+
Write-Host "[3/5] Tenant A login"
58+
$tenantAToken = Get-Token -Username $TenantAUser -Password $TenantAPassword
59+
60+
Write-Host "[4/5] Same-tenant access should succeed"
61+
Invoke-JsonPost -Url "$BaseUrl/api/v1/query" -Token $tenantAToken -Body @{
62+
database = 'tenant_tenant_a'
63+
sql = 'SELECT 1 AS ok'
64+
} | Out-Null
65+
66+
Write-Host "[5/5] Cross-tenant access should be denied"
67+
try {
68+
Invoke-JsonPost -Url "$BaseUrl/api/v1/query" -Token $tenantAToken -Body @{
69+
database = 'tenant_tenant_b'
70+
sql = 'SELECT 1 AS should_fail'
71+
} | Out-Null
72+
73+
throw 'Isolation validation failed: cross-tenant query unexpectedly succeeded.'
74+
}
75+
catch {
76+
Write-Host 'Cross-tenant denial confirmed.'
77+
}
78+
79+
Write-Host 'Tenant isolation validation completed successfully.'

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ SharpCoreDB now includes a dedicated **advanced graph analytics and GraphRAG pac
5454

5555
**See documentation:** `docs/INDEX.md`
5656

57+
**Multi-tenant SaaS reference:** `docs/server/MULTITENANT_SAAS_REFERENCE_v1.6.0.md`
58+
**Threat model:** `docs/server/MULTITENANT_THREAT_MODEL_v1.6.0.md`
59+
**Operations runbook:** `docs/server/MULTITENANT_OPERATIONS_RUNBOOK_v1.6.0.md`
60+
**Reference sample:** `Examples/Server/SharpCoreDB.MultiTenantSaaSSample/`
61+
5762
### ✅ Previously Known Limitation — Resolved
5863

5964
- `SingleFileDatabase.ExecuteCompiled` with parameterized plans previously hung due to an infinite loop in the SQL lexer (`?` parameter placeholder). Fixed: FastSqlLexer, EnhancedSqlParser, QueryCompiler. Full `IAsyncDisposable` lifecycle also implemented.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# SharpCoreDB Server Multi-Tenant Operations Runbook v1.6.0
2+
3+
## Scope
4+
Operational guidance for tenant lifecycle, encryption, quotas, auditing, and incident response.
5+
6+
## Daily Operations
7+
- review `GET /api/v1/tenant-security/audit`
8+
- review `GET /api/v1/tenant-access/audit`
9+
- monitor quota throttle metrics
10+
- monitor tenant provisioning failures
11+
12+
## Tenant Onboarding
13+
1. verify quota tier and encryption key reference
14+
2. provision tenant database through tenant API
15+
3. validate tenant-scoped login
16+
4. run isolation validation script
17+
5. archive onboarding evidence in operations logs
18+
19+
## Key Rotation
20+
1. resolve new tenant key reference
21+
2. call tenant key rotation endpoint
22+
3. verify rotation completion event
23+
4. validate post-rotation connectivity
24+
25+
## Quota Tuning
26+
1. inspect effective quota policy
27+
2. update tenant-specific overrides when justified
28+
3. re-check throttle metrics after change
29+
30+
## Audit Retention
31+
- in-memory stores are bounded and intended for operational diagnostics
32+
- attach custom sinks for SIEM/archive retention
33+
- keep exported security events immutable
34+
35+
## Backup and Restore Notes
36+
- back up `master` together with all tenant databases
37+
- preserve tenant catalog consistency during restore
38+
- verify encryption key references are available before reattaching tenant databases
39+
40+
## Incident Response
41+
### Cross-tenant access suspicion
42+
1. pull recent security audit events
43+
2. pull recent access audit events
44+
3. identify tenant/database/principal involved
45+
4. rotate credentials or revoke grants
46+
5. validate isolation again before reopening access
47+
48+
### Provisioning failure
49+
1. inspect lifecycle events
50+
2. inspect server logs for key/quota/auth failures
51+
3. verify tenant database mapping consistency in `master`
52+
4. retry only with a new idempotency key after root cause is understood

0 commit comments

Comments
 (0)