feat(prepatch_check): message SSH actionnable (capture exception, classifie type erreur)

- run_all_checks: capture l'exception levee par _connect dans ctx['ssh_error']
- check_ssh: utilise l'erreur reelle pour produire un message classifie:
  * 'No route to host' -> reseau injoignable
  * 'Connection timed out' -> port 22 filtre/host down
  * 'Connection refused' -> sshd arrete/bloque
  * 'no matching kex/key exchange' -> algos incompatibles
  * 'host key' -> known_hosts probleme
  * 'permission denied' / 'authentication failed' -> auth refusee
  * 'no authentication methods' -> aucune methode acceptee
  * 'name or service not known' -> DNS KO cote SSH
- details inclut le message d'exception complet pour debug
This commit is contained in:
Pierre & Lumière 2026-05-07 11:54:08 +02:00
parent 31f3a3c632
commit 1747447f82

View File

@ -131,12 +131,40 @@ def check_ssh(ctx: Dict[str, Any]) -> Dict[str, Any]:
"message": "Pas de cible (DNS KO en amont)", "message": "Pas de cible (DNS KO en amont)",
"details": "", "details": "",
} }
err = (ctx.get("ssh_error") or "").strip()
target = ctx.get("target") or "?"
# Classification du type d'erreur (pour message actionnable)
err_low = err.lower()
if "no route to host" in err_low or "network is unreachable" in err_low:
msg = f"Réseau injoignable ({target}) — vérifier routage/firewall"
elif "connection timed out" in err_low or "timed out" in err_low:
msg = f"Timeout connexion vers {target} — port SSH 22 filtré ou hôte down"
elif "connection refused" in err_low:
msg = f"Port 22 refusé sur {target} — sshd arrêté ou bloqué"
elif "no matching" in err_low and ("kex" in err_low or "key exchange" in err_low or "host key" in err_low):
msg = f"Algos KEX incompatibles avec {target} — durcissement SSH"
elif "host key" in err_low or "hostkey" in err_low:
msg = f"Host key inconnue/changée pour {target} — known_hosts ?"
elif "permission denied" in err_low or "authentication failed" in err_low:
msg = f"Authentification refusée par {target} — vérifier user/clé/password"
elif "no authentication methods" in err_low:
msg = f"Aucune méthode d'auth acceptée par {target}"
elif "name or service not known" in err_low or "could not resolve" in err_low:
msg = f"DNS échoué côté SSH ({target})"
elif err:
msg = f"Échec SSH vers {target}"
else:
msg = f"Échec connexion SSH vers {target} (raison inconnue)"
details = (err or "Pas d'exception capturée") + \
"\nVérifier ssh_method/clé/PSMP/mot de passe dans Settings (section SSH)."
return { return {
"name": "ssh", "name": "ssh",
"label": "Connexion SSH", "label": "Connexion SSH",
"status": "ko", "status": "ko",
"message": "Échec connexion SSH", "message": msg,
"details": "Vérifier ssh_method/clé/PSMP/mot de passe dans Settings.", "details": details,
} }
@ -315,14 +343,22 @@ def run_all_checks(hostname: str, row: Dict[str, Any] | None = None,
only_set = set(only) if only else None only_set = set(only) if only else None
target = _resolve(hostname) target = _resolve(hostname)
client = None client = None
ssh_error = None
ssh_method = None
if target and PARAMIKO_OK: if target and PARAMIKO_OK:
try: try:
client = _connect(target, hostname) client = _connect(target, hostname)
# _connect peut renvoyer un tuple (client, method) selon implem ; fallback :
if isinstance(client, tuple) and len(client) >= 1:
ssh_method = client[1] if len(client) > 1 else None
client = client[0]
except Exception as e: except Exception as e:
log.warning(f"_connect raised on {hostname}: {e}") log.warning(f"_connect raised on {hostname}: {e}")
ssh_error = f"{type(e).__name__}: {e}"
client = None client = None
ctx = {"hostname": hostname, "target": target, "client": client, "row": row or {}} ctx = {"hostname": hostname, "target": target, "client": client,
"row": row or {}, "ssh_error": ssh_error, "ssh_method": ssh_method}
results = [] results = []
for name, fn in CHECKS.items(): for name, fn in CHECKS.items():
if only_set is not None and name not in only_set: if only_set is not None and name not in only_set: