Per-instance encryption domain separation; demote deployment single-key #31

Closed
opened 2026-07-25 17:44:04 +00:00 by Cordy · 2 comments
Owner

Manuel dislikes the one-key-per-instance model (blast radius too large). Our deployment custody mode is exactly that.

  • Demote deployment custody to dev/fallback only (default stays keycloak-profile per-user).
  • Add a per-instance seed/nonce so identical plaintext never produces identical ciphertext across instances (domain separation).

Small change; closes the blast-radius objection to the fallback mode.

Manuel dislikes the one-key-per-instance model (blast radius too large). Our `deployment` custody mode is exactly that. - Demote `deployment` custody to **dev/fallback only** (default stays `keycloak-profile` per-user). - Add a **per-instance seed/nonce** so identical plaintext never produces identical ciphertext across instances (domain separation). Small change; closes the blast-radius objection to the fallback mode.
Author
Owner

Decision — approved with amended scope (Manuel's review, relayed by Nikola 2026-07-31):

"Yes, but no demote single-key per deployment."

Un-gated, scope narrowed: per-instance encryption domain separation goes ahead; the second half of the original title — demoting the deployment single-key custody mode — is dropped. keyCustody: "deployment" remains a fully supported first-class option (it's also the natural mode for small instances and the fallback when no IdP profile custody exists). Title kept for history; treat this comment as the authoritative scope.

**Decision — approved with amended scope (Manuel's review, relayed by Nikola 2026-07-31):** > "Yes, but no demote single-key per deployment." **Un-gated, scope narrowed:** per-instance encryption **domain separation** goes ahead; the second half of the original title — demoting the `deployment` single-key custody mode — is dropped. `keyCustody: "deployment"` remains a fully supported first-class option (it's also the natural mode for small instances and the fallback when no IdP profile custody exists). Title kept for history; treat this comment as the authoritative scope.
Author
Owner

Shipped in v0.3.28 (PR #99), honoring the review verdict: deployment custody stays a full peer — not demoted. Only the domain-separation half was built.

New storage.encryption.instanceSeed (or CAIRN_ENC_INSTANCE_SEED): with a seed set, deployment custody derives a per-instance identity via HKDF-SHA256(deploymentIdentity, seed) (stdlib crypto/hkdf; the derived scalar is rendered with a minimal bech32 encoder validated against age's own parser as test oracle). New content encrypts to the derived identity + recovery. Test matrix proves: same master + different seeds → mutually unreadable; the bare master no longer decrypts separated content (the exact blast-radius objection); recovery always works; pre-separation data still decrypts (master retained on the decrypt side).

Operational note: the seed belongs in the config secret next to the master identity — it's a separator, not a second secret, and deriving from ephemeral /data state would silently orphan data on pod restart. Unseeded deployments behave exactly as before.

Shipped in **v0.3.28** (PR #99), honoring the review verdict: **deployment custody stays a full peer — not demoted**. Only the domain-separation half was built. New `storage.encryption.instanceSeed` (or `CAIRN_ENC_INSTANCE_SEED`): with a seed set, deployment custody derives a per-instance identity via HKDF-SHA256(deploymentIdentity, seed) (stdlib `crypto/hkdf`; the derived scalar is rendered with a minimal bech32 encoder validated against age's own parser as test oracle). New content encrypts to the derived identity + recovery. Test matrix proves: same master + different seeds → mutually unreadable; the **bare master no longer decrypts separated content** (the exact blast-radius objection); recovery always works; pre-separation data still decrypts (master retained on the decrypt side). Operational note: the seed belongs in the config secret next to the master identity — it's a separator, not a second secret, and deriving from ephemeral `/data` state would silently orphan data on pod restart. Unseeded deployments behave exactly as before.
Cordy closed this issue 2026-08-04 10:48:21 +00:00
Sign in to join this conversation.
No labels
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#31
No description provided.