umbau 1.0

This commit is contained in:
2026-09-02 20:30:44 +02:00
parent afe6719f51
commit 5c95b21be7
77 changed files with 10733 additions and 1914 deletions

View File

@ -49,7 +49,8 @@ noetig. Fuer RDP muss zusaetzlich ein laufender `guacd` erreichbar sein (siehe
`JUMPHOST_GUACD_HOST`/`_PORT`).
Die SQLite-Migrationen 0004 (`user_groups`), 0005 (`api_tokens`), 0006
(`tenants`) und 0007 (`crud_extras`) werden beim naechsten Start automatisch
(`tenants`, seit Migration 0014 wieder vollstaendig zurueckgebaut) und 0007
(`crud_extras`) werden beim naechsten Start automatisch
angewendet (`app/db.py`, `_apply_migrations`) — kein manueller
Migrationsschritt noetig, auch nicht bei einem bestehenden Datenbestand.
@ -105,30 +106,21 @@ rendert Endpunkte gruppiert nach Tag, ausschliesslich per
`document.createElement`/`textContent` (kein `innerHTML`, keine Inline-
Styles/-Scripts, siehe `tests/test_csp_compliance.py`).
## Phase 8: Mandantenfaehigkeit, CRUD-Vervollstaendigung, Dateitransfer, Mehrfachrollen
## Phase 8: CRUD-Vervollstaendigung, Dateitransfer, Mehrfachrollen (historisch: Mandantenfaehigkeit)
Diese Session hat die Admin-Oberflaeche um sieben zusammenhaengende
Phase 8 fuehrte testweise Mandantenfaehigkeit ein (Migration
`0006_tenants.sql`); diese wurde in Teil C des Umsetzungsauftrags
vollstaendig zurueckgebaut (Migration `0014_drop_tenants.sql`) — siehe dort
fuer die Begruendung. Die CRUD-Vervollstaendigung, der Dateitransfer und
die Mehrfachrollen-Vergabe aus dieser Phase bleiben bestehen. Historische
`tenant_created`/`tenant_updated`/`tenant_deleted`/`tenant_admin_granted`/
`tenant_admin_revoked`/`tenant_admin_promoted_to_admin`-Audit-Log-Eintraege
bleiben bewusst dauerhaft im manipulationssicheren, Hash-verketteten Log
erhalten und wurden vom Rueckbau nicht angetastet. Diese Session hat die
Admin-Oberflaeche seinerzeit um sechs zusammenhaengende
Erweiterungen ergaenzt:
**1) Mandantenfaehigkeit ("volle Isolation + Mandanten-Admins", Migration
`0006_tenants.sql`)**: jede Hostgruppe, Benutzergruppe, jeder SSH-Key und
jedes API-Token gehoert zu genau einem Mandanten (`tenants`-Tabelle,
Standard-Mandant `id=1` "Standard" fuer bestehende Daten). Es gibt zwei
Admin-Stufen: **Super-Admin** (`users.is_admin=1`, unveraendert wie zuvor)
sieht/verwaltet **alle** Mandanten weiterhin vollstaendig — keine Regression
gegenueber Phase 7. **Mandanten-Admin** (neue `tenant_admins`-Zuordnung,
ein User kann Admin mehrerer Mandanten sein) sieht/verwaltet **ausschliesslich**
die Ressourcen seines/seiner Mandanten; jeder Zugriffsversuch auf eine
fremde Mandanten-ID liefert bewusst **404** (nicht 403, siehe
`app/tenancy.py`, `TenantScope`) — ein Mandanten-Admin soll aus der
Fehlerantwort nicht einmal ableiten koennen, dass eine ID ausserhalb seines
Mandanten ueberhaupt existiert. Tenant-CRUD und die Ernennung von
Mandanten-Admins sind ausschliesslich Super-Admin-Aktionen (neuer Tab
"Mandanten" in der Admin-Oberflaeche, fuer Mandanten-Admins nicht sichtbar).
Ein API-Token ist immer an **genau einen** Mandanten gebunden
(`api_tokens.tenant_id`) und kann diesen nie verlassen.
**2) CRUD-Vervollstaendigung**: jede Ressource, die die Admin-Oberflaeche
**1) CRUD-Vervollstaendigung**: jede Ressource, die die Admin-Oberflaeche
anlegen kann, ist jetzt auch bearbeitbar und loeschbar — Benutzer (inkl.
Passwort setzen, deaktivieren, reaktivieren), Benutzergruppen, Hostgruppen,
Hosts, SSH-Keys (inkl. Rotation). Zwei bewusste Sicherheits-/Integritaets-
@ -143,12 +135,12 @@ Katalog beruecksichtigt); `DELETE /admin/hosts/{id}?hard=true` versucht
zusaetzlich ein echtes Entfernen, faellt aber automatisch auf Soft-Delete
zurueck, falls der Host bereits Sitzungshistorie hat.
**3) Login-Verlauf**: neuer Tab "Login-Verlauf" filtert clientseitig aus dem
**2) Login-Verlauf**: neuer Tab "Login-Verlauf" filtert clientseitig aus dem
bestehenden Audit-Log-Feed (`GET /admin/audit-log`) gezielt Login-/Logout-/
Fehlversuch-Ereignisse heraus — kein neuer Backend-Endpunkt noetig, da die
Rohdaten bereits vorhanden waren, nur bisher nicht dediziert sichtbar.
**4) "Details"-Fix fuer Hosts**: der defekte "Details"-Button in der Hosts-
**3) "Details"-Fix fuer Hosts**: der defekte "Details"-Button in der Hosts-
Tabelle wurde durch einen neuen Endpunkt `GET /admin/hosts/{id}` (liefert den
vollstaendigen, aktuellen Datensatz inkl. zugeordneter SSH-Keys und ob
RDP-Zugangsdaten hinterlegt sind) sowie eine neu geschriebene, robuste
@ -160,12 +152,12 @@ Ursache nicht direkt reproduzierbar — bitte nach dem Update pruefen, ob der
Button jetzt zuverlaessig funktioniert, oder bei einem verbleibenden Fehler
die genaue Browser-Konsolenmeldung mitteilen.
**5) Eigener "Zugangsdaten"-Tab**: buendelt SSH-Keys UND RDP/Windows-
**4) Eigener "Zugangsdaten"-Tab**: buendelt SSH-Keys UND RDP/Windows-
Passwoerter (bisher Teil des Hosts-Formulars) an einer Stelle, inkl. Uebersicht,
welche RDP-Hosts bereits ein Passwort hinterlegt haben (`GET
/admin/rdp-credentials`).
**6) Dateitransfer-Fenster (SSH-Terminal)**: der bisherige, `prompt()`-
**5) Dateitransfer-Fenster (SSH-Terminal)**: der bisherige, `prompt()`-
basierte Einzel-Upload-Knopf in der Terminal-Sitzung (`templates/terminal.html`,
`static/js/terminal.js`) wurde durch ein eigenstaendiges Dateitransfer-Panel
ersetzt, das **beide Richtungen** abdeckt — Upload (wie zuvor, jetzt mit
@ -177,7 +169,7 @@ verlassen). Das Panel fuehrt zusaetzlich ein kurzes Transfer-Log der
laufenden Sitzung. Gilt bewusst nur fuer SSH (RDP hat in dieser
Implementierung keinen eigenen Dateitransfer-Endpunkt).
**7) Mehrfachauswahl bei Rollenvergabe**: `POST /admin/roles/grant` und
**6) Mehrfachauswahl bei Rollenvergabe**: `POST /admin/roles/grant` und
`POST /admin/group-roles/grant` akzeptieren jetzt `role_names` (Liste, 1-6
Rollen) statt einer einzelnen `role_name` — ein Benutzer bzw. eine
Benutzergruppe kann damit in einem Schritt mehrere Rollen auf derselben
@ -214,8 +206,8 @@ Verbindungsaufbau (`jumphost.*`-Logger werden beim Start auf DEBUG gesetzt,
siehe `app/security/log_stream.py`). Bewusst ein reiner In-Memory-Ring-Buffer
(letzte 1000 Zeilen) + Pub/Sub ohne DB-Persistenz — ein Live-Tail wie
`journalctl -f`, kein durchsuchbares Archiv. Nur fuer Super-Admins sichtbar/
erreichbar, da die Logs mandantenuebergreifend technische Details preisgeben
koennen. Der Endpunkt liegt bewusst als eigener Top-Level-Router unter
erreichbar, da die Logs technische Details zum Verbindungsaufbau aller
Hosts preisgeben koennen. Der Endpunkt liegt bewusst als eigener Top-Level-Router unter
`/ws/logs` (siehe `app/admin/log_ws.py`) statt unter `/admin/...` — der
nginx-Reverse-Proxy setzt die fuer WebSockets noetigen Upgrade-Header nur
fuer die `location /ws/ { ... }` (siehe
@ -225,7 +217,7 @@ Handshake wuerde hinter dem Proxy fehlschlagen (Status bliebe dauerhaft
"getrennt", ohne dass ein Fehler in der Anwendung selbst sichtbar wird).
**4) Sessionview-Dashboard (nur Super-Admin)**: neuer Tab "Sessions" +
`GET /admin/sessions` (aktive + historische Sitzungen ueber alle Mandanten),
`GET /admin/sessions` (aktive + historische Sitzungen),
`POST /admin/sessions/{id}/terminate` (zwangsweises Trennen einer laufenden
Sitzung) und `GET /admin/sessions/{id}/recording` (Integritaetspruefung der
Aufzeichnung). "Beenden" nutzt eine neue prozesslokale Registry
@ -246,8 +238,8 @@ getrennt).
`ssh_connect`/`rdp_connect`/`file_transfer` — pro Hostgruppe an einzelne User
oder Benutzergruppen vergeben werden koennen. Damit koennen auch NICHT-Admins
gezielt Zugangsdaten (RDP-Passwort setzen/entfernen, SSH-Key-Zuordnung, nie
der Klartext selbst) fuer Hosts "ihrer" Hostgruppe verwalten, ohne Admin oder
Mandanten-Admin sein zu muessen (neue Dependency
der Klartext selbst) fuer Hosts "ihrer" Hostgruppe verwalten, ohne Admin
sein zu muessen (neue Dependency
`require_admin_scope_or_host_role` in `app/auth/deps.py`, neuer Endpunkt
`GET /admin/hosts/{id}/credentials`, Dashboard zeigt einen "Zugangsdaten"-
Knopf bei Hosts mit dieser Rolle).
@ -425,7 +417,7 @@ falschen Objekt: der Anmeldename ist Teil der Zugangsdaten. Ab jetzt:
den Host-Wert zurueck, bis ein Admin den Namen am Schluessel setzt (die
Oberflaeche weist darauf hin).
* Die alten Spalten bleiben als Fallback lesbar (ein Spalten-Drop erzwaenge in
SQLite einen Tabellen-Rebuild, siehe Begruendung in `0006_tenants.sql`), die
SQLite einen Tabellen-Rebuild, siehe Begruendung in `0014_drop_tenants.sql`), die
API nimmt sie als `deprecated` weiterhin entgegen.
### Nach dem Deployment zu tun
@ -458,21 +450,20 @@ Admin-only-Durchsetzung fuer alle neuen Endpunkte, Token-Scope-Durchsetzung
Scope), dass ein Token niemals andere Tokens verwalten kann (Rechte-
Eskalationsschutz), sowie Admin-Gating von `/docs` und `/openapi.json`.
Details, Vorgehen und Ergebnisse der urspruenglichen 27 Tests: siehe
`Pentest_Report.md`. 15 weitere neue Tests zu Phase 8
(`tests/test_tenants.py`) decken zusaetzlich ab: Tenant-CRUD (Super-Admin
only, Mandanten-Admin explizit ausgeschlossen), volle Mandanten-Isolation
(Hostgruppen/Hosts/Benutzer/SSH-Keys/Audit-Log — fremde Mandanten-IDs
liefern 404), Mehrfachauswahl bei Einzel- UND Gruppen-Rollenvergabe,
`Pentest_Report.md`. 9 der urspruenglich zu Phase 8 hinzugekommenen Tests
(`tests/test_admin_crud.py`, Nachfolger von `tests/test_tenants.py` — die
6 reinen Tenant-CRUD-/-Isolationstests entfielen mit dem Rueckbau in Teil C)
decken weiterhin ab: Mehrfachauswahl bei Einzel- UND Gruppen-Rollenvergabe,
Update/Deactivate/Delete fuer Benutzer (inkl. Anonymisieren-statt-Hart-
Loeschen bei vorhandener Audit-Historie vs. echtem Hard-Delete ohne),
Update/Delete fuer Hostgruppen (blockiert solange Hosts enthalten sind),
Hosts (Soft- vs. Hard-Delete), SSH-Keys (inkl. Rotation) und Benutzergruppen,
sowie den neuen `GET /admin/hosts/{id}`-Detailendpunkt inkl. SSH-Key-
Zuordnungen und RDP-Zugangsdaten-Status. 7 weitere neue Tests zu Phase 9
sowie den `GET /admin/hosts/{id}`-Detailendpunkt inkl. SSH-Key-Zuordnungen
und RDP-Zugangsdaten-Status. 7 weitere neue Tests zu Phase 9
(`tests/test_phase9.py`) decken zusaetzlich ab: dass "Host-Key ermitteln" bei
einem echten Verbindungsfehler 502 statt 500 liefert und bei einem Auth-Fehler
NACH erfolgreichem Key-Exchange weiterhin als Erfolg gilt; dass
`/admin/sessions*` ausschliesslich Super-Admins erlaubt ist (Mandanten-Admin
`/admin/sessions*` ausschliesslich Super-Admins erlaubt ist (Nicht-Admin
= 403) und `terminate`/`recording` fuer nicht (mehr) laufende bzw. nicht
aufgezeichnete Sitzungen sauber 409/404 statt einen internen Fehler liefern;
dass die neuen Rollen `credentials_view`/`credentials_manage` Nicht-Admins
@ -486,9 +477,12 @@ Payload-Feld `role_name` (Einzahl) an `/admin/group-roles/grant`, das
mehr kennt (erwartet `role_names`, eine Liste) — ein beim Phase-8-Umbau
liegen gebliebener Regressionsfehler, jetzt auf `role_names` korrigiert.
> **Hinweis:** Alle 41 in dieser und den beiden vorherigen Sessions neu
> hinzugekommenen Tests (5 CSP + 14 Admin/Gruppen/Token + 15 Mandanten/CRUD +
> 7 Phase 9) konnten in der verwendeten Cloud-Sandbox nicht mit `pytest -q`
> **Hinweis:** Von den 41 in dieser und den beiden vorherigen Sessions neu
> hinzugekommenen Tests (5 CSP + 14 Admin/Gruppen/Token + 15 Tenant-CRUD/
> -Isolation + 7 Phase 9) sind nach dem Mandanten-Rueckbau in Teil C nur noch
> 35 relevant (9 CRUD-/Rollen-Tests aus den urspruenglichen 15 blieben als
> `tests/test_admin_crud.py` erhalten, 6 reine Tenant-Tests entfielen). Sie
> konnten in der damals verwendeten Cloud-Sandbox nicht mit `pytest -q`
> ausgefuehrt werden, da diese Sandbox keinen Netzwerkzugriff auf PyPI hat und
> `fastapi`/`aiosqlite`/`argon2`/`asyncssh` dort nicht vorinstalliert sind
> (auch nicht ueber die Geraete-Bruecke zum lokalen Rechner erreichbar).
@ -499,10 +493,12 @@ liegen gebliebener Regressionsfehler, jetzt auf `role_names` korrigiert.
> `async`-Interface) erlauben es, die **echten** Endpunkt-Funktionen aus
> `app/admin/routes.py` direkt (ohne HTTP-Layer) gegen eine echte
> In-Memory-`sqlite3`-Datenbank mit allen 7 Migrationen auszufuehren — damit
> wurden ueber 20 Integrations-Assertions (Mandanten-Isolation,
> Rollen-Mehrfachvergabe, Delete-Semantik) tatsaechlich lauffaehig
> verifiziert, nicht nur simulierte SQL-Queries. Der neue `tests/test_tenants.py`
> selbst nutzt weiterhin `httpx.AsyncClient` gegen die echte ASGI-App (wie
> wurden ueber 20 Integrations-Assertions (u.a. Tenant-Isolation — seither
> mit Migration 0014 wieder entfernt, siehe oben —, Rollen-Mehrfachvergabe,
> Delete-Semantik) tatsaechlich lauffaehig verifiziert, nicht nur simulierte
> SQL-Queries. Der damalige `tests/test_tenants.py` (heute Nachfolger
> `tests/test_admin_crud.py`) selbst nutzte weiterhin `httpx.AsyncClient`
> gegen die echte ASGI-App (wie
> alle anderen Testdateien) und wurde daher zeilenweise gegen die tatsaechliche
> Endpunkt-Implementierung gegengeprueft (Pfade, Payload-Felder, Statuscodes),
> aber nicht selbst mit `pytest` ausgefuehrt. Fuer Phase 9 zusaetzlich: die
@ -626,7 +622,7 @@ ist die massgebliche, aktuelle Fassung dieser Liste. Kurzfassung:
-> automatisch deaktivieren"-Routine.
11. Ein erneuter `bandit`/`pip-audit`-Lauf ueber die in dieser und der
Phase-8-Session neu hinzugekommenen Dateien (Admin-Oberflaeche,
Gruppen-RBAC, API-Tokens, Mandantenfaehigkeit, Dateitransfer-Panel)
Gruppen-RBAC, API-Tokens, Dateitransfer-Panel)
steht noch aus (keine neuen Fremdabhaengigkeiten hinzugekommen, siehe
oben).
12. **Phase 8 / "Details"-Fix**: der Root-Cause des urspruenglich gemeldeten