Files
florian.wildeandClaude Opus 5 2f45e33e20 feat(auth): Rollen, Benutzerverwaltung und Passkeys (#027, #028)
Drei Rollen: Administrator, Techniker, Kunde. Jede der 28 Aktionen nennt das
Recht, das sie verlangt; die Rechte stehen an einer Stelle. Kundenkonten
haengen an genau einem Kunden und sehen nur dessen Hosts und Rechnungen -
ohne Einkaufspreise und Marge. Die Einschraenkung sitzt in den Abfragen
selbst, damit keine Seite vergessen kann, sie weiterzugeben; sechs Tests
pruefen das.

Dabei gefunden: Neu ueber OIDC angelegte Konten haetten die Vorgabe des
Datenmodells geerbt - ADMIN. Jetzt gibt es dafuer eine Einstellung mit der
Vorgabe Techniker.

Passkeys (#028) als gleichwertiger, passwortloser Anmeldeweg neben Passwort
und OIDC. Mehrere je Konto, Aufgabe in einem kurzlebigen Cookie, rpID aus der
oeffentlichen Adresse. Ohne HTTPS sagt die Seite es, statt still zu scheitern.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 00:46:29 +02:00

6.2 KiB
Raw Permalink Blame History

Sicherheit

Dieses Dokument hält fest, was ProxLedger gegen die üblichen Angriffe tut – und was es nicht tut. Die Gliederung folgt den OWASP Top 10 (2021).

A01 – Fehlerhafte Zugriffskontrolle

  • src/proxy.ts schickt jede Anfrage ohne gültige Sitzung zur Anmeldung. Öffentlich sind nur die Anmeldung, die Agent-API (eigene Tokens), die Gesundheitsprüfung, die beiden Agent-Skripte und die Symbole.
  • Jede Server Action prüft zusätzlich selbst (sitzungErforderlich()). Server Actions sind eigene Endpunkte; eine einzige Schutzschicht davor wäre zu wenig, falls der Musterausdruck im Proxy je danebengreift.
  • Die Prüfung geht in die Datenbank: Ein gesperrtes Konto oder ein Widerruf beendet laufende Sitzungen sofort (npm run benutzer -- sperren <email>), statt bis zum Ablauf des Tokens zu warten.
  • Die Anmeldung prüft den Origin, damit keine fremde Seite jemanden unbemerkt in ein untergeschobenes Konto anmeldet (Login-CSRF). Für Server Actions erledigt das Next.js selbst.

Rollen (#027): Administrator, Techniker und Kunde. Jede Aktion nennt das Recht, das sie verlangt; die Rechte stehen an einer Stelle (src/server/auth/rollen.ts). Kundenkonten sind an einen Kunden gebunden, und die Einschränkung sitzt in den Abfragen – nicht in der Anzeige. Einkaufspreise und Marge werden für sie schon beim Abfragen entfernt, stehen also nicht im ausgelieferten HTML.

Weiterhin nicht vorhanden: Mandantenfähigkeit im eigentlichen Sinn. Mehrere Systemhäuser in einer Installation wären ein Umbau, kein Anbau.

A02 – Kryptografische Fehler

  • Passwörter: argon2id (@node-rs/argon2), keine eigene Krypto.
  • Sitzungen: signiertes JWT (HS256) aus APP_SECRET, 12 Stunden gültig, Cookie httpOnly, sameSite=lax und secure, sobald APP_URL auf https:// steht.
  • Zugangsdaten für SMTP und Webhooks liegen AES-256-GCM-verschlüsselt in der Datenbank (src/lib/geheimnisse.ts); der Schlüssel wird aus APP_SECRET abgeleitet.
  • Agent- und Enrollment-Tokens werden nur als SHA-256-Hash gespeichert. Das ist hier richtig und nicht zu schwach: Die Tokens sind 32 Byte Zufall, es gibt nichts zu raten.

Wichtig: APP_SECRET ist der Generalschlüssel. Geht er verloren, sind alle Sitzungen ungültig und die gespeicherten Zugangsdaten unlesbar. Er gehört in die Sicherung.

A03 – Injektion

  • Datenbankzugriffe ausschließlich über Prisma mit gebundenen Werten; kein $queryRawUnsafe im Projekt.
  • Ausgaben maskiert React selbst.
  • Zusätzlich greift eine Content-Security-Policy mit frischem Nonce je Anfrage und strict-dynamic (src/proxy.ts). Eingeschleustes <script> – etwa über einen manipulierten Hostnamen aus einer Proxmox-Ausgabe – läuft damit nicht.
  • Der Agent baut sein JSON mit einer eigenen Maskierung; Sonderzeichen aus Proxmox-Ausgaben sind als Testfall hinterlegt.

A04 – Unsicheres Design

  • Push-Modell: Das Dashboard braucht keinen Zugang zu den Proxmox-Hosts. Es gibt keine gespeicherten root-Zugänge, die abhandenkommen könnten – die Hosts melden von sich aus.
  • Der Agent sendet nur Statusangaben (Subscription, Version, Sockel, IPs). Keine Gastliste, keine Daten.
  • Enrollment-Tokens sind befristet und in der Zahl der Nutzungen begrenzt.

A05 – Sicherheitsrelevante Fehlkonfiguration

  • Kopfzeilen: X-Content-Type-Options, X-Frame-Options: DENY, Referrer-Policy, Permissions-Policy, Strict-Transport-Security (next.config.ts) sowie die CSP aus dem Proxy.
  • Die Anwendung startet nicht mit zu kurzem APP_SECRET.
  • Im Container läuft die Anwendung als unprivilegierter Benutzer; Anhänge werden mit Rechten 640 abgelegt.
  • Fehlermeldungen nennen keine Einzelheiten: Bei der Anmeldung steht dieselbe Meldung für „Adresse unbekannt“ und „Passwort falsch“.

A06 – Verwundbare Komponenten

  • npm audit --omit=dev meldet keine bekannten Schwachstellen (Stand 18.09.2026).
  • Zwei Fassungen werden über overrides angehoben: mysql2 und deepmerge-ts kommen als Beiwerk von Prisma mit, obwohl wir Postgres nutzen. Ohne die Anhebung bliebe nur ein Rückschritt auf Prisma 6.
  • Die CI prüft bei jedem Push (#019).

A07 – Fehler bei Identifizierung und Authentifizierung

  • Ratenbremse: fünf Anmeldeversuche je Kennung und Viertelstunde.

  • Anmeldung wahlweise per Passwort oder OIDC (authentik, mit PKCE und state).

  • Jede Anmeldung – erfolgreich oder nicht – landet im Audit-Log.

  • Passkeys (WebAuthn, #028) als dritter, gleichwertiger Weg. Passwortlos, an die Domäne gebunden, mehrere je Konto. Setzt HTTPS voraus (außer auf localhost).

  • Automatisch angelegte OIDC-Konten bekommen die eingestellte Rolle – Vorgabe Techniker, nicht Administrator.

Nicht vorhanden: klassische Zwei-Faktor-Anmeldung mit Einmalkennwort. Ein Passkey ersetzt beides in einem Schritt; wer einen zweiten Faktor zum Passwort erzwingen will, stellt OIDC davor.

A08 – Datenintegrität

  • Der Agent prüft vor dem Selbstaktualisieren, ob die geladene Datei wie ein Bash-Skript beginnt.
  • Webhooks werden mit HMAC-SHA256 signiert, damit die Gegenstelle die Herkunft prüfen kann.
  • Hochgeladene Rechnungen werden nicht dem gemeldeten Typ nach beurteilt, sondern am Dateianfang geprüft (%PDF-), auf 10 MB begrenzt und unter ihrer Prüfsumme abgelegt.

A09 – Protokollierung

  • Audit-Log für Anmeldungen, Änderungen an Kunden, Rechnungen, Tokens, Kanälen und Warnregeln – einsehbar unter Einstellungen → Protokoll.
  • Der Agent protokolliert auf dem Host über logger ins Journal.

A10 – Server-Side Request Forgery

  • Die einzige ausgehende Verbindung, die sich einstellen lässt, ist der Benachrichtigungs-Webhook. Er wird bewusst von einem Angemeldeten eingetragen und ist damit kein fremdgesteuerter Aufruf.
  • Sonst ruft das Dashboard keine Adressen auf, die aus Daten stammen.

Was ein Betreiber selbst tun muss

  1. HTTPS davorschalten (Beispiele unter deploy/reverse-proxy/) und APP_URL auf https:// setzen.
  2. APP_SECRET und die Datenbank sichern (deploy/backup.sh).
  3. Das Dashboard nicht ins offene Internet stellen, wenn es nicht sein muss. Die Agent-API muss erreichbar sein, die Oberfläche nicht.
  4. Enrollment-Tokens nach dem Ausrollen widerrufen.