What this is about
Tenant isolation is the most important security promise of CDMS: a customer never sees the data of another. For that to hold, there are a few rules that apply everywhere and have no exception. Such rules are called invariants.
This page collects them in one place. Every rule has a reason. Whoever writes custom code, a hook, a job or an extension, must not bypass any of them.
The checklist
| No. | Rule | Why |
|---|---|---|
| 1 | No fallback to the system database. If the tenant is missing, the RequestContext is missing, or the tenant database cannot be opened, the access is aborted. | Otherwise tenant data ends up in the system database and is reachable there for all tenants. |
| 2 | System models always go to the system database, and this is decided before any check of the context. | A missing or manipulated context can neither redirect nor block system data. |
| 3 | Separate type sets. The system database knows only system models, a tenant database only tenant and user models. | A misrouted object fails in Hibernate instead of being stored in the wrong database. That is a second safeguard, independent of routing. |
| 4 | Only three places set a tenant: reading the token, the allowed tenant switch, and the frame for work without a request. | Every further place would be a way to change the tenant past the checks. |
| 5 | The tenant comes from a signed source. Headers are only wishes; the list of allowed tenants comes from the token. | Any client can set a header. |
| 6 | A switch is checked twice: in CIAS and once more in the persistence. | If one check falls victim to a bug, the other holds. |
| 7 | The gate asks about the tenant actually used, so once more after a switch. | A switch must not bring back a suspended tenant, not even for administrators. |
| 8 | No unconfirmed answer during an outage. If CIAS cannot be reached, only an already remembered answer counts; without one the request is refused. | An outage must not let in anyone who was not in before. |
| 9 | A database is created only with approval, and “unreachable” never counts as “missing”. | A short outage or a typo in the key must not create a database. |
| 10 | Nothing in CDMS deletes a database. | A lost database cannot be recovered. An empty, orphaned one is the lesser evil. |
| 11 | The tenant key is fixed: only a-z, 0-9 and -, at most 64 characters, not system or single, never renamed. It is checked, not sanitized. | It is the database name and the directory name. Sanitizing could map two tenants to the same name. |
| 12 | The operating mode has no default, and contradictory settings stop the start. | A guessed value would silently switch a separation off or invent one. |
| 13 | The RequestContext is cleared after every request. | Otherwise the next job on the same thread carries tenant, person and roles of the previous request. |
Forbidden fallbacks
These shortcuts do not exist and must not exist in custom code either:
| Situation | What happens |
|---|---|
| Tenant missing | 400 CDMS_TENANT_REQUIRED, or 403 tenant-required before that; never the system database |
| RequestContext missing | 500 CDMS_PERSISTENCE_CONTEXT_MISSING; never the system database |
| Tenant unknown or suspended | 403 tenant-not-served; never create automatically |
| Switch not allowed | 403 CDMS_TENANT_SWITCH_NOT_AUTHORIZED; not silently continuing in the own tenant |
| Tenant database missing or unreachable | 500 or 503; never the system database |
| Token names several tenants without selection | 403 tenant-unresolved; never the first one or a default tenant |
One exception is intended and is not a shortcut: if only the role is missing for a switch whose target is allowed, the request silently continues in the own tenant. The token did not demand a switch then; the client only wished for one. See Tenant switch by header.
The three places that set a tenant
- determines the tenant from the organization or the attribute
tenant - asks the tenant gate
- fills the list of allowed tenants
tenant- only with the realm role
allowed-tenant-context-switch - only to a target from the list of allowed tenants
- afterwards the gate asks again
- code names the tenant and the technical account it acts as
- asks the tenant gate first
- runs without roles and only in this one tenant
What the third way looks like is described in Working for a tenant without a request.
What this means for custom code
Where to go next
- The decision step by step: Which database? The persistence target
- The check in CIAS: Is the tenant served?
- The same rules for files: Storage layout and tenant isolation