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

229 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
- [x] **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).
- [x] **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.js``if (!config.apiToken) next()` entfernt; Token-Prüfung immer aktiv.
- [x] **3. HTTPS zwischen Management und Agent erzwingen** ✅ erledigt
- `management/src/routes/nodes.js:102-112``validateBaseUrl` 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`.
- [x] **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.
- [x] **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.
- [x] **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`+`prune`en, Job auf `failed`.
- [x] **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.
- [x] **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:56``Secure`-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`.
- [x] **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.
- [x] **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 bereits `timingSafeEqual` 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 dokumentieren**`chmod 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.