feat(qualys/agents): suggestions auto resolution selon patterns logs (disque sature, crash loop, conn KO, service masked, agent obsolete)

This commit is contained in:
Pierre & Lumière 2026-04-28 00:01:14 +02:00
parent 54a2a59991
commit a877589cf3
2 changed files with 129 additions and 0 deletions

View File

@ -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

View File

@ -60,6 +60,24 @@
{% if audit.status == 'OK' %}
{% if audit.suggestions %}
<!-- Suggestions de résolution -->
<div class="card p-4 mb-4" style="border:1px solid #00ffc8;background:rgba(0,255,200,0.05)">
<h3 class="text-sm font-bold text-cyber-accent mb-3">💡 Suggestions de résolution ({{ audit.suggestions|length }})</h3>
<div style="display:flex;flex-direction:column;gap:10px">
{% for s in audit.suggestions %}
<div style="border-left:3px solid {% if s.severity == 'critical' %}#ef4444{% elif s.severity == 'high' %}#f97316{% elif s.severity == 'medium' %}#f59e0b{% else %}#3b82f6{% endif %};padding:8px 12px;background:#0b0f1a;border-radius:4px">
<div class="flex items-center gap-2 mb-1">
<span class="badge {% if s.severity == 'critical' %}badge-red{% elif s.severity == 'high' %}badge-red{% elif s.severity == 'medium' %}badge-yellow{% else %}badge-gray{% endif %}" style="font-size:9px">{{ s.severity|upper }}</span>
<span class="text-cyber-accent font-bold text-xs">{{ s.title }}</span>
</div>
<pre style="color:#d1d5db;font-size:11px;white-space:pre-wrap;margin:0">{{ s.fix }}</pre>
</div>
{% endfor %}
</div>
</div>
{% endif %}
<!-- OS détecté -->
<div class="card p-4 mb-4">
<h3 class="text-sm font-bold text-cyber-accent mb-2">OS détecté</h3>