connect fix 3

This commit is contained in:
2026-08-20 23:35:42 +02:00
parent fc32b1e4ef
commit bf2a02679d
14 changed files with 956 additions and 49 deletions

View File

@ -252,13 +252,89 @@ Mandanten-Admin sein zu muessen (neue Dependency
`GET /admin/hosts/{id}/credentials`, Dashboard zeigt einen "Zugangsdaten"-
Knopf bei Hosts mit dieser Rolle).
## Phase 10: SSH-Key-Passphrasen und funktionsfaehige RDP-Sitzungen
Behebt die Ursachen dafuer, dass weder SSH- noch RDP-Sitzungen zustande kamen.
**SSH -- passphrasegeschuetzte Schluessel (Migration 0009,
`ssh_keys.passphrase_enc`).** `asyncssh.import_private_key()` wurde ohne
Passphrase aufgerufen; ein mit `ssh-keygen` ueblich erzeugter, verschluesselter
Schluessel liess sich damit prinzipiell nicht laden
(`KeyImportError: Passphrase must be specified to import encrypted private
keys`). Weil dieser Fehler ein `ValueError` und *kein* `asyncssh.Error` ist,
lief er an allen Fehlerbehandlungen der WebSocket-Route vorbei -- der Browser
sah nur ein wortloses Verbindungsende. Neu:
* Beim Anlegen und Rotieren eines Schluessels kann eine Passphrase angegeben
werden. Sie wird wie das Schluesselmaterial selbst mit dem KEK
(AES-256-GCM) verschluesselt gespeichert und ueber keinen Endpunkt jemals
zurueckgegeben -- die Liste zeigt nur `has_passphrase`.
* Zu einem **bereits hinterlegten** Schluessel laesst sich die Passphrase
nachtragen (Adminbereich -> Zugangsdaten -> SSH-Key bearbeiten). Sie wird
dabei sofort gegen das gespeicherte Schluesselmaterial geprueft; passt sie
nicht, wird nichts gespeichert.
* Jeder Upload wird sofort gegen asyncssh validiert: ein unbrauchbarer
Schluessel wird mit HTTP 400 und Klartextbegruendung abgelehnt, statt erst
beim ersten Verbindungsversuch eines Benutzers aufzufallen.
* Scheitert das Laden trotzdem, meldet die Sitzung jetzt `PrivateKeyUnusableError`
mit verstaendlichem Text an Terminal und Dateitransfer statt kommentarlos
abzubrechen.
**RDP -- vier unabhaengige Fehler, die jede Sitzung verhinderten.**
1. `static/js/rdp.js` haengte die Verbindungsparameter an die Tunnel-URL an --
`Guacamole.WebSocketTunnel.connect(data)` haengt aber selbst noch
`"?" + data` an. Daraus wurde `...&dpi=96?undefined`; FastAPI wies den
WebSocket wegen der ungueltigen Query noch vor dem Routenhandler ab, und
im Verbindungslog erschien **kein einziger Eintrag**. Die Parameter werden
jetzt an `connect()` uebergeben.
2. guacamole-common-js oeffnet den Socket immer mit dem Subprotokoll
`guacamole`. Der Server bestaetigte es nicht, woraufhin der Browser die
Verbindung nach RFC 6455 sofort wieder verwarf. `websocket.accept()` setzt
es jetzt.
3. `load_host()` selektierte `rdp_username`, `rdp_domain`, `rdp_require_nla`
und `clipboard_enabled` nicht -- `build_rdp_params()` baute daraus eine
`connect`-Instruktion **ohne Benutzernamen** (Anmeldung am Ziel scheitert,
der Client haengt in "Warte auf Server ..."), und die Zwischenablage war
unabhaengig von der Hostkonfiguration immer gesperrt. Fehlt jetzt eine der
Angaben, meldet der Server das im Klartext statt still eine kaputte
Verbindung aufzubauen.
4. `ignore-cert` war hart auf `false` verdrahtet. Windows-Ziele ohne eigene
PKI weisen sich mit einem selbstsignierten Zertifikat aus, guacd/FreeRDP
bricht dann vor dem ersten Bild ab. Neu: `hosts.rdp_ignore_cert`
(Migration 0009, Default: ignorieren) mit Checkbox im Host-Formular.
Ausserdem im Guacamole-Protokoll behoben: die Laengenangaben zaehlen
**Zeichen**, nicht Bytes (so ist das Protokoll definiert, und so zaehlen guacd
und guacamole-common-js). Zuvor stand dort die UTF-8-Bytelaenge -- ein
einziger Umlaut, etwa in einem RDP-Passwort oder in der Zwischenablage,
verschob den gesamten nachfolgenden Datenstrom. Tunnelinterne Instruktionen
(leerer Opcode: `ping`, Tunnel-UUID) werden nicht mehr an guacd
durchgereicht, ein `ping` wird gespiegelt, und der Server sendet die
Tunnel-UUID als erste Instruktion -- sonst blieb der Client bis zum ersten
Bild in "Warte auf Server ..." und lief bei einem langsamen RDP-Handshake in
den 15-Sekunden-Timeout.
**Sichtbarkeit.** Saemtliche Abbruchpfade *vor* dem eigentlichen
Sitzungsbeginn (fehlende Berechtigung, unbekannter Host, falsches Protokoll,
fehlendes RDP-Passwort) schlossen den Socket bisher kommentarlos. Sie
protokollieren jetzt und geben den Grund als WebSocket-Close-Reason mit, den
`rdp.js` direkt anzeigt.
### Nach dem Deployment zu tun
1. Migration 0009 laeuft beim Start automatisch.
2. Zu jedem bereits hinterlegten, passphrasegeschuetzten SSH-Key die
Passphrase nachtragen (Adminbereich -> Zugangsdaten -> SSH-Key bearbeiten).
3. Bei RDP-Hosts pruefen, dass ein RDP-Benutzername gesetzt ist.
## Tests
```bash
pytest -q
```
68 Tests (vorher 61) decken ab: Argon2id/TOTP-Grundfunktionen, Audit-Hash-Chain
78 Tests (vorher 68; `tests/test_phase10.py` kam hinzu) 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