diff --git a/app/services/realtime_audit_service.py b/app/services/realtime_audit_service.py index 6baba86..9cca199 100644 --- a/app/services/realtime_audit_service.py +++ b/app/services/realtime_audit_service.py @@ -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)'; " "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": ( "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); " @@ -661,15 +681,45 @@ def _analyze_qualys_audit(r): 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): + s_lvm = (r.get("lvm_info") or "") + 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({ "severity": "critical", - "title": "Partition saturée", - "fix": "Libérer /var/log/qualys (souvent les .log.0/1/2 du crash loop) :\n" + "title": "Partition /var/log saturée — cleanup vieux logs (action sûre)", + "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" - "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: suggestions.append({ "severity": "critical", @@ -744,6 +794,27 @@ def _analyze_qualys_audit(r): 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) s_os = (r.get("os_release") or "").lower() if "release 5" in s_os or "release 6" in s_os: diff --git a/app/templates/qualys_agent_audit.html b/app/templates/qualys_agent_audit.html index bd1c63b..0905eec 100644 --- a/app/templates/qualys_agent_audit.html +++ b/app/templates/qualys_agent_audit.html @@ -123,6 +123,18 @@ })(); + +
+

LVM (Volume Groups + LV log/var)

+
{{ audit.lvm_info or '(vide)' }}
+
+ + +
+

Configuration logrotate Qualys

+
{{ audit.logrotate_config or '(vide)' }}
+
+

Connectivité console Qualys