feat(qualys/agents): suggestions auto resolution selon patterns logs (disque sature, crash loop, conn KO, service masked, agent obsolete)
This commit is contained in:
parent
54a2a59991
commit
a877589cf3
@ -641,6 +641,111 @@ _qualys_audit_cache = {} # hostname -> {status, result, started_at, finished_at
|
|||||||
_qualys_audit_lock = _threading.Lock()
|
_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):
|
def start_qualys_audit_async(hostname, force=False):
|
||||||
"""Lance audit_qualys_agent_only en background. Reuse run pending récent (<2min)."""
|
"""Lance audit_qualys_agent_only en background. Reuse run pending récent (<2min)."""
|
||||||
with _qualys_audit_lock:
|
with _qualys_audit_lock:
|
||||||
@ -731,5 +836,11 @@ def audit_qualys_agent_only(hostname):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Analyser les sorties pour suggerer des resolutions
|
||||||
|
if result["status"] == "OK":
|
||||||
|
result["suggestions"] = _analyze_qualys_audit(result)
|
||||||
|
else:
|
||||||
|
result["suggestions"] = []
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|||||||
@ -60,6 +60,24 @@
|
|||||||
|
|
||||||
{% if audit.status == 'OK' %}
|
{% 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é -->
|
<!-- OS détecté -->
|
||||||
<div class="card p-4 mb-4">
|
<div class="card p-4 mb-4">
|
||||||
<h3 class="text-sm font-bold text-cyber-accent mb-2">OS détecté</h3>
|
<h3 class="text-sm font-bold text-cyber-accent mb-2">OS détecté</h3>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user