feat(qualys/agents): checks LVM + logrotate + suggestions extend FS / cleanup / fix logrotate avec snapshot LVM obligatoire

This commit is contained in:
Pierre & Lumière 2026-04-28 00:11:45 +02:00
parent b81343d5ca
commit 640292c1ce
2 changed files with 88 additions and 5 deletions

View File

@ -625,6 +625,26 @@ QUALYS_AGENT_CMDS = {
" curl --connect-timeout 3 -sS -o /dev/null -w 'qualysagent direct: HTTP %{http_code} | %{time_total}s\\n' https://qualysagent.qualys.eu 2>&1 || echo 'route directe KO (normal sur LAN SANEF, sortie via proxy obligatoire)'; " " curl --connect-timeout 3 -sS -o /dev/null -w 'qualysagent direct: HTTP %{http_code} | %{time_total}s\\n' https://qualysagent.qualys.eu 2>&1 || echo 'route directe KO (normal sur LAN SANEF, sortie via proxy obligatoire)'; "
"fi" "fi"
), ),
"lvm_info": (
"echo '=== Volume Groups (espace libre dans le VG) ==='; "
"(sudo -n vgs --noheadings --units g -o vg_name,vg_size,vg_free 2>/dev/null || "
" vgs --noheadings --units g -o vg_name,vg_size,vg_free 2>/dev/null) | head -10 || echo '(pas LVM ou commande non autorisee)'; "
"echo; echo '=== Logical Volumes (filtre log/var) ==='; "
"(sudo -n lvs --noheadings --units g -o lv_name,vg_name,lv_size,lv_attr 2>/dev/null || "
" lvs --noheadings --units g -o lv_name,vg_name,lv_size,lv_attr 2>/dev/null) | grep -iE 'log|var' || echo '(pas de LV log/var ou non lisible)'; "
"echo; echo '=== FS type sur /var/log ==='; "
"(stat -f -c '%T' /var/log 2>/dev/null) || (df -T /var/log 2>/dev/null | awk 'NR==2{print $2}') || echo '(stat KO)'"
),
"logrotate_config": (
"FOUND=0; "
"for f in /etc/logrotate.d/qualys-cloud-agent /etc/logrotate.d/qualys "
"/etc/logrotate.d/qualysagent; do "
" if [ -e \"$f\" ]; then echo \"=== $f ===\"; (cat \"$f\" 2>/dev/null || sudo -n cat \"$f\" 2>/dev/null); FOUND=1; fi; "
"done; "
"if [ $FOUND -eq 0 ]; then echo '(pas de config logrotate dediee Qualys — l agent gere ses logs en interne)'; fi; "
"echo; echo '=== /etc/qualys/cloud-agent/qagent-log.conf ==='; "
"(cat /etc/qualys/cloud-agent/qagent-log.conf 2>/dev/null || sudo -n cat /etc/qualys/cloud-agent/qagent-log.conf 2>/dev/null) || echo '(non trouve / non lisible)'"
),
"system_log": ( "system_log": (
"if command -v journalctl >/dev/null 2>&1; then " "if command -v journalctl >/dev/null 2>&1; then "
" out=$(journalctl -u qualys-cloud-agent --no-pager -n 50 2>/dev/null || sudo -n journalctl -u qualys-cloud-agent --no-pager -n 50 2>/dev/null); " " out=$(journalctl -u qualys-cloud-agent --no-pager -n 50 2>/dev/null || sudo -n journalctl -u qualys-cloud-agent --no-pager -n 50 2>/dev/null); "
@ -661,15 +681,45 @@ def _analyze_qualys_audit(r):
s_conn = (r.get("qualys_connectivity") or "").lower() s_conn = (r.get("qualys_connectivity") or "").lower()
s_ver = (r.get("agent_version") or "") s_ver = (r.get("agent_version") or "")
# Disque saturé / agent ne peut écrire s_lvm = (r.get("lvm_info") or "")
if " 100%" in s_disk or "no space left" in (s_log + s_sys): s_lrt = (r.get("logrotate_config") or "").lower()
# Disque saturé / agent ne peut écrire — multi-suggestions selon contexte
disk_full = " 100%" in s_disk or "no space left" in (s_log + s_sys)
if disk_full:
# 1. Cleanup des vieux logs (action sûre, à proposer en premier)
suggestions.append({ suggestions.append({
"severity": "critical", "severity": "critical",
"title": "Partition saturée", "title": "Partition /var/log saturée — cleanup vieux logs (action sûre)",
"fix": "Libérer /var/log/qualys (souvent les .log.0/1/2 du crash loop) :\n" "fix": "Libérer l'espace en supprimant les .log archivés du crash loop :\n"
"sudo find /var/log/qualys -name '*.log.[0-9]*' -mtime +7 -delete\n" "sudo find /var/log/qualys -name '*.log.[0-9]*' -mtime +7 -delete\n"
"puis : sudo systemctl restart qualys-cloud-agent" "sudo find /var/log -name 'messages-*' -mtime +30 -delete\n"
"sudo journalctl --vacuum-size=200M\n"
"Puis : sudo systemctl restart qualys-cloud-agent"
}) })
# 2. Si LVM avec free dans VG -> extend FS
# Cherche pattern : "vg_name size free" où free > 0
m_vg = re.search(r"(\S+)\s+([\d.]+)g\s+([\d.]+)g", s_lvm.lower())
if m_vg and float(m_vg.group(3)) > 0.5:
vg_name = m_vg.group(1)
free_gb = float(m_vg.group(3))
# Trouver le LV /var/log
m_lv = re.search(r"(varloglv|var_log|log)\s+(\S+)\s+([\d.]+)g", s_lvm.lower())
lv_name = m_lv.group(1) if m_lv else "varloglv"
extend_size = min(2.0, free_gb) # +2G max ou tout ce qui reste
suggestions.append({
"severity": "high",
"title": f"LVM : VG {vg_name} a {free_gb}G libres — étendre /var/log",
"fix": f"⚠ SNAPSHOT LVM AVANT TOUTE ACTION (sécurité) :\n"
f"sudo lvcreate -s -L 500M -n {lv_name}-snap-$(date +%Y%m%d-%H%M) /dev/{vg_name}/{lv_name}\n\n"
f"Puis extension du LV :\n"
f"sudo lvextend -L +{extend_size}G /dev/{vg_name}/{lv_name}\n\n"
f"Puis grow du FS (selon type — voir bloc LVM pour XFS/ext4) :\n"
f"sudo xfs_growfs /var/log # si XFS\n"
f"sudo resize2fs /dev/{vg_name}/{lv_name} # si ext4\n\n"
f"En cas de souci, rollback via le snapshot."
})
if "cannot write file" in s_sys or "logger initialization failed" in s_sys: if "cannot write file" in s_sys or "logger initialization failed" in s_sys:
suggestions.append({ suggestions.append({
"severity": "critical", "severity": "critical",
@ -744,6 +794,27 @@ def _analyze_qualys_audit(r):
f"(https://qualysguard.qualys.eu)." f"(https://qualysguard.qualys.eu)."
}) })
# Logrotate Qualys mal configuré
if s_lrt and "qualys" in s_lrt:
if "compress" in s_lrt and ("nocompress" in s_lrt or
not re.search(r"^\s*compress\s*$", s_lrt, re.MULTILINE)):
suggestions.append({
"severity": "medium",
"title": "Logrotate Qualys : compression désactivée",
"fix": "Logrotate sans compression = vieux logs prennent 5-10x plus de place. "
"Editer /etc/logrotate.d/qualys-cloud-agent et ajouter (ou décommenter) :\n"
"compress\ndelaycompress\nrotate 7\n"
"Puis : sudo logrotate -f /etc/logrotate.d/qualys-cloud-agent"
})
if "rotate" not in s_lrt:
suggestions.append({
"severity": "low",
"title": "Logrotate Qualys : pas de directive rotate",
"fix": "Ajouter une politique de rétention dans /etc/logrotate.d/qualys-cloud-agent :\n"
"rotate 7 # 7 fichiers archives max\n"
"size 100M # rotation auto a 100M"
})
# OS EOL (RHEL 5/6) # OS EOL (RHEL 5/6)
s_os = (r.get("os_release") or "").lower() s_os = (r.get("os_release") or "").lower()
if "release 5" in s_os or "release 6" in s_os: if "release 5" in s_os or "release 6" in s_os:

View File

@ -123,6 +123,18 @@
})(); })();
</script> </script>
<!-- LVM info -->
<div class="card p-4 mb-4">
<h3 class="text-sm font-bold text-cyber-accent mb-2">LVM (Volume Groups + LV log/var)</h3>
<pre style="background:#0b0f1a;color:#e5e7eb;padding:10px;border-radius:4px;font-size:11px;overflow-x:auto;white-space:pre-wrap">{{ audit.lvm_info or '(vide)' }}</pre>
</div>
<!-- Logrotate config -->
<div class="card p-4 mb-4">
<h3 class="text-sm font-bold text-cyber-accent mb-2">Configuration logrotate Qualys</h3>
<pre style="background:#0b0f1a;color:#e5e7eb;padding:10px;border-radius:4px;font-size:11px;overflow-x:auto;white-space:pre-wrap">{{ audit.logrotate_config or '(vide)' }}</pre>
</div>
<!-- Connectivité console Qualys --> <!-- Connectivité console Qualys -->
<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">Connectivité console Qualys</h3> <h3 class="text-sm font-bold text-cyber-accent mb-2">Connectivité console Qualys</h3>