more admin stuff 2
This commit is contained in:
@ -30,6 +30,20 @@ class HostKeyMismatchError(Exception):
|
||||
super().__init__(f"Host-Key-Mismatch: erwartet={expected!r} beobachtet={observed!r}")
|
||||
|
||||
|
||||
class HostKeyDiscoveryError(Exception):
|
||||
"""Wird geworfen, wenn beim Host-Key-Discovery-Versuch (siehe
|
||||
discover_and_store_host_key) ueberhaupt KEIN Fingerprint beobachtet
|
||||
werden konnte (TCP/DNS/Timeout-Fehler VOR dem SSH-Key-Exchange) -- im
|
||||
Unterschied zu einem erwarteten Auth-Fehler NACH dem KEX (siehe dort).
|
||||
Der admin-only Endpunkt (admin/routes.py) faengt dies ab und liefert
|
||||
eine saubere 502 statt eines unbehandelten 500."""
|
||||
|
||||
def __init__(self, host_id: int, reason: str) -> None:
|
||||
self.host_id = host_id
|
||||
self.reason = reason
|
||||
super().__init__(f"Host-Key-Ermittlung fuer Host {host_id} fehlgeschlagen: {reason}")
|
||||
|
||||
|
||||
class _PinnedHostKeyClient(asyncssh.SSHClient):
|
||||
"""Erzwingt Strict Host Key Checking gegen einen fest hinterlegten
|
||||
SHA-256-Fingerprint. Kein automatisches Trust-on-First-Use (TOFU)."""
|
||||
@ -114,14 +128,45 @@ async def discover_and_store_host_key(
|
||||
"""Verbindet EINMALIG ohne Pinning, um den Host-Key-Fingerprint zu erfassen
|
||||
und in der DB zu hinterlegen. Nur ueber einen dedizierten, admin-only
|
||||
Endpunkt aufrufbar -- jeder Aufruf ist eine bewusste Vertrauensentscheidung
|
||||
und wird im Audit-Log als solche vermerkt (siehe admin/routes.py)."""
|
||||
und wird im Audit-Log als solche vermerkt (siehe admin/routes.py).
|
||||
|
||||
Bugfix (Fehler 500 bei 'Host-Key ermitteln'): asyncssh.connect() fuehrt
|
||||
nach dem Key-Exchange (bei dem validate_host_public_key() den Fingerprint
|
||||
bereits erfasst) automatisch die Authentifizierung durch -- ohne
|
||||
hinterlegten Client-Key/Passwort schlaegt die IMMER fehl (asyncssh.Error,
|
||||
z.B. PermissionDenied), obwohl der Fingerprint laengst bekannt ist. Diese
|
||||
fuer die reine Discovery irrelevante Auth-Fehlermeldung wurde bisher NICHT
|
||||
abgefangen und riss als unbehandelte Exception bis zu FastAPI durch ->
|
||||
500 Internal Server Error bei praktisch jedem Aufruf. Fix: Verbindungs-
|
||||
fehler abfangen; wenn dabei bereits ein Fingerprint beobachtet wurde, gilt
|
||||
die Discovery als erfolgreich. Nur wenn wirklich KEIN Fingerprint erfasst
|
||||
wurde (Host nicht erreichbar, DNS-Fehler, Timeout -- also ein Fehler VOR
|
||||
dem Key-Exchange), ist es ein echter Fehler (HostKeyDiscoveryError)."""
|
||||
host = await load_host(conn, host_id)
|
||||
if host["protocol"] != "ssh":
|
||||
raise HostNotConfiguredError("Host-Key-Ermittlung ist nur fuer SSH-Ziele moeglich")
|
||||
|
||||
client = _PinnedHostKeyClient(None, discovery_mode=True)
|
||||
connection = await asyncssh.connect(
|
||||
host["address"], port=host["port"], username=host["ssh_username"],
|
||||
known_hosts=None, client_factory=lambda: client, connect_timeout=10,
|
||||
)
|
||||
connection.close()
|
||||
connection = None
|
||||
try:
|
||||
connection = await asyncssh.connect(
|
||||
host["address"], port=host["port"], username=host["ssh_username"],
|
||||
known_hosts=None, client_factory=lambda: client, connect_timeout=10,
|
||||
)
|
||||
except (asyncssh.Error, OSError) as exc:
|
||||
if client.observed_fingerprint is None:
|
||||
logger.warning("Host-Key-Ermittlung fuer Host %s fehlgeschlagen: %s", host_id, exc)
|
||||
raise HostKeyDiscoveryError(host_id, str(exc)) from exc
|
||||
# Fingerprint wurde bereits waehrend des Key-Exchange erfasst -- der
|
||||
# anschliessende Auth-Fehler ist fuer die Discovery unschaedlich.
|
||||
logger.info(
|
||||
"Host-Key fuer Host %s erfasst (Auth-Phase erwartungsgemaess fehlgeschlagen: %s)",
|
||||
host_id, exc,
|
||||
)
|
||||
finally:
|
||||
if connection is not None:
|
||||
connection.close()
|
||||
|
||||
fingerprint = client.observed_fingerprint
|
||||
await conn.execute(
|
||||
"UPDATE hosts SET ssh_host_key_fingerprint = ? WHERE id = ?", (fingerprint, host_id)
|
||||
|
||||
Reference in New Issue
Block a user