446 lines
27 KiB
Markdown
446 lines
27 KiB
Markdown
# Jumphost Gateway — Implementierung
|
|
|
|
Umsetzung des Konzepts `Jumphost_Konzept.md` (v0.2): browserbasiertes
|
|
SSH/RDP-Gateway mit TOTP-Pflicht, Gruppen-/Hostgruppen-RBAC, manipulationssicherem
|
|
Audit-Log, SQLite/Python-Backend, browserbasierter Admin-Oberflaeche und
|
|
Ansible-Deployment (mit/ohne nginx, mehrere TLS-Modi).
|
|
|
|
## Verzeichnisstruktur
|
|
|
|
```
|
|
app/ Backend (FastAPI, Python 3.11+)
|
|
security/ Crypto, Passwoerter, TOTP, Sessions, Audit-Hash-Chain, AV-Scan, API-Tokens
|
|
auth/ Login-Flow, RBAC-/Token-Dependencies
|
|
admin/ Admin-API (User/Gruppen/Hosts/Hostgruppen/Rollen/SSH-Keys/API-Tokens)
|
|
catalog/ Sicht fuer normale Nutzer (direkt und ueber Gruppen zugewiesene Hosts)
|
|
ssh_proxy/ SSH-Terminal-WebSocket + SFTP-Filetransfer
|
|
rdp_proxy/ Guacamole-Protokoll-Tunnel zu guacd
|
|
recordings/ Hash-verkettete Session-Aufzeichnung
|
|
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)
|
|
tests/ pytest-Suite
|
|
```
|
|
|
|
## Lokale Entwicklung / Ausprobieren
|
|
|
|
```bash
|
|
python3 -m venv .venv && source .venv/bin/activate
|
|
pip install -r requirements-dev.txt
|
|
bash scripts/fetch_frontend_assets.sh # vendored xterm.js / guacamole-common-js
|
|
|
|
export JUMPHOST_ENV=development
|
|
export JUMPHOST_DATA_DIR=/tmp/jumphost-dev
|
|
export JUMPHOST_DEV_KEK=$(python3 -c "import secrets;print(secrets.token_hex(32))")
|
|
export JUMPHOST_DEV_SESSION_SECRET=$(python3 -c "import secrets;print(secrets.token_hex(32))")
|
|
|
|
uvicorn app.main:app --reload --port 8000
|
|
python scripts/create_admin.py --username admin # in zweitem Terminal
|
|
```
|
|
|
|
Danach `http://127.0.0.1:8000/` oeffnen, anmelden, TOTP einrichten (QR-Code
|
|
scannen). Auf dem Dashboard erscheint fuer Admin-Konten oben rechts der Link
|
|
**"Admin-Bereich"** (`/admin`) — darueber lassen sich Benutzer, Benutzergruppen,
|
|
Hostgruppen/Hosts/SSH-Keys/RDP-Zugangsdaten, Rollenzuweisungen (direkt und
|
|
gruppenweise) sowie API-Tokens vollstaendig ueber die Oberflaeche anlegen und
|
|
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`), 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
|
|
|
|
In dieser Session wurde die bis dahin nur per JSON-API bedienbare Verwaltung
|
|
um eine vollstaendige, CSP-konforme Web-Oberflaeche unter `/admin` erweitert
|
|
(`templates/admin.html`, `static/js/admin.js`), erreichbar nur fuer Admin-
|
|
Konten. Tabs: Benutzer, Benutzergruppen, Hosts & Verbindungen (inkl.
|
|
Connection-Anlage: Hostgruppe, Host, SSH-Key bzw. RDP-Zugangsdaten in einem
|
|
Formular), Rollen, API-Tokens, Audit-Log.
|
|
|
|
**Gruppen-RBAC (Migration `0004_user_groups.sql`)**: Neu sind
|
|
*Benutzergruppen* (`user_groups`/`user_group_members`) als Teams von
|
|
Personen, unabhaengig von den bestehenden *Hostgruppen* (Server-Gruppen).
|
|
Eine Rolle kann einer Benutzergruppe auf eine Hostgruppe gewaehrt werden
|
|
(`group_hostgroup_roles`) — das ist das "Sharen einer Verbindung mit einer
|
|
Gruppe". Es gilt **volle Rollen-Vererbung**: jedes Mitglied einer Gruppe hat
|
|
automatisch alle Rollen, die dieser Gruppe gewaehrt wurden, zusaetzlich zu
|
|
seinen individuell zugewiesenen (`user_hostgroup_roles`, unveraendert
|
|
bestehen geblieben). `app/rbac.py::user_has_role()` prueft dafuer beide
|
|
Zuweisungswege (UNION-Query); `app/catalog/routes.py` (Host-Liste fuer
|
|
normale Nutzer) beruecksichtigt beide ebenso.
|
|
|
|
**API-Tokens (Migration `0005_api_tokens.sql`, `app/security/api_tokens.py`)**:
|
|
Jeder Admin kann unter dem Tab "API-Tokens" persoenliche Tokens erzeugen
|
|
(`Authorization: Bearer <token>`), mit granularen **Read/Write-Scopes pro
|
|
Ressource** (z. B. `hosts:ro`, `hosts:rw`, `users:ro`, `groups:rw`, ...
|
|
vollstaendige Liste unter `GET /admin/scopes`). Tokens werden nur gehasht
|
|
gespeichert (Klartext ist nur einmal direkt nach dem Anlegen sichtbar) und
|
|
koennen jederzeit widerrufen werden. `app/auth/deps.py::require_admin_or_scope()`
|
|
akzeptiert entweder eine gueltige Admin-Session (Cookie, wie bisher) oder
|
|
einen Token mit passendem Scope — **niemals beides vermischt**: sobald ein
|
|
`Authorization`-Header vorhanden ist, wird ausschliesslich er geprueft, ein
|
|
evtl. noch gueltiges Session-Cookie wird dabei ignoriert. Aus Gruenden der
|
|
Rechte-Eskalation sind die Token-Verwaltungs-Endpunkte selbst
|
|
(`/admin/tokens/*`) bewusst **nicht** per Token nutzbar, sondern ausschliesslich
|
|
per Admin-Session (`require_global_admin`) — ein geleaktes Token kann also nie
|
|
weitere Tokens anlegen, auflisten oder widerrufen. Tokens sind ausschliesslich
|
|
fuer die Management-API vorgesehen; SSH-/RDP-/SFTP-Sitzungen bleiben
|
|
Session-Cookie-authentifiziert.
|
|
|
|
**API-Dokumentation (`/docs`, `/openapi.json`)**: Nur fuer eingeloggte Admins
|
|
sichtbar (`Depends(require_global_admin)` auf beiden Routen in `app/main.py`)
|
|
— das bestehende Haertungskonzept, `/docs`/`/redoc`/`/openapi` nicht oeffentlich
|
|
per FastAPI-Default auszuliefern (`docs_url=None, redoc_url=None,
|
|
openapi_url=None`), bleibt unangetastet. Die Ansicht selbst
|
|
(`templates/api_docs.html`, `static/js/api-docs.js`) ist bewusst **kein**
|
|
vendored/CDN-bezogenes Swagger-UI-Bundle, sondern ein selbstgebauter,
|
|
CSP-konformer Viewer ohne jede Laufzeit-Abhaengigkeit zu Drittanbietern: er
|
|
liest `/openapi.json` per `fetch()` (`credentials: "same-origin"`) und
|
|
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.
|
|
|
|
## Phase 9: Host-Key-Bugfix, Sessionview, Live-Verbindungslog, Credentials-RBAC
|
|
|
|
Sechs zusammenhaengende Punkte aus einer erneuten nummerierten Anfrage, in
|
|
derselben Session direkt nach Phase 8 umgesetzt:
|
|
|
|
**1) Bugfix "Host Key ermitteln" (Fehler 500)**: `discover_and_store_host_key()`
|
|
(`app/ssh_proxy/proxy.py`) verband sich zwar erfolgreich fuer den Key-Exchange,
|
|
liess aber den danach IMMER folgenden Authentifizierungsfehler (kein
|
|
Client-Key fuer die reine Discovery hinterlegt) unbehandelt durchreichen —
|
|
das riss als unbehandelte Exception bis zu FastAPI durch und fuehrte praktisch
|
|
bei jedem Klick auf "Host-Key ermitteln" zu einem 500. Fix: der waehrend des
|
|
Key-Exchange bereits erfasste Fingerprint gilt jetzt trotz des danach
|
|
erwarteten Auth-Fehlers als Erfolg; nur ein echter Verbindungsfehler VOR dem
|
|
Key-Exchange (Host nicht erreichbar, DNS, Timeout) liefert jetzt sauber 502
|
|
statt 500 (neue `HostKeyDiscoveryError`).
|
|
|
|
**2) Login-Verlauf entfernt**: der in Phase 8 hinzugefuegte Tab "Login-Verlauf"
|
|
wurde aus der Admin-Oberflaeche entfernt (Login-/Logout-/Fehlversuch-Ereignisse
|
|
bleiben weiterhin vollstaendig im Audit-Log-Tab sichtbar, dort war die
|
|
Information ohnehin redundant vorhanden).
|
|
|
|
**3) Verbindungslog (Live-Tail, inkl. Debug)**: neuer Tab "Verbindungslog" +
|
|
WebSocket `GET /admin/ws/logs` streamen die Anwendungslogs dieses
|
|
Server-Prozesses live in den Browser — inkl. DEBUG-Detail zum SSH/RDP-
|
|
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.
|
|
|
|
**4) Sessionview-Dashboard (nur Super-Admin)**: neuer Tab "Sessions" +
|
|
`GET /admin/sessions` (aktive + historische Sitzungen ueber alle Mandanten),
|
|
`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
|
|
(`app/security/active_sessions.py`) + `asyncio.Task.cancel()` auf die
|
|
WebSocket-Route der Sitzung, was den bestehenden Cleanup-Pfad (DB-Update,
|
|
Audit-Log-Eintrag, WebSocket schliessen) unveraendert durchlaufen laesst —
|
|
funktioniert nur fuer Sitzungen auf demselben Server-Prozess (`killable`-Feld
|
|
in der Response zeigt das an, statt einen Beenden-Versuch fehlschlagen zu
|
|
lassen).
|
|
|
|
**5) Hostgruppen/Server im Menue getrennt**: der bisherige kombinierte Tab
|
|
"Hosts & Verbindungen" wurde in zwei eigene Tabs "Hostgruppen" und "Server"
|
|
aufgeteilt (gleicher Ladepfad/gleiche Formulare, nur die Navigation ist jetzt
|
|
getrennt).
|
|
|
|
**6) Credentials ins RBAC-Modell**: zwei neue Rollen `credentials_view`/
|
|
`credentials_manage` (Migration `0008_credentials_roles.sql`), die — wie
|
|
`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
|
|
`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).
|
|
|
|
## Tests
|
|
|
|
```bash
|
|
pytest -q
|
|
```
|
|
|
|
68 Tests (vorher 61) 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
|
|
dedizierte Security-/Pentest-Tests (`tests/test_pentest_security.py`), 5
|
|
CSP-Regressionstests (`tests/test_csp_compliance.py`, inkl. `/admin` und
|
|
`/docs`) sowie 14 neue Tests zu Gruppen-RBAC-Vererbung, Admin-Oberflaeche und
|
|
API-Tokens (`tests/test_admin_groups_tokens.py`): Rollen-Vererbung ueber
|
|
Gruppenmitgliedschaft, dass Gruppenrollen NICHT auf Nicht-Mitglieder wirken,
|
|
Admin-only-Durchsetzung fuer alle neuen Endpunkte, Token-Scope-Durchsetzung
|
|
(read/write/implizites read-bei-write/widerrufen/abgelaufen/unbekannter
|
|
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,
|
|
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
|
|
(`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
|
|
= 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
|
|
gezielten Lese- bzw. Lese+Schreib-Zugriff auf Zugangsdaten geben (und ein User
|
|
ganz ohne diese Rolle weiterhin 403 bekommt), inkl. des neuen
|
|
`can_view_credentials`-Felds in `GET /catalog/hosts`. Nebenbei beim
|
|
Gegenpruefen entdeckt und mitkorrigiert: drei Tests in
|
|
`tests/test_admin_groups_tokens.py` sendeten noch das VOR Phase 8 gueltige
|
|
Payload-Feld `role_name` (Einzahl) an `/admin/group-roles/grant`, das
|
|
`GroupRoleGrantRequest` seit der Umstellung auf Mehrfachauswahl aber gar nicht
|
|
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`
|
|
> 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).
|
|
> 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. Fuer Phase 9 zusaetzlich: die
|
|
> reparierte Kontrollfluss-Logik von `discover_and_store_host_key()` wurde
|
|
> mit einem eigenstaendigen Fake-`asyncssh`-Nachbau isoliert durchgespielt
|
|
> (alle drei Faelle: Verbindungsfehler vor Key-Exchange, Auth-Fehler NACH
|
|
> Key-Exchange = der eigentliche Bug, voller Erfolg), Migration
|
|
> `0008_credentials_roles.sql` wurde sowohl gegen eine leere als auch gegen
|
|
> eine bereits mit 0001-0007 befuellte In-Memory-DB angewendet (Lehre aus dem
|
|
> Migrations-Vorfall in Phase 8, siehe unten), und die neuen, rein
|
|
> In-Process-Module `app/security/log_stream.py` und
|
|
> `app/security/active_sessions.py` (kein DB-/HTTP-Bezug) wurden direkt
|
|
> importiert und gegen echtes `asyncio`/`logging` ausgefuehrt statt nur
|
|
> gelesen. Alle geaenderten Python-/JS-Dateien wurden mit
|
|
> `py_compile`/`node --check` auf Syntaxfehler geprueft, und jeder
|
|
> `document.getElementById`-Aufruf in `admin.js` wurde automatisiert gegen
|
|
> die tatsaechlichen HTML-IDs in `admin.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
|
|
Login+TOTP-Flow per curl, Admin-CRUD (Hostgruppe/Host anlegen), Audit-Log-
|
|
Chain-Verifikation per `/admin/audit-log/verify`.
|
|
|
|
## Security-Tests & Haertungs-Nachweis
|
|
|
|
- **SAST**: `bandit -r app -c .bandit.yml` sowie `pip-audit` (Dependency-CVE-
|
|
Scan) liefen zu Beginn dieser Implementierung sauber durch (0 Findings, 0
|
|
bekannte Schwachstellen) — Details inkl. vorher/nachher-Tabellen und der
|
|
dabei gefundenen und gefixten Starlette-Regression in `Pentest_Report.md`.
|
|
Fuer die in dieser Session neu hinzugekommenen Dateien (Admin-Oberflaeche,
|
|
Gruppen-RBAC, API-Tokens) steht ein erneuter `bandit`/`pip-audit`-Lauf noch
|
|
aus (keine neuen Fremdabhaengigkeiten hinzugekommen).
|
|
- **DAST**: `tests/test_pentest_security.py` (17 Tests) sowie
|
|
`tests/test_admin_groups_tokens.py` (14 Tests, s.o.) simulieren konkrete
|
|
Angriffsmuster gegen die laufende ASGI-App.
|
|
- **OS-Haertung (CIS/STIG)**: die `ansible/roles/os_hardening`-Rolle wurde
|
|
in einer frueheren Session um ~10 zusaetzliche Task-Dateien vertieft
|
|
(Kernel-Module, sysctl, PAM/Passwort-Policy, erweiterte auditd-Regeln,
|
|
AIDE, rkhunter, Banner, cron/at-Restriktion, Dateirechte/sudo-Logging,
|
|
SSHD-Haertung). Vollstaendiges Mapping auf CIS-Controls inkl. bewusst
|
|
nicht automatisierter Punkte (mit Begruendung, z.B. Partitionslayout,
|
|
Bootloader-Passwort, Volltextverschluesselung, physische Sicherheit) und
|
|
bekannter Einschraenkungen: `ansible/roles/os_hardening/CIS_STIG_MAPPING.md`.
|
|
- Abhaengigkeiten sind in `requirements.txt`/`requirements-dev.txt` exakt
|
|
auf gegen `pip-audit` gepruefte Versionen gepinnt (u.a. fastapi 0.141.1,
|
|
starlette 1.6.0, cryptography 50.0.0) — bewusste Reproduzierbarkeits-/
|
|
Haertungsmassnahme, siehe `Pentest_Report.md` Abschnitt 2.
|
|
|
|
## Deployment
|
|
|
|
```bash
|
|
cd ansible
|
|
cp inventory/production.ini.example inventory/production.ini # anpassen
|
|
cp inventory/group_vars/jumphost_with_nginx.yml.example inventory/group_vars/jumphost_with_nginx.yml
|
|
ansible-vault encrypt inventory/group_vars/vault.yml # vorher aus vault.yml.example befuellen
|
|
ansible-playbook -i inventory/production.ini site.yml --ask-vault-pass
|
|
```
|
|
|
|
`enable_nginx_proxy` und `tls_mode` (`internal_pki` / `external_reverse_proxy`
|
|
/ `acme_public`) steuern Proxy- und TLS-Verhalten, siehe Konzept Kap. 7.2/7.2a
|
|
und `ansible/inventory/group_vars/all.yml`.
|
|
|
|
`ansible-playbook site.yml --syntax-check` laeuft sauber durch (in einer
|
|
frueheren Session verifiziert); ein voller `--check`-Lauf gegen eine echte
|
|
Testumgebung (inkl. `apt`, `systemd`, `guacd`-Paketverfuegbarkeit auf der
|
|
Zieldistribution) steht noch aus. Ein Neustart des Dienstes (`systemctl
|
|
restart jumphost`) reicht aus, damit die Migrationen 0004/0005 angewendet
|
|
werden.
|
|
|
|
## Was bewusst noch offen ist
|
|
|
|
Diese Implementierung ist ein funktionsfaehiges, getestetes und in einer
|
|
frueheren Session per SAST+DAST geprueftes Grundgeruest, aber weiterhin
|
|
**kein fertig auditiertes Produktivsystem**. Der vollstaendige Befund inkl.
|
|
Methodik, Vorgehen und Restrisikobewertung steht in `Pentest_Report.md` — die
|
|
dortige Abschnitt-4-Tabelle ("Restrisiko / vor Produktivbetrieb noch zu tun")
|
|
ist die massgebliche, aktuelle Fassung dieser Liste. Kurzfassung:
|
|
|
|
1. **Echter Netzwerk-Penetrationstest** gegen eine laufende Instanz
|
|
(Portscan, TLS-Konfiguration live, Session-Isolation unter Last,
|
|
Guacamole-Protokoll-Fuzzing) — in dieser Sandbox ohne Netzwerkzugriff auf
|
|
ein reales Zielsystem nicht durchfuehrbar. Was stattdessen gemacht wurde:
|
|
SAST (bandit, pip-audit) und ein DAST-Testlauf gegen die App im Prozess
|
|
(17+14 Security-/Admin-Tests, siehe `Pentest_Report.md` bzw. oben).
|
|
2. **RDP/guacd-Integrationstest gegen echte Zielsysteme** — die
|
|
Guacamole-Protokoll-Implementierung (`app/rdp_proxy/guacd_client.py`) wurde
|
|
gegen die Protokollspezifikation implementiert und die Handshake-Logik
|
|
lokal auf Korrektheit der Kodierung geprueft, aber NICHT gegen einen
|
|
laufenden `guacd` + FreeRDP + Windows-Ziel end-to-end getestet (keine
|
|
RDP-Zielumgebung in dieser Sitzung verfuegbar). Parameter-Namen/-Reihenfolge
|
|
sollten gegen die tatsaechlich eingesetzte guacd-Version verifiziert werden.
|
|
3. **Ansible-Rollout gegen ein reales Zielsystem** (`--check`-Dry-Run und
|
|
echter Rollout in einer Staging-Umgebung) — bisher nur
|
|
`ansible-playbook site.yml --syntax-check` sowie YAML-/Jinja2-Parsing
|
|
verifiziert, siehe `ansible/roles/os_hardening/CIS_STIG_MAPPING.md`.
|
|
4. **OpenSCAP-Compliance-Scan** (`oscap xccdf eval`) gegen das zutreffende
|
|
CIS/STIG-Profil — die `os_hardening`-Rolle wurde in einer frueheren
|
|
Session um ca. 10 Task-Dateien vertieft (siehe CIS_STIG_MAPPING.md fuer
|
|
das vollstaendige Mapping inkl. bewusst nicht automatisierter Punkte),
|
|
ersetzt aber keinen zertifizierten Benchmark-Scan.
|
|
5. **Verteiltes Rate-Limiting** — der aktuelle Login-Rate-Limiter ist
|
|
In-Memory/Single-Process (siehe `app/security/rate_limit.py`); bei
|
|
horizontaler Skalierung durch einen geteilten Store ersetzen.
|
|
6. **fail2ban-Filter** setzt strukturierte Access-Logs mit `client_ip=`-Feld
|
|
voraus, die die App aktuell nicht schreibt — vor Produktivbetrieb ein
|
|
Access-Log-Middleware ergaenzen oder auf das Audit-Log umstellen.
|
|
7. **CSRF**: SameSite=Strict-Cookies mindern das Risiko bereits deutlich;
|
|
ein expliziter CSRF-Token fuer zustandsaendernde JSON-Requests ist als
|
|
zusaetzliche Haertungsstufe vorgesehen, aber noch nicht implementiert.
|
|
8. Alle in Konzept Kap. 12 genannten Erweiterungen (LDAP/AD, WebAuthn,
|
|
PostgreSQL-Migrationspfad, Just-in-Time-Zugriff, HA) sind noch nicht
|
|
umgesetzt.
|
|
9. **Social Engineering / physische Sicherheit / Lastest (DoS)** wurden
|
|
nicht getestet — ausserhalb des Scopes eines Code-/Konfigurations-Reviews
|
|
in dieser Sandbox-Umgebung.
|
|
10. **API-Token-Rotation/-Ablaufrichtlinien**: Tokens koennen zwar mit
|
|
optionalem Ablaufdatum erzeugt und jederzeit widerrufen werden, aber es
|
|
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 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>
|