"""Wazuh SOC — dados para Audit Overview (tenant VM104).""" from __future__ import annotations import json import os import sqlite3 from datetime import datetime, timedelta, timezone from typing import Any import httpx WAZUH_TENANT_ID = 2 WAZUH_API_URL = os.getenv("WAZUH_API_URL", "https://10.10.10.104:55000/") WAZUH_MIN_LEVEL = int(os.getenv("WAZUH_MIN_TICKET_LEVEL", "10")) def _now() -> str: return datetime.now(timezone.utc).isoformat() def _parse_payload(raw: str | dict) -> dict: if isinstance(raw, dict): return raw try: return json.loads(raw or "{}") except json.JSONDecodeError: return {} def wazuh_api_status() -> dict: try: with httpx.Client(timeout=5.0, verify=False) as client: response = client.get(WAZUH_API_URL) online = response.status_code in (200, 401) return { "reachable": True, "http_status": response.status_code, "api_online": online, } except Exception as exc: return {"reachable": False, "http_status": None, "api_online": False, "error": str(exc)} def _parse_alert_row(row: sqlite3.Row) -> dict: payload = _parse_payload(row["payload"]) data = payload.get("data") or {} level = int(data.get("level") or 0) return { "id": row["id"], "event_type": row["event_type"], "created_at": row["created_at"], "level": level, "rule_id": data.get("rule_id"), "description": (data.get("description") or "").strip(), "agent": (data.get("agent") or payload.get("domain") or "—").strip(), "agent_ip": data.get("agent_ip"), "srcip": data.get("srcip"), "session_id": payload.get("session_id"), "severity": _level_severity(level), } def _level_severity(level: int) -> str: if level >= 12: return "critical" if level >= WAZUH_MIN_LEVEL: return "high" if level >= 7: return "medium" return "low" def list_wazuh_alerts(conn: sqlite3.Connection, limit: int = 200) -> list[dict]: rows = conn.execute( """ SELECT id, event_type, payload, created_at FROM webhook_events WHERE source = 'wazuh' ORDER BY id DESC LIMIT ? """, (limit,), ).fetchall() return [_parse_alert_row(r) for r in rows] def _in_hours(iso: str | None, hours: int) -> bool: if not iso: return False try: ts = datetime.fromisoformat(iso.replace("Z", "+00:00")) if ts.tzinfo is None: ts = ts.replace(tzinfo=timezone.utc) return datetime.now(timezone.utc) - ts <= timedelta(hours=hours) except ValueError: return False def _build_agents(alerts: list[dict]) -> list[dict]: agents: dict[str, dict] = {} for alert in alerts: name = alert["agent"] or "—" entry = agents.setdefault( name, { "agent": name, "agent_ip": alert.get("agent_ip"), "alerts_count": 0, "max_level": 0, "last_seen": alert["created_at"], }, ) entry["alerts_count"] += 1 entry["max_level"] = max(entry["max_level"], alert["level"]) if alert["created_at"] > entry["last_seen"]: entry["last_seen"] = alert["created_at"] if alert.get("agent_ip"): entry["agent_ip"] = alert["agent_ip"] return sorted(agents.values(), key=lambda a: (-a["max_level"], -a["alerts_count"])) def _overall_status(alerts: list[dict], api_online: bool, open_tickets: int) -> str: recent_24h = [a for a in alerts if _in_hours(a["created_at"], 24)] max_level_24h = max((a["level"] for a in recent_24h), default=0) if max_level_24h >= 12 or open_tickets > 0: return "critical" if any(a["level"] >= WAZUH_MIN_LEVEL for a in recent_24h): return "degraded" if alerts and api_online: return "healthy" if api_online: return "healthy" if alerts: return "degraded" return "unknown" def wazuh_tenant_overview( conn: sqlite3.Connection, tenant_id: int, name: str, ip: str, ) -> dict: alerts = list_wazuh_alerts(conn, 200) agents = _build_agents(alerts) api = wazuh_api_status() open_tickets = conn.execute( "SELECT COUNT(*) c FROM tickets WHERE tenant_id = ? AND status NOT IN ('closed', 'resolved')", (tenant_id,), ).fetchone()["c"] alerts_24h = sum(1 for a in alerts if _in_hours(a["created_at"], 24)) alerts_high = sum(1 for a in alerts if a["level"] >= WAZUH_MIN_LEVEL) status = _overall_status(alerts, api.get("api_online", False), open_tickets) last_alert = alerts[0]["created_at"] if alerts else None top_issues = [ { "domain": a["agent"], "check_id": f"L{a['level']}", "status": a["severity"], "message": a["description"] or a["event_type"], "at": a["created_at"], } for a in alerts[:5] ] return { "tenant_id": tenant_id, "name": name, "ip": ip, "kind": "wazuh_soc", "status": status, "api_online": api.get("api_online", False), "http_status": api.get("http_status"), "alerts_total": len(alerts), "alerts_24h": alerts_24h, "alerts_high": alerts_high, "agents_count": len(agents), "open_tickets": open_tickets, "min_ticket_level": WAZUH_MIN_LEVEL, "domains_count": 0, "last_audit_at": last_alert, "last_alert_at": last_alert, "score": { "pass": len(agents), "warn": alerts_high, "fail": open_tickets, "total": max(len(alerts), 1), }, "top_issues": top_issues, } def wazuh_tenant_details( conn: sqlite3.Connection, tenant_id: int, name: str, ip: str, ) -> dict: alerts = list_wazuh_alerts(conn, 100) agents = _build_agents(alerts) api = wazuh_api_status() tickets = conn.execute( """ SELECT id, subject, status, created_at, session_id FROM tickets WHERE tenant_id = ? ORDER BY id DESC LIMIT 50 """, (tenant_id,), ).fetchall() ticket_rows = [dict(r) for r in tickets] open_tickets = sum(1 for t in ticket_rows if t["status"] not in ("closed", "resolved")) alerts_24h = [a for a in alerts if _in_hours(a["created_at"], 24)] alerts_7d = [a for a in alerts if _in_hours(a["created_at"], 168)] level_10_plus = sum(1 for a in alerts if a["level"] >= WAZUH_MIN_LEVEL) level_12_plus = sum(1 for a in alerts if a["level"] >= 12) return { "tenant_id": tenant_id, "name": name, "ip": ip, "kind": "wazuh_soc", "generated_at": _now(), "api": api, "min_ticket_level": WAZUH_MIN_LEVEL, "summary": { "alerts_total": len(alerts), "alerts_24h": len(alerts_24h), "alerts_7d": len(alerts_7d), "agents_total": len(agents), "level_10_plus": level_10_plus, "level_12_plus": level_12_plus, "open_tickets": open_tickets, "api_online": api.get("api_online", False), }, "agents": agents, "alerts": alerts, "tickets": ticket_rows, "domains": [], }