Secrets management
Where Olympus secrets come from, where they live, how they rotate
Olympus uses many cryptographic secrets across its eight repos. This page is the inventory and lifecycle reference.
The secret catalog
| Secret | Used by | Source in prod | Rotatable? |
|---|---|---|---|
ENCRYPTION_KEY | SDK, settings encryption | GitHub Secrets → compose env | yes, runbook |
SESSION_SIGNING_KEY | Athena, session HMAC | GitHub Secrets → compose env | yes, runbook |
CIAM_RELOAD_API_KEY, IAM_RELOAD_API_KEY | Kratos schema reload sidecar | GitHub Secrets → compose env | yes, runbook |
Kratos secrets.cookie | Kratos, session cookie HMAC | GitHub Secrets → compose env | yes, rotate Kratos config |
Kratos secrets.cipher | Kratos, recovery / verification HMAC tokens | GitHub Secrets → compose env | yes, rotate Kratos config |
Hydra secrets.system | Hydra, JWT signing key store encryption | GitHub Secrets → compose env | yes, rotate Hydra config |
| OAuth2 client secrets | Hydra OAuth2 clients | Created via Athena, stored in Hydra DB (encrypted at rest by Hydra) | yes, via Athena UI |
| Social IdP client secrets (Google, Apple, …) | Hera, OIDC flows | Created in the IdP, stored in Olympus settings (encrypted by SDK with ENCRYPTION_KEY) | yes, via Athena social-connections UI |
| Email provider API key (Resend, etc.) | Kratos courier | GitHub Secrets → Kratos env | yes, in the provider account |
TURNSTILE_SECRET_KEY | Hera, captcha validation | GitHub Secrets → Hera env | yes, in the Cloudflare dashboard |
| Cloudflare API token (if Cloudflare DNS) | Caddy DNS-01 ACME | GitHub Secrets → Caddy env | yes, in the Cloudflare dashboard |
DATABASE_URL (incl. password) | Every service | GitHub Secrets → compose env | yes, via DB admin |
| Postgres CA cert | Every service, SSL verify-full | GitHub Actions artifact or static file | yes, runbook |
| Daedalus secret material (DigitalOcean PAT, etc.) | Daedalus deploy wizard | Stored in daedalus.json locally, never committed | rotated per provider |
| GHCR pull token (if private images) | VPS pull during deploy | GitHub Secrets → compose env | yes, GitHub PAT rotation |
| SSH host key | VPS access | Generated once during VPS init | rare, replace VPS if compromised |
| Daedalus MCP, n/a | (localhost only, no auth, see ADR 0022) | - | , |
Where secrets live
GitHub repository secrets
The canonical store for production secrets is GitHub Actions Secrets in the OlympusOSS/platform repo. The deploy.yml workflow injects them into the compose stack at deploy time.
To list current secrets:
gh secret list -R OlympusOSS/platformTo rotate:
gh secret set ENCRYPTION_KEY --body "$(openssl rand -base64 32)" -R OlympusOSS/platform
gh workflow run deploy.yml -R OlympusOSS/platformCompose environment
The deploy workflow renders a .env.prod from GitHub Secrets, scp's it to the VPS, and starts the compose stack with --env-file. The file is not persisted long-term, it's regenerated each deploy.
Database (Hydra-stored secrets)
OAuth2 client secrets created via Athena are stored in iam_hydra / ciam_hydra databases. Hydra encrypts them at rest using secrets.system (which is in turn loaded from compose env).
Database (SDK settings)
Social IdP secrets, SMTP credentials, etc. are stored in the olympus database as encrypted settings. The SDK encrypts using ENCRYPTION_KEY (compose env) with HKDF-derived per-record keys. See Security, Encryption at Rest.
Daedalus
When using Daedalus to deploy, the wizard collects secrets during the Secrets wizard step and writes them to daedalus.json on the operator's laptop. Daedalus then gh secret set's them into the GitHub repo Secrets store. The local daedalus.json is not committed (gitignored).
What's NEVER in source
The platform CI workflow verify-prod-config.yml enforces:
- No literal secret strings in compose files.
- No literal secret strings in Kratos / Hydra configs (only
${VAR}references). - No secrets in identity schemas.
The gitleaks step (when properly licensed) scans git history for secrets, see the platform CI for the current state.
Audit cadence
Every quarter, run the Secrets Audit runbook. Checklist:
- For each secret in the catalog, verify it has rotated within the last N days (where N depends on the secret type,
ENCRYPTION_KEYquarterly, OAuth2 secrets yearly unless suspected, etc.). - Verify GitHub Secrets has only the expected secrets and no orphans.
- Verify no secret material is in repo source.
- Verify the
verify-prod-config.ymlworkflow has been passing. - Confirm offboarded employees no longer have access to any secret material (clean up Daedalus instances, rotate any secret they had access to).
When a secret is compromised
- Rotate immediately (don't wait for the quarterly audit).
- Revoke or invalidate the leaked value where possible (e.g. for OAuth2 client secrets, change them in Athena → the old one stops working).
- Audit what the compromise reach: which services used this secret, what data could have been read or modified.
- Document in the incident retrospective, what happened, what we changed, what's the long-term mitigation.
For ENCRYPTION_KEY compromise specifically: every encrypted settings value should be considered compromised. Rotate the key, but also assume the leaked plaintexts (OAuth2 client secrets, SMTP credentials, etc.) are compromised, rotate those too.