first commit
This commit is contained in:
+97
@@ -0,0 +1,97 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from .policy import PolicyEngine
|
||||
from .routeros import RouterOSClient
|
||||
from .state import RuntimeStats
|
||||
from .store import AlertStore
|
||||
|
||||
|
||||
class EVEWatcher(threading.Thread):
|
||||
def __init__(
|
||||
self,
|
||||
path: str,
|
||||
store: AlertStore,
|
||||
policy: PolicyEngine,
|
||||
routeros: RouterOSClient,
|
||||
block_timeout: str,
|
||||
stats: RuntimeStats,
|
||||
stop_event: threading.Event,
|
||||
) -> None:
|
||||
super().__init__(name="eve-watcher", daemon=True)
|
||||
self.path = path
|
||||
self.store = store
|
||||
self.policy = policy
|
||||
self.routeros = routeros
|
||||
self.block_timeout = block_timeout
|
||||
self.stats = stats
|
||||
self.stop_event = stop_event
|
||||
|
||||
def run(self) -> None:
|
||||
while not self.stop_event.is_set():
|
||||
if not os.path.exists(self.path):
|
||||
time.sleep(0.5)
|
||||
continue
|
||||
try:
|
||||
self._follow_file()
|
||||
except OSError as exc:
|
||||
print(f"[eve] file error: {exc}", flush=True)
|
||||
time.sleep(1.0)
|
||||
|
||||
def _follow_file(self) -> None:
|
||||
with open(self.path, "r", encoding="utf-8", errors="replace") as handle:
|
||||
handle.seek(0, os.SEEK_END)
|
||||
inode = os.fstat(handle.fileno()).st_ino
|
||||
print(f"[eve] following {self.path}", flush=True)
|
||||
|
||||
while not self.stop_event.is_set():
|
||||
line = handle.readline()
|
||||
if line:
|
||||
self._process_line(line)
|
||||
continue
|
||||
|
||||
try:
|
||||
stat = os.stat(self.path)
|
||||
if stat.st_ino != inode or stat.st_size < handle.tell():
|
||||
return
|
||||
except FileNotFoundError:
|
||||
return
|
||||
time.sleep(0.2)
|
||||
|
||||
def _process_line(self, line: str) -> None:
|
||||
try:
|
||||
event: dict[str, Any] = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
self.stats.inc("eve_parse_errors")
|
||||
return
|
||||
|
||||
self.stats.inc("eve_events")
|
||||
if event.get("event_type") != "alert":
|
||||
return
|
||||
|
||||
self.stats.inc("eve_alerts")
|
||||
self.stats.stamp("last_alert_at")
|
||||
decision = self.policy.evaluate(event)
|
||||
blocked = False
|
||||
reason = decision.reason
|
||||
|
||||
if decision.should_block and decision.target:
|
||||
self.stats.inc("block_attempts")
|
||||
alert = event.get("alert") or {}
|
||||
sid = alert.get("signature_id", "unknown")
|
||||
signature = str(alert.get("signature", "Suricata alert"))
|
||||
result = self.routeros.block_ip(
|
||||
decision.target,
|
||||
self.block_timeout,
|
||||
f"Suricata SID {sid}: {signature}",
|
||||
)
|
||||
blocked = result.success
|
||||
reason = result.message if result.success else f"{decision.reason}; {result.message}"
|
||||
self.stats.inc("block_success" if result.success else "block_errors")
|
||||
|
||||
self.store.insert_alert(event, blocked, decision.target, reason)
|
||||
Reference in New Issue
Block a user