fix(securitate): hardening prod — headere, body-cap, non-root, backup
Findings security-review P1/P2 (2026-07-03): - headere de securitate pe toate raspunsurile (nosniff, X-Frame-Options, Referrer-Policy, HSTS doar pe HTTPS) + teste - body-cap global 10MB ca middleware ASGI pur (413 inainte de parserul multipart/JSON; verificarea per-endpoint ramane strat 2) - imagine Docker non-root (uid 10001), port 8010 aliniat, loguri pe volumul /data - fail-fast la boot cu rar_env=prod fara AUTOPASS_REQUIRE_API_KEY sau AUTOPASS_SESSION_SECRET - compose: env-uri critice obligatorii (:?) ca api/worker sa nu diverga tacit; FORWARDED_ALLOW_IPS ca rate-limit-ul sa vada IP-ul real dupa Traefik - signup fara PII in stdout: log_event in loc de print cu email (idem notify degradat) - ratelimit: sterge cheile fara timestamp-uri valide (crestere monotona a memoriei pe IP-uri reale) - backup criptat SQLite (backup online API, gpg AES256) + verificare restore + docs/backup.md Suita completa verde: 1557 passed, 1 skipped (live). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
99
docs/backup.md
Normal file
99
docs/backup.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Backup SQLite (T2/P0-4)
|
||||
|
||||
## Ce se salveaza si de ce
|
||||
|
||||
Baza SQLite (`AUTOPASS_DB_PATH`, prod `/data/autopass.db`) e sistemul legal de evidenta
|
||||
al declaratiilor RAR AUTOPASS (Legea 142/2023). `FINALIZATA` e **terminal** la RAR — nu
|
||||
exista anulare/corectie prin API. Pierderea volumului fara backup inseamna, la restaurare
|
||||
dintr-un mediu gol, retrimiterea acelorasi randuri si duplicate pe care RAR le accepta
|
||||
fara sa poata fi corectate. De aceea backup-ul e trigger dur: **configureaza-l inainte
|
||||
de prima declaratie reala in prod**, nu dupa.
|
||||
|
||||
Backup-urile sunt criptate (gpg AES256) pentru ca snapshot-ul contine PII criptat Fernet
|
||||
(nume, VIN, date client) plus metadate — un backup necriptat pe disc/remote ar fi un al
|
||||
doilea loc unde datele astea pot scapa.
|
||||
|
||||
## Rulare manuala
|
||||
|
||||
```bash
|
||||
# in container (api sau worker, acelasi image + volum):
|
||||
docker compose exec api bash tools/backup_db.sh
|
||||
|
||||
# local (dev), cu parola de test:
|
||||
AUTOPASS_DB_PATH=./data/autopass.db \
|
||||
AUTOPASS_BACKUP_DIR=./data/backups \
|
||||
AUTOPASS_BACKUP_PASSPHRASE_FILE=/cale/catre/parola \
|
||||
bash tools/backup_db.sh
|
||||
```
|
||||
|
||||
Scriptul foloseste `sqlite3.Connection.backup` (online, sigur cu WAL activ — NU `cp`),
|
||||
comprima (gzip) si cripteaza (gpg simetric). Fara `AUTOPASS_BACKUP_PASSPHRASE_FILE` sau
|
||||
`AUTOPASS_BACKUP_PASSPHRASE` seteaza, scriptul **refuza** sa scrie un backup necriptat si
|
||||
iese cu cod diferit de 0. Pastreaza ultimele `AUTOPASS_BACKUP_KEEP` (implicit 14) fisiere
|
||||
in `AUTOPASS_BACKUP_DIR` (implicit `/data/backups`, pe volumul persistent). Daca
|
||||
`AUTOPASS_BACKUP_RCLONE_REMOTE` e setat si `rclone` e disponibil, urca automat
|
||||
backup-ul nou catre remote (off-site); altfel sare peste, cu log explicit.
|
||||
|
||||
## Programare
|
||||
|
||||
Doua variante, alege una:
|
||||
|
||||
1. **Serviciu compose optional** (`docker-compose.yml`, serviciu `backup`, profil
|
||||
`backup`, comentat implicit) — ruleaza scriptul intr-o bucla cu `sleep 86400` in
|
||||
propriul container, pe acelasi volum `autopass-data`. Activare:
|
||||
```bash
|
||||
docker compose --profile backup up -d backup
|
||||
```
|
||||
sau, in Dokploy, seteaza `COMPOSE_PROFILES=backup` in mediul aplicatiei. Preferata
|
||||
pentru un deploy Dokploy: calatoreste cu image-ul si nu depinde de acces SSH separat
|
||||
la hostul care ruleaza containerele (host care in Dokploy poate fi gestionat/efemer).
|
||||
2. **Cron pe host** — daca ai acces SSH direct la masina care ruleaza containerele:
|
||||
```cron
|
||||
0 3 * * * cd /cale/catre/autopass && docker compose exec -T api bash tools/backup_db.sh
|
||||
```
|
||||
|
||||
In ambele cazuri, parola de criptare trebuie sa fie accesibila containerului (secret
|
||||
montat, indicat prin `AUTOPASS_BACKUP_PASSPHRASE_FILE`) — nu doar in mediul shell al
|
||||
cron-ului de pe host.
|
||||
|
||||
## Restaurare (pas cu pas)
|
||||
|
||||
1. **Opreste worker-ul** (`docker compose stop worker`) — altfel worker-ul poate scrie
|
||||
in baza in timp ce o inlocuiesti, sau poate re-prelua randuri `sending` in timp ce
|
||||
restaurarea e pe jumatate facuta.
|
||||
2. Alege backup-ul de restaurat (fisier `autopass-YYYYmmdd-HHMMSS.db.gz.gpg` din
|
||||
`AUTOPASS_BACKUP_DIR`).
|
||||
3. Decripteaza + dezarhiveaza intr-un fisier temporar (NU suprascrie direct baza vie):
|
||||
```bash
|
||||
gpg --batch --decrypt --passphrase-file /cale/catre/parola \
|
||||
--output /tmp/restore.db.gz autopass-20260706-030000.db.gz.gpg
|
||||
gzip -d /tmp/restore.db.gz
|
||||
```
|
||||
4. Verifica integritatea inainte sa promovezi fisierul (vezi sectiunea urmatoare).
|
||||
5. Muta fisierul verificat peste `AUTOPASS_DB_PATH` (backup-uieste intai baza curenta
|
||||
daca mai exista, chiar corupta — pastreaz-o pentru investigare).
|
||||
6. Porneste din nou worker-ul (`docker compose start worker`).
|
||||
|
||||
## Verificare (restore_check)
|
||||
|
||||
Un backup neverificat nu e backup. `tools/restore_check.sh` decripteaza + dezarhiveaza
|
||||
cel mai recent backup (sau unul explicit, ca argument) intr-un fisier temporar si ruleaza
|
||||
`PRAGMA integrity_check` + `SELECT count(*) FROM submissions`. Iese cu cod diferit de 0
|
||||
daca decriptarea, integritatea sau interogarea esueaza.
|
||||
|
||||
```bash
|
||||
AUTOPASS_BACKUP_PASSPHRASE_FILE=/cale/catre/parola tools/restore_check.sh
|
||||
# sau explicit:
|
||||
tools/restore_check.sh /data/backups/autopass-20260706-030000.db.gz.gpg
|
||||
```
|
||||
|
||||
Ruleaza-l periodic (ex. dupa fiecare backup programat) — un backup care nu se poate
|
||||
restaura si verifica nu ofera nicio garantie reala.
|
||||
|
||||
## Unde stau parolele
|
||||
|
||||
Parola de criptare (`AUTOPASS_BACKUP_PASSPHRASE_FILE`) e un secret separat de
|
||||
`AUTOPASS_CREDS_KEY` (Fernet, creds RAR) — nu le refolosi una pe cealalta. In prod,
|
||||
monteaz-o ca fisier secret (nu variabila de mediu inline in `.env` necriptat pe disc)
|
||||
si restrictioneaza accesul la fisier. `AUTOPASS_BACKUP_PASSPHRASE` (variabila inline)
|
||||
e doar pentru teste locale/CI, nu pentru prod.
|
||||
Reference in New Issue
Block a user