From a877589cf371aff61ff2aff355f870e2349bc982 Mon Sep 17 00:00:00 2001 From: Admin MPCZ Date: Tue, 28 Apr 2026 00:01:14 +0200 Subject: [PATCH] feat(qualys/agents): suggestions auto resolution selon patterns logs (disque sature, crash loop, conn KO, service masked, agent obsolete) --- app/services/realtime_audit_service.py | 111 +++++++++++++++++++++++++ app/templates/qualys_agent_audit.html | 18 ++++ 2 files changed, 129 insertions(+) diff --git a/app/services/realtime_audit_service.py b/app/services/realtime_audit_service.py index 9151e4f..45243f5 100644 --- a/app/services/realtime_audit_service.py +++ b/app/services/realtime_audit_service.py @@ -641,6 +641,111 @@ _qualys_audit_cache = {} # hostname -> {status, result, started_at, finished_at _qualys_audit_lock = _threading.Lock() +def _analyze_qualys_audit(r): + """Analyse les sorties d'audit pour suggerer des resolutions concretes. + Retourne liste de {severity: critical|high|medium|low, title, fix}.""" + import re + suggestions = [] + s_status = (r.get("agent_status") or "").lower() + s_log = (r.get("agent_log") or "").lower() + s_sys = (r.get("system_log") or "").lower() + s_disk = (r.get("disk_space") or "") + s_conn = (r.get("qualys_connectivity") or "").lower() + s_ver = (r.get("agent_version") or "") + + # Disque saturé / agent ne peut écrire + if " 100%" in s_disk or "no space left" in (s_log + s_sys): + suggestions.append({ + "severity": "critical", + "title": "Partition saturée", + "fix": "Libérer /var/log/qualys (souvent les .log.0/1/2 du crash loop) :\n" + "sudo find /var/log/qualys -name '*.log.[0-9]*' -mtime +7 -delete\n" + "puis : sudo systemctl restart qualys-cloud-agent" + }) + if "cannot write file" in s_sys or "logger initialization failed" in s_sys: + suggestions.append({ + "severity": "critical", + "title": "Agent ne peut pas écrire son log", + "fix": "Vérifier permissions /var/log/qualys (drwxrwx--- root:root) ET espace dispo. " + "Si /var/log saturé : libérer l'espace puis restart agent." + }) + + # Crash loop + m = re.search(r"restart counter is at (\d+)", s_sys) + if m and int(m.group(1)) > 50: + suggestions.append({ + "severity": "high", + "title": f"Crash loop ({m.group(1)} restarts)", + "fix": "Stopper l'agent pour éviter d'amplifier le problème :\n" + "sudo systemctl stop qualys-cloud-agent\n" + "Corriger la cause racine (voir suggestions ci-dessus), puis :\n" + "sudo systemctl reset-failed qualys-cloud-agent && sudo systemctl start qualys-cloud-agent" + }) + + # Connectivité KO + if any(k in s_conn for k in ["connexion echec", "connection refused", "timed out", + "could not resolve", "no route", "unreachable"]): + suggestions.append({ + "severity": "high", + "title": "Connectivité Qualys cloud KO", + "fix": "Flux sortant 443/TCP bloqué vers qualysagent.qualys.eu et qualysguard.qualys.eu. " + "Vérifier : pfSense/firewall périmétrique, proxy HTTP(S) si configuré, NAT, " + "DNS interne (résolution *.qualys.eu). Test depuis le serveur :\n" + "curl -v --connect-timeout 5 https://qualysagent.qualys.eu/" + }) + if "certificate verify failed" in s_conn or "ssl" in s_conn and "verify" in s_conn: + suggestions.append({ + "severity": "high", + "title": "Erreur TLS/SSL", + "fix": "Possible interception SSL (proxy MITM) ou bundle CA obsolète. " + "Vérifier proxy d'entreprise et chaîne de certificats du serveur." + }) + + # Service désactivé / arrêté + if "masked" in s_status: + suggestions.append({ + "severity": "medium", + "title": "Service masked", + "fix": "sudo systemctl unmask qualys-cloud-agent && sudo systemctl enable --now qualys-cloud-agent" + }) + elif "disabled" in s_status: + suggestions.append({ + "severity": "medium", + "title": "Service disabled au boot", + "fix": "sudo systemctl enable --now qualys-cloud-agent" + }) + elif any(k in s_status for k in ["inactive (dead)", "stopped", "not running"]) \ + and "active" not in s_status: + suggestions.append({ + "severity": "medium", + "title": "Service arrêté", + "fix": "sudo systemctl start qualys-cloud-agent\n" + "Si crash immédiat, consulter les logs ci-dessous pour la cause." + }) + + # Agent obsolète + if re.match(r"^qualys-cloud-agent-([0-5]\.|6\.[01]\.)", s_ver): + suggestions.append({ + "severity": "low", + "title": "Agent obsolète", + "fix": f"Version installée: {s_ver.strip()}. Mettre à jour vers la dernière version " + f"stable (7.x) — Activation Key et package depuis console Qualys " + f"(https://qualysguard.qualys.eu)." + }) + + # OS EOL (RHEL 5/6) + s_os = (r.get("os_release") or "").lower() + if "release 5" in s_os or "release 6" in s_os: + suggestions.append({ + "severity": "low", + "title": "OS en fin de vie", + "fix": "RHEL 5/6 EOL — agent Qualys 7.x non supporté. Migrer vers RHEL 8/9 " + "ou conserver l'agent legacy en attendant la décom du serveur." + }) + + return suggestions + + def start_qualys_audit_async(hostname, force=False): """Lance audit_qualys_agent_only en background. Reuse run pending récent (<2min).""" with _qualys_audit_lock: @@ -731,5 +836,11 @@ def audit_qualys_agent_only(hostname): except Exception: pass + # Analyser les sorties pour suggerer des resolutions + if result["status"] == "OK": + result["suggestions"] = _analyze_qualys_audit(result) + else: + result["suggestions"] = [] + return result diff --git a/app/templates/qualys_agent_audit.html b/app/templates/qualys_agent_audit.html index f774113..12244a3 100644 --- a/app/templates/qualys_agent_audit.html +++ b/app/templates/qualys_agent_audit.html @@ -60,6 +60,24 @@ {% if audit.status == 'OK' %} +{% if audit.suggestions %} + +
+

💡 Suggestions de résolution ({{ audit.suggestions|length }})

+
+ {% for s in audit.suggestions %} +
+
+ {{ s.severity|upper }} + {{ s.title }} +
+
{{ s.fix }}
+
+ {% endfor %} +
+
+{% endif %} +

OS détecté