ligbox-ops-platform/api/app/wazuh_soc_store.py
Ligbox Spec Hub 3a2c64834b Initial import: ligbox-ops-platform + specs + LAPTOP + obsidian merge (CT130)
Source: VM122 /opt + obsidian-infra + LAPTOP
Hub: CT130 spec-hub 10.10.10.130
2026-06-19 17:26:41 +00:00

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": [],
}