poc2_worked

This commit is contained in:
Mateusz Gruszczyński
2026-08-15 18:29:36 +02:00
parent fc3a2944b2
commit 71b6c0d86f
62 changed files with 9112 additions and 375 deletions
+134
View File
@@ -0,0 +1,134 @@
from __future__ import annotations
import json
import os
import shutil
import sqlite3
import tarfile
import tempfile
import threading
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
class BackupManager:
"""Create bounded portable backups of persistent IDS state.
Runtime-heavy data (Redis AOF/RDB, EVE logs and PCAP ring) is intentionally
excluded. Those are caches/evidence streams, not configuration state. The
SQLite database is copied with SQLite's online backup API for consistency.
"""
def __init__(self, db_path: str, data_dir: str = "/data", keep: int = 8) -> None:
self.db_path = Path(db_path)
self.data_dir = Path(data_dir)
self.backup_dir = self.data_dir / "backups"
self.backup_dir.mkdir(parents=True, exist_ok=True)
self.keep = max(2, min(30, int(keep)))
self._lock = threading.RLock()
def create(self, label: str = "manual") -> dict[str, Any]:
safe_label = "".join(ch if ch.isalnum() or ch in "-_." else "-" for ch in str(label or "manual"))[:40].strip("-") or "manual"
stamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
name = f"mikrosuricata-{stamp}-{safe_label}-{uuid.uuid4().hex[:6]}.tar.gz"
target = self.backup_dir / name
with self._lock, tempfile.TemporaryDirectory(prefix="ms-backup-") as td:
root = Path(td)
db_copy = root / "ids.db"
self._sqlite_backup(db_copy)
manifest = {
"format": 1,
"created_at": datetime.now(timezone.utc).isoformat(),
"label": safe_label,
"contents": ["ids.db", "suricata/", "lib/suricata/update/sources/", "lib/suricata/rules/suricata.rules"],
"excluded": ["redis/", "logs/", "pcap/", "backups/"],
}
(root / "manifest.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
with tarfile.open(target, "w:gz") as tar:
tar.add(db_copy, arcname="ids.db", recursive=False)
tar.add(root / "manifest.json", arcname="manifest.json", recursive=False)
self._add_if_exists(tar, self.data_dir / "suricata", "suricata")
self._add_if_exists(tar, self.data_dir / "lib" / "suricata" / "update" / "sources", "lib/suricata/update/sources")
self._add_if_exists(tar, self.data_dir / "lib" / "suricata" / "rules" / "suricata.rules", "lib/suricata/rules/suricata.rules")
os.chmod(target, 0o600)
self._prune()
return self.info(name) or {"id": name, "path": str(target)}
def list(self) -> list[dict[str, Any]]:
with self._lock:
paths = self._paths()
out = []
for path in paths:
try:
stat = path.stat()
except OSError:
continue
out.append({
"id": path.name,
"size_bytes": int(stat.st_size),
"created_at": datetime.fromtimestamp(stat.st_mtime, timezone.utc).isoformat(),
})
return out
def info(self, backup_id: str) -> dict[str, Any] | None:
path = self.path(backup_id)
if path is None:
return None
stat = path.stat()
return {
"id": path.name,
"size_bytes": int(stat.st_size),
"created_at": datetime.fromtimestamp(stat.st_mtime, timezone.utc).isoformat(),
"path": str(path),
}
def path(self, backup_id: str) -> Path | None:
name = os.path.basename(str(backup_id or ""))
if not name.startswith("mikrosuricata-") or not name.endswith(".tar.gz"):
return None
path = (self.backup_dir / name).resolve()
if path.parent != self.backup_dir.resolve() or not path.is_file():
return None
return path
def delete(self, backup_id: str) -> bool:
path = self.path(backup_id)
if path is None:
return False
with self._lock:
try:
path.unlink()
return True
except OSError:
return False
def _sqlite_backup(self, destination: Path) -> None:
source = sqlite3.connect(str(self.db_path), timeout=10)
target = sqlite3.connect(str(destination))
try:
source.backup(target)
target.execute("PRAGMA wal_checkpoint(TRUNCATE)")
target.commit()
finally:
target.close()
source.close()
@staticmethod
def _add_if_exists(tar: tarfile.TarFile, source: Path, arcname: str) -> None:
if source.exists():
tar.add(source, arcname=arcname, recursive=True)
def _paths(self) -> list[Path]:
try:
return sorted(self.backup_dir.glob("mikrosuricata-*.tar.gz"), key=lambda p: p.stat().st_mtime, reverse=True)
except OSError:
return []
def _prune(self) -> None:
for path in self._paths()[self.keep:]:
try:
path.unlink()
except OSError:
pass