v.0.11.4
This commit is contained in:
+77
-9
@@ -34,6 +34,40 @@ from .tzsp_rust import RustTZSPReceiver
|
||||
from .webui import WebServer
|
||||
|
||||
|
||||
def _process_rss_mib(pid: int | None) -> float | None:
|
||||
if not pid:
|
||||
return None
|
||||
try:
|
||||
for line in Path(f"/proc/{int(pid)}/status").read_text(encoding="ascii", errors="replace").splitlines():
|
||||
if line.startswith("VmRSS:"):
|
||||
parts = line.split()
|
||||
return round(int(parts[1]) / 1024.0, 1)
|
||||
except (OSError, ValueError, IndexError):
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _cgroup_memory_mib() -> dict[str, float | None]:
|
||||
def read_number(path: str) -> int | None:
|
||||
try:
|
||||
raw = Path(path).read_text(encoding="ascii").strip()
|
||||
if not raw or raw == "max":
|
||||
return None
|
||||
return int(raw)
|
||||
except (OSError, ValueError):
|
||||
return None
|
||||
|
||||
current = read_number("/sys/fs/cgroup/memory.current")
|
||||
limit = read_number("/sys/fs/cgroup/memory.max")
|
||||
if current is None:
|
||||
current = read_number("/sys/fs/cgroup/memory/memory.usage_in_bytes")
|
||||
limit = read_number("/sys/fs/cgroup/memory/memory.limit_in_bytes")
|
||||
return {
|
||||
"current_mib": round(current / (1024 * 1024), 1) if current is not None else None,
|
||||
"limit_mib": round(limit / (1024 * 1024), 1) if limit is not None else None,
|
||||
}
|
||||
|
||||
|
||||
def _routeros_target(cfg: Config) -> tuple[str, int]:
|
||||
parsed = urlparse(cfg.routeros_url)
|
||||
host = parsed.hostname or cfg.routeros_url
|
||||
@@ -240,8 +274,8 @@ def main() -> int:
|
||||
traffic_history = TrafficHistory(
|
||||
cfg.redis_url,
|
||||
cfg.traffic_retention_hours,
|
||||
0,
|
||||
0,
|
||||
cfg.traffic_max_events,
|
||||
cfg.traffic_memory_events,
|
||||
require_redis=True,
|
||||
allow_memory_fallback=False,
|
||||
archive_store=store,
|
||||
@@ -282,6 +316,26 @@ def main() -> int:
|
||||
)
|
||||
routeros_host, routeros_port = _routeros_target(cfg)
|
||||
|
||||
def healthcheck() -> dict:
|
||||
# Docker calls this frequently. Keep it strictly O(1): no rule-file
|
||||
# scans, archive counts, directory walks or dashboard aggregates.
|
||||
suricata_up = suricata.poll() is None
|
||||
tzsp_up = receiver.is_alive()
|
||||
tap_up = os.path.exists(f"/sys/class/net/{cfg.tap_name}")
|
||||
eve_up = watcher.is_alive()
|
||||
db_up = store.ping()
|
||||
routeros_required_ok = (not cfg.auto_block) or routeros.configured
|
||||
operational = suricata_up and tzsp_up and tap_up and eve_up and db_up and routeros_required_ok
|
||||
return {
|
||||
"status": "ok" if operational else "degraded",
|
||||
"operational": operational,
|
||||
"suricata_running": suricata_up,
|
||||
"tzsp_running": tzsp_up,
|
||||
"tap_up": tap_up,
|
||||
"eve_running": eve_up,
|
||||
"database_ok": db_up,
|
||||
}
|
||||
|
||||
def health() -> dict:
|
||||
suricata_up = suricata.poll() is None
|
||||
tzsp_up = receiver.is_alive()
|
||||
@@ -294,6 +348,10 @@ def main() -> int:
|
||||
runtime = stats.snapshot()
|
||||
receiver_status = receiver.status()
|
||||
redis_status = redis_supervisor.status()
|
||||
history_status = traffic_history.status()
|
||||
ndr_status = ndr_analyzer.status()
|
||||
ndr_summary = store.ndr_summary()
|
||||
notifier_status = notifier.status()
|
||||
suri_stats = runtime.get("suricata") or {}
|
||||
kernel_packets = int(suri_stats.get("capture.kernel_packets", 0) or 0)
|
||||
kernel_drops = int(suri_stats.get("capture.kernel_drops", 0) or 0)
|
||||
@@ -316,6 +374,13 @@ def main() -> int:
|
||||
core_up = suricata_up and tzsp_up and tap_up and eve_up and db["ok"]
|
||||
routeros_required_ok = (not cfg.auto_block) or routeros.configured
|
||||
operational = core_up and routeros_required_ok
|
||||
process_memory = {
|
||||
"python_rss_mib": _process_rss_mib(os.getpid()),
|
||||
"rust_rss_mib": _process_rss_mib(receiver.pid),
|
||||
"suricata_rss_mib": _process_rss_mib(suricata.pid if suricata_up else None),
|
||||
"redis_rss_mib": _process_rss_mib(redis_status.get("pid")),
|
||||
"cgroup": _cgroup_memory_mib(),
|
||||
}
|
||||
|
||||
return {
|
||||
"status": "ok" if operational else "degraded",
|
||||
@@ -328,11 +393,13 @@ def main() -> int:
|
||||
"suricata_pid": suricata.pid,
|
||||
"auto_block": cfg.auto_block,
|
||||
"routeros_configured": routeros.configured,
|
||||
"ndr": {**ndr_analyzer.status(), **store.ndr_summary()},
|
||||
"ndr": {**ndr_status, **ndr_summary},
|
||||
"database": db,
|
||||
"storage": storage,
|
||||
"rules": rules,
|
||||
"redis": redis_status,
|
||||
"memory": process_memory,
|
||||
"traffic_history": history_status,
|
||||
"services": {
|
||||
"web": {
|
||||
"name": "Web UI / API",
|
||||
@@ -387,7 +454,7 @@ def main() -> int:
|
||||
},
|
||||
"traffic_history": {
|
||||
"name": "Live traffic history",
|
||||
"status": "up" if traffic_history.status().get("redis_ok") else "degraded",
|
||||
"status": "up" if history_status.get("redis_ok") else "degraded",
|
||||
"details": f"Redis ingest buffer -> SQLite archive; retention={cfg.traffic_retention_hours}h",
|
||||
},
|
||||
"analytics_cache": {
|
||||
@@ -397,13 +464,13 @@ def main() -> int:
|
||||
},
|
||||
"ndr": {
|
||||
"name": "MikroSuricata NDR correlation",
|
||||
"status": "up" if ndr_analyzer.status().get("running") else "disabled" if not cfg.ndr_enabled else "degraded",
|
||||
"details": f"assets={store.ndr_summary()['assets']}; incidents={store.ndr_summary()['incidents']}; IOC={store.ndr_summary()['enabled_iocs']}; queue={ndr_analyzer.status()['queue']}",
|
||||
"status": "up" if ndr_status.get("running") else "disabled" if not cfg.ndr_enabled else "degraded",
|
||||
"details": f"assets={ndr_summary['assets']}; incidents={ndr_summary['incidents']}; IOC={ndr_summary['enabled_iocs']}; queue={ndr_status['queue']}",
|
||||
},
|
||||
"notifications": {
|
||||
"name": "High-risk webhook notifications",
|
||||
"status": "up" if notifier.status().get("running") else "disabled" if not notifier.enabled else "degraded",
|
||||
"details": f"min risk={cfg.notify_min_risk}; sent={notifier.status()['sent']}; failed={notifier.status()['failed']}; queue={notifier.status()['queue']}",
|
||||
"status": "up" if notifier_status.get("running") else "disabled" if not notifier.enabled else "degraded",
|
||||
"details": f"min risk={cfg.notify_min_risk}; sent={notifier_status['sent']}; failed={notifier_status['failed']}; queue={notifier_status['queue']}",
|
||||
},
|
||||
"redis": {
|
||||
"name": "Managed Redis",
|
||||
@@ -455,7 +522,7 @@ def main() -> int:
|
||||
"status": routeros_status,
|
||||
},
|
||||
],
|
||||
"runtime": stats.snapshot(),
|
||||
"runtime": runtime,
|
||||
}
|
||||
|
||||
version_path = Path(__file__).resolve().parents[1] / "VERSION"
|
||||
@@ -514,6 +581,7 @@ def main() -> int:
|
||||
forensic_pcap=forensic_pcap,
|
||||
traffic_source=receiver,
|
||||
metrics_provider=prometheus_metrics.render,
|
||||
healthcheck_provider=healthcheck,
|
||||
)
|
||||
|
||||
def housekeeping() -> None:
|
||||
|
||||
Reference in New Issue
Block a user