Files
mesh-configs/skills/devops/security-audit-hardening/references/jit-ssh-2fa-portal.md
T

3.6 KiB
Raw Blame History

JIT SSH-Portal mit Telegram-2FA (Just-In-Time Firewall-Freischaltung)

Muster (2026-08-03, https://ssh.perfekt-ohne-en.de): SSH-Port 22 bleibt standardmäßig für die Welt GESCHLOSSEN und wird nur nach doppelter Authentifizierung (Passwort + Telegram-2FA) für die angefragte IP temporär geöffnet (1h) → Auto-Close. Besser als permanent offenes SSH.

Architektur

ssh.perfekt-ohne-en.de (nginx+SSL)  →  proxy_pass  →  127.0.0.1:8091 (Flask)
                                                          ├─ SQLite (users, sessions)
                                                          ├─ Telegram-2FA (6-stelliger Code an Nutzer-Chat)
                                                          └─ UFW: öffnet Port 22 für angefragte IP (1h)

Ablauf (end-to-end):

  1. Login mehlzoerwer + Erstpasswort St230918kM@vps → gilt NUR 1×, dann PW-Wechsel-Pflicht
    1. Login mit neuem Passwort → liest pw_changed-Flag → leitet zur 2FA-Seite
  2. Backend sendet 6-stelligen Code per Telegram an Nutzer-Chat (out-of-band, Kanal getrennt vom Web)
  3. Nach korrektem Code: ufw allow from <ANFRAGE-IP> to any port 22 proto tcp (Kommentar "SSH JIT Portal")
  4. Auto-Close: Hintergrund-Thread prüft alle 60s Sessions mit expiry <= now → ufw delete, Status closed

Dateien & Infrastruktur

  • App: /opt/sshportal/app.py (Flask, Telegram-Token aus /root/.hermes/.env, einheitlich via re.search(key+r"\s*=\s*(\S+)"))
  • venv: /opt/sshportal/venv (flask, requests; Passwort-Hash via werkzeug.security.generate_password_hash/check_password_hash — kein eigener bcrypt-Aufruf nötig)
  • systemd: /etc/systemd/system/sshportal.service → active auf 127.0.0.1:8091 (nur localhost; nginx davor)
  • DB: /opt/sshportal/portal.db, Log: /opt/sshportal/portal.log
  • nginx vhost + Let's-Encrypt via Certbot (Standard certbot --nginx -d ... --redirect)

Sicherheits-Parameter (der Wert des Musters)

  • Erstpasswort-Einmalnutzung: pw_changed=0 → Login erzwingt PW-Wechsel.
  • 2FA out-of-band: Telegram = getrennter Kanal vom Web-Portal.
  • Code-TTL 120s, max 3 Fehlversuche → Login gesperrt + Telegram-Alarm.
  • Firewall-Freischaltung zeitlich begrenzt: 1h, danach automatische UFW-Löschung.
  • Nur die angefragte IP wird geöffnet (keine Subnetze).
  • Flask-Inhalte: render_template_string(..., content=Markup(...)) damit HTML-Strings als Form nicht escaped werden.

⚠️ KRITISCHER PITFALL: Test verändert den laufenden Zustand

End-to-End-Test (Login mit Erstpasswort → PW-Wechsel auf Test-Passwort) überschreibt das echte Passwort. Der Nutzer kann danach nicht mehr mit dem Erstpasswort einloggen → fälschliche "falsches Passwort"-Meldung.

Regel (Betriebsregel #1): Tests/Änderungen an laufenden Systemen MÜSSEN beendet oder zurückgesetzt werden — nie Test-Artefakte hinterlassen. Sonst Rückfrage statt Test.

Reset nach Test:

systemctl stop sshportal.service
/opt/sshportal/venv/bin/python -c "
import sqlite3; from werkzeug.security import generate_password_hash
c=sqlite3.connect('/opt/sshportal/portal.db')
c.execute('UPDATE users SET pw_hash=?, pw_changed=0 WHERE username=?', (generate_password_hash('St230918kM@vps'),'mehlzoerwer'))
c.commit(); c.close()"
systemctl start sshportal.service

Verifikation (im Bau bestätigt)

Test Status
Login Erstpasswort → Redirect change-pw ✅ 302
PW-Wechsel → "Passwort geändert" (Log) ✅
Login neues PW → Redirect 2fa ✅ 302
2FA-Code → Telegram-Chat ✅ Log "2FA-Code gesendet"
UFW allow für IP ✅ "Rule added"
Auto-Close (ufw delete nach 1h) ✅ "Rule deleted"