first commit

This commit is contained in:
2026-08-19 22:31:07 +02:00
commit 411812e954
2 changed files with 547 additions and 0 deletions

547
Jumphost_Konzept.md Normal file
View File

@ -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 `<canvas>`-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).