Skip to content

Multi-tenancy

One Drupal instance can host several isolated vault realms, called tenants. Each tenant is a separate vault: its data is partitioned per (tenant, owner) and it can use its own Master KEK, so compromising one tenant's key cannot read another's. A person keeps a single Drupal account; if they appear in two tenants they hold a distinct, separately-encrypted vault in each.

Every install has one tenant out of the box - the default tenant, created on install - and that is all a single-tenant site ever needs. The tenant controls below stay hidden until you create a second tenant, so multi-tenancy is invisible unless you opt into it.

When to use it

Use multiple tenants when one site serves vaults that must not share an encryption boundary - for example several agencies or municipalities behind one Drupal, each wanting its own Master KEK and its own consumers, while citizens keep one login. If you only need one vault, leave the default tenant as is.

How isolation works

The key hierarchy gains the tenant as a partition:

  • Each owner has a distinct Subject KEK per tenant, so the same person's data in tenant A is encrypted independently of their data in tenant B.
  • Each tenant resolves a Master KEK: its own key, or - when left empty - the site-wide Master KEK from Configuration -> Personal Data Vault (/admin/config/pdv/settings). The default tenant ships with no key of its own, so it inherits the site-wide one and behaves exactly as a single-tenant install always did.
  • Items and subject keys carry their tenant, so a query, a grant, or a purge in one tenant never reaches another's rows.

Managing tenants

With the administer pdv tenants permission (restricted; grant only to trusted roles), manage realms at Configuration -> Personal Data Vault -> Vault tenants (/admin/config/pdv/tenants).

Each tenant has:

  • a machine name and label;
  • a Master KEK - select a key from the Key module to give this tenant its own cryptographic boundary, or leave it as Site default to inherit the site-wide Master KEK.

The Master KEK rules from Installation apply per tenant: it must resolve to 32 bytes, it cannot be deleted while it still protects a tenant's data, and pointing a tenant at a different key requires a re-wrap (see Rotating a tenant's Master KEK).

Binding a consumer to a tenant

A consumer (an OAuth client, or a same-site module) acts inside exactly one tenant. When more than one tenant exists, the consumer add form shows a Tenant selector; the choice is immutable once the consumer is created (the field is locked on edit, and a change is refused). A consumer can only read and write vaults in its bound tenant - this is what scopes the cross-site API to a realm. With a single tenant the selector is hidden and every consumer belongs to the default tenant.

Shared and tenant-scoped item kinds

The item-kind vocabulary is hybrid: a kind is either shared (offered in every tenant, the default) or scoped to one tenant. On the kind form, a Tenant selector appears once more than one tenant exists - leave it Shared (all tenants) or pick a single tenant. The Item kinds list shows a Tenant column so you can see each kind's scope at a glance. Shared kinds and the current tenant's scoped kinds are the ones offered when storing an item.

Rotating a tenant's Master KEK

Master KEK rotation is per tenant: changing one tenant's key re-wraps only that tenant's subject keys, and never scans the others. After pointing a tenant at a new key, drain its re-wrap backlog:

  • From the UI, at Configuration -> Personal Data Vault -> Vault subjects (/admin/config/pdv/subjects), use the per-tenant Rotate to current Master KEK action; it re-wraps that tenant's subject keys in bounded batches and reports progress.
  • From the command line, drush pdv:rotate-keys drains every active tenant, and drush pdv:rotate-keys <tenant> drains a single tenant.

Cron advances each active tenant's backlog automatically. Once no subject is left on a tenant's old key, that key can be retired.

Resolving the current tenant (developers)

Which tenant a request acts in is decided by tenant resolvers: services tagged pdv.tenant_resolver implementing TenantResolverInterface. Each resolver returns a tenant id or NULL to defer; the first non-null answer wins, and if none answer the default tenant is used.

pdv ships one resolver, ConsumerTenantResolver, which resolves the tenant from the calling consumer's immutable binding - so a cross-site API call lands in the consumer's tenant automatically. To resolve tenants another way (for example by domain or path), implement TenantResolverInterface and tag the service:

services:
  my_module.tenant_resolver:
    class: Drupal\my_module\MyTenantResolver
    tags:
      - { name: pdv.tenant_resolver }

A domain-based connector is not shipped yet; see the Roadmap.