LANCOM Switches und Access Points sauber in Wazuh dekodieren: Warum Syslog-Header „verschwinden“ und wie program_name-Decoder Kollisionen vermeiden

Einleitung

Netzwerkgeräte wie LANCOM Switches und Access Points liefern häufig „syslog-ähnliche“ Zeilen, die auf den ersten Blick trivial zu parsen sind: Zeitstempel, Host/IP, Programmkennung, Nachricht. In Wazuh ist genau dieser Header jedoch gleichzeitig Fluch und Segen: Der integrierte Predecoder erkennt solche Header automatisch und extrahiert Felder wie timestamp, hostname und program_name bereits vor der eigentlichen Decoder-Phase. Wer dann Decoder-prematch/Regex gegen Teile dieses Headers schreibt, die Wazuh bereits „konsumiert“ hat, landet schnell bei „No decoder matched“ – oder bei Decodern, die zu generisch sind und andere Syslog-Quellen versehentlich mitmatchen.

Dieser Artikel zeigt anhand zweier LANCOM-Formate (Switch und AP), warum IP-basierte Prematches scheitern können, warum der AP-Decoder nicht griff, und wie man mit program_name bzw. gezieltem Syslog-Routing (rsyslog + separate Localfiles) robuste, kollisionsarme Decoder baut.

Ausgangslage / Problemstellung

Es liegen zwei Logformate vor:

Switches
2022-01-01T01:00:23+01:00 10.100.250.68 syslog SFP module inserted on port 28

APs
2026-02-13T11:12:01+00:00 AP-054 hostapd: [WLAN] Station 4a:70:98:be:ca:77 disassociated from BSSID 16:a0:57:73:ad:3a

Symptome:

  1. Ein Switch-Decoder mit IP-Regex in <prematch> matcht nicht, während ein Prematch mit der fixen IP funktioniert.
  2. Ein Ansatz über localfile + log_output/out_format (z. B. Präfix LF-syslog:) funktioniert für Switches, aber nicht für APs.
  3. Ziel ist, keine globalen, generischen Syslog-Prematches („syslog“) zu verwenden, um Decoder-Kollisionen zu vermeiden.

Technische Analyse

1) Warum der IP-Prematch „plötzlich“ nicht greift

Wazuh-Decoding läuft in zwei Phasen: Pre-decoding und Decoding. In der Pre-decoding-Phase versucht Wazuh, bei syslog-ähnlichen Headers allgemeine Felder (z. B. Zeitstempel, Hostname, Programmnamen) automatisch zu extrahieren. Dadurch ist der „sichtbare“ String, gegen den dein Decoder später matcht, oft nicht mehr die komplette Originalzeile, sondern nur der Teil nach dem erkannten Header. Genau das erklärt, warum ein Prematch auf syslog funktioniert, ein Prematch auf die IP jedoch nicht: Die IP kann bereits in Phase 1 als Host/Location erkannt und „verbraucht“ sein, sodass sie in Phase 2 nicht mehr im Match-Buffer auftaucht. Wazuh dokumentiert dieses Verhalten ausdrücklich: Pre-decoding extrahiert u. a. program_name aus Syslog-Headern, bevor Custom-Decoder greifen.

Praktischer Effekt:

  • Du kannst in Phase 2 zuverlässig gegen den Rest matchen (z. B. „syslog …“),
  • aber nicht stabil gegen Header-Komponenten, die schon in Phase 1 identifiziert wurden (IP/Hostname/Programm).

2) Warum der AP-Decoder nicht funktioniert hat

Beim AP-Beispiel erkennt Wazuh bereits im Pre-decoding:

  • timestamp: 2026-02-13T11:12:01+00:00
  • program_name: hostapd

Wenn du nun versuchst, erneut den kompletten Header inklusive hostapd: und Hostname (AP-054) über einen Localfile-Präfix (LF-syslog:) und eine Regex zu dekodieren, konkurrierst du mit dem Predecoder: Teile des Headers sind bereits parsed, dein Decoder sieht im Zweifel nur noch den Rest. In genau solchen Fällen ist der Wazuh-Standardweg, den Decoder über <program_name> zu „verankern“ und im Child-Decoder ausschließlich den Nachrichtenteil nach dem Header zu extrahieren. Das ist auch in der Wazuh-Dokumentation zum program_name-Element so vorgesehen.

Zusätzlich gab es in den gezeigten Regex-Fragmenten zwei typische Stolpersteine:

  • Escaping von Punkten in IP-Adressen: \d+.\d+ matcht „beliebiges Zeichen“ zwischen Zahlengruppen (weil . in Regex ein Wildcard ist). Für IPs brauchst du \d+\.\d+\.\d+\.\d+. Das erklärt zwar nicht allein das „geht nur mit fixer IP“, ist aber ein echter Fehler, der später zu falschen Matches führt.
  • (\.*) matcht keine „beliebige Nachricht“: \. ist ein literaler Punkt. (\.*) matcht also nur „0 bis n Punkte“. Für „alles“ brauchst du (.*).

3) Warum LF-syslog: als globaler Workaround riskant ist

out_format/Tagging ist ein legitimer Trick, um Header zu „entwerten“ und wieder komplett selbst zu dekodieren. Aber: Sobald du große Mengen unterschiedlicher Syslog-Quellen mit einem identischen Präfix versiehst, steigt das Risiko von Decoder-Kollisionen oder ungewolltem Parsing. Der nachhaltige Weg ist daher meist:

  • Logs vor Wazuh trennen (rsyslog conditional routing) und pro Logklasse separate Localfiles nutzen, oder
  • Decoder in Wazuh über stabile Anker bauen (program_name, hostname) statt über fragile Header-Strings.

