Files
rar-autopass/docs/backup.md
Claude Agent 63b6cbc01d 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>
2026-07-06 12:46:49 +00:00

4.6 KiB

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

# 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:
    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:
    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):
    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.

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.