Master KEK in OpenBao or HashiCorp Vault¶
pdv_vault lets the Master KEK live inside OpenBao
or HashiCorp Vault instead of in a local Key.
The root key never reaches Drupal: pdv sends the per-user Subject KEK to the
store's Transit engine to be
wrapped, and stores only the returned ciphertext.
How it works¶
pdv uses three-tier envelope encryption (see Concepts); the
Master KEK only ever wraps the per-user Subject KEK. With pdv_vault that wrap
step happens server-side in the secret store:
- Wrap sends the Subject KEK to
transit/encrypt/<key>; the store returns avault:v<n>:...ciphertext, which pdv stores in the subject-key row. The Master KEK itself never leaves the store. - Unwrap sends that ciphertext to
transit/decrypt/<key>. - Rotation uses
transit/rewrap/<key>: the store re-encrypts the blob under the latest key version without ever exposing the Subject KEK plaintext.
Owner-binding. The Transit key is created with derived=true, and every
call passes the owner's identity (pdv:subject:<uid>) as the per-call
context. A blob wrapped for one owner cannot be unwrapped under another
owner's context, mirroring the additional-authenticated-data binding of pdv's
local AEAD.
Requirements¶
- An OpenBao or HashiCorp Vault server reachable from the Drupal site.
- The Transit secrets engine enabled.
- A Transit key created with
derived=true(required for owner-binding). - A token whose policy allows
encrypt,decryptandrewrapon that key.
1. Prepare the secret store¶
With the bao CLI (use vault for HashiCorp Vault, the commands are
identical):
bao secrets enable transit
bao write -f transit/keys/pdv-master derived=true
derived=true is mandatory
Owner-binding relies on per-owner derivation. A key created without
derived=true rejects the context parameter and pdv's wrap calls fail.
Grant a least-privilege policy for just this key. Save it as pdv-transit.hcl:
path "transit/encrypt/pdv-master" { capabilities = ["update"] }
path "transit/decrypt/pdv-master" { capabilities = ["update"] }
path "transit/rewrap/pdv-master" { capabilities = ["update"] }
Register the policy and mint a token bound to it:
bao policy write pdv-transit pdv-transit.hcl
bao token create -policy=pdv-transit -no-default-policy -period=768h
The token value that command prints is what goes in settings.php below.
Token lifetime is yours to manage
pdv_vault calls Transit directly and does not renew the token for you. Use
a periodic (-period) token and renew it out of band (bao token renew),
or a lifetime that matches your operations. For unattended renewal, AppRole
authentication is a natural next step.
(For local development, OpenBao dev/auto-init setups often expose a fixed root token; that is fine for a dev stack but never for production.)
2. Point Drupal at the store (settings.php)¶
The store's address and access token come from settings.php, never from
configuration, so the token is not written to the database or exported with
config:
$settings['pdv_vault']['address'] = 'https://vault.example.com:8200';
$settings['pdv_vault']['token'] = 'your-secret-store-token';
The environment variables PDV_VAULT_ADDR and PDV_VAULT_TOKEN are read as a
fallback, which suits container deployments that inject secrets as env vars.
Reachability is on the crypto path
The store is contacted on every vault read and write. If it is unreachable the operation fails (after a short timeout) rather than exposing data, so treat the store's availability as part of the vault's availability, and use TLS to it in production.
3. Enable the module¶
drush en pdv_vault
4. Create the Master KEK Key¶
At Configuration -> System -> Keys (/admin/config/system/keys), add a key:
- Key type: Vault/OpenBao Transit (PDV Master KEK).
- Transit key name: the name from step 1, e.g.
pdv-master.
This Key holds only the name of the Transit key, no cryptographic material.
5. Select it as the Master KEK¶
At Configuration -> Personal Data Vault (/admin/config/pdv/settings) the
Master KEK select now lists the Transit Key alongside any local 256-bit
encryption keys. Select it and save.
From then on, new Subject KEKs are wrapped in the store. Subjects created
under the previous Master KEK keep resolving under it (each row records the key
that wrapped it) until you re-wrap them: run Vault subjects -> Rotate to
current Master KEK (/admin/config/pdv/subjects), exactly as for any Master
KEK change in Installation. Cron drains the same backlog.
A single tenant can also point its own Master KEK at a Transit Key, the same way.
Rotating the Transit key¶
Rotation is a property of the store, and pdv keeps working across it:
- Rotate in the store:
bao write -f transit/keys/pdv-master/rotate. This adds a new key version; existing ciphertexts keep decrypting against their original version. - To move pdv's stored blobs onto the new version, run Vault subjects ->
Rotate to current Master KEK. It calls
transit/rewrapserver-side, so no Subject KEK plaintext is ever exposed during rotation.
Step 2 is optional unless you raise the store's min_decryption_version to
retire old versions, in which case re-wrap first so no blob is left on a
version about to be disallowed.
HashiCorp Vault instead of OpenBao¶
The Transit API is identical on both, and pdv_vault works against either with
no code or configuration difference: point address at the Vault server and use
a Vault token. Substitute vault for bao in the commands above.