Files
keep/INTEGRATION.md
Fredrik Johansson 67000b66ee Implement keep: self-hosted E2E encrypted secrets sync
Server: Express + better-sqlite3 (WAL), multi-recipient key-wrapping
per IMPLEMENTATION.md's design — vaults/recipients/vault_grants/
access_log. Two auth paths: ADMIN_PASSWORD header for recipient
management and revoke (pure metadata operations), signed-request
auth (Ed25519 signature over method+path+timestamp+body-hash) for
push/pull/grant, mirroring the spirit of this project family's other
signed-handshake patterns without naming them.

CLI: identity init/show/set-id, push/pull/grant/log, admin recipient
add/list/remove and revoke. Grant is a client-side crypto operation
(the granter unwraps the vault's current key locally and reseals it
for the new recipient) rather than a server-side operation, since the
server never holds an unwrapped key to grant with.

Verified end-to-end with two independent local identities against a
live server and separately against the built Docker image: register,
push, pull (granted and ungranted), grant without re-pushing, admin
revoke, a subsequent rotation confirming the revoked recipient stays
excluded, and rejection of missing/malformed signed-request auth.

Two real bugs caught during verification, not just written up:
- libsodium-wrappers' published ESM build does a relative import only
  resolvable under bundler-style resolution — broken under plain Node
  ESM. Fixed via createRequire to force the CJS build.
- Express's req.path inside a sub-router is relative to the mount
  point, which would have silently mismatched a client signing the
  full request path. Fixed by verifying against req.originalUrl.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 19:38:24 +02:00

88 lines
3.5 KiB
Markdown

# Incorporating keep into an existing deploy setup
Concrete, by deploy *pattern* rather than by naming specific projects —
the shape of the fit depends on how a project currently gets its config
onto a target machine, not on what the project is.
## The general shape
Wherever a deploy script or a manually-maintained `.env` exists today, the
change is the same: replace "assume `.env` is already sitting there,
hand-copied at some point" with a `keep pull` at the top of the script.
```bash
# before — a deploy script assumes .env already exists on this machine
set -euo pipefail
source .env # or docker-compose reads it directly
# after
set -euo pipefail
keep pull <project>/<env> --out .env
```
The one-time setup cost per machine: `keep identity init` once, send the
printed public key to whoever runs `keep recipient add`, then
`keep identity set-id <id>` with what comes back. After that, every future
deploy from that machine just works — no more "wait, what's the DB
password again" when setting up a new box.
## Pattern: docker-compose reading a local `.env`
A common shape — `docker compose up -d` reads `environment:` values from
a `.env` file sitting next to `docker-compose.yml` on the deploy machine,
hand-maintained and containing real secrets.
```bash
keep pull myapp/production --out .env
docker compose up -d
```
No code changes needed in the target project — `keep` only changes *how
the `.env` file gets there*, not what reads it. This is the easiest
category to retrofit.
## Pattern: cross-compile + scp + SSH-restart deploy scripts
A shape where a deploy script builds a binary locally, ships it to a VPS,
and restarts a service over SSH — reading a local `.env` next to the
script for things like shared secrets that shouldn't be hardcoded or
re-typed on every invocation.
```bash
keep pull myapp/production --out .env
set -a && source .env && set +a
# ...rest of the deploy script unchanged
```
Same retrofit as the docker-compose case, just applied before whatever
build/ship step already exists.
## Pattern: a plaintext config file served to a browser
Not every `config.js`/similar file that *looks* like environment config
actually holds secrets — some of it is public-by-design (an API base URL,
a feature flag) that's explicitly meant to be visible to anyone loading
the page. `keep` solves a confidentiality problem; a file with nothing
confidential in it doesn't have one. Worth checking this distinction
before wiring `keep` into anything — retrofitting a non-secret config
file would be solving a problem that file doesn't have.
## A vault server's own configuration
Whatever runs `keep` itself needs its own `ADMIN_PASSWORD` to be a real,
manually-set secret on its own host — it can't bootstrap its own trust
root by pulling itself from itself. This is the same "root of trust
starts somewhere unmanaged" fact every secrets system has (Vault's own
unseal keys aren't stored in Vault either). Not a gap, just worth being
explicit about: `keep`'s own deploy stays on a plain `.env`/
`docker-compose.yml` environment variables.
## New projects going forward
The actual best use case isn't retrofitting existing deploys — it's never
having the "hand-reconstruct a `.env` from memory" moment for a *new*
deploy target in the first place. For whatever gets built next: register
its production host as a `keep` recipient as part of first setup, write
the deploy script to `keep pull` from day one, and the annoyance this was
built to fix never has a chance to happen.