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
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
# 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.
|
||||
+11
-11
@@ -12,7 +12,7 @@ scenarios, test results, and how to manage the system yourself.
|
||||
|---|---|---|
|
||||
| App (frontend) | http://localhost (Traefik edge; production: your domain) | — |
|
||||
| API (backend) | http://localhost:8000 (Swagger: `/docs`) | — |
|
||||
| Casdoor console | http://localhost:8080 | **admin / 123** (built-in org) |
|
||||
| Casdoor console | http://localhost:8080 | admin credentials come from `CASDOOR_ADMIN_PW` when running the seeder — never committed |
|
||||
| Casdoor OIDC discovery | http://localhost:8080/.well-known/openid-configuration | — |
|
||||
| Casdoor public JWKS | http://localhost:8080/.well-known/jwks | — |
|
||||
| In-network Casdoor (from the api container) | http://casdoor:8000 | — |
|
||||
@@ -22,13 +22,13 @@ scenarios, test results, and how to manage the system yourself.
|
||||
|
||||
| Username | Password | Casdoor org | App role (our `users` table) |
|
||||
|---|---|---|---|
|
||||
| `razaei` | `123456` | inventory | `admin` (super user) |
|
||||
| `mousavi` | `123456` | inventory | `procurement_manager` (مدیر تدارکات) |
|
||||
| `mohaghegh` | `123456` | inventory | `pm` (مدیر پروژه، بدون پروژه) |
|
||||
| `tester` | `123456` | inventory | `pm` (مدیر پروژه — پروژه «تست») |
|
||||
| `razaei` | `CASDOOR_SEED_PASSWORD` | inventory | `admin` (super user) |
|
||||
| `mousavi` | `CASDOOR_SEED_PASSWORD` | inventory | `procurement_manager` (مدیر تدارکات) |
|
||||
| `mohaghegh` | `CASDOOR_SEED_PASSWORD` | inventory | `pm` (مدیر پروژه، بدون پروژه) |
|
||||
| `tester` | `CASDOOR_SEED_PASSWORD` | inventory | `pm` (مدیر پروژه — پروژه «تست») |
|
||||
|
||||
These users are created by `backend/tools/seed_casdoor.py` (idempotent — safe
|
||||
to re-run).
|
||||
Passwords are stored bcrypt-hashed (passwordType `bcrypt`). These users are
|
||||
created by `backend/tools/seed_casdoor.py` (idempotent — safe to re-run).
|
||||
|
||||
---
|
||||
|
||||
@@ -95,7 +95,7 @@ identity only.** No new authorization model was introduced.
|
||||
the OIDC client. Grant types: `authorization_code`, `refresh_token`,
|
||||
`password`. Token format JWT (so the backend can verify via JWKS).
|
||||
- **Casdoor Organization** (`inventory`): the tenant; every app user belongs
|
||||
to it. (The Casdoor console admin `admin/123` lives in the separate
|
||||
to it. (The Casdoor console admin lives in the separate
|
||||
`built-in` org.)
|
||||
- **User — two records per person:**
|
||||
1. Casdoor user (login credential: name + password).
|
||||
@@ -158,7 +158,7 @@ After that, log in as the admin and use the Organization panel
|
||||
|
||||
| Test | Action | Result | Evidence |
|
||||
|---|---|---|---|
|
||||
| 1 | Login: `POST /api/v1/auth/login` (tester/123456) | **PASS** | 200, JWT + profile returned |
|
||||
| 1 | Login: `POST /api/v1/auth/login` (tester / CASDOOR_SEED_PASSWORD) | **PASS** | 200, JWT + profile returned |
|
||||
| 2 | Authenticated API: `GET /users/me` with token | **PASS** | 200, role `pm` + project «تست» |
|
||||
| 3 | No token: `GET /inventory/warehouses` | **PASS** | 401 `UNAUTHORIZED` |
|
||||
| 4 | Invalid token: corrupted Bearer | **PASS** | 401 `UNAUTHORIZED` «توکن نامعتبر است» |
|
||||
@@ -179,7 +179,7 @@ After that, log in as the admin and use the Organization panel
|
||||
| 6 | Organization exists | ✅ `inventory` |
|
||||
| 7 | Application exists | ✅ `inventory-app` |
|
||||
| 8 | Client exists | ✅ `inventory-app-client` (grants incl. `password`) |
|
||||
| 9 | Test users exist | ✅ razaei, mousavi, mohaghegh, tester (password 123456) |
|
||||
| 9 | Test users exist | ✅ razaei, mousavi, mohaghegh, tester (password: CASDOOR_SEED_PASSWORD) |
|
||||
| 10 | Redirect URI matches | ✅ N/A for the in-page flow (redirects unused). The seeded app holds the legacy `http://localhost:3000/login`; the seeder now defaults to `http://localhost/login` |
|
||||
| 11 | OIDC works | ✅ discovery doc + token endpoint + JWT issuance verified |
|
||||
| 12 | PKCE works | ➖ **N/A** — the PKCE/redirect flow was replaced by the in-page password-grant login (per the redesign spec); nothing to verify |
|
||||
@@ -270,7 +270,7 @@ After that, log in as the admin and use the Organization panel
|
||||
|
||||
## 9. Managing Casdoor yourself
|
||||
|
||||
- **Console:** http://localhost:8080 — log in as **admin / 123**. From there
|
||||
- **Console:** http://localhost:8080 — log in as the admin (credentials from the seeder run). From there
|
||||
you can manage Organizations, Users, Applications, Providers, Certificates.
|
||||
- **Seeding** (org `inventory`, app `inventory-app`, the four users) —
|
||||
re-runnable at any time:
|
||||
|
||||
Reference in New Issue
Block a user