feat(qualys/agents): bouton Check + page audit cible Qualys agent (status + version + logs agent/systeme via SSH)

This commit is contained in:
Pierre & Lumière 2026-04-27 23:09:05 +02:00
parent dc9c197274
commit 03229d4d08
4 changed files with 145 additions and 0 deletions

View File

@ -1351,3 +1351,19 @@ async def qualys_asset_delete(request: Request, asset_id: int, db=Depends(get_db
from app.services.qualys_service import delete_qualys_asset
res = delete_qualys_asset(db, asset_id)
return JSONResponse(res)
@router.get("/qualys/agents/{hostname}/audit-qualys", response_class=HTMLResponse)
def qualys_agent_audit_page(hostname: str, request: Request, db=Depends(get_db)):
"""Audit cible Qualys Agent : status service + version + logs (agent + systeme)."""
user = get_current_user(request)
if not user:
return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "qualys"):
return RedirectResponse(url="/dashboard")
from app.services.realtime_audit_service import audit_qualys_agent_only
audit = audit_qualys_agent_only(hostname)
ctx = base_context(request, db, user)
ctx.update({"audit": audit, "hostname": hostname})
return templates.TemplateResponse("qualys_agent_audit.html", ctx)

View File

@ -554,3 +554,63 @@ def save_audit_to_db(db, results):
db.commit()
return updated, inserted
# ===========================================================================
# AUDIT CIBLE QUALYS AGENT — pour bouton "Check" sur page Agents inactifs
# Utilise la meme mecanique de connexion que audit_single_server (DB-driven)
# ===========================================================================
QUALYS_AGENT_CMDS = {
"agent_status": "systemctl status qualys-cloud-agent --no-pager 2>&1 | head -25 || /etc/init.d/qualys-cloud-agent status 2>&1 | head -25",
"agent_log": "tail -50 /var/log/qualys/qualys-cloud-agent.log 2>/dev/null || tail -50 /var/log/qualys-cloud-agent.log 2>/dev/null || echo \"log Qualys introuvable (chemins testes: /var/log/qualys/*, /var/log/qualys-cloud-agent.log)\"",
"system_log": "journalctl -u qualys-cloud-agent --no-pager -n 50 2>/dev/null || tail -50 /var/log/messages 2>/dev/null | grep -i qualys || echo \"journalctl + /var/log/messages indisponibles\"",
"agent_version": "/usr/local/qualys/cloud-agent/bin/qualys-cloud-agent.sh -v 2>&1 || rpm -q qualys-cloud-agent 2>/dev/null || echo \"version introuvable\"",
}
def audit_qualys_agent_only(hostname):
"""Audit cible Qualys Agent uniquement: status service + version + logs.
Utilise _resolve + _connect + _run comme audit_single_server.
Retourne dict {hostname, status, connection_method, resolved_fqdn, ...cmds}."""
result = {
"hostname": hostname,
"audit_date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"status": "PENDING",
"connection_method": None,
"resolved_fqdn": None,
}
for k in QUALYS_AGENT_CMDS:
result[k] = None
target = _resolve(hostname)
if not target:
result["status"] = "CONNECTION_FAILED"
result["connection_method"] = f"DNS: aucun suffixe resolu ({hostname})"
return result
result["resolved_fqdn"] = target
client = _connect(target, hostname)
if not client:
result["status"] = "CONNECTION_FAILED"
result["connection_method"] = f"SSH: connexion echouee a {target}"
return result
method = _resolve_ssh_method(hostname) or "ssh_key"
result["connection_method"] = f"{method} -> {target}"
try:
for key, cmd in QUALYS_AGENT_CMDS.items():
result[key] = _run(client, cmd) or "(empty)"
result["status"] = "OK"
except Exception as e:
result["status"] = "ERROR"
result["error_msg"] = str(e)
finally:
try:
client.close()
except Exception:
pass
return result

View File

@ -0,0 +1,67 @@
{% extends 'base.html' %}
{% block title %}Audit Qualys Agent — {{ hostname }}{% endblock %}
{% block content %}
<div class="flex justify-between items-center mb-4">
<div>
<h2 class="text-xl font-bold text-cyber-accent">Audit Qualys Agent</h2>
<p class="text-xs text-gray-500 mt-1 font-mono">{{ hostname }}{% if audit.resolved_fqdn %} → {{ audit.resolved_fqdn }}{% endif %}</p>
</div>
<div style="display:flex;gap:8px">
<a href="/qualys/agents/{{ hostname }}/audit-qualys" class="btn-sm bg-cyber-border text-gray-300 px-3 py-2 text-xs">Relancer</a>
<a href="/qualys/agents#inactive-list" class="btn-sm bg-cyber-border text-gray-300 px-3 py-2 text-xs">← Retour</a>
</div>
</div>
<!-- Bandeau statut connexion -->
<div class="card p-3 mb-4" style="
{% if audit.status == 'OK' %}border:1px solid #22c55e;background:rgba(34,197,94,0.08);
{% elif audit.status == 'CONNECTION_FAILED' %}border:1px solid #ef4444;background:rgba(239,68,68,0.08);
{% else %}border:1px solid #f59e0b;background:rgba(245,158,11,0.08);{% endif %}">
<div class="flex justify-between items-center text-xs">
<div>
<span class="font-bold {% if audit.status == 'OK' %}text-cyber-green{% elif audit.status == 'CONNECTION_FAILED' %}text-cyber-red{% else %}text-cyber-yellow{% endif %}">
{% if audit.status == 'OK' %}✓ Connecté{% elif audit.status == 'CONNECTION_FAILED' %}✗ Connexion échouée{% else %}⚠ {{ audit.status }}{% endif %}
</span>
<span class="text-gray-400 ml-3">{{ audit.connection_method or '-' }}</span>
{% if audit.error_msg %}<span class="text-cyber-red ml-3">{{ audit.error_msg }}</span>{% endif %}
</div>
<span class="text-gray-500 font-mono">{{ audit.audit_date }}</span>
</div>
</div>
{% if audit.status == 'OK' %}
<!-- Statut du service -->
<div class="card p-4 mb-4">
<h3 class="text-sm font-bold text-cyber-accent mb-2">État du service <code class="text-cyber-yellow">qualys-cloud-agent</code></h3>
<pre style="background:#0b0f1a;color:#e5e7eb;padding:10px;border-radius:4px;font-size:11px;overflow-x:auto;white-space:pre-wrap">{{ audit.agent_status or '(vide)' }}</pre>
</div>
<!-- Version agent -->
<div class="card p-4 mb-4">
<h3 class="text-sm font-bold text-cyber-accent mb-2">Version agent</h3>
<pre style="background:#0b0f1a;color:#e5e7eb;padding:10px;border-radius:4px;font-size:11px;overflow-x:auto;white-space:pre-wrap">{{ audit.agent_version or '(vide)' }}</pre>
</div>
<!-- Log agent Qualys -->
<div class="card p-4 mb-4">
<h3 class="text-sm font-bold text-cyber-accent mb-2">Log agent Qualys (50 dernières lignes)</h3>
<pre style="background:#0b0f1a;color:#e5e7eb;padding:10px;border-radius:4px;font-size:11px;overflow-x:auto;max-height:400px;white-space:pre-wrap">{{ audit.agent_log or '(vide)' }}</pre>
</div>
<!-- Log système -->
<div class="card p-4 mb-4">
<h3 class="text-sm font-bold text-cyber-accent mb-2">Log système (journalctl / messages)</h3>
<pre style="background:#0b0f1a;color:#e5e7eb;padding:10px;border-radius:4px;font-size:11px;overflow-x:auto;max-height:400px;white-space:pre-wrap">{{ audit.system_log or '(vide)' }}</pre>
</div>
{% else %}
<div class="card p-4 mb-4">
<p class="text-sm text-gray-400">Impossible de récupérer les informations de l'agent. Vérifie : SSH ouvert (port 22), méthode SSH configurée pour ce serveur dans <a href="/server/{{ hostname }}" class="text-cyber-accent hover:underline">la fiche serveur</a>, agent installé, OS supporté.</p>
</div>
{% endif %}
{% endblock %}

View File

@ -290,6 +290,7 @@ function refreshAgents(mode) {
<th class="p-2 sortable" onclick="sortTable(this)">Version agent</th>
<th class="p-2 sortable" onclick="sortTable(this)">Dernier check-in</th>
<th class="p-2 sortable" onclick="sortTable(this)">État</th>
<th class="p-2">Action</th>
</tr></thead>
<tbody>
{% for a in inactive_agents %}
@ -299,6 +300,7 @@ function refreshAgents(mode) {
<td class="p-2 text-center font-mono">{{ a.agent_version or '-' }}</td>
<td class="p-2 text-center text-cyber-yellow">{% if a.last_checkin %}{{ (a.last_checkin|string)[:10] }}{% else %}-{% endif %}</td>
<td class="p-2 text-center">{{ a.etat or '-' }}</td>
<td class="p-2 text-center"><a href="/qualys/agents/{{ a.hostname }}/audit-qualys" class="btn-sm bg-cyber-border text-cyber-accent px-2 py-1 text-xs hover:bg-cyber-hover" title="Audit SSH ciblé : status agent + logs">Check</a></td>
</tr>
{% endfor %}
</tbody>