Separable crypto compute role via --target=crypto (#33) #73

Merged
Cordy merged 5 commits from feat/crypto-role into main 2026-07-30 03:10:23 +00:00
Owner

Closes #33 — honours Manuel's actual point: the age/argon2 work is a compute choke under many concurrent users and must scale independently of storage and the HTTP front end. Until now --target= was a code seam; this makes it a deployment seam, without splitting the codebase (the Loki/MinIO "one binary, many roles" pattern).

  • internal/cryptosvc — the crypto role's wire surface: POST /internal/v1/{encrypt,decrypt} streaming plaintext↔ciphertext, bearer-token auth (shared deployment secret; the role refuses everything when unset), X-Cairn-User carrying the identity whose age key to use, plus /healthz. No JSON envelope — bodies are whole files, streamed, so memory stays flat.
  • encrypt.LocalCrypter / encrypt.RemoteDriver — content crypto split from the storage decorator. ContentCrypter is satisfied structurally by both the in-process crypter and cryptosvc.Client, so the storage decorator can't tell local from remote and semantics are identical (same age file at rest, same plaintext byte count reported).
  • cairnd --target=crypto serves only the crypto endpoints; --target=all|api runs the full server and switches to the remote path when storage.encryption.remoteUrl is set (remoteToken / CAIRN_CRYPTO_TOKEN required in both roles). Key custody is unchanged and now shared by both roles through a single buildKeys.

Deployment: run N crypto replicas behind a Service, point the API deployment's remoteUrl at it, scale the two independently. Trust note in the package doc: the crypto role decrypts for any username the caller names, so it must stay on the cluster network — that's exactly the trust the API role already holds.

Tests: over-the-wire round trip incl. a 2 MB stream, user propagation, token rejection (wrong/empty/unconfigured-server), and local-vs-remote driver parity including age-file-at-rest and wrong-identity rejection. Green on the runner.

Closes #33 — honours Manuel's actual point: the age/argon2 work is a **compute choke** under many concurrent users and must scale independently of storage and the HTTP front end. Until now `--target=` was a code seam; this makes it a deployment seam, without splitting the codebase (the Loki/MinIO "one binary, many roles" pattern). - **`internal/cryptosvc`** — the crypto role's wire surface: `POST /internal/v1/{encrypt,decrypt}` streaming plaintext↔ciphertext, bearer-token auth (shared deployment secret; the role refuses everything when unset), `X-Cairn-User` carrying the identity whose age key to use, plus `/healthz`. No JSON envelope — bodies are whole files, streamed, so memory stays flat. - **`encrypt.LocalCrypter` / `encrypt.RemoteDriver`** — content crypto split from the storage decorator. `ContentCrypter` is satisfied structurally by both the in-process crypter and `cryptosvc.Client`, so the storage decorator can't tell local from remote and semantics are identical (same age file at rest, same plaintext byte count reported). - **`cairnd --target=crypto`** serves only the crypto endpoints; `--target=all|api` runs the full server and switches to the remote path when `storage.encryption.remoteUrl` is set (`remoteToken` / `CAIRN_CRYPTO_TOKEN` required in both roles). Key custody is unchanged and now shared by both roles through a single `buildKeys`. Deployment: run N crypto replicas behind a Service, point the API deployment's `remoteUrl` at it, scale the two independently. Trust note in the package doc: the crypto role decrypts for any username the caller names, so it must stay on the cluster network — that's exactly the trust the API role already holds. Tests: over-the-wire round trip incl. a 2 MB stream, user propagation, token rejection (wrong/empty/unconfigured-server), and local-vs-remote driver parity including age-file-at-rest and wrong-identity rejection. Green on the runner.
Cordy merged commit 2c142b1e06 into main 2026-07-30 03:10:23 +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#73
No description provided.