platform-docs/adr/0011-kundenplattform-getrennter-dienst.md
Thomas Stallinger faa06a51fc docs: Gegenlese aller 15 ADRs - sicherheitsrelevanten Widerspruch behoben
Wichtigster Fund: 13-live-provisioning-boot.md widersprach sich selbst
zum GRUB-Standardeintrag - ein noch nicht korrigierter Abschnitt
('Auto-Eintrag ist Index 0 und damit Standardauswahl') stand neben der
bereits vorhandenen Korrektur weiter oben im selben Dokument ('Standard
ist von lokaler Festplatte booten', Erkenntnis vom 01.08.2026). Ein
Reader des älteren Abschnitts hätte das sicherheitsrelevante Verhalten
bei destruktiven Installationen falsch verstanden. Jetzt konsistent,
mit Verweis auf die Korrektur. ADR-0003 (Ursprungsentscheidung, dort
unkorrigiert) bekam denselben Nachtrag.

Kleinere Korrekturen: zwei Anführungszeichen-Inkonsistenzen (ADR-0009,
ADR-0011) - gerade Anführungszeichen für eigene Zusammenfassungen des
Autors statt der sonst durchgängigen „..."-Form, während echte wörtliche
Zitate (Terminal-Fehlermeldungen, Anaconda-Doku) unangetastet bleiben,
weil die Originalschreibweise dort bewusst erhalten werden soll.

Alle ADR-Querverweise (0001-0015) auf Gültigkeit geprüft, keine toten
Verweise gefunden.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 21:47:02 +02:00

5.5 KiB

ADR-0011: Kundenplattform als eigenständiger, von Anode getrennter Dienst

Status: Beschlossen Datum: 04.08.2026

Kontext

Für den heute gebauten Auftragskatalog (ADR-0010) sollte eine Web-UI entstehen, über die Kunden Aufträge geräteweise aus-/abwählen können. Die naheliegende erste Idee — die UI direkt als weitere Routen in provisioning-server (anode) zu bauen — wurde verworfen: Anode bedient den kompletten Geräte-Flotten-Traffic (Aktivierung, Agent-Check-in/Report für potenziell tausende Schulrechner) über maschinenorientierte Endpoints mit einem einzigen globalen Admin-Token, ohne jedes Kunden-/Benutzerkonto-Konzept.

Eine menschliche, sitzungsbasierte Kunden-Login-Oberfläche im selben Prozess/derselben Vertrauenszone zu betreiben hätte drei konkrete Nachteile:

  1. Sicherheit: Eine Web-UI mit Login/Sessions ist eine deutlich größere Angriffsfläche (XSS, CSRF, Session-Handling) als reine Maschine-zu-Maschine-Endpoints. Ein UI-Bug könnte potenziell die Flotten-Infrastruktur mitgefährden.
  2. Mehrmandantenfähigkeit: Der bestehende TUXFLOTTE_ADMIN_TOKEN ist ein einzelnes globales Betreiber-Secret, keine pro-Organisation skalierende Authentifizierung. Ein echtes Kunden-Login braucht Accounts, die einer Organisation zugeordnet sind und nur deren eigene Geräte sehen dürfen.
  3. Verfügbarkeit: Anode muss zuverlässig für die Geräte-Flotte erreichbar bleiben. UI-Traffic (z.B. viele gleichzeitig eingeloggte Kunden) sollte diese Zuverlässigkeit nicht gefährden können.

Im Repo existiert noch kein Benutzerkonto-Konzept (nur Maschinen-Secrets für Geräte, siehe devices.agent_secret_hash) und keine bestehende „Kundenplattform"-Dokumentation — nur vereinzelte Prosa-Erwähnungen einer künftigen „Verwaltungsoberfläche"/eines „Self-Service-Portals" (u.a. ADR-0008, das bereits eine Neugeräte-Bestätigung über eine solche künftige Oberfläche vorsieht, dort aber nicht spezifiziert).

Entscheidung

Ein neuer, eigenständiger Dienst Kundenplattform entsteht als getrenntes Repository (kundenplattform), getrennter Prozess (eigener systemd-Dienst, eigener Port) und getrennte Datenbank (eigene Postgres-Datenbank im selben Cluster, eigene Credentials, keine Cross-Database-Queries oder Foreign Keys zu provisioning-servers Datenbank).

Hosting: vorerst auf demselben physischen Host wie anode, da aktuell keine Last-/Sicherheitsdringlichkeit für einen zweiten Host besteht — die Trennung ist auf Prozess-/Auth-/Datenbankebene bereits vollständig, ein späterer Umzug auf einen eigenen Host ist jederzeit möglich und kein großer Aufwand.

Backend-for-Frontend-Muster: Kundenplattform authentifiziert Menschen (E-Mail/Passwort, signierte Session-Cookie) und spricht selbst mit anodes API über ein eigenes Service-Token (TUXFLOTTE_KUNDENPLATTFORM_TOKEN), getrennt vom bestehenden TUXFLOTTE_ADMIN_TOKEN — Kompromittierung oder Rotation des einen betrifft den anderen nicht. Anode bekommt dafür einen neuen Endpoint GET /api/v1/organizations/{organization_id}/devices zur Geräteauflistung; die bestehenden Auftragskatalog-Endpoints akzeptieren zusätzlich das neue Token.

Autorisierung bewusst auf Anwendungsebene, nicht in Anode: Anode selbst bekommt noch keine feingranulare Pro-Kunde-Autorisierung (z.B. Row-Level-Security nach Organisation). Kundenplattform prüft vor jedem Aufruf gegen anode selbst, ob das angefragte Gerät zur Organisation des eingeloggten Benutzers gehört (Abgleich gegen die zuvor geladene Geräteliste). Das ist kein Sicherheitsloch, da Kundenplattform der einzige Inhaber des neuen Service-Tokens ist und somit die einzige Instanz, die diese Endpoints überhaupt aufrufen kann — es ist eine bewusste, dokumentierte Vereinfachung für die erste Version, keine dauerhafte Architekturentscheidung.

Technologie: FastAPI + serverseitig gerendertes Jinja2 (kein SPA-Framework) — passt zum bestehenden minimalistischen Python/FastAPI-Stack im Projekt, erfordert kein neues Build-Tooling. Bei Bedarf später mit gezielter Interaktivität (z.B. HTMX) nachrüstbar, ohne einen SPA-Umbau zu erzwingen.

Umfang der ersten Version: ausschließlich der Auftragskatalog (Login, Geräteliste, geräteweise Auswahl). Weitere Anforderungen — insbesondere die in ADR-0008 vorgesehene Neugerät-Bestätigung/Workspace-Zuweisung — sind spätere, eigenständige Ergänzungen auf derselben Architektur, keine Neuarchitektur.

Konsequenzen

  • Zwei separat deploybare Dienste statt einem: mehr Betriebsaufwand (zwei systemd-Einheiten, zwei Datenbanken, zwei Auth-Tokens zu verwalten), aber klare Trennung der Vertrauenszonen.
  • Anode braucht für jeden neuen Kundenplattform-Anwendungsfall künftig potenziell weitere, gezielt scoped Endpoints (wie den neuen Organizations-Devices-Endpoint) statt direkter Datenbankzugriffe — das ist beabsichtigt und hält die Kopplung auf API-Ebene explizit.
  • Solange Kundenplattform die einzige Inhaberin des TUXFLOTTE_KUNDENPLATTFORM_TOKEN ist, bleibt die Organisationszugehörigkeits-Prüfung dort zentralisiert. Sollte anode selbst irgendwann direkte externe Aufrufer (z.B. eine zweite Kundenplattform-Instanz oder Drittintegrationen) bekommen, muss die Autorisierung in anode selbst nachgezogen werden — das ist hier bewusst nicht vorweggenommen.
  • Kein Self-Service-Registration/Invite-Flow in v1 — Kundenkonten werden manuell angelegt (analog zur bestehenden manuellen Aktivierungscode-Vergabe). Eine spätere Erweiterung ist ohne Schema-Bruch möglich (benutzer-Tabelle ist bereits generisch genug).