From 229e0cf5d8ab79d7dc9b1bfea8861dc80af6d73e Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Fri, 30 Oct 2026 14:22:28 +0100 Subject: [PATCH] feat(alloy-syslog): label UniFi plain syslog on udp-5514 separately from CEF UniFi activity logging has a single target, so the UDM SIEM CEF events, the UDM system log (log_all_contents) and the AP/switch device logs (debug) all arrive on udp-5514. Split them in loki.process.udm_cef: - CEF lines keep source="udm-siem" and the existing CEF labels. - Everything else gets source="unifi-syslog" plus host and app parsed from the line header (UDM: " [pid]:", devices: " ,-: [: ][pid]:"). The listener no longer sets source statically; each stage.match sets it, so no label is overwritten. Regex checked against 10 min of live lines (all non-CEF lines match); config validated with alloy v1.21.3 `validate --stability.level=experimental`. Co-Authored-By: Claude Opus 5.5 --- .../monitoring/helmrelease-alloy-syslog.yaml | 72 +++++++++++++------ 1 file changed, 49 insertions(+), 23 deletions(-) diff --git a/kubernetes/apps/monitoring/helmrelease-alloy-syslog.yaml b/kubernetes/apps/monitoring/helmrelease-alloy-syslog.yaml index bda7cf7f..9c83356a 100644 --- a/kubernetes/apps/monitoring/helmrelease-alloy-syslog.yaml +++ b/kubernetes/apps/monitoring/helmrelease-alloy-syslog.yaml @@ -158,43 +158,69 @@ spec: relabel_rules = loki.relabel.syslog.rules } - // UniFi UDM SIEM Server (Network app) — UDP/5514. UDM emits CEF - // (Common Event Format), not RFC syslog: payload starts with - // " CEF:0|Vendor|Product|...". - // raw listener captures verbatim; loki.process.udm_cef extracts - // CEF header + UNIFI* extension fields and promotes a curated - // low-cardinality set to labels. + // UniFi activity logging — UDP/5514, one target for two payloads: + // the UDM SIEM events in CEF (" CEF:0|...") + // and plain syslog from the UDM (log_all_contents) and from every + // adopted AP/switch (debug). raw listener captures verbatim; + // loki.process.udm_cef sets `source` per payload type. loki.source.syslog "udp_5514" { listener { address = "0.0.0.0:5514" protocol = "udp" syslog_format = "raw" - labels = { job = "syslog", listener = "udp-5514", source = "udm-siem" } + labels = { job = "syslog", listener = "udp-5514" } } forward_to = [loki.process.udm_cef.receiver] } - // CEF parser for UDM SIEM payload. The listener also receives the - // UDM's plain syslog (log_all_contents), which does not match and - // passes through unlabelled. CEF extension keys differ per event - // type, so the regex anchors only on the header - // (vendor|product|product_version|sig_id|name|sev), UNIFIcategory, - // UNIFIhost and the trailing msg. The full raw line stays the log - // content. loki.process "udm_cef" { forward_to = [loki.write.default.receiver] - stage.regex { - expression = "CEF:\\d+\\|(?P[^|]*)\\|(?P[^|]*)\\|(?P[^|]*)\\|(?P[^|]*)\\|(?P[^|]*)\\|(?P[^|]*)\\|UNIFIcategory=(?P.+?) UNIFIhost=(?P.+?) \\S+=.* msg=(?P.+)$" + // CEF: extension keys differ per event type, so the regex + // anchors only on the header + // (vendor|product|product_version|sig_id|name|sev), + // UNIFIcategory, UNIFIhost and the trailing msg. The full raw + // line stays the log content. + stage.match { + selector = "{listener=\"udp-5514\"} |= \"CEF:\"" + + stage.static_labels { + values = { source = "udm-siem" } + } + + stage.regex { + expression = "CEF:\\d+\\|(?P[^|]*)\\|(?P[^|]*)\\|(?P[^|]*)\\|(?P[^|]*)\\|(?P[^|]*)\\|(?P[^|]*)\\|UNIFIcategory=(?P.+?) UNIFIhost=(?P.+?) \\S+=.* msg=(?P.+)$" + } + + stage.labels { + values = { + vendor = "vendor", + product = "product", + signature_id = "signature_id", + event_name = "event_name", + category = "category", + } + } } - stage.labels { - values = { - vendor = "vendor", - product = "product", - signature_id = "signature_id", - event_name = "event_name", - category = "category", + // Plain syslog. UDM: " [pid]: ..."; + // AP/switch: " ,-: [: ][pid]: ...". + stage.match { + selector = "{listener=\"udp-5514\"} != \"CEF:\"" + + stage.static_labels { + values = { source = "unifi-syslog" } + } + + stage.regex { + expression = "^\\S+ +\\d+ \\S+ (?P\\S+) (?:[0-9a-f]{12},\\S+:(?: :)? |\\S+ )(?P[A-Za-z0-9_./+-]+)" + } + + stage.labels { + values = { + host = "host", + app = "app", + } } } } -- 2.54.0