first commit

This commit is contained in:
Mateusz Gruszczyński
2026-08-13 15:58:52 +02:00
commit adfdb0b86c
100 changed files with 6216 additions and 0 deletions
+144
View File
@@ -0,0 +1,144 @@
from __future__ import annotations
import json
import os
import sqlite3
import threading
from datetime import datetime, timedelta, timezone
from typing import Any
class AlertStore:
def __init__(self, path: str) -> None:
self.path = path
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
self._lock = threading.Lock()
self._conn = sqlite3.connect(path, check_same_thread=False)
self._conn.row_factory = sqlite3.Row
self._init_schema()
def _init_schema(self) -> None:
with self._lock:
self._conn.executescript(
"""
PRAGMA journal_mode=WAL;
PRAGMA synchronous=NORMAL;
CREATE TABLE IF NOT EXISTS alerts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT NOT NULL,
flow_id TEXT,
src_ip TEXT,
src_port INTEGER,
dest_ip TEXT,
dest_port INTEGER,
proto TEXT,
signature_id INTEGER,
signature TEXT,
category TEXT,
severity INTEGER,
action TEXT,
blocked INTEGER NOT NULL DEFAULT 0,
block_target TEXT,
block_reason TEXT,
raw_json TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_alerts_timestamp ON alerts(timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_alerts_signature_id ON alerts(signature_id);
CREATE INDEX IF NOT EXISTS idx_alerts_blocked ON alerts(blocked);
"""
)
self._conn.commit()
def insert_alert(
self,
event: dict[str, Any],
blocked: bool,
block_target: str | None,
block_reason: str,
) -> int:
alert = event.get("alert") or {}
values = (
str(event.get("timestamp") or datetime.now(timezone.utc).isoformat()),
str(event.get("flow_id") or ""),
event.get("src_ip"),
event.get("src_port"),
event.get("dest_ip"),
event.get("dest_port"),
event.get("proto"),
_as_int(alert.get("signature_id")),
alert.get("signature"),
alert.get("category"),
_as_int(alert.get("severity")),
alert.get("action"),
1 if blocked else 0,
block_target,
block_reason,
json.dumps(event, ensure_ascii=False, separators=(",", ":")),
)
with self._lock:
cursor = self._conn.execute(
"""
INSERT INTO alerts (
timestamp, flow_id, src_ip, src_port, dest_ip, dest_port, proto,
signature_id, signature, category, severity, action,
blocked, block_target, block_reason, raw_json
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
values,
)
self._conn.commit()
return int(cursor.lastrowid)
def recent(self, limit: int = 100) -> list[dict[str, Any]]:
limit = min(max(int(limit), 1), 500)
with self._lock:
rows = self._conn.execute(
"""
SELECT id, timestamp, src_ip, src_port, dest_ip, dest_port, proto,
signature_id, signature, category, severity, action,
blocked, block_target, block_reason
FROM alerts ORDER BY id DESC LIMIT ?
""",
(limit,),
).fetchall()
result = []
for row in rows:
item = dict(row)
item["blocked"] = bool(item["blocked"])
result.append(item)
return result
def summary(self) -> dict[str, Any]:
with self._lock:
total = self._conn.execute("SELECT COUNT(*) FROM alerts").fetchone()[0]
blocked = self._conn.execute("SELECT COUNT(*) FROM alerts WHERE blocked=1").fetchone()[0]
sev = self._conn.execute(
"SELECT severity, COUNT(*) AS count FROM alerts GROUP BY severity ORDER BY severity"
).fetchall()
return {
"total_alerts": int(total),
"blocked_alerts": int(blocked),
"by_severity": {str(row["severity"]): int(row["count"]) for row in sev},
}
def purge_older_than(self, days: int) -> int:
if days <= 0:
return 0
cutoff = (datetime.now(timezone.utc) - timedelta(days=days)).isoformat()
with self._lock:
cursor = self._conn.execute("DELETE FROM alerts WHERE timestamp < ?", (cutoff,))
self._conn.commit()
return int(cursor.rowcount)
def close(self) -> None:
with self._lock:
self._conn.close()
def _as_int(value: Any) -> int | None:
if value is None or value == "":
return None
try:
return int(value)
except (TypeError, ValueError):
return None