Tenant Isolation
Tenant Isolation
Section titled “Tenant Isolation”Configure multi-tenant access control and data isolation in Mantis.
Overview
Section titled “Overview”Mantis supports multi-tenancy where resources can be scoped to specific tenants, providing logical separation between different teams, customers, or environments.
Tenant Model
Section titled “Tenant Model”| Field | Type | Description |
|---|---|---|
id | UUID | Unique identifier |
name | String | Tenant name (unique) |
description | String | Optional description |
logo_url | String | Optional logo URL |
contact_email | String | Optional contact email |
created_at | DateTime | Creation timestamp |
updated_at | DateTime | Last update timestamp |
How Tenant Isolation Works
Section titled “How Tenant Isolation Works”User Tenant Assignment
Section titled “User Tenant Assignment”Each user can optionally have a tenant_id:
JWT Claims
Section titled “JWT Claims”Tenant scope is embedded in JWT claims:
{ "sub": "019b937d-4862-8e07-ac3a-1b8589d1b807", "email": "alice@example.com", "username": "alice", "roles": ["operator"], "tenant_id": "019b937d-4862-84ae-9c2a-6ac230cc4c7e", "exp": 1704067200}Query Filtering
Section titled “Query Filtering”All database queries automatically filter by tenant:
-- For tenant-scoped user (tenant_id = '019b937d-4862-84ae-9c2a-6ac230cc4c7e')SELECT * FROM targetsWHERE tenant_id = '019b937d-4862-84ae-9c2a-6ac230cc4c7e';
-- For global user (tenant_id = null)SELECT * FROM targets;-- No tenant filter appliedResource Scoping
Section titled “Resource Scoping”Tenant-Scoped Resources
Section titled “Tenant-Scoped Resources”| Resource | Scoped | Notes |
|---|---|---|
| Targets | ✓ | Assigned via tenants_targets |
| Deployments | ✓ | Inherit from target |
| Tags | ✓ | Assigned via tenant_tags |
| Variables | ✓ | Tenant-specific variables |
Optionally Tenant-Scoped & Global Resources
Section titled “Optionally Tenant-Scoped & Global Resources”| Resource | Scoped | Notes |
|---|---|---|
| Actions | Partial | Nullable tenant_id — global when NULL, tenant-private when set |
| Sequences | Partial | Nullable tenant_id — global when NULL, tenant-private when set |
| Solutions | Partial | Nullable tenant_id — global when NULL, tenant-private when set |
| Environments | Partial | Nullable tenant_id — global when NULL, tenant-private when set |
| Users | Partial | Have tenant_id but can be global |
| Roles | ✗ | Shared role definitions |
| Permissions | ✗ | System-wide definitions |
Configuration
Section titled “Configuration”Creating a Tenant
Section titled “Creating a Tenant”curl -X POST \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "acme-corp", "description": "Primary production tenant", "contact_email": "ops@acme-corp.example" }' \ "https://api.mantis.local/api/v1/tenants"Assigning Users to Tenant
Section titled “Assigning Users to Tenant”curl -X PUT \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "tenant_id": "019b937d-4862-84ae-9c2a-6ac230cc4c7e" }' \ "https://api.mantis.local/api/v1/admin/users/$USER_ID"Assigning Resources to Tenant
Section titled “Assigning Resources to Tenant”Assign Targets
Section titled “Assign Targets”curl -X POST \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "target_id": "019b937d-4862-8aaa-bccd-1122334455ff", "environment_id": null }' \ "https://api.mantis.local/api/v1/tenants/$TENANT_ID/targets"Assign one target per request (environment_id is optional).
Assign Solutions
Section titled “Assign Solutions”curl -X POST \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "solution_id": "019b937d-4862-8ccc-ddee-3344556677bb", "environment_id": "019b937d-4862-8eee-ff00-5566778899cc" }' \ "https://api.mantis.local/api/v1/tenants/$TENANT_ID/solutions"Assign one solution per request; both solution_id and environment_id are required.
Access Control Patterns
Section titled “Access Control Patterns”Tenant Admin Pattern
Section titled “Tenant Admin Pattern”Delegate full tenant management:
Configuration:
- Create
tenant_adminrole user for each tenant - Assign user to tenant:
tenant_id = X - Assign
tenant_adminrole - User can manage their tenant’s resources
Shared Resource Pattern
Section titled “Shared Resource Pattern”Allow tenants to use shared actions/sequences:
Actions, sequences, and solutions all support optional tenant scoping — they are global when tenant_id is NULL and tenant-private when set:
- Global (unscoped) definitions can be referenced by any tenant
- Tenant-scoped definitions are visible only to their owning tenant
- Deployments are scoped to tenant targets
Cross-Tenant Visibility
Section titled “Cross-Tenant Visibility”Global users can view across tenants:
| User Type | Can View | Can Modify |
|---|---|---|
| Global Admin | All tenants | All tenants |
| Global Viewer | All tenants | None |
| Tenant Admin | Own tenant | Own tenant |
| Tenant User | Own tenant | Based on role |
Security Considerations
Section titled “Security Considerations”Preventing Information Disclosure
Section titled “Preventing Information Disclosure”Cross-tenant access returns 404 (not 403):
# Tenant A user trying to access Tenant B resourcecurl -X GET \ -H "Authorization: Bearer $TENANT_A_TOKEN" \ "https://api.mantis.local/api/v1/targets/$TENANT_B_TARGET"
# Response: 404 Not Found (not 403 Forbidden)This prevents enumeration attacks.
Tenant Boundary Enforcement
Section titled “Tenant Boundary Enforcement”Cross-Tenant Denials
Section titled “Cross-Tenant Denials”A cross-tenant access attempt does not match the caller’s tenant scope, so the resource is simply not returned — the API responds with 404 Not Found rather than revealing that the resource exists under a different tenant.
Tenant Variables
Section titled “Tenant Variables”Each tenant can have specific variables:
# Set tenant variablecurl -X POST \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "template_id_or_key": "DATABASE_URL", "value": "postgres://tenant-a-db:5432/app", "is_encrypted": true, "variable_type": "common" }' \ "https://api.mantis.local/api/v1/tenants/$TENANT_ID/variables"Variables are injected during deployments:
Database Schema
Section titled “Database Schema”Tenant Table
Section titled “Tenant Table”CREATE TABLE tenants ( id UUID PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT, logo_url TEXT, contact_email TEXT, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP);Tenant Assignment Tables
Section titled “Tenant Assignment Tables”-- Tenant solutions (assignment is scoped to an environment)CREATE TABLE tenants_solutions_environments ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL REFERENCES tenants(id), solution_id UUID NOT NULL REFERENCES solutions(id), environment_id UUID NOT NULL REFERENCES environments(id), is_active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE (tenant_id, solution_id, environment_id));
-- Tenant targetsCREATE TABLE tenants_targets ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL REFERENCES tenants(id), target_id UUID NOT NULL REFERENCES targets(id), environment_id UUID REFERENCES environments(id), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE (tenant_id, target_id, environment_id));
-- Tenant tagsCREATE TABLE tenant_tags ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL REFERENCES tenants(id), tag_id UUID NOT NULL REFERENCES tags(id), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE (tenant_id, tag_id));
-- Tenant variables (template-based, environment-scoped)CREATE TABLE tenant_variables ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL REFERENCES tenants(id), template_id UUID NOT NULL REFERENCES tenant_variable_templates(id), environment_id UUID REFERENCES environments(id), value TEXT NOT NULL, value_encrypted TEXT, is_encrypted BOOLEAN NOT NULL DEFAULT false, encryption_version INTEGER, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP, UNIQUE (tenant_id, template_id, environment_id));User Tenant Assignment
Section titled “User Tenant Assignment”-- Users have optional tenant_idALTER TABLE users ADD COLUMN tenant_id UUID REFERENCES tenants(id);
-- Index for query performanceCREATE INDEX idx_users_tenant_id ON users(tenant_id);Best Practices
Section titled “Best Practices”Tenant Design
Section titled “Tenant Design”- One tenant per customer/team - Clear organizational boundary
- Use meaningful names -
acme-corp,team-platform,prod-west - Document tenant purpose - Clear descriptions for each tenant
Access Control
Section titled “Access Control”- Minimize global users - Most users should be tenant-scoped
- Use tenant_admin role - Delegate management within tenants
- Regular audits - Review tenant user assignments
Resource Assignment
Section titled “Resource Assignment”- Assign resources explicitly - Don’t rely on defaults
- Document ownership - Track which tenant owns what
- Clean up unused - Remove resources from inactive tenants
Troubleshooting
Section titled “Troubleshooting”User Cannot See Resources
Section titled “User Cannot See Resources”-
Check user’s tenant assignment:
Terminal window curl -s -H "Authorization: Bearer $TOKEN" \https://api.mantis.local/api/v1/admin/users/$USER_ID | jq '.tenant_id' -
Check resource’s tenant assignment:
Terminal window curl -s -H "Authorization: Bearer $TOKEN" \https://api.mantis.local/api/v1/tenants/$TENANT_ID/targets -
Verify tenant matches
Global User Has Limited View
Section titled “Global User Has Limited View”- Verify tenant_id is null in user record
- Check role permissions - Global users still need appropriate roles
Resources Missing After Tenant Assignment
Section titled “Resources Missing After Tenant Assignment”- Check assignment tables -
tenants_targets,tenants_solutions_environments - Verify the environment - solution/target assignments are environment-scoped, so check the
environment_id
Migration Guide
Section titled “Migration Guide”Single-Tenant to Multi-Tenant
Section titled “Single-Tenant to Multi-Tenant”-
Create default tenant:
INSERT INTO tenants (name, description)VALUES ('default', 'Default Tenant'); -
Assign existing resources:
-- IDs are UUIDs; substitute the real tenant UUID belowINSERT INTO tenants_targets (id, tenant_id, target_id)SELECT gen_random_uuid(), '019b937d-4862-84ae-9c2a-6ac230cc4c7e', id FROM targets; -
Update existing users:
UPDATE users SET tenant_id = '019b937d-4862-84ae-9c2a-6ac230cc4c7e'WHERE tenant_id IS NULL; -
Keep admin users global:
UPDATE users SET tenant_id = NULLWHERE id IN (SELECT ur.user_id FROM users_roles urJOIN roles r ON r.id = ur.role_idWHERE r.name = 'admin');
Next Steps
Section titled “Next Steps”- RBAC Overview - Access control overview
- Users - User management
- Multi-tenancy - Tenant administration
