3.6 KiB
3.6 KiB
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):
- Login
mehlzoerwer+ ErstpasswortSt230918kM@vps→ gilt NUR 1×, dann PW-Wechsel-Pflicht -
- Login mit neuem Passwort → liest
pw_changed-Flag → leitet zur 2FA-Seite
- Login mit neuem Passwort → liest
- Backend sendet 6-stelligen Code per Telegram an Nutzer-Chat (out-of-band, Kanal getrennt vom Web)
- Nach korrektem Code:
ufw allow from <ANFRAGE-IP> to any port 22 proto tcp(Kommentar "SSH JIT Portal") - Auto-Close: Hintergrund-Thread prüft alle 60s Sessions mit
expiry <= now→ufw delete, Statusclosed
Dateien & Infrastruktur
- App:
/opt/sshportal/app.py(Flask, Telegram-Token aus/root/.hermes/.env, einheitlich viare.search(key+r"\s*=\s*(\S+)")) - venv:
/opt/sshportal/venv(flask, requests; Passwort-Hash viawerkzeug.security.generate_password_hash/check_password_hash— kein eigener bcrypt-Aufruf nötig) - systemd:
/etc/systemd/system/sshportal.service→activeauf127.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" |