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 aloneCI asserts that: it stands the deployment up, re-runs just up, and compares
MASTER_SECRETS_KEY byte for byte.
Where they come from: the secrets seam
Section titled “Where they come from: the secrets seam”| mode | source of truth |
|---|---|
reference | the cluster. Something put a Secret there; this repo reads it by name and says nothing about how it arrived |
sops | this repo, as ciphertext. Values live in secrets/platform.enc.yaml, reviewable in a diff, decrypted into the cluster by just secrets-sync |
infisical | an 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.
cp secrets/platform.example.yaml secrets/platform.enc.yaml$EDITOR secrets/platform.enc.yamlsops --encrypt --in-place secrets/platform.enc.yamljust secrets-syncAfter 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.
Two layers
Section titled “Two layers”Platform secrets — MASTER_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 value is in this repo
Section titled “No value is in this repo”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.