Files
siem-backend/README.md
jbergner e9d9583f28
Some checks failed
release-tag / release-image (push) Failing after 1m8s
Funktionsrollback
2026-07-24 07:17:38 +02:00

445 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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: <agent-api-key>
X-Enrollment-Key: <nur bei Erstanmeldung erforderlich>
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
<batch_uid>:<event-index-im-batch>
```
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/<object>.json.gz' <raw_index>
```
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
```