more admin stuff

This commit is contained in:
2026-08-20 14:49:19 +02:00
parent 91758a2701
commit 0e67092ba0
14 changed files with 3030 additions and 247 deletions

154
README.md
View File

@ -16,7 +16,7 @@ app/ Backend (FastAPI, Python 3.11+)
ssh_proxy/ SSH-Terminal-WebSocket + SFTP-Filetransfer
rdp_proxy/ Guacamole-Protokoll-Tunnel zu guacd
recordings/ Hash-verkettete Session-Aufzeichnung
db/ SQLite-Migrationen (0001-0005, laufen automatisch beim Start)
db/ SQLite-Migrationen (0001-0007, laufen automatisch beim Start)
static/, templates/ Frontend (Vanilla JS, xterm.js, guacamole-common-js, Admin-Oberflaeche, API-Doku)
scripts/ Betriebs-/Hilfsskripte (Admin anlegen, Assets bauen)
ansible/ Deployment (Rollen, systemd-Unit-Templates)
@ -48,10 +48,10 @@ verwalten; ein manueller Umweg ueber die rohe JSON-API ist dafuer nicht mehr
noetig. Fuer RDP muss zusaetzlich ein laufender `guacd` erreichbar sein (siehe
`JUMPHOST_GUACD_HOST`/`_PORT`).
Die SQLite-Migrationen 0004 (`user_groups`) und 0005 (`api_tokens`) werden
beim naechsten Start automatisch angewendet (`app/db.py`, `_apply_migrations`)
— kein manueller Migrationsschritt noetig, auch nicht bei einem bestehenden
Datenbestand.
Die SQLite-Migrationen 0004 (`user_groups`), 0005 (`api_tokens`), 0006
(`tenants`) 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.
## Admin-Oberflaeche, Gruppen-RBAC, API-Tokens und API-Dokumentation
@ -105,13 +105,94 @@ 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
Diese Session hat die Admin-Oberflaeche um sieben 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
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-
Entscheidungen dabei: ein Benutzerkonto mit vorhandener Audit-Historie wird
**nicht** hart geloescht (die Hash-Chain verweist absichtlich ohne `ON DELETE
CASCADE` auf `users.id`, siehe Migration `0007_crud_extras.sql`), sondern
deaktiviert und anonymisiert (`deleted_user_<id>`, Passwort/TOTP geloescht);
nur ein Konto ganz ohne Audit-Spuren wird tatsaechlich entfernt — die
Response (`hard_deleted: true/false`) zeigt an, welcher Fall eintrat. Ein
Host wird per Default **soft-deleted** (`is_active=0`, wie schon zuvor vom
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
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-
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
`showHostDetail()`-Funktion in `admin.js` ersetzt, die bei jedem Aufruf
frisch nachlaedt statt sich auf ggf. veraltete Listendaten zu verlassen, und
Fehler inline anzeigt statt sie zu verschlucken. Da diese Sandbox die App
nicht tatsaechlich im Browser ausfuehren kann, war die urspruengliche
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-
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()`-
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
eigenem Formular statt Browser-`prompt()`) und **Download** (neu in der UI;
der Backend-Endpunkt `GET /ssh/{host_id}/files/download` existierte bereits
und wird per `fetch()` + Blob + synthetischem `<a download>`-Link
angesteuert, damit Fehler inline im Panel erscheinen statt die Seite zu
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
`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
Hostgruppe erhalten (Checkbox-Raster statt Dropdown in der Oberflaeche).
Das Entziehen bleibt bewusst pro Zeile/Rolle (`role_name`, Einzahl) — das
entspricht dem bestehenden "Entziehen"-Knopf pro Tabellenzeile und braucht
keine Mehrfachauswahl.
## Tests
```bash
pytest -q
```
46 Tests (vorher 27) decken ab: Argon2id/TOTP-Grundfunktionen, Audit-Hash-Chain
61 Tests (vorher 46) decken ab: Argon2id/TOTP-Grundfunktionen, Audit-Hash-Chain
(inkl. Manipulationserkennung und Trigger-Durchsetzung), RBAC-Logik inkl.
Ablaufdaten, den vollstaendigen Login-Flow (Passwort -> TOTP-Enrollment ->
Session-Cookie -> geschuetzte Endpunkte) gegen die echte FastAPI-App, 17
@ -125,19 +206,42 @@ 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`.
`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,
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.
> **Hinweis:** Die 19 in dieser Session neu hinzugekommenen Tests (5 CSP +
> 14 Admin/Gruppen/Token) konnten in der verwendeten Cloud-Sandbox nicht
> mit `pytest -q` ausgefuehrt werden, da diese Sandbox keinen Netzwerkzugriff
> auf PyPI hat und `fastapi`/`aiosqlite` dort nicht vorinstalliert sind.
> Stattdessen wurden alle neuen/gaenderten SQL-Queries gegen eine echte
> `sqlite3`-Instanz mit allen 5 Migrationen (inkl. der beiden neuen) manuell
> durchgespielt, `app/security/api_tokens.py` und die neuen Pydantic-Schemas
> direkt importiert und mit echten Assertions verifiziert, und alle
> geaenderten Python-/JS-Dateien mit `py_compile`/`node --check` auf
> Syntaxfehler geprueft. Bitte `pytest -q` lokal ausfuehren und Ergebnis
> melden.
> **Hinweis:** Alle 34 in dieser und der vorherigen Session neu
> hinzugekommenen Tests (5 CSP + 14 Admin/Gruppen/Token + 15 Mandanten/CRUD)
> konnten in der 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.
> Fuer Phase 8 wurde stattdessen ein tieferes Verifikationsverfahren
> angewendet als in der vorherigen Session: minimale Stub-Module fuer die
> vier fehlenden Pakete (`fastapi`s `APIRouter`-Dekoratoren als No-Ops, ein
> synchrones `sqlite3`-basiertes Shim mit aiosqlite-kompatiblem
> `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
> alle anderen Testdateien) und wurde daher zeilenweise gegen die tatsaechliche
> Endpunkt-Implementierung gegengeprueft (Pfade, Payload-Felder, Statuscodes),
> aber nicht selbst mit `pytest` ausgefuehrt. Alle geaenderten Python-/
> JS-Dateien wurden mit `py_compile`/`node --check` auf Syntaxfehler geprueft,
> und jeder `document.getElementById`-Aufruf in `admin.js`/`terminal.js` wurde
> automatisiert gegen die tatsaechlichen HTML-IDs in `admin.html`/`terminal.html`
> abgeglichen (0 Abweichungen). Bitte `pytest -q` lokal ausfuehren und
> Ergebnis melden.
Manuell zusaetzlich verifiziert (siehe Entwicklungs-Log dieser Session):
Server-Start, Static-/Template-Auslieferung, Security-Header, vollstaendiger
@ -241,7 +345,15 @@ ist die massgebliche, aktuelle Fassung dieser Liste. Kurzfassung:
gibt noch keine erzwungene maximale Laufzeit, keine automatische
Benachrichtigung vor Ablauf und keine "Token zuletzt benutzt vor X Tagen
-> automatisch deaktivieren"-Routine.
11. Ein erneuter `bandit`/`pip-audit`-Lauf ueber die in dieser Session neu
hinzugekommenen Dateien (Admin-Oberflaeche, Gruppen-RBAC, API-Tokens)
steht noch aus (siehe oben).
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)
steht noch aus (keine neuen Fremdabhaengigkeiten hinzugekommen, siehe
oben).
12. **Phase 8 / "Details"-Fix**: der Root-Cause des urspruenglich gemeldeten
defekten "Details"-Buttons konnte in dieser Sandbox nicht reproduziert
werden (die App laesst sich hier nicht im Browser ausfuehren) — der neue
`GET /admin/hosts/{id}`-Endpunkt plus robustere `showHostDetail()`-Logik
sollten das Problem loesen, sollten aber nach dem Deployment einmal
manuell im Browser bestaetigt werden.
</content>