Configure pseudonymisation keys in OpenBao¶
Set up an OpenBao or Vault transit engine, JWT roles and policies so the transit adapter can pseudonymise and crypto-shred, with a key per tenant that no replica can mint twice.
Before you start¶
-
Use this only when you must crypto-shred and chose
transit. The default is nokeysblock. See key custody. -
You administer an OpenBao namespace that can reach the cluster's service-account issuer.
-
The key that signs seals is a different key: see signing key. Keys are never rotated: see never rotated.
Steps¶
- In the namespace the keys live in, create a transit engine at
openbao.mountand a JWT auth mount for the cluster's service-account tokens. Add one JWT role per component, binding its service account as subject andopenbaoas audience.
| component | service account (chart default) | role set in | policy |
|---|---|---|---|
| writer | <release> |
writer.config.keys.openbao.login.role |
writer |
| query service, if it resolves | <release>-query |
query.config.keys.openbao.login.role |
resolve |
The pseudonym purpose has one transit key per profile and tenant, <keys.pseudonym>.pseudonym.<escaped profile/tenant>, created on first use. Any byte but a-z, 0-9 and - is written _ and two hex digits, so security/acme is audit-pseudonym.pseudonym.security_2facme.
- Write the policies.
security_2f*ends at the escaped/, so it does not matchsecurity2.
# writer, one block per profile. It seals and pseudonymises, never decrypts,
# and has nothing that rotates, configures or trims. It reads the key.
path "transit/hmac/audit-pseudonym.pseudonym.security_2f*" { capabilities = ["update"] }
path "transit/encrypt/audit-pseudonym.pseudonym.security_2f*" { capabilities = ["create", "update"] }
path "transit/keys/audit-pseudonym.pseudonym.security_2f*" { capabilities = ["read"] }
# query service, if it resolves sealed identifiers: decrypt only
path "transit/decrypt/audit-pseudonym.pseudonym.security_2f*" { capabilities = ["update"] }
path "transit/keys/audit-pseudonym.pseudonym.security_2f*" { capabilities = ["read"] }
# erasure operator, a human group. The glob on keys/ reaches rotate, config
# and trim; encrypt create destroys a tenant that was never seen.
path "transit/keys/audit-pseudonym.pseudonym.*" { capabilities = ["read", "create", "update"] }
path "transit/encrypt/audit-pseudonym.pseudonym.*" { capabilities = ["create", "update"] }
Grant nobody delete on transit/keys/ and set no deletion_allowed: a deleted key is created afresh and gives the person a second identity.
- Configure each component to sign in with its projected service-account token. Add
tokens: [{audience: openbao, mountPath: /var/run/openbao}]besideconfig:in the chart.
keys:
adapter: transit
instance: audit
pseudonym: audit-pseudonym
openbao:
address: https://openbao.example.com:8200
mount: transit
login:
mount: jwt-devel
role: audit-writer
jwtFile: /var/run/openbao/token
For an engine without JWT logins, set one of tokenFile or tokenSecret instead of jwtFile. audit key destroy reads BAO_ADDR, BAO_NAMESPACE, BAO_CACERT and BAO_TOKEN, or the VAULT_ names.
- For a private certificate chain, set
trust.configMapandopenbao.caFile: /etc/audit/trust/<key>.
Verify¶
The backend's tests run these policies as their own tokens against a dev server.
docker run -d --rm --name bao -p 8200:8200 -e BAO_DEV_ROOT_TOKEN_ID=root \
openbao/openbao:2.4.1 server -dev -dev-listen-address=0.0.0.0:8200
cd storage && STORAGE_OPENBAO_ADDR=http://127.0.0.1:8200 STORAGE_OPENBAO_ROOT_TOKEN=root go test ./keys/transit/
Then destroy a test tenant's key. See erase a tenant's keys.
Roll back¶
Remove the roles and policies. A key destroyed by audit key destroy stays destroyed.
Trap: backups¶
A snapshot taken before the trim still holds the key version, and a restore brings it back. Erasure is complete once the last such snapshot ages out. Publish that period with your retention terms.