Files
siem-backend/README.md
groot c49bfa1c34
Some checks failed
release-tag / release-image (push) Failing after 5m54s
Vollständiges Redesign
2026-07-23 14:45:35 +02:00

336 lines
12 KiB
Markdown

# 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: <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
```
## 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/<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 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.