poc3
This commit is contained in:
+45
-4
@@ -6,7 +6,9 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import re
|
||||
import time
|
||||
from dataclasses import replace
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse
|
||||
@@ -16,6 +18,7 @@ from .backup import BackupManager
|
||||
from .config import Config
|
||||
from .eve import EVEWatcher
|
||||
from .flow_tracker import FlowTracker
|
||||
from .forensics import ForensicPcapRing
|
||||
from .live import EventBus, LiveEventPipeline, TrafficHistory, TrafficNormalizer
|
||||
from .maintenance import clear_suricata_logs, storage_info
|
||||
from .ndr import NDRAnalyzer, ThreatIntelManager
|
||||
@@ -45,6 +48,27 @@ def _ensure_suricata_state(cfg: Config) -> None:
|
||||
Path(path).touch(exist_ok=True)
|
||||
|
||||
|
||||
def _prepare_suricata_output_config(cfg: Config) -> Config:
|
||||
source = Path(cfg.suricata_output_config)
|
||||
text = source.read_text(encoding="utf-8")
|
||||
match = re.search(r"(?ms)^ - pcap-log:\n.*?(?=^ - |\Z)", text)
|
||||
if match is None:
|
||||
raise RuntimeError("Suricata output profile has no pcap-log section")
|
||||
|
||||
block = match.group(0)
|
||||
enabled = cfg.forensic_pcap_mode in {"alerts", "all"}
|
||||
conditional = "all" if cfg.forensic_pcap_mode == "all" else "alerts"
|
||||
block = re.sub(r"(?m)^ enabled: .*?$", f" enabled: {'yes' if enabled else 'no'}", block)
|
||||
block = re.sub(r"(?m)^ conditional: .*?$", f" conditional: {conditional}", block)
|
||||
rendered = text[:match.start()] + block + text[match.end():]
|
||||
|
||||
runtime = Path("/run/suricata/ids-output.runtime.yaml")
|
||||
runtime.parent.mkdir(parents=True, exist_ok=True)
|
||||
runtime.write_text(rendered, encoding="utf-8")
|
||||
os.environ["SURICATA_OUTPUT_CONFIG"] = str(runtime)
|
||||
return replace(cfg, suricata_output_config=str(runtime))
|
||||
|
||||
|
||||
def _suricata_common_args(cfg: Config, log_dir: str) -> list[str]:
|
||||
return [
|
||||
"-c",
|
||||
@@ -75,7 +99,7 @@ def _suricata_common_args(cfg: Config, log_dir: str) -> list[str]:
|
||||
|
||||
|
||||
def main() -> int:
|
||||
cfg = Config.from_env()
|
||||
cfg = _prepare_suricata_output_config(Config.from_env())
|
||||
stop_event = threading.Event()
|
||||
stats = RuntimeStats()
|
||||
started_at = datetime.now(timezone.utc)
|
||||
@@ -160,6 +184,14 @@ def main() -> int:
|
||||
cfg.routeros_http_timeout,
|
||||
)
|
||||
notifier = WebhookNotifier(cfg.notify_webhook_url, cfg.notify_min_risk, cfg.notify_timeout_seconds)
|
||||
forensic_pcap = ForensicPcapRing(
|
||||
cfg.forensic_pcap_mode,
|
||||
log_dir,
|
||||
window_seconds=cfg.forensic_pcap_window_seconds,
|
||||
memory_mb=cfg.forensic_pcap_memory_mb,
|
||||
max_files=cfg.forensic_pcap_max_files,
|
||||
max_total_mb=cfg.forensic_pcap_max_total_mb,
|
||||
)
|
||||
ndr_analyzer = NDRAnalyzer(
|
||||
store, threat_intel, routeros, cfg.monitored_networks, cfg.never_block, cfg.block_timeout,
|
||||
enabled=cfg.ndr_enabled,
|
||||
@@ -168,6 +200,7 @@ def main() -> int:
|
||||
auto_block=cfg.ndr_auto_block,
|
||||
auto_block_risk=cfg.ndr_auto_block_risk,
|
||||
notifier=notifier,
|
||||
block_evidence_callback=lambda target, label: forensic_pcap.capture_target(target, label=label),
|
||||
)
|
||||
redis_supervisor = RedisSupervisor(
|
||||
cfg.redis_managed,
|
||||
@@ -203,8 +236,12 @@ def main() -> int:
|
||||
normalizer = TrafficNormalizer(cfg.monitored_networks)
|
||||
flow_tracker = FlowTracker(normalizer, live_pipeline, update_interval_seconds=cfg.live_flow_update_seconds)
|
||||
|
||||
def observe_frame(frame: bytes) -> None:
|
||||
forensic_pcap.observe(frame)
|
||||
flow_tracker.observe(frame)
|
||||
|
||||
receiver = TZSPReceiver(
|
||||
cfg.tzsp_bind, cfg.tzsp_port, tap.write, stats, stop_event, frame_observer=flow_tracker.observe
|
||||
cfg.tzsp_bind, cfg.tzsp_port, tap.write, stats, stop_event, frame_observer=observe_frame
|
||||
)
|
||||
watcher = EVEWatcher(
|
||||
cfg.eve_path,
|
||||
@@ -219,6 +256,7 @@ def main() -> int:
|
||||
normalizer=normalizer,
|
||||
live_pipeline=live_pipeline,
|
||||
ndr_analyzer=ndr_analyzer,
|
||||
forensic_pcap=forensic_pcap,
|
||||
)
|
||||
rule_manager = RuleManager(
|
||||
cfg,
|
||||
@@ -237,6 +275,7 @@ def main() -> int:
|
||||
storage = storage_info(cfg.db_path, cfg.eve_path)
|
||||
rules = rule_manager.status()
|
||||
runtime = stats.snapshot()
|
||||
redis_status = redis_supervisor.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)
|
||||
@@ -263,6 +302,7 @@ def main() -> int:
|
||||
"database": db,
|
||||
"storage": storage,
|
||||
"rules": rules,
|
||||
"redis": redis_status,
|
||||
"services": {
|
||||
"web": {
|
||||
"name": "Web UI / API",
|
||||
@@ -332,12 +372,12 @@ def main() -> int:
|
||||
"redis": {
|
||||
"name": "Managed Redis",
|
||||
"status": (
|
||||
"up" if redis_supervisor.status().get("running")
|
||||
"up" if redis_status.get("ready")
|
||||
else "disabled" if not cfg.redis_managed
|
||||
else "degraded"
|
||||
),
|
||||
"details": (
|
||||
f"{cfg.redis_data_dir}; maxmemory=unlimited; persistence={redis_supervisor.status().get('persistence')}"
|
||||
f"{cfg.redis_data_dir}; maxmemory=unlimited; persistence={redis_status.get('persistence')}"
|
||||
if cfg.redis_managed
|
||||
else "Managed Redis disabled; REDIS_URL may point to an external server"
|
||||
),
|
||||
@@ -396,6 +436,7 @@ def main() -> int:
|
||||
threat_intel=threat_intel,
|
||||
ndr_analyzer=ndr_analyzer,
|
||||
backup_manager=backup_manager,
|
||||
forensic_pcap=forensic_pcap,
|
||||
)
|
||||
|
||||
def housekeeping() -> None:
|
||||
|
||||
Reference in New Issue
Block a user