poc3
This commit is contained in:
+37
-3
@@ -26,6 +26,7 @@ from .config import Config
|
||||
from .auth import SessionAuth
|
||||
from .analytics_cache import AnalyticsSnapshotCache
|
||||
from .backup import BackupManager
|
||||
from .forensics import ForensicPcapRing
|
||||
from .live import EventBus, LiveEventPipeline, RedisUnavailableError, TrafficHistory, event_matches
|
||||
from .maintenance import clear_suricata_logs
|
||||
from .ndr import NDRAnalyzer, ThreatIntelManager
|
||||
@@ -71,6 +72,7 @@ class WebServer:
|
||||
threat_intel: ThreatIntelManager | None = None,
|
||||
ndr_analyzer: NDRAnalyzer | None = None,
|
||||
backup_manager: BackupManager | None = None,
|
||||
forensic_pcap: ForensicPcapRing | None = None,
|
||||
) -> None:
|
||||
self.config = config
|
||||
self.store = store
|
||||
@@ -84,6 +86,7 @@ class WebServer:
|
||||
self.analytics_cache = analytics_cache
|
||||
self.threat_intel = threat_intel
|
||||
self.ndr_analyzer = ndr_analyzer
|
||||
self.forensic_pcap = forensic_pcap
|
||||
self.backup_manager = backup_manager or BackupManager(config.db_path, os.path.dirname(config.db_path) or ".")
|
||||
self.auth = SessionAuth(config, store)
|
||||
self._login_lock = threading.Lock()
|
||||
@@ -139,6 +142,7 @@ class WebServer:
|
||||
threat_intel = self.threat_intel
|
||||
ndr_analyzer = self.ndr_analyzer
|
||||
backup_manager = self.backup_manager
|
||||
forensic_pcap = self.forensic_pcap
|
||||
auth = self.auth
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
@@ -254,10 +258,11 @@ class WebServer:
|
||||
return
|
||||
if parsed.path == "/api/forensics/pcaps":
|
||||
files = self._pcap_files()
|
||||
max_bytes = config.forensic_pcap_max_total_mb * 1024 * 1024
|
||||
self._json({"files": [
|
||||
{"name": path.name, "size_bytes": path.stat().st_size, "modified_at": path.stat().st_mtime}
|
||||
for path in files
|
||||
], "max_bytes": 8 * 64 * 1024 * 1024})
|
||||
], "mode": config.forensic_pcap_mode, "max_bytes": max_bytes})
|
||||
return
|
||||
if parsed.path == "/api/forensics/pcap":
|
||||
query = urllib.parse.parse_qs(parsed.query)
|
||||
@@ -307,6 +312,18 @@ class WebServer:
|
||||
payload = rule_manager.source_catalog()
|
||||
self._json(payload, status=200 if payload.get("ok") else 503)
|
||||
return
|
||||
if parsed.path == "/api/rules/merged":
|
||||
if rule_manager is None:
|
||||
self._json({"error": "rule manager unavailable"}, status=503)
|
||||
else:
|
||||
query = urllib.parse.parse_qs(parsed.query)
|
||||
payload = rule_manager.merged_rules(
|
||||
self._query_text(query, "q", 300),
|
||||
self._query_int(query, "offset", 0, 0, 100000000),
|
||||
self._query_int(query, "limit", 1000, 1, 5000),
|
||||
)
|
||||
self._json(payload, status=200 if payload.get("ok") else 404)
|
||||
return
|
||||
if parsed.path == "/api/admin/rules":
|
||||
if not self._require_admin():
|
||||
return
|
||||
@@ -526,6 +543,14 @@ class WebServer:
|
||||
self._json({"error": "sources must be an array"}, status=400)
|
||||
return
|
||||
result = rule_manager.queue_sources([str(item) for item in sources])
|
||||
elif parsed.path == "/api/admin/rules/sources/add":
|
||||
result = rule_manager.add_manual_source(
|
||||
str(body.get("name") or ""),
|
||||
str(body.get("url") or ""),
|
||||
body.get("no_checksum", True) is not False,
|
||||
)
|
||||
elif parsed.path == "/api/admin/rules/sources/remove":
|
||||
result = rule_manager.remove_manual_source(str(body.get("source") or ""))
|
||||
elif parsed.path in {"/api/admin/rules/sources/enable", "/api/admin/rules/sources/disable"}:
|
||||
result = rule_manager.set_source_enabled(
|
||||
str(body.get("source") or ""), parsed.path.endswith("/enable")
|
||||
@@ -567,6 +592,11 @@ class WebServer:
|
||||
return
|
||||
comment = str(body.get("comment") or "Manual dashboard block").strip()[:180]
|
||||
result = routeros.block_ip(address, timeout_value, comment)
|
||||
if result.success and forensic_pcap is not None:
|
||||
try:
|
||||
forensic_pcap.capture_target(address, label="manual")
|
||||
except Exception as exc:
|
||||
print(f"[forensics] manual block PCAP capture failed: {exc}", flush=True)
|
||||
self._json({"ok": result.success, "message": result.message}, status=200 if result.success else 502)
|
||||
|
||||
def _manual_unblock(self, body: dict) -> None:
|
||||
@@ -802,10 +832,14 @@ class WebServer:
|
||||
def _pcap_files(self) -> list[Path]:
|
||||
root = Path(config.eve_path).resolve().parent
|
||||
try:
|
||||
files = [p for p in root.glob("alert*.pcap*") if p.is_file() and p.resolve().parent == root]
|
||||
files = [
|
||||
p for pattern in ("alert*.pcap*", "block-*.pcap")
|
||||
for p in root.glob(pattern)
|
||||
if p.is_file() and p.resolve().parent == root
|
||||
]
|
||||
except OSError:
|
||||
return []
|
||||
return sorted(files, key=lambda p: p.stat().st_mtime, reverse=True)[:32]
|
||||
return sorted(files, key=lambda p: p.stat().st_mtime, reverse=True)[:max(32, config.forensic_pcap_max_files)]
|
||||
|
||||
def _send_file(self, path: Path, content_type: str) -> None:
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user