# 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 '' mc admin user add local '' # then: mc admin policy attach local consoleAdmin --user mc admin user remove local # (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 '';" 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.