Files
ERP/docs/SECRET-MANAGEMENT.md
T
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

68 lines
3.4 KiB
Markdown

# 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:
```bash
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.