commit 411812e9546b2f64ed86753bb241adf8ac5abd33 Author: Midas Wollinger Date: Wed Aug 19 22:31:07 2026 +0200 first commit diff --git a/Jumphost_Konzept.md b/Jumphost_Konzept.md new file mode 100644 index 0000000..af03935 --- /dev/null +++ b/Jumphost_Konzept.md @@ -0,0 +1,547 @@ +# Konzept: Sicherer Multi-Protokoll-Jumphost (SSH/RDP) mit Web-Gateway + +**Version:** 0.2 (Entwurf)  |  **Datum:** 19.08.2026  |  **Autor:** Konzepterstellung für Midas + +**Änderungen gegenüber v0.1:** Grundannahme „Jumphost läuft auf Linux" bestätigt; TLS-Zertifikatsmodi für den nginx-Betrieb ergänzt (internes PKI-Zertifikat auf dem Jumphost selbst **oder** vorgelagerter externer Reverse Proxy mit eigener PKI/TLS-Terminierung), siehe Kap. 6.3, 7.2 und 9. + +## Inhaltsverzeichnis + +1. Zielsetzung & Grundannahmen +2. Anforderungsübersicht +3. Architekturüberblick +4. Komponentenbeschreibung +5. Datenmodell (SQLite) +6. Security- und Hardening-Konzept +7. Ansible-Deployment-Konzept +8. Systemd-Services +9. Betrieb mit vs. ohne nginx +10. Rollout-Plan & Teststrategie +11. Technologie-Stack (Zusammenfassung) +12. Offene Punkte & Erweiterungsoptionen + +--- + +## 1. Zielsetzung & Grundannahmen + +Ziel ist ein zentraler **Jumphost/Bastion-Gateway**, über den sämtlicher administrativer Zugriff auf Ziel-Systeme (Linux via SSH, Windows via RDP) erfolgt. Nutzer greifen ausschließlich **browserbasiert** auf eine Weboberfläche zu – es wird kein lokal installierter SSH- oder RDP-Client benötigt. Dadurch lassen sich Vollbildmodus, Copy & Paste, Session-Aufzeichnung und Auditierung zentral und clientunabhängig umsetzen. + +**Grundannahmen:** + +- Der **Jumphost-Server selbst läuft auf Linux** (Voraussetzung für systemd, empfohlen: Debian/Ubuntu LTS oder RHEL/Rocky). "Windows und Linux" bezieht sich auf die **Ziel-Systeme**, nicht auf das Betriebssystem des Jumphosts. ✅ *Bestätigt.* +- Zielgröße: kleine bis mittlere Umgebung (bis ca. 200 Nutzer, bis ca. 1000 verwaltete Hosts). SQLite ist dafür bei korrekter Konfiguration (WAL-Modus, ein Schreibprozess) ausreichend performant. Bei deutlich höherer Last ist ein Umstieg auf PostgreSQL als Erweiterungsoption vorgesehen (siehe Kap. 12). +- Es wird von einer **On-Premises- oder privaten Cloud-Umgebung** ausgegangen, in der der Jumphost die einzige erlaubte Route zu den Zielsystemen ist (Netzwerksegmentierung: Zielsysteme sind aus dem Nutzernetz nicht direkt erreichbar). + +--- + +## 2. Anforderungsübersicht + +| Anforderung | Umsetzung im Konzept | +|---|---| +| Jumphost für Windows- und Linux-Ziele | RDP-Proxy für Windows-Ziele, SSH-Proxy für Linux-Ziele | +| Userverwaltung mit TOTP | Lokale Userverwaltung, Pflicht-2FA via TOTP (RFC 6238) | +| Hostgruppen | RBAC-Modell: Rollen × Hostgruppen × Rechte | +| SSH inkl. Keyverwaltung | Zentrale, verschlüsselte Keyverwaltung, serverseitige Nutzung, keine Key-Auslieferung an Browser | +| RDP | Browserbasierter RDP-Client über guacd/FreeRDP | +| Dateitransfer | SFTP-Kanal (SSH-Ziele) und Laufwerksumleitung (RDP-Ziele) | +| Vollbild | HTML5-Client im Browser-Fullscreen-Modus | +| Copy & Paste | Clipboard-Synchronisation über Guacamole-Protokoll, pro Hostgruppe deaktivierbar | +| Auditlog | Manipulationssicheres, verkettetes Audit-Log aller Aktionen | +| SQLite & Python | Backend in Python (FastAPI/ASGI), SQLite als Datenhaltung | +| Ansible-Playbook | Rollenbasiertes Playbook, Modus mit/ohne nginx | +| systemd Service | Gehärtete Unit-Files für alle Dienste | +| Höchste Security/Hardening | Durchgängiges Hardening auf Anwendungs-, Transport- und OS-Ebene (Kap. 6) | + +--- + +## 3. Architekturüberblick + +``` + ┌─────────────────────────────────────────┐ + │ Nutzer (Browser) │ + │ HTML5-Terminal (xterm.js) + Guacamole- │ + │ Client (guacamole-common-js), TOTP-Login│ + └───────────────────┬───────────────────────┘ + │ HTTPS/WSS (TLS 1.3) + ┌────────────────────────────────┼────────────────────────────────┐ + │ (optional) nginx – TLS-Terminierung, │ + │ Security-Header, Rate-Limiting, WS-Proxy │ + └────────────────────────────────┬────────────────────────────────┘ + │ (localhost / unix socket) + ┌────────────────────────────────▼────────────────────────────────┐ + │ Jumphost-App (Python, FastAPI, ASGI) │ + │ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌──────────────────┐ │ + │ │ Auth & │ │ RBAC / │ │ Session- │ │ Audit-/Logging- │ │ + │ │ TOTP │ │ Hostgrp. │ │ Broker │ │ Engine │ │ + │ └───────────┘ └───────────┘ └───────────┘ └──────────────────┘ │ + │ ┌───────────────────┐ ┌───────────────────────────────────┐ │ + │ │ SSH-Proxy-Modul │ │ RDP-Proxy-Anbindung (guacd) │ │ + │ │ (asyncssh) │ │ via Guacamole-Protokoll │ │ + │ └─────────┬─────────┘ └───────────────┬───────────────────┘ │ + │ │ SQLite (WAL) – Users, Hosts, Keys (verschl.), │ + │ │ Sessions, Audit-Log, Hostgruppen │ + └────────────┼─────────────────────────────┬─────────────────────┘ + │ SSH (Keys aus Vault) │ Guacamole-Protokoll + ┌────────────▼───────────┐ ┌───────────▼──────────────┐ + │ Linux-Zielsysteme │ │ guacd (Protokoll-Daemon) │ + │ (SSH, SFTP) │ │ → RDP via FreeRDP │ + └─────────────────────────┘ └───────────┬───────────────┘ + │ RDP (verschl.) + ┌───────────▼──────────────┐ + │ Windows-Zielsysteme │ + └───────────────────────────┘ +``` + +**Designentscheidung RDP-Proxy:** Für RDP wird empfohlen, **nicht** das komplette Protokoll neu zu implementieren, sondern den bewährten, quelloffenen **guacd**-Daemon (Apache-Guacamole-Projekt, Protokoll-Engine inkl. FreeRDP-Anbindung) als reine Protokoll-Übersetzungskomponente einzusetzen. Die eigentliche Anwendung – Auth, TOTP, RBAC, Hostgruppen, Audit, Oberfläche – bleibt vollständig eigenentwickelt in Python/SQLite; **die Java-basierte Guacamole-Client-Webanwendung selbst wird nicht verwendet**. guacd läuft als eigener, isolierter systemd-Dienst und kommuniziert nur lokal (Unix-Socket oder localhost) mit dem Python-Backend. Für SSH wird kein guacd benötigt – hier terminiert das Python-Backend die Verbindung selbst über eine asynchrone SSH-Bibliothek (`asyncssh`) und pusht Terminal-I/O per WebSocket an `xterm.js` im Browser. + +--- + +## 4. Komponentenbeschreibung + +### 4.1 Backend-Anwendung (Python) +- **Framework:** FastAPI (ASGI) mit Uvicorn/Hypercorn als Server, `asyncio`-basiert für nebenläufige Sessions. +- **API:** REST für Verwaltung (User, Hosts, Gruppen, Keys), WebSocket-Endpunkte für Live-Sessions (SSH-Terminal-Stream, Guacamole-Protokoll-Tunnel). +- **ORM/DB-Zugriff:** SQLAlchemy Core oder direkt `sqlite3`/`aiosqlite` mit strikt parametrisierten Queries (kein String-Concat), Pydantic-Modelle zur Validierung aller Eingaben. +- **Templating/Frontend:** Serverseitig ausgeliefertes, schlankes SPA (z. B. mit Jinja2 + Vanilla-JS/xterm.js/guacamole-common-js), keine unnötigen externen Abhängigkeiten, Assets lokal gehostet (kein CDN-Bezug, reduziert Angriffsfläche und funktioniert offline). + +### 4.2 SSH-Proxy-Modul +- Verbindungsaufbau zum Zielsystem **serverseitig** über `asyncssh`, unter Verwendung des zentral verwalteten Schlüssels (siehe 4.4). +- Der Nutzer sieht im Browser nur ein Terminal (xterm.js über WebSocket) – der private Schlüssel verlässt den Server nie. +- **Strict Host Key Checking**: bekannte Host-Keys werden pro Zielsystem in der DB gepinnt; unbekannte/abweichende Host-Keys blockieren die Verbindung und lösen einen Alert aus (kein automatisches TOFU-Accept in Produktion). +- Terminal-I/O wird parallel in einen Session-Recorder gestreamt (siehe 6.5). +- Sitzungsspezifische Idle-Timeouts und harte Maximaldauer konfigurierbar pro Hostgruppe. + +### 4.3 RDP-Proxy-Anbindung +- Python-Backend baut einen Guacamole-Protokoll-Tunnel zu `guacd` auf (lokal, kein Netzwerkzugriff von außen). +- `guacd` verbindet sich seinerseits per RDP (TLS-verschlüsselt, NLA aktiviert) zum Windows-Zielsystem. +- Browser rendert die Session über `guacamole-common-js` in einem ``-Element. +- **Vollbild:** über die Browser-Fullscreen-API (`element.requestFullscreen()`), vom Backend nicht beeinflussbar, rein clientseitig aktivierbar per Button. +- **Copy & Paste:** native Guacamole-Clipboard-Synchronisation (bidirektional Browser ↔ Zielsystem). Pro Hostgruppe/Verbindung serverseitig deaktivierbar (Compliance-Anforderung für hochsensible Systeme). +- **Dateitransfer bei RDP:** über Guacamole-Laufwerksumleitung (virtuelles Laufwerk `Guacamole RDP FS`), serverseitig ein-/ausschaltbar, mit Upload-Größenlimit und optionalem AV-Scan-Hook (ClamAV) vor Freigabe. + +### 4.4 Keyverwaltung (SSH) +- Private Schlüssel werden **verschlüsselt in SQLite** abgelegt (AES-256-GCM), der Schlüssel-Verschlüsselungs-Schlüssel (KEK) liegt **nicht** in der Datenbank, sondern in einer separaten, restriktiv berechtigten Datei (`0600`, dedizierter Service-User) oder – empfohlen – im systemd-Credential-Store (`systemd-creds`) bzw. optional einer externen KMS/Vault-Anbindung. +- Unterstützt werden sowohl klassische statische Schlüsselpaare (Ed25519, min. RSA-3072 falls Legacy nötig) als auch **kurzlebige, CA-signierte SSH-Zertifikate** (empfohlener Zielzustand): der Jumphost besitzt eine eigene SSH-CA, signiert bei Sessionstart ein Zertifikat mit z. B. 5 Minuten Gültigkeit, das Zielsystem vertraut nur der CA. Dadurch entfällt die Verteilung/Rotation statischer Public Keys auf allen Zielsystemen komplett. +- Rotation: automatisierte Rotation statischer Schlüssel über Ansible-Playbook, Journaling jeder Rotation im Audit-Log. + +### 4.5 Userverwaltung & Authentifizierung +- Lokale User-Tabelle mit Argon2id-Passwort-Hashes (Fallback: bcrypt, falls Argon2-Bibliothek in der Zielumgebung nicht verfügbar). +- **TOTP verpflichtend** für alle Accounts (RFC 6238, kompatibel mit gängigen Authenticator-Apps), Secret verschlüsselt gespeichert, QR-Code-Enrollment nur einmalig anzeigbar, Recovery-Codes (einmalig, gehasht gespeichert). +- Vorbereitet für optionale spätere Erweiterung um WebAuthn/FIDO2 als Ersatz/Ergänzung zu TOTP sowie LDAP/AD-Bind als externe Quelle (siehe Kap. 12). +- Account-Lockout nach N Fehlversuchen (progressive Verzögerung), erzwungener Passwortwechsel nach Erstanmeldung, Session-Timeout (idle + absolute). + +### 4.6 Rollen, Hostgruppen & Rechte (RBAC) +- **Hostgruppen** bündeln Zielsysteme (z. B. „Prod-Linux-DB", „Windows-Fileserver", „DMZ"). +- **Rollen** definieren, was ein Nutzer/eine Gruppe innerhalb einer Hostgruppe darf: `ssh_connect`, `rdp_connect`, `file_transfer`, `clipboard`, `session_recording_view` (Playback fremder Sessions, z. B. für Auditoren), `admin_hostgroup`. +- Zuordnung erfolgt über eine Verknüpfungstabelle User/Gruppe × Hostgruppe × Rolle (siehe Datenmodell), sodass granular z. B. „Team A darf SSH auf Hostgruppe X, aber kein Copy & Paste und keinen Filetransfer" abgebildet werden kann. +- Optional vorbereitet: zeitlich befristete/Just-in-Time-Freigaben (Access-Requests mit Ablaufzeit) als Erweiterung. + +### 4.7 Audit-Log +- Erfasst: Login/Logout (inkl. Fehlversuche), TOTP-Ereignisse, Sessionstart/-ende je Zielsystem, Dateitransfers (Richtung, Dateiname, Größe, Hash), Rechteänderungen, Admin-Aktionen, Verbindungsabbrüche, Host-Key-Mismatches. +- Manipulationssicherheit: jeder Log-Eintrag enthält den Hash des Vorgängereintrags (Hash-Chain, ähnlich einer einfachen Blockchain) – nachträgliches Verändern oder Löschen einzelner Einträge wird durch Kettenbruch erkennbar. Zusätzlich optionale Ausleitung an externen Syslog/SIEM in Echtzeit (Log verlässt damit den Jumphost, unabhängige Kopie). +- Die Audit-Tabelle ist **append-only** auf Anwendungsebene (kein UPDATE/DELETE-Pfad in der API); zusätzlich empfiehlt sich ein DB-Trigger, der `UPDATE`/`DELETE` auf der Tabelle verweigert. + +--- + +## 5. Datenmodell (SQLite) + +Vereinfachtes Kernschema (Auszug, ohne Indizes/Constraints der Übersicht halber gekürzt): + +```sql +PRAGMA journal_mode = WAL; +PRAGMA foreign_keys = ON; + +CREATE TABLE users ( + id INTEGER PRIMARY KEY, + username TEXT UNIQUE NOT NULL, + password_hash TEXT NOT NULL, -- Argon2id + totp_secret_enc BLOB NOT NULL, -- AES-256-GCM verschlüsselt + totp_enrolled INTEGER NOT NULL DEFAULT 0, + is_active INTEGER NOT NULL DEFAULT 1, + is_admin INTEGER NOT NULL DEFAULT 0, + failed_logins INTEGER NOT NULL DEFAULT 0, + locked_until TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + password_changed_at TEXT +); + +CREATE TABLE recovery_codes ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + code_hash TEXT NOT NULL, + used_at TEXT +); + +CREATE TABLE roles ( + id INTEGER PRIMARY KEY, + name TEXT UNIQUE NOT NULL -- z.B. ssh_connect, rdp_connect, file_transfer, clipboard, admin +); + +CREATE TABLE host_groups ( + id INTEGER PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + description TEXT +); + +CREATE TABLE hosts ( + id INTEGER PRIMARY KEY, + host_group_id INTEGER NOT NULL REFERENCES host_groups(id), + hostname TEXT NOT NULL, + address TEXT NOT NULL, + protocol TEXT NOT NULL CHECK (protocol IN ('ssh','rdp')), + port INTEGER NOT NULL, + os_type TEXT NOT NULL CHECK (os_type IN ('linux','windows')), + ssh_host_key_fingerprint TEXT, -- Pinning für Strict Host Key Checking + rdp_require_nla INTEGER NOT NULL DEFAULT 1, + clipboard_enabled INTEGER NOT NULL DEFAULT 1, + file_transfer_enabled INTEGER NOT NULL DEFAULT 1, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE TABLE ssh_keys ( + id INTEGER PRIMARY KEY, + label TEXT NOT NULL, + owner_user_id INTEGER REFERENCES users(id), -- NULL = shared/service key + private_key_enc BLOB NOT NULL, -- AES-256-GCM, KEK extern + public_key TEXT NOT NULL, + key_type TEXT NOT NULL, -- ed25519, rsa-3072, ca-cert... + created_at TEXT NOT NULL DEFAULT (datetime('now')), + rotated_at TEXT, + expires_at TEXT +); + +CREATE TABLE host_ssh_key_map ( + host_id INTEGER NOT NULL REFERENCES hosts(id) ON DELETE CASCADE, + ssh_key_id INTEGER NOT NULL REFERENCES ssh_keys(id), + PRIMARY KEY (host_id, ssh_key_id) +); + +CREATE TABLE user_hostgroup_roles ( + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + host_group_id INTEGER NOT NULL REFERENCES host_groups(id) ON DELETE CASCADE, + role_id INTEGER NOT NULL REFERENCES roles(id), + granted_by INTEGER REFERENCES users(id), + granted_at TEXT NOT NULL DEFAULT (datetime('now')), + expires_at TEXT, -- optionale zeitlich befristete Freigabe + PRIMARY KEY (user_id, host_group_id, role_id) +); + +CREATE TABLE sessions ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id), + host_id INTEGER NOT NULL REFERENCES hosts(id), + protocol TEXT NOT NULL, + started_at TEXT NOT NULL DEFAULT (datetime('now')), + ended_at TEXT, + client_ip TEXT NOT NULL, + recording_path TEXT, -- Pfad/Referenz zur Session-Aufzeichnung + end_reason TEXT -- logout, timeout, error, admin_kill +); + +CREATE TABLE file_transfers ( + id INTEGER PRIMARY KEY, + session_id INTEGER NOT NULL REFERENCES sessions(id), + direction TEXT NOT NULL CHECK (direction IN ('upload','download')), + filename TEXT NOT NULL, + size_bytes INTEGER NOT NULL, + sha256 TEXT NOT NULL, + av_scan_result TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE TABLE audit_log ( + id INTEGER PRIMARY KEY, + ts TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')), + user_id INTEGER REFERENCES users(id), + event_type TEXT NOT NULL, -- login, login_failed, session_start, ... + details_json TEXT NOT NULL, + prev_hash TEXT NOT NULL, + entry_hash TEXT NOT NULL -- sha256(prev_hash || ts || event_type || details_json) +); + +-- Schutz gegen nachträgliche Manipulation auf DB-Ebene: +CREATE TRIGGER no_audit_update BEFORE UPDATE ON audit_log +BEGIN SELECT RAISE(ABORT, 'audit_log ist append-only'); END; +CREATE TRIGGER no_audit_delete BEFORE DELETE ON audit_log +BEGIN SELECT RAISE(ABORT, 'audit_log ist append-only'); END; +``` + +--- + +## 6. Security- und Hardening-Konzept + +Dies ist der **wichtigste Teil** des Konzepts und durchzieht alle Ebenen: Anwendung, Transport, Betriebssystem, Prozessisolation. + +### 6.1 Threat-Model (Kurzfassung) + +Wesentliche Angriffsvektoren, gegen die das Design hart absichert: kompromittierte Nutzer-Credentials (→ Pflicht-2FA, Lockout), Diebstahl von SSH-Schlüsseln (→ Keys nie im Client, Verschlüsselung at rest, CA-Zertifikate mit kurzer TTL), Man-in-the-Middle (→ TLS 1.3 only, Host-Key-Pinning), Rechteausweitung über die Web-App (→ strikte Eingabevalidierung, parametrisierte Queries, RBAC serverseitig durchgesetzt), Manipulation von Audit-Daten durch einen kompromittierten Admin-Account (→ Hash-Chain + externe Log-Ausleitung), Kompromittierung des Jumphost-Prozesses selbst (→ systemd-Sandboxing, minimale Rechte, Netzwerksegmentierung als zusätzliche Schicht). + +### 6.2 Authentifizierung & Session-Sicherheit +- Argon2id-Hashing (Parameter an aktuelle OWASP-Empfehlung angelehnt, z. B. m=19 MiB, t=2, p=1 als Startwert, umgebungsabhängig kalibrieren). +- TOTP verpflichtend, kein Login ohne zweiten Faktor möglich; Secret-Verschlüsselung mit separatem Schlüssel (nicht identisch mit KEK der SSH-Keys, Prinzip der Schlüsseltrennung). +- Rate-Limiting & progressive Lockout gegen Brute-Force (pro Account und pro Quell-IP), zusätzlich Fail2ban auf OS-Ebene als zweite Verteidigungslinie gegen Login-Endpoint-Scanning. +- Session-Cookies: `HttpOnly`, `Secure`, `SameSite=Strict`, kurze Lebensdauer + Refresh, serverseitige Session-Invalidierung bei Passwortänderung. +- Absolute und Idle-Timeouts pro Hostgruppe konfigurierbar; sensible Aktionen (z. B. Schlüsselverwaltung, Rechtevergabe) verlangen Re-Authentifizierung. + +### 6.3 Transportverschlüsselung +- **Ausschließlich TLS 1.3** (TLS 1.2 nur als Übergangs-Fallback, falls zwingend erforderlich), moderne Cipher-Suiten, HSTS mit langer `max-age` und `includeSubDomains`. +- WebSocket-Verbindungen (Terminal-Stream, Guacamole-Tunnel) laufen ausschließlich über WSS. +- Optional Mutual-TLS für administrative API-Zugriffe (z. B. Ansible-getriebene Konfigurationsänderungen von einer festen Management-Quelle aus). +- Zertifikate über Ansible-Rolle verwaltet. Im nginx-Betrieb werden dabei **zwei TLS-Zertifikatsmodi** unterstützt (Details und Konfiguration siehe Kap. 7.2): + 1. **`internal_pki`** – nginx auf dem Jumphost terminiert TLS selbst, mit einem Zertifikat aus einer internen/unternehmenseigenen PKI (kein direkter Internetbezug nötig). + 2. **`external_reverse_proxy`** – ein bereits vorhandener, vorgelagerter externer Reverse Proxy/Load Balancer übernimmt die öffentliche TLS-Terminierung mit einer eigenen (unternehmensweiten oder öffentlichen) PKI; das lokale nginx auf dem Jumphost dient dann als zweite, interne Schicht. + In beiden Fällen bleibt der Grundsatz „TLS 1.3 end-to-end bis zur Anwendung" erhalten – unverschlüsseltes internes HTTP wird nicht als Standard vorgesehen (siehe 7.2). + +### 6.4 Schlüssel- und Geheimnisverwaltung +- Trennung von Datenverschlüsselungsschlüsseln (DEK, in DB pro Datensatz) und Schlüsselverschlüsselungsschlüsseln (KEK, außerhalb der DB). +- KEK-Ablage über `systemd-creds` (LoadCredentialEncrypted) oder TPM-gebundene Speicherung, wo verfügbar; alternativ Anbindung an HashiCorp Vault/OpenBao als Erweiterungsoption. +- Keine Secrets in Ansible-Klartext-Variablen – ausschließlich über `ansible-vault` verschlüsselt, mit Vault-Passwort außerhalb des Repositories (z. B. über CI-Secret-Store oder separates Passwort-Management). +- Konsequentes Prinzip: **kein Private Key verlässt jemals den Server-Prozess** – weder Richtung Browser noch Richtung Log. + +### 6.5 Session-Aufzeichnung & Integrität +- SSH-Sessions werden als Terminal-Cast (z. B. asciicast-kompatibles Format) aufgezeichnet, RDP-Sessions als Guacamole-native Aufzeichnung (serverseitig, ohne Zusatzsoftware auf dem Client). +- Aufzeichnungen werden mit fortlaufendem Hash verkettet (analog Audit-Log), um nachträgliche Manipulation erkennbar zu machen. +- Zugriff auf Aufzeichnungen selbst wieder RBAC-gesteuert (`session_recording_view`), jeder Playback-Zugriff wird auditiert (Audit-Log über das Audit-Log, „wer hat wessen Session angeschaut"). +- Aufbewahrungsfrist konfigurierbar, automatische Löschung/Archivierung nach Ablauf (Datenschutz-/Compliance-Anforderungen beachten, insb. bei mitgeloggtem Tastatur-Input). + +### 6.6 Web-Anwendungs-Hardening +- CSRF-Schutz für alle zustandsändernden Endpunkte, striktes Content-Security-Policy-Header (kein `unsafe-inline`, keine externen Script-Quellen), `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer`. +- Sämtliche API-Eingaben über Pydantic-Schemas validiert (Typ, Länge, Whitelist erlaubter Zeichen), Datei-Uploads mit Größenlimit, Dateityp-Prüfung (Magic-Bytes, nicht nur Endung) und optionalem AV-Scan (ClamAV) vor Freigabe zum Download durch andere Nutzer. +- Abhängigkeiten fixiert (Lockfile), regelmäßiger Scan mit `pip-audit`/`safety` in CI, minimaler Dependency-Footprint. +- Keine Debug-/Stacktrace-Ausgabe in Produktionsfehlermeldungen; strukturierte Fehlerbehandlung, generische Fehlermeldungen nach außen. + +### 6.7 Betriebssystem- und Prozess-Hardening +- Dedizierter, unprivilegierter Service-User pro Dienst (App, guacd), kein Root-Betrieb. +- systemd-Sandboxing (Details Kap. 8): `NoNewPrivileges`, `ProtectSystem=strict`, `ProtectHome=true`, `PrivateTmp=true`, `ProtectKernelTunables`, `ProtectKernelModules`, `ProtectControlGroups`, `RestrictNamespaces`, `RestrictAddressFamilies`, `RestrictSUIDSGID`, `MemoryDenyWriteExecute`, `SystemCallFilter=@system-service`, `CapabilityBoundingSet=` minimal (nur was zwingend nötig, im Regelfall leer). +- Firewall (nftables) im Default-Deny-Modus: eingehend nur 443/tcp (bzw. App-Port bei Betrieb ohne nginx) und ggf. 22/tcp für administrativen Zugriff auf den Jumphost selbst (idealerweise ebenfalls nur aus einem Management-Netz); ausgehend nur zu den definierten Zielsystem-Netzen. +- Automatische Sicherheitsupdates (`unattended-upgrades`) für das Basissystem, geregelter Patch-Prozess für die Anwendung selbst. +- `auditd` auf OS-Ebene zusätzlich zum Anwendungs-Audit-Log, insbesondere für Zugriffe auf die SQLite-Datei und Konfigurationsdateien. +- SELinux (RHEL-Familie) bzw. AppArmor (Debian/Ubuntu)-Profil als zusätzliche Verteidigungslinie empfohlen (im Playbook als optionale Rolle vorgesehen). + +### 6.8 Backup & Wiederherstellung +- Verschlüsselte, regelmäßige Backups der SQLite-Datei (Achtung: konsistente Snapshots via SQLite-Online-Backup-API oder `VACUUM INTO`, nicht per einfachem Filecopy während des Betriebs). +- KEK/Vault-Backup getrennt von DB-Backup aufbewahren (sonst hebelt ein gemeinsamer Backup-Diebstahl die Verschlüsselung aus). +- Dokumentierter Wiederherstellungs-Testlauf als fester Bestandteil des Betriebskonzepts (mind. jährlich). + +### 6.9 Monitoring & Alerting +- Prometheus-Metrics-Endpoint (intern, nicht öffentlich) für aktive Sessions, fehlgeschlagene Logins, Fehlerraten. +- Alerts bei: gehäuften Fehlversuchen, Host-Key-Mismatch, Zugriff außerhalb üblicher Zeiten/Netze, Bruch der Audit-Hash-Chain. +- Externe Log-Ausleitung (Syslog/SIEM) als unabhängige zweite Quelle für forensische Auswertung, auch falls der Jumphost selbst kompromittiert würde. + +--- + +## 7. Ansible-Deployment-Konzept + +### 7.1 Rollenstruktur + +``` +ansible/ +├── site.yml +├── inventory/ +│ ├── production.ini +│ └── group_vars/ +│ ├── all.yml # gemeinsame Variablen +│ ├── jumphost_with_nginx.yml # enable_nginx_proxy: true +│ └── jumphost_no_nginx.yml # enable_nginx_proxy: false +├── roles/ +│ ├── os_hardening/ # CIS-nahe Basis-Härtung (Firewall, SSH-Daemon des Hosts, auditd, unattended-upgrades) +│ ├── firewall_nftables/ +│ ├── fail2ban/ +│ ├── python_runtime/ # venv, gehärtete Paketinstallation +│ ├── jumphost_app/ # Deployment der eigentlichen Anwendung + systemd unit +│ ├── guacd/ # Installation guacd + FreeRDP, systemd unit, lokal gebunden +│ ├── sqlite_init/ # Schema-Migration, Verzeichnis-/Rechtevergabe +│ ├── tls_certificates/ # tls_mode: internal_pki | external_reverse_proxy | acme_public +│ ├── nginx_proxy/ # NUR wenn enable_nginx_proxy: true +│ └── backup/ # Cron/systemd-Timer für verschlüsselte Backups +└── files/ / templates/ +``` + +### 7.2 Steuerung mit/ohne nginx + +Die Variable `enable_nginx_proxy` (in `group_vars`) steuert per `when`-Bedingung, ob die Rolle `nginx_proxy` inkludiert wird: + +```yaml +# site.yml (Auszug) +- hosts: jumphosts + roles: + - os_hardening + - firewall_nftables + - fail2ban + - python_runtime + - sqlite_init + - guacd + - jumphost_app + - { role: nginx_proxy, when: enable_nginx_proxy | default(false) } + - tls_certificates + - backup +``` + +Bei `enable_nginx_proxy: false` terminiert die Python-App TLS **direkt** (Uvicorn/Hypercorn mit `--ssl-certfile`/`--ssl-keyfile`), die Firewall-Rolle öffnet dann direkt den App-Port (Standard 8443) statt 443 auf nginx. + +### 7.2a TLS-Zertifikatsmodi im nginx-Betrieb + +Zusätzlich zu `enable_nginx_proxy` steuert die Variable `tls_mode` (nur relevant, wenn nginx aktiv ist), **woher** das TLS-Zertifikat kommt, mit dem nginx auf dem Jumphost selbst terminiert bzw. ob nginx überhaupt selbst terminiert: + +```yaml +# group_vars/jumphost_with_nginx.yml +enable_nginx_proxy: true +tls_mode: internal_pki # internal_pki | external_reverse_proxy | acme_public +``` + +**Modus `internal_pki` – nginx terminiert selbst, Zertifikat aus interner PKI:** +- Für rein interne Umgebungen ohne öffentliche Erreichbarkeit gedacht. +- Die Rolle `tls_certificates` bezieht das Zertifikat entweder (a) automatisiert per ACME-Protokoll von einer internen CA, die ACME spricht (z. B. `step-ca`, oder Microsoft ADCS mit ACME-Plugin), oder (b) aus einem vorab per `ansible-vault` verschlüsselten Cert/Key-Paar, das zentral von der bestehenden Unternehmens-PKI ausgestellt und ins Repository (verschlüsselt) eingespielt wird. +- Damit Browser der Nutzer das Zertifikat ohne Warnung akzeptieren, muss das interne Root-/Intermediate-CA-Zertifikat auf den Client-Rechnern als vertrauenswürdig hinterlegt sein (i. d. R. bereits über GPO/MDM im Unternehmen vorhanden – kein Bestandteil dieses Playbooks, aber als Voraussetzung dokumentiert). +- Erneuerung automatisiert über systemd-Timer + Ansible-Handler (Reload von nginx bei neuem Zertifikat, ohne Verbindungsabbruch bestehender Sessions dank `nginx -s reload`). + +**Modus `external_reverse_proxy` – TLS-Terminierung erfolgt vorgelagert:** +- Für Umgebungen, in denen bereits ein zentraler Reverse Proxy/Load Balancer (z. B. F5, HAProxy, zentrales nginx/Traefik-Ingress, Cloud-LB) existiert, der die öffentliche TLS-Terminierung mit der unternehmensweiten oder einer öffentlichen PKI übernimmt. +- Empfohlene Umsetzung (**Defense-in-Depth, "TLS all the way"**): Die Strecke zwischen externem Reverse Proxy und dem lokalen nginx auf dem Jumphost wird **erneut verschlüsselt** (Re-Encryption) – das lokale nginx terminiert dabei ein zweites, internes TLS mit einem Zertifikat aus der internen PKI (technisch identisch zum `internal_pki`-Zweig), optional mit Mutual-TLS, sodass nur der bekannte externe Proxy sich verbinden kann. +- Bewusste Ausnahme, **nicht Standardeinstellung**: Klartext-HTTP zwischen externem Proxy und Jumphost ist nur zulässig, wenn diese Strecke nachweislich in einem eigenen, physisch/logisch getrennten und zugriffskontrollierten Segment liegt, und erfordert eine dokumentierte Risikoakzeptanz (`tls_mode: external_reverse_proxy` mit zusätzlichem Flag `internal_hop_plaintext_accepted: true`, das im Playbook explizit gesetzt werden muss – kein stiller Default). +- In diesem Modus muss dem lokalen nginx die IP-Adresse/das Subnetz des externen Reverse Proxys als vertrauenswürdig bekannt gemacht werden, damit `X-Forwarded-For` und `X-Forwarded-Proto` korrekt und nicht durch Dritte fälschbar ausgewertet werden – sonst wäre das Feld `client_ip` im Audit-Log (Kap. 5) manipulierbar: + +```nginx +# roles/nginx_proxy/templates/jumphost.conf.j2 (Auszug, Modus external_reverse_proxy) +set_real_ip_from {{ external_reverse_proxy_cidr }}; # z.B. 10.10.5.0/24 +real_ip_header X-Forwarded-For; +real_ip_recursive on; + +server { + listen 443 ssl; # zweite, interne TLS-Schicht (Re-Encryption) + ssl_certificate /etc/jumphost/tls/internal.crt; + ssl_certificate_key /etc/jumphost/tls/internal.key; + ssl_protocols TLSv1.3; + + location / { + proxy_pass http://unix:/run/jumphost/app.sock; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header X-Real-IP $remote_addr; + } +} +``` + +**Modus `acme_public`** (unverändert gegenüber v0.1): Let's-Encrypt/öffentliches ACME für Deployments mit direkter Internet-Erreichbarkeit und öffentlichem DNS-Namen. + +Alle drei Modi werden von **derselben** Rolle `tls_certificates` über eine `case`/`when`-Verzweigung auf `tls_mode` abgedeckt – es entsteht kein separater Wartungspfad. + +### 7.3 Secrets im Playbook +- Alle sensiblen Variablen (KEK-Ausgangsmaterial, initiales Admin-Passwort, ggf. Vault-Zugangsdaten) liegen ausschließlich in `ansible-vault`-verschlüsselten Dateien (`group_vars/*/vault.yml`). +- Playbook-Läufe erfordern `--ask-vault-pass` oder eine extern bereitgestellte Vault-ID-Datei (nicht im Repository). +- Idempotenz: alle Rollen so gestaltet, dass wiederholte Ausführung keine Nebenwirkungen hat (Handler statt Direktbefehle, `creates`/`state`-Parameter konsequent genutzt). + +--- + +## 8. Systemd-Services + +### 8.1 Haupt-Anwendung (`jumphost-app.service`) + +```ini +[Unit] +Description=Jumphost Gateway Application +After=network.target + +[Service] +Type=simple +User=jumphost +Group=jumphost +ExecStart=/opt/jumphost/venv/bin/uvicorn app.main:app \ + --uds /run/jumphost/app.sock +Restart=on-failure +RestartSec=5 + +# --- Hardening --- +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +PrivateTmp=true +PrivateDevices=true +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectControlGroups=true +RestrictNamespaces=true +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 +RestrictSUIDSGID=true +MemoryDenyWriteExecute=true +LockPersonality=true +SystemCallFilter=@system-service +SystemCallErrorNumber=EPERM +CapabilityBoundingSet= +ReadWritePaths=/var/lib/jumphost /run/jumphost +UMask=0077 + +[Install] +WantedBy=multi-user.target +``` + +### 8.2 guacd-Dienst (`guacd.service`) +Analoge Hardening-Direktiven, zusätzlich `RestrictAddressFamilies=AF_UNIX AF_INET` (nur lokale Kommunikation zur App und ausgehend RDP), eigener unprivilegierter User `guacd`, kein Zugriff auf das SQLite-Datenverzeichnis. + +### 8.3 Backup-Timer (`jumphost-backup.timer` / `.service`) +`OnCalendar=daily`, ruft ein Skript auf, das `sqlite3 ... "VACUUM INTO '/backup/....db'"` ausführt und anschließend verschlüsselt (z. B. `age` oder GPG) ablegt; ebenfalls mit reduzierten systemd-Capabilities. + +--- + +## 9. Betrieb mit vs. ohne nginx + +| Kriterium | **Mit nginx** | **Ohne nginx (direkt)** | +|---|---|---| +| TLS-Terminierung | Ausgereift, breite Erfahrung, einfache Cipher-Konfiguration | Möglich (Uvicorn/Hypercorn), weniger battle-tested für Internet-exponierte Lasten | +| WebSocket-Handling | Sehr robust, granulare Timeout-/Buffer-Konfiguration | Funktioniert, aber weniger Tuning-Optionen | +| Security-Header/Rate-Limiting | Zentral und einfach über nginx-Module/`limit_req` | Muss in der Anwendung selbst implementiert werden | +| WAF-Anbindung (z. B. ModSecurity) | Möglich | Nicht ohne Zusatzkomponente | +| Angriffsfläche | Zusätzliche Komponente = zusätzlicher Patch-Bedarf | Minimalere Angriffsfläche, weniger Komponenten | +| Eignung | **Empfohlen für internet-exponierten/produktiven Betrieb** | Sinnvoll für rein internen Betrieb in bereits stark segmentiertem Netz oder minimalistische Testumgebungen | + +**Empfehlung:** Produktivbetrieb, insbesondere bei Erreichbarkeit außerhalb eines bereits stark abgesicherten internen Netzes, **mit nginx** als vorgelagertem Reverse Proxy. Der Modus „ohne nginx" bleibt als bewusst unterstützte, schlankere Option für stark isolierte interne Deployments erhalten – beide Pfade werden vom selben Playbook über eine einzige Variable gesteuert, wodurch kein Wartungsaufwand für zwei getrennte Codebasen entsteht. + +**TLS-Zertifikatsmodi innerhalb des nginx-Betriebs** (Details Kap. 7.2a): Sowohl der Fall „nginx terminiert selbst mit einem Zertifikat aus der internen PKI" (`internal_pki`) als auch der Fall „ein vorgelagerter externer Reverse Proxy übernimmt die TLS-Terminierung mit eigener PKI, das lokale nginx dient als zweite interne Schicht" (`external_reverse_proxy`) werden unterstützt und über die Variable `tls_mode` gesteuert – ohne dass dafür zwei getrennte Playbook-Varianten gepflegt werden müssen. + +--- + +## 10. Rollout-Plan & Teststrategie + +1. **Proof of Concept**: Kernfunktionen (Login+TOTP, ein SSH-Ziel, ein RDP-Ziel, Audit-Log) in isolierter Testumgebung. +2. **Security-Review/Penetrationstest**: vor jedem produktiven Rollout verpflichtend, insbesondere Prüfung von Session-Isolation, Key-Handling, RBAC-Bypass-Versuchen. +3. **Pilotbetrieb**: begrenzter Nutzerkreis, begrenzte Hostgruppen, engmaschiges Audit-Log-Review. +4. **Rollout**: schrittweise Ausweitung, Altlösungen (z. B. direkte SSH/RDP-Zugänge) parallel abschalten. +5. **Kontinuierlich**: automatisierte Tests (Unit-Tests für RBAC-Logik und Audit-Hash-Chain, Integrationstests für SSH-/RDP-Sessionaufbau, Dependency-Scans in CI, jährlicher Penetrationstest). + +--- + +## 11. Technologie-Stack (Zusammenfassung) + +| Bereich | Technologie | +|---|---| +| Backend-Framework | Python 3.12+, FastAPI, Uvicorn/Hypercorn (ASGI) | +| Datenbank | SQLite (WAL-Modus), `aiosqlite`/SQLAlchemy Core | +| SSH | `asyncssh` | +| RDP | guacd (Apache-Guacamole-Protokoll-Daemon) + FreeRDP, `guacamole-common-js` im Frontend | +| Terminal-Frontend | `xterm.js` | +| Auth | Argon2id (`argon2-cffi`), TOTP (`pyotp`) | +| Verschlüsselung | AES-256-GCM für Secrets at rest, TLS 1.3 in transit | +| Reverse Proxy (optional) | nginx | +| Deployment | Ansible (Rollen, Vault) | +| Prozess-Management | systemd (gehärtete Units) | +| Monitoring | Prometheus-Exporter (optional), Syslog/SIEM-Anbindung | +| CI/Dependency-Sicherheit | `pip-audit`, Lockfile-basierte Builds | + +--- + +## 12. Offene Punkte & Erweiterungsoptionen + +- **LDAP/Active-Directory-Anbindung** als alternative/zusätzliche Identitätsquelle statt rein lokaler User. +- **WebAuthn/FIDO2** als Ersatz oder Ergänzung zu TOTP für höhere Phishing-Resistenz. +- **Skalierung**: bei Bedarf Migrationspfad von SQLite auf PostgreSQL (Schema ist bewusst so gehalten, dass eine Migration ohne grundlegenden Redesign möglich ist). +- **Just-in-Time-Zugriff**: zeitlich befristete, genehmigungspflichtige Zugriffsanfragen statt dauerhafter Rechtevergabe. +- **Hochverfügbarkeit**: Aktiv/Passiv-Setup mit synchronisierter DB und geteiltem Session-Storage bei Bedarf an Ausfallsicherheit. +- **Mobile-/Tablet-Optimierung** der Weboberfläche. +- **Externe Vault-Integration** (HashiCorp Vault/OpenBao) als Ersatz für die dateibasierte KEK-Verwaltung in größeren Umgebungen. + +--- + +### Nächste Schritte + +1. Zielgröße und Netzsegmentierung final festlegen (Kap. 1) sowie gewünschte Linux-Distribution für den Jumphost bestimmen. +2. Entscheidung, welcher TLS-Zertifikatsmodus (Kap. 7.2a: `internal_pki`, `external_reverse_proxy` oder `acme_public`) initial umgesetzt wird – ggf. auch mehrere, je nach Zielumgebung (z. B. intern vs. DMZ). +3. Entscheidung zu kurzlebigen SSH-CA-Zertifikaten vs. ausschließlich statischen Schlüsseln treffen (beeinflusst Ansible-Rolle und Zielsystem-Vorbereitung). +4. Freigabe dieses Konzepts als Grundlage für die Implementierungsphase (Backend-Projektskelett, DB-Migrationen, Ansible-Rollen). diff --git a/jumphost.zip b/jumphost.zip new file mode 100644 index 0000000..0537552 Binary files /dev/null and b/jumphost.zip differ