Files
incus-backup-ui/docs/fixes-todo.md
T
Philipp 8a80978aef added security settings
changed fixes and issues
2026-06-04 15:08:54 +02:00

14 KiB
Raw Blame History

Fixes & Hardening TODO

Kritisches Review-Backlog vor Produktiveinsatz und OSS-Release. Sortiert nach Risikoklasse. Datei-/Zeilenreferenzen beziehen sich auf den Stand bei Review-Erstellung.


🔴 Kritisch — vor Produktion blockierend

  • 1. Default-Admin admin/admin entfernen erledigt

    • management/src/db.js:83 — wirft jetzt Exception bei leerem AUTH_PASSWORD. Kein Silent-Fallback mehr.
    • README/docs/deployment.md entsprechend anpassen (noch offen).
  • 2. API_TOKEN im Node-Agent zur Pflicht machen erledigt

    • agent/src/config.js:44 — Start schlägt fehl wenn Token fehlt oder kürzer als 32 Zeichen.
    • agent/src/index.jsif (!config.apiToken) next() entfernt; Token-Prüfung immer aktiv.
  • 3. HTTPS zwischen Management und Agent erzwingen erledigt

    • management/src/routes/nodes.js:102-112validateBaseUrl erzwingt https://. Escape-Hatch ALLOW_INSECURE_AGENT_HTTP=true nur für Dev.
    • management/src/agentClient.js — eigene CA über AGENT_CA_FILE konfigurierbar.
    • Agent-Server unterstützt TLS via HTTPS_ENABLED, TLS_CERT_FILE, TLS_KEY_FILE.
  • 4. CSRF-Risiko durch reflektierendes CORS schließen erledigt

    • management/src/index.js:18-27 — Nur Origins aus CORS_ORIGINS-Env zugelassen.
    • management/src/auth.js:57 — Cookie auf SameSite=Strict gesetzt.
  • 5. Brute-Force-Schutz beim Login erledigt

    • management/src/routes/auth.js:7-9 — In-Memory Rate-Limit: 5 Versuche / 15 min / IP+User.
    • Audit-Event login_blocked und login_failed implementiert.
    • Hinweis: Kein externer express-rate-limit nötig, eigenständige Implementierung ausreichend.
  • 6. Restore atomarisieren — kein direktes dd auf Produktiv-Volume staged Restore umgesetzt

    • Datei: agent/src/executor.js:92 (streamResticDumpToDd), agent/src/routes/restore.js
    • Umsetzung: Restore schreibt zuerst in ein temporäres ZFS-Volume, prüft die Größe vorab und tauscht danach per zfs rename gegen das Produktiv-Volume.
    • Das alte Volume bleibt als *.pre-restore-* Rollback-Kopie erhalten.
    • Bei Fehlern vor dem Swap wird nur das temporäre Volume entfernt; bei Fehlern nach dem Swap versucht der Agent den alten Volume-Namen wiederherzustellen.
  • 7. Backup-Verifikation einbauen

    • Datei: agent/src/routes/backup.js (VM-Pfad ~Z.71, Container-Pfad ~Z.137)
    • Aktuell: Bei Pipe-Fehler (zfs send !=0, createReadStream-Abbruch) committed restic ggf. einen unvollständigen Snapshot mit Status „success".
    • Fix:
      • Vor Start erwartete Größe ermitteln (zfs get volsize / used).
      • Nach Restic-Commit restic stats <snapshotId> vergleichen.
      • Bei Mismatch oder Pipe-Fehler: Restic-Snapshot per ID forget+pruneen, Job auf failed.
  • 8. Bearer-Token aus Frontend-localStorage entfernen erledigt

    • frontend/src/api.js — nur noch Cookie-Auth (withCredentials: true). localStorage-Token und VITE_API_TOKEN entfernt.
  • 45. Secrets in GET /settings maskieren (Write-only) erledigt

    • Datei: agent/src/config.js (readEnvSettings/writeEnvSettings), frontend/src/components/Settings.jsx
    • Problem: readEnvSettings gab den Wert aller Felder zurück — inkl. RESTIC_PASSWORD, AWS_SECRET_ACCESS_KEY, API_TOKEN. Über den Management-Proxy konnte damit jeder eingeloggte UI-User das Restic-Verschlüsselungspasswort und die S3-Credentials enthüllen (= alle Backups entschlüsseln und löschen).
    • Fix: Secret-Felder liefern beim Lesen keinen Wert mehr, nur hasValue: true|false. Beim Schreiben gilt ein leeres Secret-Feld als „unverändert" (bestehender Wert bleibt erhalten). Frontend zeigt Platzhalter „gesetzt leer lassen zum Beibehalten".

