# Greenfield SIEM Kompletter Neuaufbau des bisherigen Projekts. Vom Altprojekt bleibt absichtlich nur der HTTP-Ingress-Vertrag erhalten. ## Was dieses Projekt löst Der Eventpfad ist append-only, asynchron und auf große Datenmengen ausgelegt: ```text Windows Agents │ POST /ingest ▼ Ingress API ──► PostgreSQL (nur Agent-Authentifizierung / Control Plane) │ ▼ Redpanda (persistente Queue, 24h Recovery-Puffer) │ ▼ Processor ├──► ClickHouse (normalisierte, durchsuchbare Events, 90 Tage) └──► gzip Spool ──► Garage/S3 (Raw-Batches, 30 Tage) │ └── rclone übernimmt Upload + Retention ClickHouse ──► Detector ──► PostgreSQL (Detections / Status) ClickHouse + PostgreSQL ──► API/UI ``` **Keine Event-Zeilen in PostgreSQL. Keine MariaDB. Keine synchronen Detection-Abfragen im Ingress. Keine Raw-XML-Duplikate in ClickHouse.** ## One-click Deployment Voraussetzung: Docker Engine / Docker Desktop mit Docker Compose Plugin. ### Linux / macOS / WSL ```bash chmod +x deploy.sh ./deploy.sh ``` ### Windows PowerShell ```powershell ./deploy.ps1 ``` Das Deployment erzeugt beim ersten Start kryptographisch zufällige Secrets in `.env`, erzeugt die Garage-Konfiguration, baut die SIEM-Binaries und startet alle Dienste. Danach: - UI: `http://SERVER:8080/ui` - Ingress: `http://SERVER:8090/ingest` - Redpanda Console: lokal `http://127.0.0.1:8081` - Prometheus: lokal `http://127.0.0.1:9090` Beim ersten Start werden `UI_USERNAME=admin` und ein zufälliges `UI_PASSWORD` erzeugt. Browser fragen diese Zugangsdaten beim Öffnen von `/ui` ab. UI-Benutzer/Passwort und Enrollment-Key werden am Ende des Deployments ausgegeben und stehen außerdem in der geschützten `.env`. Die UI/API ist standardmäßig per HTTP Basic Auth geschützt; für Zugriff über ein fremdes Netz gehört zusätzlich TLS davor. > Bestehende Collector können den bisherigen Enrollment-Key weiterverwenden, ohne eine `.env` von Hand anzulegen. Linux/macOS/WSL: `ENROLLMENT_KEY="ALTER_KEY" ./deploy.sh`. PowerShell: `$env:ENROLLMENT_KEY="ALTER_KEY"; ./deploy.ps1`. Alle anderen Secrets werden trotzdem zufällig erzeugt. ## Bestehender Ingress bleibt kompatibel ```http POST /ingest X-API-Key: X-Enrollment-Key: Content-Type: application/json ``` Beispiel: ```json [ { "host": "CLIENT-01", "channel": "Security", "id": 4740, "source": "windows-agent", "ts": "2026-07-23T12:00:00Z", "meta": { "provider": "Microsoft-Windows-Security-Auditing", "target_user": "alice", "workstation": "CLIENT-42", "src_ip": "10.10.20.15" } } ] ``` Alternativ darf `msg` weiterhin das bisherige Windows-XML enthalten. Der Processor extrahiert daraus die relevanten Felder. Das XML wird **nicht** als ClickHouse-Spalte gespeichert, sondern nur im komprimierten Raw-Archiv. Erfolgreiche Aufnahme: ```json {"accepted":1} ``` HTTP `202` bedeutet: Der Batch wurde authentifiziert und dauerhaft in die Queue geschrieben. ClickHouse muss dafür nicht synchron antworten. ## Speicherdesign ### ClickHouse `siem.events` Eine kanonische Tabelle enthält u. a.: - Event- und Ingest-Zeit - Agent / Host / Channel / Provider / Event-Code - normalisierte `category`, `action`, `outcome`, `severity` - User / Subject / Target User - Source-/Destination-IP und Ports - Workstation - Logon-Type - Prozess / Parent-Prozess / Commandline - seltene XML-Felder in `attributes Map(String,String)` - Raw-Object-Key + Index im archivierten Originalbatch - Queue-Partition / Offset / stabile Event-UID - Ingest-Verzögerung zur Erkennung von Agent-/Queue-Zeitproblemen - häufige Authentifizierungsdetails (Domain, Status/Substatus, FailureReason, Auth-Package) Häufig genutzte Felder werden **nicht** zusätzlich in `attributes` dupliziert. Die Map enthält nur unbekannte Restfelder, maximal 32 Einträge pro Event mit begrenzter Länge. Das vollständige Original bleibt im Raw-Archiv. Die Tabelle verwendet `ReplacingMergeTree`, Monats-Partitionen und einen zeitbasierten Sortierschlüssel. Die Standard-TTL beträgt 90 Tage. ### Deduplication Die Pipeline ist absichtlich at-least-once. Ein Crash nach ClickHouse-Insert aber vor dem Redpanda-Commit kann einen Batch erneut liefern. Daher erhält schon der Ingress-Batch eine stabile ID aus `SHA-256(agent_id + Batch-Inhalt)`. Die Event-ID lautet: ```text : ``` Damit werden sowohl ein Redpanda-Retry **als auch ein identischer HTTP-Retry des Collectors** als dasselbe Event erkannt. Queue-Partition und Offset werden zusätzlich nur zur Provenienz gespeichert. - Eventtabelle: `ReplacingMergeTree` - Detection-Zähler: `uniqExact(event_uid)` - Dashboard-Rollups: `uniqExactState(event_uid)` / `uniqExactMerge` Wiederholte Verarbeitung verfälscht damit keine Zähler. ### Dashboard-Rollup `siem.events_5m` wird über eine Materialized View fortlaufend gepflegt. Die Startseite zählt nicht Millionen Einzelereignisse, sondern arbeitet auf dem 5-Minuten-Rollup. Standard-Retention: | Datenklasse | Retention | |---|---:| | Vollständig durchsuchbare normalisierte Events | 90 Tage | | 5-Minuten-Rollups | 730 Tage | | Raw-Batches in Garage/S3 | 30 Tage | | Redpanda Recovery-Queue | 24 Stunden | | Detections / Agent-Metadaten | dauerhaft, bis organisatorisch bereinigt | | Prometheus Betriebsmetriken | 30 Tage | Anpassbar vor dem ersten Start in `.env`: ```env EVENT_RETENTION_DAYS=90 ROLLUP_RETENTION_DAYS=730 RAW_RETENTION=720h KAFKA_RETENTION_MS=86400000 ``` ## Detections in Version 1 Der Detector läuft unabhängig vom UI alle 30 Sekunden mit einem begrenzten Lookback und schreibt nur Findings in PostgreSQL. Enthalten: - Audit Log gelöscht (1102) - Dienst installiert (7045) - Account Lockout (4740) - Failed Logon Burst (4625) - Password Spray (4625 gegen viele Benutzer) - privilegierte Gruppenmitgliedschaft geändert (4728 / 4732 / 4756) **Es gibt bewusst keine pauschale `new_event_id`-Detection mehr.** Event 5857 und vergleichbare Betriebsereignisse sind normale Events, keine Incidents. ## UI `/ui` enthält: - Eventzahl 24h aus Rollup - aktive Hosts 24h - offene High-/Critical-Detections - Eventsuche nach Zeitraum, Host, User, IP, Event-ID und Channel - Detection-Liste mit Statusänderung - Agent-Liste / Last Seen Die initiale Eventsuche ist auf 24 Stunden und 500 Ergebnisse begrenzt. Der ClickHouse-Sortierschlüssel beginnt mit Tenant und Event-Zeit; alle UI-Suchen besitzen zusätzlich ein hart begrenztes Zeitfenster und Ergebnislimit. Das verhindert wiederkehrende Vollscans über die gesamte Historie. ## Raw-Event wiederherstellen Die Eventliste enthält `raw_object_key`. Das Original ist ein gzip-komprimierter Ingress-Batch. Einfacher über das mitgelieferte Hilfsskript: ```bash ./raw-event.sh '2026/07/23/12/.json.gz' ``` Ohne `raw_index` wird der gesamte dekomprimierte Ingress-Batch ausgegeben. `raw_index` bezeichnet das Event innerhalb dieses JSON-Batches. ## Stress-Test Der Stress-Agent ist im selben Image enthalten und benötigt eine explizite Bestätigung: ```bash docker compose run --rm ingress stress-agent \ --url http://ingress:8080/ingest \ --api-key stress-agent-key \ --enrollment-key "$(grep '^ENROLLMENT_KEY=' .env | cut -d= -f2-)" \ --scenario mixed \ --rate 5000 \ --batch 250 \ --duration 5m \ --confirm-load-test ``` Szenarien: `mixed`, `failed-logon`, `lockout`, `catalog`. **Nur in einer kontrollierten Umgebung beziehungsweise gegen das eigene SIEM verwenden.** ## Betriebschecks Der schnellste Gesamtcheck: ```bash ./doctor.sh ``` Er prüft Containerstatus, HTTP-Readiness, Redpanda/Consumer-Lag, ClickHouse-Zeilen und Speicher, aktuelle Ingest-Verzögerung, Control-Plane-Zähler, Raw-Spool und aktuelle Fehler. Zusätzlich: ```bash docker compose ps docker compose logs -f ingress processor detector api ``` Ingress: ```bash curl http://127.0.0.1:8090/healthz curl http://127.0.0.1:8090/readyz ``` ClickHouse Zeilenzahl / Speicher: ```bash docker compose exec clickhouse clickhouse-client \ --user siem --password "$(grep '^CLICKHOUSE_PASSWORD=' .env | cut -d= -f2-)" \ --query "SELECT formatReadableQuantity(sum(rows)), formatReadableSize(sum(bytes_on_disk)) FROM system.parts WHERE database='siem' AND table='events' AND active" ``` Queue: ```bash docker compose exec redpanda rpk group describe siem-processor-v1 ``` ## Hardware für das Single-Node-Deployment Für einen Test reichen typischerweise 4 CPU-Kerne, 8 GB RAM und SSD-Speicher. Für eure bisher beobachtete Produktivrate würde ich **mindestens 8 Kerne, 16 GB RAM und schnelle lokale NVMe** einplanen. Die benötigte SSD-Größe hängt stark von Eventgröße, Commandlines und Raw-XML ab; `./doctor.sh` zeigt die reale Kompression und Wachstumsrate, sodass die Retention nach einigen Tagen anhand echter Werte dimensioniert werden kann. ## Kapazität Die bisher beobachteten ca. 19,2 Mio. Events in 48 Stunden entsprechen nur rund 111 Events/s. Das ist für ClickHouse/Redpanda eine kleine Last. Der neue Pfad ist bewusst für deutlich höhere Raten gebaut; die reale Grenze hängt vor allem von CPU, SSD-Durchsatz, Eventgröße, Anzahl der Hosts und Query-Last ab. Für hohe Last zuerst skalieren: 1. `KAFKA_PARTITIONS` erhöhen und mehrere Processor-Instanzen starten. 2. ClickHouse auf schnelle lokale NVMe legen. 3. Redpanda und ClickHouse nicht auf dieselbe langsame Netzwerkplatte legen. 4. Raw-Retention reduzieren, wenn XML sehr groß ist. 5. Erst ab echtem Bedarf ClickHouse / Redpanda / Garage als Mehrknoten-Cluster betreiben. ## Single-node vs. HA `compose.yml` ist ein **One-click Single-node Deployment**. Es eliminiert die bisherigen Architekturprobleme, ist aber kein hochverfügbarer Cluster: fällt der eine Docker-Host aus, sind alle Komponenten betroffen. Für echte HA sind mindestens sinnvoll: - 3 Redpanda Broker, Replication Factor 3 - repliziertes ClickHouse (oder ClickHouse Cloud) - 3 Garage Nodes / anderes redundantes S3 - PostgreSQL mit Backup/Replica - Reverse Proxy/TLS und zentraler Identity Provider vor UI/API Die Anwendungskomponenten selbst sind stateless und können danach horizontal vervielfacht werden. ## Sicherheit - Datenbank-, Redpanda-, Garage- und Prometheus-Adminports binden standardmäßig nur an `127.0.0.1`. - Nur Ingress und UI binden standardmäßig an `0.0.0.0`. - UI/API verlangen ein beim Deployment zufällig erzeugtes Basic-Auth-Passwort. - Für produktiven externen Zugriff gehört trotzdem TLS/Reverse-Proxy vor Ingress und UI, da Basic Auth allein keine Transportverschlüsselung bietet. - `.env` enthält Secrets und darf nicht ins Repository. - Der Ingress speichert ausschließlich SHA-256-Hashes der Agent-API-Keys in PostgreSQL. - Raw-Logs können personenbezogene Daten enthalten. Retention und Zugriffsrechte entsprechend festlegen. ## Projektstruktur ```text cmd/siem ein Binary, mehrere Rollen internal/ingress kompatible HTTP-Aufnahme internal/queue Redpanda/Kafka Producer + Consumer internal/processor Normalisierung + ClickHouse + Raw-Spool internal/normalize Windows XML / Meta → kanonisches Event internal/clickhouse ClickHouse HTTP-Client internal/postgres Agenten + Detections internal/detector kontinuierliche Regeln internal/api Investigation API + UI internal/stress begrenzter Lastgenerator doctor.sh Pipeline-/Kapazitätsdiagnose raw-event.sh Raw-Archiv abrufen deploy/clickhouse analytisches Schema deploy/postgres Control-Plane-Schema deploy/garage S3-Archivkonfiguration deploy/prometheus Betriebsmetriken ``` ## Reset Nur wenn wirklich alle Daten weg sollen: ```bash ./reset-DANGEROUS.sh ``` Das Skript verlangt zusätzlich die Eingabe `DELETE` und entfernt anschließend die Docker-Volumes.