1
0

Compare commits

...

1 Commits

Author SHA1 Message Date
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
32 changed files with 136 additions and 36 deletions
+4 -4
View File
@@ -21,16 +21,16 @@ PHOTOS_STORAGE_PATH=/srv/dev-disk-by-uuid-2d34f1a9-4284-4cad-ae9a-f1ef36244201/p
EMAIL_ADMIN=lukas@kucharczyk.xyz
EMAIL_FROM=kucharczyk.lukas@gmail.com
EMAIL_HOST=smtp.gmail.com
EMAIL_PASSWORD=sebrubdsgkuptcjr
EMAIL_PASSWORD=
EMAIL_PORT=587
POSTGRES_HOST=postgres
POSTGRES_USER=lukas
POSTGRES_PASSWORD=kralovna
POSTGRES_PASSWORD=
POSTGRES_PORT=5432
MYSQL_HOST=mariadb
MYSQL_USER=lukas
MYSQL_PASSWORD=kralovna
MYSQL_ROOT_PASSWORD=kralovna
MYSQL_PASSWORD=
MYSQL_ROOT_PASSWORD=
MYSQL_PORT=3306
PUID=1000
PGID=100
+3 -1
View File
@@ -1 +1,3 @@
git-crypt-key
git-crypt-key
# Real environment file with secrets; use .env.example as the template
/.env
+91
View File
@@ -0,0 +1,91 @@
# 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.
+1 -1
View File
@@ -2,7 +2,7 @@ BASEROW_PUBLIC_URL=https://baserow.${DOMAIN}
DATABASE_HOST=${POSTGRES_HOST}
DATABASE_NAME=baserow
DATABASE_USER=baserow
DATABASE_PASSWORD=S@8rBtSApf@YpNLXS!2hr2F$
# DATABASE_PASSWORD provided via secrets/baserow.env
EMAIL_SMTP=1
EMAIL_SMTP_HOST=${EMAIL_HOST}
EMAIL_SMTP_PASSWORD=${EMAIL_PASSWORD}
+20 -8
View File
@@ -4,6 +4,8 @@ secrets:
file: secrets/gitea_runner_token.txt
authentik_secret_key:
file: secrets/authentik_secret_key
authentik_email_password:
file: secrets/authentik_email_password
email_host:
file: secrets/email_host
email_username:
@@ -288,7 +290,7 @@ services:
OIDC_PROVIDER_NAME: Authentik
OIDC_CONFIGURATION_URL: https://authentik.kucharczyk.xyz/application/o/mealie/.well-known/openid-configuration
OIDC_CLIENT_ID: asDhzvutfxxpgwaaz0Jjr6SNpEtZo8GKjjs1WzUU
OIDC_CLIENT_SECRET: iIgP3aaF1t0sTd8JPwXrCYmd3Ycc5hhfQROdHN7ByDU81gFJiNbRQ1OrTU7e9yzuPAyqLShRQ2Ve7ov03maHpQtyZzZ2FBdb0OHCkoS4brVuV8uZ4cnVPCzwLEO9bk9U
# OIDC_CLIENT_SECRET provided via secrets/mealie.env
OIDC_SIGNUP_ENABLED: false
OIDC_USER_GROUP: mealie-users
OIDC_ADMIN_GROUP: mealie-admins
@@ -297,6 +299,7 @@ services:
ALLOW_PASSWORD_LOGIN: false
env_file:
- mealie.env
- secrets/mealie.env
volumes:
- "${DOCKER_STORAGE_PATH}/mealie/data/:/app/data"
networks:
@@ -332,6 +335,7 @@ services:
- ${DOCKER_STORAGE_PATH}/valheim/data:/opt/valheim
env_file:
- valheim.env
- secrets/valheim.env
ports:
- ${VALHEIM_EXTERNAL_PORT}:${VALHEIM_INTERNAL_PORT}
cap_add:
@@ -403,9 +407,10 @@ services:
# caddy.@api_expiry.status: "3xx"
# caddy.forward_auth_0.handle_response_0: "path /api/*"
# caddy.forward_auth_0.handle_response_1: "replace_status 401"
env_file:
- secrets/navidrome.env
environment:
ND_LASTFM_APIKEY: 29e22ee836a0cb51cfaacb72d605e30d
ND_LASTFM_SECRET: 10aa58294eeffa142685e78a0cd78ad6
# ND_LASTFM_APIKEY / ND_LASTFM_SECRET provided via secrets/navidrome.env
ND_DEEZER_ENABLED: true
ND_DEVACTIVITYPANEL: true
ND_ENABLESHARING: true
@@ -427,6 +432,7 @@ services:
- "${MALOJA_EXTERNAL_PORT}:${MALOJA_INTERNAL_PORT}"
env_file:
- maloja.env
- secrets/maloja.env
user: "${PUID}:${PGID}"
volumes:
- "${DOCKER_STORAGE_PATH}/maloja:/data"
@@ -606,6 +612,7 @@ services:
- mariadb
env_file:
- photoprism.env
- secrets/photoprism.env
volumes:
- "${PHOTOS_STORAGE_PATH}/import:/photoprism/import"
- "${PHOTOS_STORAGE_PATH}/originals:/photoprism/originals"
@@ -651,6 +658,7 @@ services:
- postgres
env_file:
- baserow.env
- secrets/baserow.env
volumes:
- "${DOCKER_STORAGE_PATH}/baserow:/baserow/data"
restart: unless-stopped
@@ -715,7 +723,7 @@ services:
# PUSH_INSTALLATION_KEY=
- PUSH_RELAY_URI=https://api.bitwarden.eu
- PUSH_IDENTITY_URI=https://identity.bitwarden.eu
- ADMIN_TOKEN=$$argon2id$$v=19$$m=65540,t=3,p=4$$aWJ2cVRvYUsySkM3M01TMTJJMnZqbUF0Wm1qRWhvd1B6Sk50Q1hwck96dz0$$FKjZ36E54pX2e0AE9OaDpiH43TyAyfVwr3IvracbqEA
# ADMIN_TOKEN provided via secrets/vaultwarden.env
- SMTP_HOST=${EMAIL_HOST}
- SMTP_FROM=${EMAIL_FROM}
- SMTP_FROM_NAME="Bitwarden (bw.kucharczyk.xyz)"
@@ -821,12 +829,14 @@ services:
- 3003:3000
env_file:
- .env
- secrets/meilisearch.env
- secrets/karakeep.env
environment:
LOG_LEVEL: debug
MEILI_ADDR: http://meilisearch:7700
BROWSER_WEB_URL: http://chrome:9222
NEXTAUTH_SECRET: lB5mx3t9mdKclELtt+cs2pVBefB+8vD4dKuzhvUP+JzR9bL1
MEILI_MASTER_KEY: Cvu7m/RIGYQPiYcIrxacHFhbfLKfKq3wwSAWJPKVWQEauiIX
# NEXTAUTH_SECRET provided via secrets/karakeep.env
# MEILI_MASTER_KEY provided via secrets/meilisearch.env
NEXTAUTH_URL: https://karakeep.${DOMAIN}
DISABLE_SIGNUPS: TRUE
CRAWLER_VIDEO_DOWNLOAD: TRUE
@@ -872,9 +882,10 @@ services:
restart: unless-stopped
env_file:
- .env
- secrets/meilisearch.env
environment:
MEILI_NO_ANALYTICS: "true"
MEILI_MASTER_KEY: Cvu7m/RIGYQPiYcIrxacHFhbfLKfKq3wwSAWJPKVWQEauiIX
# MEILI_MASTER_KEY provided via secrets/meilisearch.env
volumes:
- meilisearch:/meili_data
networks:
@@ -890,6 +901,7 @@ services:
- authentik_secret_key
- postgres_general_username
- postgres_general_password
- authentik_email_password
environment:
AUTHENTIK_POSTGRESQL__HOST: postgres
AUTHENTIK_POSTGRESQL__NAME: authentik
@@ -899,7 +911,7 @@ services:
AUTHENTIK_EMAIL__HOST: smtp.protonmail.ch
AUTHENTIK_EMAIL__PORT: 587
AUTHENTIK_EMAIL__USERNAME: lukas@kucharczyk.xyz
AUTHENTIK_EMAIL__PASSWORD: CQHMWAUWQG5FBJ2V
AUTHENTIK_EMAIL__PASSWORD: file:///run/secrets/authentik_email_password
AUTHENTIK_EMAIL__USE_TLS: true
AUTHENTIK_EMAIL__USE_SSL: false
AUTHENTIK_EMAIL__TIMEOUT: 60
+1 -1
View File
@@ -1,2 +1,2 @@
MALOJA_DATA_DIRECTORY=/data
MALOJA_FORCE_PASSWORD=kralovna
# MALOJA_FORCE_PASSWORD provided via secrets/maloja.env
-3
View File
@@ -1,3 +0,0 @@
MYSQL_DATABASE=mediawiki
MYSQL_USER=mediawiki
MYSQL_PASSWORD=41eebea0e3ef17dc68064e004e03dafeddd996bf513021b5cf7daf5a0c4d2b32
+3 -1
View File
@@ -56,11 +56,13 @@ services:
restart: always
stop_signal: SIGINT
env_file:
- secrets/penpot.env
environment:
- POSTGRES_INITDB_ARGS=--data-checksums
- POSTGRES_DB=penpot
- POSTGRES_USER=penpot
- POSTGRES_PASSWORD=penpot
# POSTGRES_PASSWORD provided via secrets/penpot.env
volumes:
- penpot_postgres_data:/var/lib/postgresql/data
+2 -2
View File
@@ -1,7 +1,7 @@
PHOTOPRISM_ADMIN_PASSWORD=kRalovna12514265!
# PHOTOPRISM_ADMIN_PASSWORD provided via secrets/photoprism.env
PHOTOPRISM_DATABASE_DRIVER=mysql
PHOTOPRISM_DATABASE_NAME=photoprism
PHOTOPRISM_DATABASE_PASSWORD=TWB64mcPZ^TSdo
# PHOTOPRISM_DATABASE_PASSWORD provided via secrets/photoprism.env
PHOTOPRISM_DATABASE_SERVER=mariadb
PHOTOPRISM_DATABASE_USER=photoprism
PHOTOPRISM_IMPORT_PATH=/photoprism/import
-6
View File
@@ -1,6 +0,0 @@
VPN_ENABLED=no
ENABLE_WEBUI_AUTH=no
ENABLE_RPC2=yes
ENABLE_RPC2_AUTH=yes
RPC2_USER=lukas
RPC2_PASS=5zpxni8N@DYCaZL
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -19,7 +19,7 @@ services:
environment:
SERVER_PORT: 80
SPRING_DATASOURCE_USERNAME: lukas
SPRING_DATASOURCE_PASSWORD: Q^k5i2^hN!wmEr6JLkYP9ME
# SPRING_DATASOURCE_PASSWORD provided via secrets/jelu.env
JELU_CORS_ALLOWED-ORIGINS: https://jelu.${DOMAIN}
restart: unless-stopped
+5 -3
View File
@@ -45,7 +45,7 @@ configs:
kavita:
baseUri: "http://localhost:5000" #or env:KOMF_KAVITA_BASE_URI
apiKey: "16707507-d05d-4696-b126-c3976ae14ffb" #or env:KOMF_KAVITA_API_KEY
apiKey: # set via env:KOMF_KAVITA_API_KEY (secrets/komf.env)
eventListener:
enabled: false # if disabled will not connect to kavita and won't pick up newly added entries
metadataLibraryFilter: [ ] # listen to all events if empty
@@ -194,12 +194,14 @@ services:
user: 1000:100
ports:
- "8085:8085"
env_file:
- ../secrets/komf.env
environment:
- KOMF_KOMGA_BASE_URI=http://komga:25600
- KOMF_KOMGA_USER=lukas@kucharczyk.xyz
- KOMF_KOMGA_PASSWORD=kRalovna12514265!
# KOMF_KOMGA_PASSWORD provided via secrets/komf.env
- KOMF_KAVITA_BASE_URI=http://kavita:${KAVITA_INTERNAL_PORT}
- KOMF_KAVITA_API_KEY=c8023836-7aab-46ed-9409-c24b950002d4
# KOMF_KAVITA_API_KEY provided via secrets/komf.env
- KOMF_LOG_LEVEL=INFO
- JAVA_TOOL_OPTIONS=-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=compact -XX:ShenandoahGuaranteedGCInterval=3600000 -XX:TrimNativeHeapInterval=3600000
configs:
+4 -2
View File
@@ -12,13 +12,15 @@ services:
volumes:
- "${DOCKER_STORAGE_PATH}/openldap/config:/etc/ldap/slapd.d"
- "${DOCKER_STORAGE_PATH}/openldap/data:/var/lib/ldap"
env_file:
- ../secrets/openldap.env
environment:
- LDAP_ORGANISATION=Homelab
- LDAP_DOMAIN=${DOMAIN}
- LDAP_ADMIN_PASSWORD=kral
# LDAP_ADMIN_PASSWORD provided via secrets/openldap.env
- LDAP_OPENLDAP_UID=${PUID}
- LDAP_OPENLDAP_GID=${PGID}
- LDAP_READONLY_USER=true
- LDAP_READONLY_USER_USERNAME=readonly
- LDAP_READONLY_USER_PASSWORD=readonly
# LDAP_READONLY_USER_PASSWORD provided via secrets/openldap.env
restart: unless-stopped
-2
View File
@@ -1,2 +0,0 @@
SECRET_KEY_BASE=sMHYqzrgJQgPynv6ZDG7M8ZpF
FORCE_SSL=false
+1 -1
View File
@@ -1,4 +1,4 @@
SERVER_NAME=LukasJirkaDominik
WORLD_NAME=Mujnovyserver
SERVER_PASS=heslo
# SERVER_PASS provided via secrets/valheim.env
VALHEIM_PLUS=true