Master Encryption Key Rotation (Machine Identity KEK)
Master Encryption Key Rotation (Machine Identity KEK)
Runbook for rotating the site master encryption key used to protect per-org JWT signing private keys and token-delegation credentials at rest.
This is not the same as per-org JWT signing key rotation (see JWT Signing Key Rotation).
Design background: SPIFFE JWT-SVID SDD §3.1.1.
API surface: Re-wrap is Core gRPC only today (
ReencryptTenantIdentitySecretsviagrpcurlwith a Forge Admin CLI mTLS certificate). A NICo REST API for the same operation is planned; this runbook will be updated when it ships. There is nonico-admin-clisubcommand for re-wrap.
Concepts
Decrypt always loads the AES key named by the envelope’s embedded key_id. The site current_encryption_key_id only selects which key is used when writing new ciphertext.
Fields re-wrapped per org (when present):
encrypted_signing_key_1encrypted_signing_key_2encrypted_auth_method_config(token delegation credentials)
Prerequisites
[machine_identity].enabled = trueand a healthynico-api.- Credentials to invoke re-wrap: today, a Forge Admin CLI mTLS client certificate (see Generating client certificates). When the REST API is available, provider-admin access via
nicocli/ bearer token will be an alternative. - Maintenance window: plan for a config change + API restart + one re-encrypt pass. Issuance can continue during re-wrap if old keys remain in secrets.
Procedure
Example rotation: kv1 → kv2.
Step 1 — Add the new key to secrets
Generate material:
Add kv2 to site credentials without removing kv1:
For Vault-backed sites, add …/machine_identity/encryption_keys/kv2 using your standard secret workflow.
Deploy/refresh credentials so nico-api can read both keys.
Step 2 — Point site config at the new key and restart API
Update site config:
Restart nico-api (this setting is not hot-reloaded). New encrypts (new orgs, signing-key rotation, delegation updates) use kv2 immediately. Existing DB rows still decrypt with kv1 via envelope metadata.
Step 3 — Dry-run re-wrap
Today (gRPC): use
grpcurlas below. Planned: equivalent dry-run/apply via NICo REST — watch this page and the REST API reference for updates.
Call ReencryptTenantIdentitySecrets with dry_run: true. Optionally scope to one org.
Single org:
Inspect the response:
Success criterion for dry run: no failures; every row either skipped as already on target or listed for re-encryption.
Step 4 — Apply re-wrap
Repeat with dry_run: false:
Verify:
rows_failed = 0- After apply, a second dry run shows only
rows_skipped_all_on_target/fields_skipped_on_target
Step 5 — Retire the old key (optional)
After a successful apply and verification:
- Confirm no ciphertext still references
kv1(second dry run withcurrent_encryption_key_id = "kv2"should skip all rows). - Remove
kv1frommachine_identity.encryption_keysin secrets. - Redeploy credentials. Do not remove
kv1until re-wrap completes — decrypt would fail for any remainingkv1envelopes.
Rollback
If apply fails partway:
- Keep both keys in secrets.
- Leave
current_encryption_key_idatkv2(or revert tokv1only if you have not written new ciphertext withkv2yet). - Fix the reported
failures(often missing key material or corrupt envelope). - Re-run dry run, then apply.
If you must revert current_encryption_key_id to kv1 before re-wrap completes, new writes use kv1 again; rows already re-wrapped to kv2 still decrypt with kv2 until you re-run re-encrypt toward kv1.
Operational Notes
- Run re-wrap after every change to
current_encryption_key_id. - Re-wrap is idempotent: rows already on the target key are skipped.
- Org-scoped runs are useful for phased rollout; site-wide run (
organization_idomitted) is typical for small fleets. - Coordinate with JWT signing key rotation — they are independent, but both affect what verifiers and decrypt paths must tolerate.
Related Documentation
- Day 0 Machine Identity — initial KEK provisioning
- Machine Identity (Day 1) — per-org config and JWT signing-key rotation
- SPIFFE JWT-SVID SDD §3.1.1 — authoritative design