Encryption key custody: Keycloak-profile + deployment toggle (v0.3, probe PR2) #12

Merged
Cordy merged 5 commits from feat/encryption-custody into main 2026-07-24 19:52:34 +00:00
Owner

Second encryption PR (see ENCRYPTION-COMPARISON.md): key custody + wiring. With this merged, storage.encryption.enabled: true turns the PR1 decorator on.

  • KeycloakKeys (custody keycloak-profile, the default — Manuel's model): per-user age X25519 identity stored in a Keycloak user attribute (cairnAgeIdentity), read per request via the Admin REST API (client-credentials service account), lazy-provisioned on a user's first encrypted operation. Fetch-merge-PUT preserves the rest of the user representation; identities and the admin token are cached in memory.
  • StaticKeys (custody deployment): one master identity from config — the Path-1 fallback for IdPs that can't store attributes. The custody choice is the storage.encryption.keyCustody config toggle (the "decide within Cairn" requirement).
  • Both custody modes add the recovery recipient (default-on per owner decision); startup warns loudly if encryption is enabled without one.
  • Config: storage.encryption block + env overrides (CAIRN_ENC_RECOVERY_IDENTITY, CAIRN_ENC_DEPLOYMENT_IDENTITY, CAIRN_ENC_KEYCLOAK_CLIENT_SECRET). Misconfig is fatal at startup — a deployment that asked for encryption never silently runs without it.
  • Wiring: the decorator wraps the driver before any consumer, so REST/WebDAV/TUS-finalize all pass through it. Documented probe limitation: public /s/ shares have no user context → shares of per-user-encrypted content can't decrypt (deferred).
  • Tests: mock-Keycloak lazy provision (+ exactly-one attribute write), merge-preserving PUT, identity cache (no repeat admin-API hits), pre-existing identity reuse, no-user/unknown-user errors, static-custody round-trip.
Second encryption PR (see `ENCRYPTION-COMPARISON.md`): key custody + wiring. With this merged, `storage.encryption.enabled: true` turns the PR1 decorator on. - **`KeycloakKeys`** (custody `keycloak-profile`, the default — Manuel's model): per-user age X25519 identity stored in a Keycloak user attribute (`cairnAgeIdentity`), read per request via the Admin REST API (client-credentials service account), **lazy-provisioned** on a user's first encrypted operation. Fetch-merge-PUT preserves the rest of the user representation; identities and the admin token are cached in memory. - **`StaticKeys`** (custody `deployment`): one master identity from config — the Path-1 fallback for IdPs that can't store attributes. The custody choice is the **`storage.encryption.keyCustody` config toggle** (the "decide within Cairn" requirement). - Both custody modes add the **recovery recipient** (default-on per owner decision); startup warns loudly if encryption is enabled without one. - Config: `storage.encryption` block + env overrides (`CAIRN_ENC_RECOVERY_IDENTITY`, `CAIRN_ENC_DEPLOYMENT_IDENTITY`, `CAIRN_ENC_KEYCLOAK_CLIENT_SECRET`). Misconfig is fatal at startup — a deployment that asked for encryption never silently runs without it. - Wiring: the decorator wraps the driver **before any consumer**, so REST/WebDAV/TUS-finalize all pass through it. Documented probe limitation: public `/s/` shares have no user context → shares of per-user-encrypted content can't decrypt (deferred). - Tests: mock-Keycloak lazy provision (+ exactly-one attribute write), merge-preserving PUT, identity cache (no repeat admin-API hits), pre-existing identity reuse, no-user/unknown-user errors, static-custody round-trip.
Cordy merged commit c5aad36db0 into main 2026-07-24 19:52:34 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
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#12
No description provided.