🟠 Hoch

  • [~] 9. Session-Cookie Secure-Flag ⚠️ teilweise erledigt

    • management/src/auth.js:56Secure-Flag via SESSION_COOKIE_SECURE konfigurierbar, auto-aktiviert bei NODE_ENV=production.
    • Offen: management/.env.example hat SESSION_COOKIE_SECURE=false — in Deployment-Doku explizit als "in Produktion auf true setzen" dokumentieren.
  • 10. Race beim Sichtbarmachen des Snapshot-Devices

    • Datei: agent/src/routes/backup.js:57
    • Aktuell: 2 s sleep reicht nicht garantiert.
    • Fix: Polling-Schleife auf fs.access(snapshotDevice) mit Timeout; zusätzlich udevadm trigger && udevadm settle.
  • 11. Persistenter Job- und Lock-Store im Agent erledigt

    • Datei: agent/src/jobs.js
    • Umsetzung: Agent speichert Jobs in SQLite (AGENT_DATABASE_PATH).
    • Beim Start werden running/queued Jobs als failed markiert, damit Management einen finalen Zustand pollen kann und VM-Locks nicht hängen bleiben.
    • Offen für später: ressourcenspezifische Cleanup-Recovery für abgebrochene Host-Operationen.
  • 12. Toten SESSION_SECRET aufräumen

    • Datei: management/src/config.js:8
    • Aktuell: SESSION_SECRET wird geladen, aber nirgendwo genutzt — toter Code.
    • Fix: Variable und zugehörigen .env.example-Eintrag entfernen, oder Session-IDs HMAC-signieren und Variable dann sinnvoll nutzen.
  • 13. Snapshot-ID-Prefix-Matching eindeutig machen

    • Datei: agent/src/validators.js:66
    • Aktuell: startsWith — bei Prefix-Kollision wird stillschweigend der erste Treffer genommen.
    • Fix: Bei >1 Treffer 409 zurückgeben; UI auf 12-Hex-Prefix umstellen.
  • 14. RBAC einführen

    • Aktuell: ein User, alle Rechte, kein Read-only.
    • Fix: Mindestens Rollen admin / operator / viewer. Restore nur für admin.
  • 15. ENV-Escaping in writeEnvSettings verbessern

    • Datei: agent/src/config.js:105
    • Aktuell: Nur \ und " escaped; $, Backticks, Newlines nicht.
    • Fix: Eigener Serializer mit korrektem Escaping aller Sonderzeichen.
  • 16. Settings-Endpunkt sperren bis API_TOKEN initial gesetzt ist

    • Datei: agent/src/routes/settings.js
    • Hängt mit Fix #2 zusammen — nach #2 automatisch erfüllt, hier zur Sicherheit dokumentieren/testen.
  • 46. Agent-Token-Vergleich timing-safe machen erledigt

    • Datei: agent/src/index.js:28
    • Problem: header === \Bearer ${config.apiToken}`— normaler String-Vergleich am root-Agent (das wertvollste Ziel), während das Login-Passwort bereitstimingSafeEqual` nutzte.
    • Fix: Vergleich über crypto.timingSafeEqual mit Längen-Guard. Leerer/fehlender konfigurierter Token verweigert weiterhin (config.apiToken &&).
  • 47. Pre-Restore-Volumes aufräumen (Disk-Space-GC)

    • Datei: agent/src/routes/restore.js
    • Aktuell: Jeder erfolgreiche Restore behält das alte Volume als *.pre-restore-* Rollback-Kopie — es gibt aber keinen Cleanup. Jeder Restore verdoppelt den Plattenbedarf der VM dauerhaft; bei mehreren Restores läuft der ZFS-Pool voll.
    • Fix: Aufbewahrungsregel (z.B. „keep last N pre-restore/failed-restore Volumes pro VM") oder expliziter Cleanup-Schritt/UI-Aktion. Mindestens im Health/Operations-View sichtbar machen.
  • 48. Agent-Tokens im Management-SQLite nicht im Klartext speichern

    • Datei: management/src/store.js (nodes.token), management/src/db.js
    • Aktuell: nodes.token liegt im Klartext in der Management-DB. DB-Diebstahl = alle Agent-Tokens = root auf allen Hosts.
    • Fix: Verschlüsselung at-rest mit einem Management-Key (z.B. aus SESSION_SECRET/dediziertem Key abgeleitet), oder zumindest DB-Dateipermissions/Disk-Encryption-Anforderung im Deployment-Doc verbindlich dokumentieren.

🟡 Mittel

  • 17. Math.random() durch crypto.randomBytes ersetzen

    • Datei: management/src/db.js:93 (cryptoId).
    • Noch vorhanden: Math.random().toString(16) für DB-interne User-IDs — kein akutes Sicherheitsproblem, aber nicht kryptografisch sicher.
  • 18. Systemd-Hardening für den Agent

    • Datei: deploy/systemd/incus-backup-agent.service
    • Hinzufügen: NoNewPrivileges=true, ProtectSystem=strict, ProtectHome=true, PrivateTmp=true, ReadWritePaths=/dev/zvol /var/lib/incus /opt/incus-backup-ui/agent, CapabilityBoundingSet=..., eingeschränkte AmbientCapabilities.
  • 19. npm start durch direkten node-Aufruf ersetzen

    • Datei: beide deploy/systemd/*.service
    • ExecStart=/usr/bin/node src/index.js — kein npm-Wrapper-Prozess, kein PATH-Risiko.
  • 20. CORS am Agent entfernen

    • Datei: agent/src/index.js:16
    • Agent wird nie aus dem Browser angesprochen → Angriffsfläche raus.
  • 21. schedules.json-Pfad explizit konfigurierbar

    • Datei: agent/src/scheduler.js:6
    • Aktuell: process.cwd()-abhängig.
    • Fix: über Env (SCHEDULES_PATH) absolut konfigurieren, Default unter /var/lib/incus-backup-agent/.
  • 22. Audit-Log-Integrität

    • Datei: management/src/store.js:71
    • Append-only Constraint + Hash-Chain (jeder Event hat prev_hash). Optional Off-Site-Versand (Webhook).
  • 23. Passwort-Hashing auf argon2id umstellen

    • Datei: management/src/crypto.js
    • Aktuell: scryptSync mit Node-Defaults, ohne explizite Parameter.
    • Fix: @node-rs/argon2 oder explizite scrypt-Parameter dokumentieren + Migration-Pfad.
  • 24. Rate-Limit / Quoten für Backups

    • Pro VM und global (z.B. max N parallele Streams nach S3). Disk-Space-/Quota-Check vor Start.
  • 25. incus snapshot delete Retry verallgemeinern

    • Datei: agent/src/routes/backup.js:303
    • Aktuell: Substring-Match auf englische Stderr — bricht bei lokalisierten Builds.
    • Fix: Generischer Retry (n Versuche, Backoff) bei nicht-0 Exit-Code.
  • 26. Restic-ls streamen statt vollständig in RAM laden

    • Datei: agent/src/routes/snapshots.js:19
    • Für Container-Backups mit vielen Files relevant.
  • 27. Container-Restore implementieren oder Container-Backup deaktivieren

    • Datei: agent/src/routes/restore.js:21
    • Aktuell: 501. Backups laufen, aber nicht wiederherstellbar = Backup-Theater.
    • Fix: Sicheren zfs receive-Workflow umsetzen oder Container-Backup im UI/API ausschalten bis fertig.
  • 28. Pre-Backup VM-Zustand prüfen

    • Live-Migration, laufende interne Snapshots, fehlende Berechtigungen → klare Fehler statt halb durchgeführter Pipeline.
  • 49. Abgelaufene Sessions serverseitig löschen + Rotation

    • Datei: management/src/store.js:12 (getUserBySession), createSession
    • Aktuell: Abgelaufene Sessions werden beim Lesen nur gefiltert, nie aus der DB entfernt → unbegrenztes Tabellenwachstum. Außerdem keine Session-Rotation nach erfolgreichem Login.
    • Fix: Periodischer Cleanup (DELETE FROM sessions WHERE expires_at <= now) und neue Session-ID nach Login ausstellen.

🟢 Niedrig / Aufräumen

  • 29. .DS_Store aus Repo entfernen und in .gitignore aufnehmen
  • 30. frontend/dist/ ist eingecheckt — ignorieren und löschen.
  • 31. incus-backup-ui-plan.md auf Secrets/Bucket-Namen prüfen bevor OSS.
  • 32. Container-Limit (Restore fehlt) prominent in README dokumentieren.
  • 33. Scheduler-Jitter einbauen (management/src/scheduler.js, agent/src/scheduler.js) — sonst belasten viele Nodes synchron S3.
  • 34. formatBytes deduplizieren (agent/src/routes/backup.js, restore.js) → executor.js oder utils.js.
  • 35. .env-Dateipermissions dokumentierenchmod 600 im Deployment-Doc verlangen.
  • 36. Schema-Versionierung statt addColumnIfMissing — z.B. schema_version-Tabelle + nummerierte Migrationen.
  • 37. audit_events.details Größenlimit oder JSON-Spalte (SQLite hat JSON1).

📦 OSS-Release-Voraussetzungen

  • 38. Tests einführen — aktuell keinerlei Tests im Repo. Mindestens:
    • validators.js (vmName-/snapshot-Regex, Pfad-Validation).
    • crypto.js (hash/verify Roundtrip, Timing-Safe).
    • jobs.js (Locking, Trimming).
    • Pipeline-Smoke-Tests mit gemockten Befehlen.
  • 39. CI-Pipeline (GitHub Actions): Lint, Tests, npm audit, Lockfile-Check.
  • 40. SECURITY.md mit Disclosure-Adresse + GPG-Key.
  • 41. Threat-Model dokumentieren — explizit machen, was außerhalb des Schutzbereichs liegt (root-Agent, vertrauenswürdiges Netz, etc.).
  • 42. LICENSE-Datei ergänzen (z.B. MIT/Apache-2.0).
  • 43. CONTRIBUTING.md + Code-of-Conduct.
  • 44. Beispiel-compose.yaml oder Ansible-Rolle für reproduzierbares Deployment.

Status-Übersicht kritische Punkte (Stand 2026-06-04)

# Titel Status
1 Default-Admin admin/admin entfernen erledigt
2 API_TOKEN Pflicht erledigt
3 HTTPS Management↔Agent erledigt
4 CORS-Whitelist + SameSite=Strict erledigt
5 Brute-Force-Schutz Login erledigt
6 Restore atomarisieren staged Restore umgesetzt
7 Backup-Verifikation offen
8 Bearer-Token aus localStorage erledigt
9 Session-Cookie Secure-Flag ⚠️ teilweise
45 Secrets in GET /settings maskieren erledigt
46 Agent-Token-Vergleich timing-safe erledigt
47 Pre-Restore-Volume-GC offen
48 Agent-Tokens in DB verschlüsseln offen
49 Session-Cleanup serverseitig offen

Nächste Prioritäten

  1. #7 Backup-Verifikation weiter härten — Pipeline-/Stream-Fehler testen und vollständiger absichern.
  2. #47 Pre-Restore-Volume-GC — sonst läuft der ZFS-Pool bei wiederholten Restores voll.
  3. #9 SESSION_COOKIE_SECURE=true in Deployment-Doku festschreiben.
  4. #12 Toten SESSION_SECRET entfernen.
  5. #15 ENV-Escaping vervollständigen ($, Backticks, Newlines).
  6. #11 Folgearbeit ressourcenspezifische Cleanup-Recovery nach Agent-Crash definieren.