Skip to content

Secrets

just secret generates the platform secret and never rotates an existing one.

MASTER_SECRETS_KEY regenerated over an existing database makes every stored secret unrecoverable, because every tenant’s inference credentials are encrypted under it. And the deploy that does it looks exactly like a successful deploy.

So the recipe reads before it writes, and leaves an existing secret alone:

secret fountain-secrets already exists — leaving it alone

CI asserts that: it stands the deployment up, re-runs just up, and compares MASTER_SECRETS_KEY byte for byte.

modesource of truth
referencethe cluster. Something put a Secret there; this repo reads it by name and says nothing about how it arrived
sopsthis repo, as ciphertext. Values live in secrets/platform.enc.yaml, reviewable in a diff, decrypted into the cluster by just secrets-sync
infisicalan Infisical server, materialised into a Secret by its operator

sops answers “where do local secrets come from” without a cloud account. The alternative is just secret minting values on the spot, which is fine for one laptop but does not carry to a second machine or a second operator.

Terminal window
cp secrets/platform.example.yaml secrets/platform.enc.yaml
$EDITOR secrets/platform.enc.yaml
sops --encrypt --in-place secrets/platform.enc.yaml
just secrets-sync

After that, a reviewer sees the keys but not the values:

SECRET_KEY_BASE: ENC[AES256_GCM,data:kvni3qRN1jYDM/BFUnxzImnlTe7z...
MASTER_SECRETS_KEY: ENC[AES256_GCM,data:ZHnk2obDBchEEI//fNH1aFAnl...

Nothing decrypted is ever written to disk: sops streams to stdout and kubectl reads it, so a decrypted file cannot be left behind because one is never created.

Platform secretsMASTER_SECRETS_KEY, SECRET_KEY_BASE, DATABASE_URL, SPRITES_TOKEN. Held in a secret store, injected into the release at start. These are the operator’s.

Per-tenant credentials — each tenant’s own inference keys, encrypted AES-256-GCM under MASTER_SECRETS_KEY and stored in fountain’s Postgres. These are the tenants’. fountain manages them; fountain-ops never touches them, it only provisions the key that protects them.

The first layer matters because it holds the key that makes the second readable.

just restore-drill proves the newest backup reads back, and ends by saying what it did not prove: the restored rows are ciphertext without the master key above. Backups and restore has the drill.

No secret value is in this repo, and none ever will be. The generation in just secret is an interim standing in for a real chant capability: INTENTIUS/chant#1365 proposes declaring a secret’s provenance without its value.