1
0
Files
docker-compose-templates/SECURITY.md
T
lukas 5aa85b0920 secrets: migrate exposed plaintext secrets to git-crypt
Move all hardcoded credentials out of tracked compose/env files into the
git-crypt-encrypted secrets/ directory, using each app's supported mechanism:

- env_file -> secrets/*.env: mealie, navidrome, karakeep, meilisearch,
  baserow, maloja, valheim, photoprism, komf, openldap, penpot, vaultwarden
- file:///run/secrets: authentik email password
- jelu DB password appended to existing secrets/jelu.env

Untrack root .env (interpolated ${VAR} secrets) and add sanitized
.env.example template; gitignore /.env.

Move unreferenced orphan files (mediawiki/rtorrent/snibox .env) into
secrets/ to preserve values while encrypting them.

Add SECURITY.md documenting the secrets conventions and a rotation
checklist. NOTE: all migrated values remain in prior git history and
must be rotated at their providers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 13:15:25 +02:00

92 lines
4.5 KiB
Markdown

# Security: secrets handling & rotation checklist
## How secrets are stored in this repo
- **`secrets/`** — git-crypt encrypted (see `.gitattributes`: `secrets/** filter=git-crypt`).
All real credentials live here. The working-tree copies are plaintext (git-crypt
only encrypts the committed blobs), so Docker reads them normally.
- **Root `.env`** — git-ignored (`/.env`) and **not** committed. It holds non-secret
config (ports, paths, domains) plus a few `${VAR}` values interpolated across
services. Use **`.env.example`** as the template; fill in the blanked secret values
locally.
- **Tracked compose / `*.env` files** — must contain **no secret values**. Pull secrets
in via one of:
- `env_file: - secrets/<svc>.env` (universal)
- `FILE__VARNAME=/run/secrets/<name>` (LinuxServer.io images, e.g. calibre-web-automated)
- `VARNAME_FILE=/run/secrets/<name>` (miniflux, gitea-runner, …)
- `file:///run/secrets/<name>` (authentik)
## Before you commit
```sh
# git-crypt must be unlocked so secrets/ files encrypt on commit
git-crypt status | grep -i 'not encrypted' || echo "all secrets/ files encrypted"
```
Quick scan for accidental plaintext secrets in tracked files:
```sh
git ls-files | grep -vE '^secrets/|^\.env\.example$' | xargs grep -nIE \
'(PASSWORD|SECRET|TOKEN|API_?KEY|CLIENT_SECRET|MASTER_KEY)=[^ ]' 2>/dev/null \
| grep -vE '_FILE|/run/secrets/|file:///|\$\{|=\s*$'
```
## Rotation checklist
All values below were committed to git history in plaintext before the 2026-06-12
migration. Migrating them to `secrets/` only protects them **going forward** — each
must be **rotated at its provider**, then history scrubbed (see bottom).
Tick each once the credential has been regenerated and the new value written to the
corresponding `secrets/` file.
### External / high priority (reachable beyond the LAN)
- [ ] **Gmail app password**`EMAIL_PASSWORD` in `.env` (reused by vaultwarden, mealie, baserow SMTP). Regenerate at Google Account → App passwords.
- [ ] **ProtonMail SMTP token**`secrets/authentik_email_password`. Regenerate in Proton → SMTP submission.
- [ ] **mealie OIDC client secret**`secrets/mealie.env`. Rotate the `mealie` provider in Authentik.
- [ ] **Last.fm API key + secret**`secrets/navidrome.env`. Reissue at last.fm/api/accounts.
- [ ] **Meilisearch master key**`secrets/meilisearch.env` (used by karakeep + meilisearch). Generate a new random key.
- [ ] **karakeep `NEXTAUTH_SECRET`**`secrets/karakeep.env`. `openssl rand -base64 36`.
- [ ] **vaultwarden `ADMIN_TOKEN`**`secrets/vaultwarden.env`. Regenerate with `vaultwarden hash` (Argon2).
- [ ] **jelu `GOOGLE_API_KEY`**`secrets/jelu.env`. Rotate in Google Cloud console.
### Internal (LAN-only, still rotate — `kralovna` is reused widely)
- [ ] **Postgres password**`POSTGRES_PASSWORD` in `.env` (`kralovna`).
- [ ] **MySQL/MariaDB passwords**`MYSQL_PASSWORD`, `MYSQL_ROOT_PASSWORD` in `.env` (`kralovna`).
- [ ] **baserow DB password**`secrets/baserow.env`.
- [ ] **photoprism admin + DB passwords**`secrets/photoprism.env`.
- [ ] **jelu DB password**`secrets/jelu.env`.
- [ ] **komf komga password + Kavita API key**`secrets/komf.env`.
- [ ] **openldap admin + readonly passwords**`secrets/openldap.env`.
- [ ] **maloja force password**`secrets/maloja.env`.
- [ ] **valheim server password**`secrets/valheim.env`.
- [ ] **penpot postgres password**`secrets/penpot.env`.
### Orphaned services (moved to `secrets/`; rotate if still running anywhere)
- [ ] **mediawiki MySQL password**`secrets/mediawiki.env`.
- [ ] **rtorrent RPC2 password**`secrets/rtorrent.env`.
- [ ] **snibox `SECRET_KEY_BASE`**`secrets/snibox.env`.
## Scrubbing git history
After rotating, remove the old plaintext values from history so the leaked secrets
become useless even to someone with an old clone:
```sh
# Using git-filter-repo (recommended). Removes the old tracked paths entirely.
git filter-repo --invert-paths \
--path .env \
--path mediawiki.env --path rtorrent.env --path snibox.env
# Then force-push and have every clone re-clone (rewritten history diverges):
git push --force --all
git push --force --tags
```
For surgical edits to lines inside files that stay tracked (e.g. a secret that lived
in `docker-compose.yml`), use `git filter-repo --replace-text <file>` with
`old==>***REMOVED***` rules, or BFG's `--replace-text`.
> Rotation is what actually neutralizes a leak. History scrubbing is best-effort —
> assume anything ever pushed is already compromised and rotate regardless.