Wazuh beschreibt das typische Muster „rsyslog empfängt Syslog → schreibt in Datei → Agent/Localfile liest Datei“ explizit als Best Practice für Syslog-Forwarding.

Lösung / Best Practices

A) AP-Logs (hostapd) sauber dekodieren – ohne LF-syslog:

Da hostapd bereits als program_name erkannt wird, nutze das als Parent-Anker und parse nur den Rest:

<decoder name="lancom-aps-hostapd">
<program_name>^hostapd$</program_name>
</decoder><decoder name="lancom-aps-hostapd-child">
<parent>lancom-aps-hostapd</parent>
<regex>(.*)</regex>
<order>lancom_message</order>
</decoder>

Ergebnis:

  • Kein Kampf gegen den Predecoder
  • Keine Abhängigkeit von AP-054 oder dem Zeitstempel
  • Minimalinvasiv und sehr kollisionsarm (weil program_name eindeutig ist)

Optional, wenn du MAC/BSSID extrahieren willst (Beispiel für zusätzliche Felder):

<decoder name="lancom-aps-hostapd-station">
<parent>lancom-aps-hostapd</parent>
<regex>\[WLAN\]\s+Station\s+([0-9a-fA-F:]{17})\s+disassociated\s+from\s+BSSID\s+([0-9a-fA-F:]{17})</regex>
<order>station_mac, bssid</order>
</decoder>

B) Switch-Logs: Warum IP-Prematch nicht zuverlässig ist – und was stattdessen funktioniert

Wenn die IP bereits im Pre-decoding als Host/Location verarbeitet wird, ist ein IP-Prematch in Phase 2 strukturell instabil. Zwei robuste Alternativen:

Variante 1: Prematch auf den „Rest“ nach dem Header (z. B. syslog) + Child-Extraktion
Wenn deine Switches konsistent … <IP> syslog <message> liefern:

<decoder name="lancom-switches">
<prematch>syslog</prematch>
</decoder><decoder name="lancom-switches-child">
<parent>lancom-switches</parent>
<regex>syslog\s+(.*)</regex>
<order>lancom_message</order>
</decoder>

Damit matchst du nur dort, wo „syslog“ im Message-Teil auftaucht, nicht jede Syslog-Quelle zwangsläufig. (Ob das „sauber“ genug ist, hängt davon ab, ob andere Quellen ebenfalls „syslog “ in der Payload enthalten.)

Variante 2 (empfohlen): Syslog vorfiltern (rsyslog) und pro Quelle getrennte Localfiles
Wenn du wirklich zielgenau bleiben willst (z. B. nur LANCOM-Switch-IP-Range), ist das sauberste Pattern:

  1. rsyslog filtert nach IP/Subnet/Hostname und schreibt in eine dedizierte Datei (z. B. /var/log/lancom-switch.log).
  2. Wazuh Agent/Manager liest genau diese Datei als localfile.
  3. Optional: out_format nur für diese Datei, nicht global.

Wazuh beschreibt genau diesen Weg (rsyslog → Datei → Wazuh) als Standardansatz für Syslog-Forwarding.

C) Wenn du LF-syslog: behalten willst: AP-Regex korrigieren

Falls du bewusst alles über einen Präfix dekodieren willst (z. B. weil du den vollständigen Header inkl. Hostname/IP extrahieren möchtest), muss die Regex korrekt und nicht gegen bereits pre-decoded Felder „doppelt“ ankämpfen.

Korrigierter Minimalansatz (ohne übermäßig greedy Gruppen, ohne \.*):

<decoder name="lf-syslog">
<prematch>^LF-syslog:\s</prematch>
</decoder><decoder name="lf-lancom-ap">
<parent>lf-syslog</parent>
<regex>^LF-syslog:\s(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\+\d{2}:\d{2})\s(\S+)\shostapd:\s(.*)$</regex>
<order>logtimestamp, apname, lancom_message</order>
</decoder>

Hinweis: Das funktioniert nur dann zuverlässig, wenn out_format exakt dieses Präfix produziert und du wirklich die komplette Zeile (inkl. Header) wieder in den Decoder schiebst.

Lessons Learned / Best Practices

  • Immer zuerst wazuh-logtest nutzen, bevor Decoder geschrieben werden: Entscheidend ist, was nach Phase 1 wirklich noch an „matchbarem“ String übrig bleibt.
  • Header nicht doppelt dekodieren: Wenn Pre-decoding program_name/timestamp erkennt, dann den Decoder an diese Felder koppeln (<program_name>…</program_name>) und im Child nur den Rest extrahieren.
  • Regex-Fehler vermeiden:
    • IP: Punkte escapen (\.)
    • „Alles“: (.*) statt (\.*)
    • Greedy-Gruppen begrenzen (\S+ statt (.*) für „ein Token“)
  • Kollisionsfreie Architektur: Wenn du bestimmte Quellen präzise matchen willst (z. B. nur LANCOM-Switches), ist Vorfilterung via rsyslog + separate Dateien langfristig stabiler als globale Tags auf „allen Syslogs“.

Fazit

Der Kern der Beobachtungen ist kein „mysteriöses Regex-Verhalten“, sondern die Wazuh-Architektur: Syslog-Header werden in Phase 1 teilweise bereits interpretiert, wodurch IP/Hostname/program_name im Decoder-Matching nicht mehr so verfügbar sind wie erwartet. Für LANCOM-APs ist der sauberste Weg ein program_name-basierter Decoder auf hostapd. Für Switches ist ein IP-Prematch häufig unzuverlässig; hier gewinnt entweder ein Prematch auf den verbleibenden Message-Teil oder – beständig und kollisionsarm – ein rsyslog-basiertes Vorfiltern in separate Logfiles, die Wazuh gezielt einsammelt.

Quellenverweise (Copy & Paste)

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/C0A933R8E/p1770985116647499