336 lines
12 KiB
Markdown
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.
|