238 lines
7.3 KiB
Python
238 lines
7.3 KiB
Python
"""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": [],
|
|
}
|