# Greenfield SIEM **Release 1.2 – Product Restoration:** Die skalierbare Greenfield-Pipeline bleibt bestehen; Rule-Sets, Suppressions, Grafana und ein deutlich umfangreicheres Analysten-UI sind wieder Bestandteil des Produkts. ClickHouse ist für den bekannten KVM-Host standardmäßig auf `26.3.17.56` gepinnt. Kompletter Neuaufbau des bisherigen Eventpfads. Der HTTP-Ingress-Vertrag bleibt kompatibel, die SIEM-Produktebene wurde gegenüber der ersten Greenfield-Version wieder vollständig ausgebaut. ## 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 ──► Rule Engine ──► PostgreSQL (Rules / Suppressions / Findings) ClickHouse + PostgreSQL ──► Analyst API/UI ClickHouse + Prometheus ──► Grafana ``` **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 vervollständigt bei jedem Start die `.env`, ohne vorhandene Secrets zu überschreiben. Fehlende Secrets werden kryptographisch zufällig erzeugt. Die Garage-Konfiguration wird anschließend automatisch von einem Compose-Init-Service erzeugt; ClickHouse-Schema und TTLs werden bei jedem Start idempotent geprüft. Danach werden die SIEM-Binaries gebaut und alle Dienste gestartet. Danach: - UI: `http://SERVER:8080/ui` - Ingress: `http://SERVER:8090/ingest` - Grafana: `http://SERVER:3000` (Port wird bei Konflikten automatisch verschoben) - 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. Alle konfigurierbaren Parameter stehen in `.env.example`. `deploy.sh`/`deploy.ps1` ergänzt fehlende Schlüssel automatisch in einer vorhandenen `.env`; damit werden auch unvollständige `.env`-Dateien aus der ersten Greenfield-Version repariert. Zugangsdaten lassen sich jederzeit mit `./credentials.sh` anzeigen. 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. ## Upgrade / Reparatur der ersten Greenfield-Version Die erste ausgelieferte Greenfield-Version hatte drei Deployment-Probleme: 1. `deploy/garage/garage.toml` wurde erst auf dem Host erzeugt und fehlte im ZIP; außerdem war `rpc_public_addr` für das Single-Node-Setup ungünstig gesetzt. 2. ClickHouse exponierte nur HTTP `8123`, nicht den nativen Client-Port `9000`. 3. `.env` enthielt nur einen Teil der tatsächlich konfigurierbaren Parameter. Mit dieser Version reicht im bestehenden Projektverzeichnis: ```bash ./deploy.sh ./doctor.sh ``` Das Deploy-Skript behält bestehende Secrets, ergänzt fehlende `.env`-Werte und erzeugt Garage-Konfiguration sowie ClickHouse-Schema über Compose-Init-Services neu. **Die Daten-Volumes werden dabei nicht gelöscht.** Falls `.env` gelöscht wurde, aber alte PostgreSQL-Volumes mit einem unbekannten alten Passwort weiter existieren, kann dieses Passwort nicht automatisch rekonstruiert werden. In einer reinen Testinstallation ist dann `./reset-DANGEROUS.sh` der saubere Neustart; bei produktiven Daten zuerst Backup beziehungsweise Passwort-Recovery durchführen. ## ClickHouse-Zugang Für den bekannten KVM-Host mit `QEMU Virtual CPU version 2.5+` ist `clickhouse/clickhouse-server:26.3.17.56` der Standard, weil diese Version auf der VM nachweislich startet, während 26.6 dort mit SIGILL/Exit 132 abbricht. Der Preflight testet das Binary vor dem Stackstart. ClickHouse besitzt zwei verschiedene Schnittstellen: - HTTP: `127.0.0.1:8123` - nativer ClickHouse-Client: `127.0.0.1:9000` Benutzer, Passwort und Datenbank stehen vollständig in `.env`: ```env CLICKHOUSE_DB=siem CLICKHOUSE_USER=siem CLICKHOUSE_PASSWORD=... CLICKHOUSE_HTTP_PORT=8123 CLICKHOUSE_NATIVE_PORT=9000 ``` Am einfachsten: ```bash ./credentials.sh docker compose exec clickhouse clickhouse-client \ --user "$(grep '^CLICKHOUSE_USER=' .env | cut -d= -f2-)" \ --password "$(grep '^CLICKHOUSE_PASSWORD=' .env | cut -d= -f2-)" \ --database "$(grep '^CLICKHOUSE_DB=' .env | cut -d= -f2-)" ``` Für DBeaver/DataGrip oder einen lokalen `clickhouse-client` den nativen Port `9000` verwenden. Port `8123` ist die HTTP-Schnittstelle, keine Login-Weboberfläche. ## Garage-Zugang / Diagnose Garage wird im Single-Node-Modus automatisch konfiguriert. Die Konfiguration liegt in einem Docker-Volume und muss nicht mehr als Host-Datei erzeugt werden. Prüfung: ```bash docker compose exec garage /garage status docker compose logs --tail=100 garage garage-config ``` S3 läuft lokal auf `127.0.0.1:3900`. Access-Key, Secret-Key und Bucket stehen in `.env`. ## 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 ``` ## Rule-Sets und Detection Engine Der Detector ist nicht mehr auf einige hart codierte Go-Regeln beschränkt. Beim Start werden versionierte Rule-Sets aus `deploy/rules/` nach PostgreSQL synchronisiert. Aktivierungszustände aus dem UI bleiben bei Updates erhalten. Eigene Regeln werden im Rule-Set `custom` gespeichert. Mitgeliefert werden 19 Regeln in vier Rule-Sets: | Rule-Set | Regeln | Beispiele | |---|---:|---| | Windows Core | 5 | Audit Log gelöscht, Dienst installiert, Scheduled Task, Audit Policy, Firewall | | Windows Authentication | 5 | Lockout, Failed-Logon-Burst, Password Spray, Kerberos, NTLM | | Windows Account & Admin | 5 | privilegierte Gruppen, Benutzer angelegt/aktiviert/gelöscht, Passwort-Reset | | Defender & PowerShell | 4 | Malware, Defender deaktiviert, EncodedCommand, verdächtige Download-/Memory-Muster | Regeltypen sind `event`, `threshold` und `distinct`. Bedingungen werden über eine Feld-/Operator-Allowlist in ClickHouse-SQL kompiliert; Rule-Dateien enthalten kein frei ausführbares SQL. Einzelne Regeln und komplette Rule-Sets können aktiviert/deaktiviert werden. Suppressions können nach Rule-ID, Host, User oder Source-IP begrenzt und zeitlich befristet werden. **Es gibt weiterhin keine pauschale `new_event_id`-Detection.** Event 5857 und vergleichbare Betriebsereignisse sind normale Events. Details zum Format stehen in [`RULES.md`](RULES.md). ## Analysten-UI `/ui` ist wieder eine eigentliche SIEM-Oberfläche und nicht nur eine einfache Eventtabelle. Enthalten sind: - **Overview:** 24h-KPIs, Eventvolumen, Top Hosts/Event-IDs und Authentication-Übersicht - **Investigation:** Zeit-, Host-, User-, IP-, Event-ID-, Channel- und Textfilter mit Drill-down - **Event Details:** Message, Commandline, relevante Attribute, Raw-Archiv-Referenz und Normalisierungsfelder - **Detections:** Severity, Status, Rule/Rule-Set, MITRE-Tags und Status-Workflow - **Rule-Sets:** Set- und Regel-Toggles sowie Custom-Rule-Editor - **Suppressions:** anlegen und entfernen, optional mit Ablaufzeit - **Agents:** Enrollment-/Last-Seen-Übersicht und Aktivierung Die initiale Investigation ist zeitlich und per Ergebnislimit begrenzt. Das Overview verwendet kleine Rollups beziehungsweise begrenzte Aggregationen statt unkontrollierter Vollscans. ## Grafana Grafana ist wieder Bestandteil des Standard-Stacks. Es wird automatisch mit einem dedizierten, **SELECT-only** ClickHouse-Benutzer und dem ClickHouse-Datasource provisioniert. Das administrative SIEM-UI und Grafana haben bewusst getrennte Aufgaben: UI für Workflow/Rules/Findings, Grafana für freie Visualisierung und Explore. Mitgelieferte Dashboards: - **SIEM Security Overview:** Eventvolumen, aktive Hosts, Failed Logons, Lockouts, Top Hosts/Event-IDs, Authentication, Top Failed Users und Source-IPs - **SIEM Pipeline Health:** Ingress/Redpanda/ClickHouse-Erreichbarkeit, Event-/Batch-Rate, Rejections und Scrape-Latenzen Zugangsdaten: ```bash ./credentials.sh ``` Standardmäßig: ```text http://SERVER:3000 ``` `GRAFANA_PORT` wird wie die anderen Host-Ports durch `preflight.sh` auf Konflikte geprüft. ## 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 "$(grep '^CLICKHOUSE_USER=' .env | cut -d= -f2-)" \ --password "$(grep '^CLICKHOUSE_PASSWORD=' .env | cut -d= -f2-)" \ --query "SELECT formatReadableQuantity(sum(rows)), formatReadableSize(sum(bytes_on_disk)) FROM system.parts WHERE database='$(grep '^CLICKHOUSE_DB=' .env | cut -d= -f2-)' 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 compose.yml erzeugt Garage-Konfiguration zur Laufzeit aus `.env` 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. ## CPU-Kompatibilität und Portkonflikte `deploy.sh` führt vor dem eigentlichen Start einen echten ClickHouse-Binary-Probe in einem kurzlebigen Container aus. Ein `Illegal instruction`/Exit 132 wird dadurch erkannt, bevor der Stack in eine Restart-Schleife gerät. Bei `amd64` benötigt das offizielle ClickHouse-Image mindestens SSE3. Bei `arm64` setzt das offizielle Image ARMv8.2-A plus RCpc voraus. Bei VMs sollte deshalb nach Möglichkeit das CPU-Modell `host` beziehungsweise CPU-Passthrough verwendet werden. Zusätzlich erkennt `deploy.sh` bereits belegte Host-Ports. Wenn `AUTO_PORTS=true` ist, werden nur kollidierende Ports auf freie Ersatzports verschoben und in `.env` gespeichert. Die tatsächlich verwendeten Werte zeigt anschließend `./credentials.sh`. Für eine vollständige Hostdiagnose: ```bash ./host-info.sh ```