Files
ERP/docs/SECRET-MANAGEMENT.md
MMD 43e317eeba chore(security): remove committed secrets/dumps, .env-based config, hardened deploy webhook
- Delete backups/*.dump and all real credentials from README/docs
  (prod MinIO creds, Casdoor passwords, server IP, admin password)
- All stack secrets now come from git-ignored .env (.env.example added);
  docker-compose.prod.yml refuses to boot without them (${VAR:?})
- seed_casdoor.py: bcrypt password hashing, generated console/client secrets
- Harden infra/deploy-webhook: HMAC-verified Gitea webhook receiver
  + systemd unit + README (replaces unauthenticated deploy endpoint)
- docs/SECRET-MANAGEMENT.md: secret inventory + rotation checklist
2026-09-18 21:55:24 +03:30

3.4 KiB

Secret management & rotation

Policy (short): real credentials live only in git-ignored .env files — the server-side /srv/erp/.env (used by the deploy webhook and compose) and local dev .env copies. Nothing secret goes into git: not in the README, not in dumps, not in docs. The repo is (or was) public — assume anything ever committed is compromised and rotate.

docker-compose.prod.yml declares every production secret as ${VAR:?set in .env} so the stack refuses to boot on defaults.

Where each secret lives

Secret Used by Defined in
POSTGRES_PASSWORD postgres, api server .env
MINIO_ROOT_USER / MINIO_ROOT_PASSWORD minio, minio-init, api server .env
CASDOOR_CLIENT_SECRET Casdoor application record, api server .env (must match the app in the Casdoor console)
CASDOOR_ADMIN_PASSWORD Casdoor console admin, api (/users/sync) server .env (rotate in the Casdoor console too)
CORS_ORIGINS, *_HOST, TRAEFIK_ACME_EMAIL compose / Traefik server .env
GITEA_WEBHOOK_SECRET deploy-webhook receiver (port 9000) server .env + Gitea webhook config
Monitoring DSNs/keys OpenPanel, GlitchTip, Grafana monitoring/.env (server-side)

Rotation checklist — run after any leak suspicion

The production branch history contained real credentials (MinIO admin, Casdoor passwords, server IP, DB dumps). All of the following must be rotated once, on the VPS:

cd /srv/erp

# 1) MinIO root — then update MINIO_ROOT_* in /srv/erp/.env
mc alias set local http://127.0.0.1:9000 <old-user> '<old-pass>'
mc admin user add local <new-user> '<new-pass>'      # then:
mc admin policy attach local consoleAdmin --user <new-user>
mc admin user remove local <old-user>
# (simplest equivalent: mc admin user svcacct or change root via env +
#  `docker compose up -d minio` — MinIO persists root creds from env)

# 2) PostgreSQL — then update POSTGRES_PASSWORD in .env
docker exec -it procurement-postgres psql -U postgres -c \
  "ALTER USER postgres PASSWORD '<new-strong-password>';"
docker compose up -d api   # api re-reads the DSN from .env

# 3) Casdoor console admin (console → Identity → users → admin → reset
#    password) — then update CASDOOR_ADMIN_PASSWORD in .env
# 4) Casdoor application client secret (console → Applications →
#    inventory-app → edit secret) — then update CASDOOR_CLIENT_SECRET in .env
# 5) Seeded human users: reset each password in the Casdoor console.
# 6) Gitea webhook secret (Gitea → repo → Settings → Webhooks) — then update
#    GITEA_WEBHOOK_SECRET in .env and restart the receiver (systemctl restart erp-deploy-webhook)

# 7) Apply everything:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
  • SSH: disable password login (PasswordAuthentication no in /etc/ssh/sshd_config), use keys only, and prefer a non-root user with sudo over root direct login.
  • Repo hygiene: keep the repo private until the git history is either purged (BFG / git filter-repo on the README + backups/ blobs) or every leaked value is rotated. Deleting a file in a later commit does not remove it from history.
  • Verify: after rotation, docker compose logs api | grep -i error, log into the app, upload + download one attachment, and confirm the webhook deploy still works with a test push.