Einleitung
Strukturierte JSON-Logs sind eine wichtige Grundlage für zuverlässige Erkennungsregeln, Korrelationen und forensische Analysen in Wazuh. Felder wie level, userId, requestId, module, method oder url lassen sich wesentlich präziser auswerten als unstrukturierte Textnachrichten.
Beim direkten Einlesen der Dateien des Docker-Logging-Treibers json-file entsteht jedoch häufig ein unerwartetes Problem: Docker speichert die eigentliche Anwendungsmeldung innerhalb eines äußeren JSON-Objekts als Zeichenkette. Gibt die Anwendung selbst bereits JSON aus, entsteht damit ein JSON-Dokument innerhalb eines JSON-Strings.
Wazuh kann das äußere Docker-Objekt korrekt dekodieren. Der darin enthaltene String wird jedoch nicht automatisch ein zweites Mal als JSON interpretiert. Für die Regelauswertung bleibt die eigentliche Anwendungsmeldung deshalb unstrukturiert.
Ausgangslage / Problemstellung
Der Wazuh Agent überwacht die von Docker erzeugten Container-Logdateien direkt:
<localfile>
<location>/var/lib/docker/containers/*/*-json.log</location>
<log_format>json</log_format>
</localfile>
Docker verwendet den Logging-Treiber json-file. Jede Container-Ausgabe wird dabei in einem eigenen JSON-Objekt gespeichert:
{
"log": "{\"level\":\"info\",\"time\":\"2026-07-03T11:48:00.464Z\",\"pid\":7,\"hostname\":\"6dd65a4a26cb\",\"module\":\"mymodule\",\"class\":\"myclass\",\"url\":\"/abcd/d/e/f/h\",\"method\":\"GET\",\"msg\":\"my log message\"}\n",
"stream": "stdout",
"attrs": {
"tag": "a.b.fr/abcd/myimage:mytag|container_name|sha256:aaaa|aaaaa"
},
"time": "2026-07-03T11:48:00.464224035Z"
}
Das äußere Objekt ist gültiges JSON. Das Feld log besitzt jedoch nicht den Datentyp Objekt, sondern den Datentyp String. Die Anführungszeichen des ursprünglichen Anwendungslogs sind deshalb mit Backslashes maskiert.
Nach der ersten Dekodierung sieht Wazuh im Wesentlichen folgende Struktur:
log: {"level":"info","time":"...","module":"mymodule",...}
stream: stdout
time: 2026-07-03T11:48:00.464224035Z
Der Inhalt von log bleibt dabei eine einzelne Zeichenkette. Felder wie level, module, url oder method stehen der Wazuh-Regelengine nicht als eigenständige dynamische Felder zur Verfügung.
Das gewünschte Ergebnis wäre dagegen:
{
"log": {
"level": "info",
"time": "2026-07-03T11:48:00.464Z",
"pid": 7,
"hostname": "6dd65a4a26cb",
"module": "mymodule",
"class": "myclass",
"url": "/abcd/d/e/f/h",
"method": "GET",
"msg": "my log message"
},
"stream": "stdout",
"time": "2026-07-03T11:48:00.464224035Z"
}
Technische Analyse
Warum Docker das Anwendungs-JSON maskiert
Der Docker-Treiber json-file erstellt für jede Ausgabe auf stdout oder stderr ein eigenes JSON-Objekt. Die eigentliche Logzeile wird im Feld log gespeichert, während Docker zusätzliche Metadaten wie den Stream und einen Zeitstempel ergänzt.
Da das Feld log laut Docker-Format eine Zeichenkette ist, muss ein darin enthaltenes JSON-Dokument maskiert werden. Das Verhalten ist daher kein fehlerhaftes Encoding, sondern eine Folge der doppelten Serialisierung:
- Die Anwendung serialisiert ihr Ereignis als JSON.
- Docker serialisiert diese komplette Zeichenkette erneut als Teil seines eigenen JSON-Objekts.
Docker weist außerdem darauf hin, dass die Dateien des json-file-Treibers primär für den Zugriff durch den Docker-Daemon vorgesehen sind. Der direkte Zugriff externer Prozesse kann zu unerwartetem Verhalten führen und sollte deshalb nicht als bevorzugte Integrationsschnittstelle betrachtet werden.
Warum der Wazuh JSON Decoder nicht rekursiv arbeitet
Der integrierte Wazuh JSON Decoder erkennt JSON-Datentypen wie Strings, Zahlen, boolesche Werte, Arrays und Objekte. Tatsächlich vorhandene verschachtelte Objekte können als dynamische Felder extrahiert und anschließend in Regeln verwendet werden.
Der Decoder unterscheidet jedoch korrekt zwischen diesen beiden Varianten:
{
"log": {
"level": "info"
}
}
und:
{
"log": "{\"level\":\"info\"}"
}
Im ersten Beispiel ist log ein JSON-Objekt. Im zweiten Beispiel ist log ein String, dessen Inhalt lediglich wie JSON aussieht. Wazuh führt auf Stringwerten keinen automatischen zweiten Decoder-Durchlauf durch.
Eine generische rekursive Deserialisierung aller Zeichenketten wäre zudem problematisch. Viele reguläre Logfelder können zufällig geschweifte Klammern oder JSON-ähnliche Inhalte enthalten. Ein automatisches rekursives Parsing würde zusätzliche CPU-Last, uneindeutige Feldtypen und schwer kontrollierbare Fehlerfälle erzeugen.
Warum ein Decoder mit after_prematch nicht ausreicht
Ein naheliegender Ansatz ist ein benutzerdefinierter Decoder:
<decoder name="docker_raw_json">
<prematch>^{"log":"</prematch>
<use_own_name>true</use_own_name>
<plugin_decoder offset="after_prematch">JSON_Decoder</plugin_decoder>
</decoder>
Dieser Decoder löst das Problem aus mehreren Gründen nicht zuverlässig.
Nach dem Prematch beginnt der Plugin-Decoder nicht bei einem gültigen, eigenständigen JSON-Objekt, sondern bei einer maskierten Zeichenfolge:
{\"level\":\"info\", ... }\n","stream":"stdout", ...
Die Backslashes gehören zur äußeren JSON-Serialisierung. Aus Sicht eines neuen JSON-Parsers ist dieser Ausschnitt deshalb kein unverändertes JSON-Dokument. Zusätzlich folgen nach dem Ende des inneren Strings weitere Felder des äußeren Docker-Objekts.
Auch use_own_name führt keinen zweiten Dekodierungsschritt aus. Die Option beeinflusst lediglich die Namensvergabe bei Child-Decodern und ist laut Decoder-Syntax für untergeordnete Decoder vorgesehen.
Wazuh verarbeitet Ereignisse während der Dekodierungsphase mit passenden Decodern und übergibt die daraus resultierenden Felder anschließend an die Regelengine. Der bereits erfolgreich erkannte äußere JSON-Datensatz wird nicht automatisch erneut anhand eines seiner Stringfelder in die Decoder-Kette eingespeist.
Warum reguläre Ausdrücke nur eingeschränkt geeignet sind
Ein benutzerdefinierter Regex-Decoder könnte ausgewählte Werte aus dem maskierten String extrahieren:
<decoder name="docker-application-json">
<parent>json</parent>
<regex type="pcre2">\\"level\\":\\"([^"]+)\\"</regex>
<order>app.level</order>
</decoder>
Für wenige feste Felder kann dieser Ansatz funktionieren. Für eine generische Plattform mit unterschiedlichen Anwendungen ist er jedoch ungeeignet:
- Jede Anwendung kann andere Felder liefern.
- Die Reihenfolge von JSON-Feldern ist nicht garantiert.
- Optionale oder verschachtelte Felder verkomplizieren die Ausdrücke.
- Escape-Sequenzen innerhalb von Logwerten können Fehlinterpretationen verursachen.
- Neue Felder erfordern laufende Decoder-Anpassungen.
- Der Regex verarbeitet Text und keine tatsächliche JSON-Struktur.
Regex-Decoder sollten deshalb nur eingesetzt werden, wenn das Format stabil ist und eine kleine, klar definierte Feldmenge extrahiert werden muss.
Verarbeitung im Wazuh Indexer
Ein OpenSearch-Ingest-Prozessor kann einen JSON-formatierten String nachträglich in ein Objekt umwandeln. Der json-Prozessor erwartet ein Feld mit einer JSON-Zeichenkette und schreibt das deserialisierte Ergebnis entweder zurück in dieses Feld oder in ein separates Zielfeld.
Konzeptionell könnte ein Prozessor beispielsweise ein Feld wie data.log nach data.application deserialisieren:
{
"json": {
"field": "data.log",
"target_field": "data.application",
"ignore_failure": true
}
}
Der tatsächliche Feldpfad muss anhand eines vorhandenen Wazuh-Dokuments geprüft werden.
Diese Lösung besitzt jedoch eine entscheidende Einschränkung: Die Indexer-Pipeline wird erst ausgeführt, nachdem der Wazuh Manager das Ereignis dekodiert und gegen seine Regeln ausgewertet hat. Die nachträglich erzeugten Felder können für Dashboards, Suchabfragen und Threat Hunting genutzt werden, stehen aber nicht für die ursprüngliche Wazuh-Regelauswertung zur Verfügung. Wazuh erzeugt zunächst die Alerts; Filebeat leitet diese anschließend an den Indexer weiter.
Lösung / Best Practices
Empfohlene Lösung: Docker Syslog Driver verwenden
Die stabilste Lösung besteht darin, die zusätzliche JSON-Hülle des json-file-Treibers zu vermeiden. Der Docker Syslog Driver überträgt die Container-Ausgabe als eigentliche Syslog-Nachricht. Gibt die Anwendung ein einzeiliges JSON-Dokument aus, bleibt dieses im Nachrichtenbereich als JSON erhalten und muss nicht aus einem maskierten log-String rekonstruiert werden.
Docker kann beispielsweise global in /etc/docker/daemon.json konfiguriert werden:
{
"log-driver": "syslog",
"log-opts": {
"syslog-address": "tcp://192.0.2.10:5514",
"syslog-format": "rfc5424micro",
"tag": "docker-{{.Name}}"
}
}
Anschließend muss Docker neu gestartet werden:
systemctl restart docker
Die Änderung des Standardtreibers gilt nur für neu erstellte Container. Bestehende Container müssen neu erstellt werden, damit sie den neuen Logging-Treiber verwenden. Docker unterstützt für den Syslog Driver unter anderem TCP, UDP und TLS-fähige TCP-Verbindungen sowie benutzerdefinierte Tags.
Für einzelne Container kann der Treiber gezielt gesetzt werden:
docker run \
--log-driver=syslog \
--log-opt syslog-address=tcp://192.0.2.10:5514 \
--log-opt syslog-format=rfc5424micro \
--log-opt tag=docker-myservice \
myimage:mytag
Die Anwendung sollte weiterhin genau ein vollständiges JSON-Objekt pro Zeile ausgeben:
{"level":"info","time":"2026-07-03T11:48:00.464Z","service":"api","requestId":"79ad10","method":"GET","url":"/abcd/d/e/f/h","msg":"Request completed"}
Syslog-Empfang auf dem Wazuh Manager
Der Wazuh Manager kann Syslog-Nachrichten direkt empfangen. Eine mögliche Konfiguration in /var/ossec/etc/ossec.conf lautet:
<remote>
<connection>syslog</connection>
<port>5514</port>
<protocol>tcp</protocol>
<allowed-ips>192.0.2.0/24</allowed-ips>
<local_ip>192.0.2.10</local_ip>
</remote>
Danach wird der Manager neu gestartet:
systemctl restart wazuh-manager
allowed-ips ist für den Syslog-Empfang verpflichtend. Der Zugriff sollte auf die tatsächlich verwendeten Docker-Hosts oder Netze begrenzt werden.
Da die Syslog-Nachricht einen Header besitzt, kann ein spezifischer Decoder den Header über die Wazuh-Predecoding-Phase zuordnen und anschließend den JSON-Nachrichteninhalt verarbeiten:
<decoder name="docker-json-syslog">
<program_name type="pcre2">^docker-.+</program_name>
<prematch type="pcre2">^\s*\{</prematch>
<plugin_decoder>JSON_Decoder</plugin_decoder>
</decoder>
Der Ausdruck für program_name muss zum tatsächlich übertragenen Docker-Tag passen. Durch:
"tag": "docker-{{.Name}}"
entstehen beispielsweise Programmnamen wie:
docker-api
docker-auth-service
docker-nginx
Benutzerdefinierte Decoder werden auf dem Wazuh Manager unter /var/ossec/etc/decoders/ abgelegt, beispielsweise in:
/var/ossec/etc/decoders/docker_decoders.xml
Kleinere Anpassungen können alternativ in /var/ossec/etc/decoders/local_decoder.xml hinterlegt werden. Die Decoder lassen sich vor der produktiven Aktivierung mit wazuh-logtest prüfen. Für die Verarbeitung realer Ereignisse ist anschließend ein Neustart des Wazuh Managers erforderlich.
Ein Test kann beispielsweise so erfolgen:
/var/ossec/bin/wazuh-logtest
Danach wird eine vollständige empfangene Syslog-Zeile eingefügt. In Phase 2 sollten unter anderem folgende Felder erscheinen:
level: 'info'
service: 'api'
requestId: '79ad10'
method: 'GET'
url: '/abcd/d/e/f/h'
msg: 'Request completed'
Eine passende Regel könnte anschließend auf diese Felder zugreifen:
<group name="docker,application_json,">
<rule id="100200" level="8">
<decoded_as>docker-json-syslog</decoded_as>
<field name="level" type="pcre2">^(error|fatal)$</field>
<description>Container-Anwendung meldet einen Fehler: $(msg)</description>
<group>application_error,</group>
</rule>
</group>
Syslog-Relay für produktive Umgebungen
Für größere oder sicherheitskritische Umgebungen sollte zwischen Docker und Wazuh ein Syslog-Relay wie rsyslog oder syslog-ng eingesetzt werden.
Ein Relay bietet zusätzliche Funktionen:
- TLS-Terminierung
- persistente Warteschlangen
- lokale Pufferung bei Netzwerkausfällen
- Filterung und Routing nach Host, Anwendung oder Umgebung
- kontrollierte Dateirotation
- Normalisierung fehlerhafter Nachrichten
- Entkopplung der Container vom Wazuh Manager
Docker unterstützt tcp+tls als Ziel. Der direkte Wazuh-Syslog-Empfang bietet dagegen keine gleichwertige TLS-Terminierung. Werden Logs über nicht vertrauenswürdige oder geroutete Netze übertragen, sollte Docker daher an ein TLS-fähiges Relay senden. Das Relay kann die Nachrichten anschließend über ein geschütztes internes Netz oder über einen lokal installierten Wazuh Agent weitergeben.
Alternative: Vorverarbeitung vor Wazuh
Wenn der json-file-Treiber beibehalten werden muss, kann ein vorgeschalteter Prozessor den Inhalt des Feldes log deserialisieren.
Mit jq lässt sich die Transformation demonstrieren:
jq -c '.log |= fromjson' docker-event.json
Aus:
{
"log": "{\"level\":\"info\",\"time\":\"aaa\",\"abcd\":\"efgh\"}",
"stream": "stdout",
"time": "bbb"
}
wird:
{
"log": {
"level": "info",
"time": "aaa",
"abcd": "efgh"
},
"stream": "stdout",
"time": "bbb"
}
Für eine produktive Pipeline muss die Vorverarbeitung dauerhaft und fehlertolerant implementiert werden. Insbesondere muss sie mit folgenden Fällen umgehen:
- Nicht jede Container-Ausgabe ist zwingend gültiges JSON.
- Leere Zeilen oder unvollständige Ereignisse dürfen die Pipeline nicht blockieren.
- Stacktraces können mehrere Zeilen umfassen.
- Das abschließende
\nim Docker-Feld muss berücksichtigt werden. - Fehlerhafte Ereignisse benötigen einen separaten Fallback-Pfad.
- Dateirotationen und Container-Neustarts dürfen keine Logzeilen verlieren.
- Die Quelldateien sollten nicht verändert werden.
Ein möglicher Fallback mit jq ist:
jq -c '
if (.log | type) == "string" then
try (.log |= fromjson)
catch .
else
.
end
'
Die transformierten Ereignisse können in eine separate Datei geschrieben werden, die der Wazuh Agent anschließend mit log_format=json überwacht. Wegen der Docker-Warnung zum direkten Zugriff auf json-file-Dateien bleibt dies dennoch eine Übergangs- oder Speziallösung.
Archive nur gezielt zur Fehlersuche aktivieren
Zur Prüfung der tatsächlich vom Wazuh Manager empfangenen Ereignisse können vorübergehend die Archive aktiviert werden:
<global>
<logall_json>yes</logall_json>
</global>
Die Ereignisse werden anschließend unter folgendem Pfad gespeichert:
/var/ossec/logs/archives/archives.json
Beispielsweise:
grep '"docker-api"' /var/ossec/logs/archives/archives.json
Die Archive enthalten alle empfangenen Ereignisse, unabhängig davon, ob eine Regel ausgelöst wurde. Dadurch kann das Datenvolumen erheblich steigen. Wazuh deaktiviert die Archive standardmäßig und weist ausdrücklich auf die Auswirkungen auf Speicherplatz und Systemleistung hin. Die Option sollte deshalb nur kontrolliert, mit ausreichender Kapazitätsplanung oder für ein begrenztes Diagnosefenster aktiviert werden.
Lessons Learned / Best Practices
JSON bereits an der Quelle konsistent erzeugen
Die Anwendung sollte pro Ereignis genau ein vollständiges JSON-Objekt in einer Zeile ausgeben. Pretty-Printing und echte Zeilenumbrüche innerhalb eines Ereignisses sind für Logpipelines ungeeignet.
Mehrzeilige Informationen wie Stacktraces sollten innerhalb eines JSON-Feldes als maskierte Zeilenumbrüche enthalten sein:
{
"level": "error",
"message": "Request failed",
"stacktrace": "Error: failed\n at handler (/app/api.js:42)\n at processTicksAndRejections (...)"
}
Einheitliches Anwendungsschema definieren
Unkontrolliert unterschiedliche JSON-Strukturen können im Wazuh Indexer zu Mapping-Konflikten und einer hohen Anzahl dynamischer Felder führen.
Besonders problematisch ist ein Feld, das in einer Anwendung als String und in einer anderen als Objekt verwendet wird:
{"user":"alice"}
gegen:
{"user":{"id":"alice","role":"admin"}}
Für zentrale Container-Logs sollte deshalb ein Basisschema definiert werden:
{
"timestamp": "2026-07-03T11:48:00.464Z",
"level": "info",
"service": "api",
"environment": "production",
"event": {
"category": "web",
"action": "request_completed"
},
"trace": {
"request_id": "79ad10",
"correlation_id": "bb3812"
},
"http": {
"method": "GET",
"path": "/abcd/d/e/f/h",
"status_code": 200
},
"message": "Request completed"
}
Container-Metadaten bewusst erhalten
Der Syslog Driver transportiert weniger Docker-spezifische Metadaten als das json-file-Format. Der Containername sollte deshalb über den Docker-Tag erhalten bleiben.
Geschäftlich oder sicherheitstechnisch wichtige Informationen sollten zusätzlich im Anwendungsevent enthalten sein:
- Service
- Umgebung
- Version
- Cluster
- Namespace
- Request-ID
- Trace-ID
- Tenant-ID
Nicht jede Information sollte ausschließlich aus dem Hostnamen oder Container-Tag abgeleitet werden.
Mehrere Zeitstempel vermeiden oder priorisieren
Bei Container-Logs können mehrere Zeitstempel auftreten:
- Zeitstempel der Anwendung
- Zeitstempel des Docker-Logging-Treibers
- Syslog-Zeitstempel
- Wazuh-Empfangszeitpunkt
- Indexer-Zeitstempel
Für Analysen und Korrelationen sollte festgelegt werden, welcher Zeitstempel fachlich maßgeblich ist. Üblicherweise ist dies der von der Anwendung zum Zeitpunkt des Ereignisses erzeugte UTC-Zeitstempel.
Transportausfälle einplanen
Ein direkter TCP-Versand ist zuverlässiger als UDP, schützt aber nicht automatisch vor längeren Ausfällen des Empfängers. Ein lokales Syslog-Relay mit Disk Queue verhindert, dass temporäre Wazuh- oder Netzwerkprobleme unmittelbar zu Logverlusten oder Rückwirkungen auf Container führen.
Die Ausfallstrategie sollte unter Last getestet werden:
- Verhalten bei nicht erreichbarem Syslog-Ziel
- maximale Puffergröße
- Wiederanlauf nach Netzwerkausfällen
- Reihenfolge nach Wiederherstellung
- Speicherverbrauch des Relays
- Auswirkungen auf Container-Workloads
Decoder und Regeln automatisiert testen
Beispielereignisse sollten zusammen mit den benutzerdefinierten Decodern und Regeln versioniert werden. Nach Wazuh-Upgrades müssen mindestens folgende Fälle erneut getestet werden:
- gültiges Anwendungs-JSON
- ungültiges JSON
- fehlende optionale Felder
- zusätzliche neue Felder
- sehr lange Nachrichten
- Unicode-Zeichen
- maskierte Zeilenumbrüche
- unterschiedliche Log-Level
- Syslog-Tags verschiedener Container
wazuh-logtest ermöglicht die Validierung der Dekodierung und Regelzuordnung, ohne Testereignisse in die produktive Agent-Pipeline einspeisen zu müssen.
Fazit
Wazuh kann verschachtelte JSON-Objekte verarbeiten, deserialisiert aber nicht automatisch einen JSON-formatierten String innerhalb eines bereits dekodierten JSON-Ereignisses. Beim Docker-Treiber json-file bleibt die eigentliche Anwendungsmeldung deshalb im Feld log als maskierte Zeichenkette erhalten.
Ein Regex-Decoder kann einzelne bekannte Felder extrahieren, bietet jedoch keine generische und wartbare Lösung für unterschiedliche Anwendungsschemas. Eine Indexer-Pipeline kann das Feld nachträglich deserialisieren, stellt die erzeugten Felder aber nicht für die vorherige Wazuh-Regelauswertung bereit.
Für regelbasierte Erkennung ist es daher am sinnvollsten, die zusätzliche Docker-JSON-Hülle zu vermeiden. Der Docker Syslog Driver überträgt die ursprüngliche JSON-Logzeile als Nachrichteninhalt, sodass der Wazuh JSON Decoder die Anwendungsfelder direkt extrahieren kann. In produktiven Umgebungen sollte ein gepuffertes und gegebenenfalls TLS-fähiges Syslog-Relay eingesetzt werden.
Damit bleiben strukturierte Felder bereits während der Wazuh-Analyse verfügbar und können zuverlässig für Regeln, Korrelationen, Alarmtexte und Incident-Analysen verwendet werden.
Quellenverweise
Wazuh JSON Decoder
https://documentation.wazuh.com/current/user-manual/ruleset/decoders/json-decoder.html
Wazuh Decoder-Dokumentation
https://documentation.wazuh.com/current/user-manual/ruleset/decoders/index.html
Wazuh Decoder-Syntax
https://documentation.wazuh.com/current/user-manual/ruleset/ruleset-xml-syntax/decoders.html
Benutzerdefinierte Wazuh Decoder
https://documentation.wazuh.com/current/user-manual/ruleset/decoders/custom.html
Wazuh Event Logging und Archive
https://documentation.wazuh.com/current/user-manual/manager/event-logging.html
Syslog-Empfang auf dem Wazuh Server
https://documentation.wazuh.com/current/user-manual/capabilities/log-data-collection/syslog.html
Docker JSON File Logging Driver
https://docs.docker.com/engine/logging/drivers/json-file/
Docker Syslog Logging Driver
https://docs.docker.com/engine/logging/drivers/syslog/
OpenSearch JSON Ingest Processor
https://docs.opensearch.org/latest/ingest-pipelines/processors/json/
Mehr zu Wazuh …
https://wazuh.com/?utm_source=ambassadors&utm_medium=referral&utm_campaign=ambassadors+program
Mehr zum Wazuh Ambassador Program …
https://wazuh.com/ambassadors-program/?utm_source=ambassadors&utm_medium=referral&utm_campaign=ambassadors+program
https://wazuh.slack.com/archives/C07BK5RJM3R/p1783061513828609