SIEM output¶
Emitting every integrity finding and every signed operator action as CEF or LEEF over syslog, for Splunk, QRadar, and other structured-log ingesters.
Enterprise edition
SIEM output is part of the Etminan Enterprise edition. The siem module
is compiled only under the enterprise Cargo feature; in the Standard build
alarm::emit_siem is a no-op and nothing is emitted. See
Editions for the full matrix.
SIEM output is a structured addition for a SOC — one event per finding and per signed operator action — alongside, never instead of, the notification channels. It is off by default: not every deployment runs a SIEM, so nothing is emitted unless it's explicitly switched on.
Consistent with the rest of Etminan: no in-process HTTP client
SIEM output goes over syslog — a local socket or UDP — never an in-process HTTP client. Anything that needs HTTP (Slack, PagerDuty) is an external notify plugin. CEF/LEEF over syslog is the SOC's native ingestion path, so it needs no plugin at all.
Turning it on¶
One environment variable in /etc/etminan-verifier/verifier.env picks the format and switches it on:
ETMINAN_SIEM_FORMAT=cef # cef | leef ; unset/empty/other = OFF
ETMINAN_SIEM_SYSLOG_ADDR=siem.internal:514 # optional: UDP to a remote collector
# unset -> local /dev/log; your syslog daemon forwards it
cef— ArcSight Common Event Format, parsed by Splunk (and ArcSight).leef— IBM QRadar's Log Event Extended Format, parsed natively by QRadar.
An unrecognized value logs a warning and stays off — it never silently guesses a format.
Transport and severity¶
Events default to the local syslog datagram socket /dev/log (your syslog daemon forwards them to the central SIEM — the usual enterprise topology), or go as UDP to ETMINAN_SIEM_SYSLOG_ADDR=host:port. The facility is local0. Emission is strictly best-effort — a broken or absent syslog target must never break a run, so send errors are swallowed.
| Event severity | syslog severity | CEF numeric (0–10) |
|---|---|---|
critical |
err (3) |
10 |
warning |
warning (4) |
5 |
| action / other | info (6) |
3 |
The event model¶
Everything Etminan emits is one of two categories, split on the cat field so your SIEM can route them to different data models.
flowchart LR
A[run/check finding] -->|alarm::fire → emit_findings| S{ETMINAN_SIEM_FORMAT}
B[signed operator action] -->|audit_log::append → emit_audit| S
S -->|cef| C[CEF line over syslog]
S -->|leef| D[LEEF 2.0 line over syslog]
C --> T[(local0 / /dev/log or UDP)]
D --> T
T --> F[Splunk / QRadar]
| Event | cat |
When |
|---|---|---|
| finding | finding |
An integrity check result — one per finding from a run/check cycle, fed only the findings new this cycle (transition-based, so no per-cycle re-fire). |
| operator-action | operator-action |
A signed trust-changing action, taken from the hash-chained audit log. The internal security_finding mirror action is skipped here to avoid double-reporting a finding already emitted. |
Format strings¶
The <version> header field carries the running etminan-verifier version at emit time.
CEF header
LEEF 2.0 header (x09 declares the TAB attribute delimiter)
Field dictionary¶
Field names are stable. Signature and payload blobs are deliberately never placed on the wire — they live in the hash-chained audit log, not the SIEM event.
| Field (CEF / LEEF) | finding | operator-action |
|---|---|---|
sigId / eventId |
the finding kind (pcr-mismatch, template-hash-mismatch, unexpected-pcr-selection, sla-exceeded, pending-overflow, plugin-failure, check-error, audit-chain-invalid, …) |
the audit action (host_enrolled, baseline_approved, baseline_rejected, baseline_exclusion_created/_revoked, operator_key_authorized/_revoked/_rotated, tls_cert_rotated, ak_rotated, profile_assigned, …) |
cat |
finding |
operator-action |
dhost / dst |
monitored host id | resource acted on (host id, or a key for registry actions) |
cs1 (cs1Label=kind) / kind |
the finding kind | — |
suser / usrName |
— | the signing operator key (hex) |
act |
— | the audit action |
msg |
the finding text | compact k:v, k:v detail summary (blobs dropped) |
sev (LEEF) / CEF numeric |
LEEF sev is the raw word (critical/warning/action); CEF is the numeric mapping above |
same |
Sample events¶
CEF — finding
CEF:0|Etminan|etminan-verifier|<version>|pcr-mismatch|Host integrity finding|10|dhost=web-01 cat=finding cs1Label=kind cs1=pcr-mismatch msg=quoted PCR10 digest does not match the replayed IMA measurement log
CEF — operator action
CEF:0|Etminan|etminan-verifier|<version>|baseline_approved|Signed operator action|3|suser=3a1f9c2b…e7 cat=operator-action act=baseline_approved dhost=web-01 msg=path:/usr/local/bin/app, reason:scheduled deploy 4821
LEEF — finding (attributes are TAB-separated)
LEEF:2.0|Etminan|etminan-verifier|<version>|pcr-mismatch|x09|cat=finding dst=web-01 kind=pcr-mismatch sev=critical msg=quoted PCR10 digest does not match the replayed IMA measurement log
Injection-safe by construction
Finding text can come straight off an IMA log on a possibly-compromised host. CEF/LEEF header and value fields escape the delimiters and newlines and strip any remaining control byte (ESC/BEL/backspace) so an attacker-controlled field can't inject a forged second syslog line or terminal escape sequences into a SOC's raw view.
Splunk — normalize to CIM¶
Ingest as CEF; a dedicated sourcetype = etminan:cef is recommended. Split on cat and normalize findings to Alerts, operator actions to Change (so registry/baseline changes surface in Splunk ES change analysis).
| Etminan | Splunk CIM field | Data model |
|---|---|---|
cat=finding events |
— | Alerts (and/or Endpoint integrity) |
dhost |
dest, dvc |
Alerts / Endpoint |
cs1 (kind) |
signature, category |
Alerts |
| CEF severity | severity |
Alerts |
msg |
description |
Alerts |
cat=operator-action events |
— | Change |
suser |
user |
Change |
act |
action / change_type |
Change |
dhost |
object / dest |
Change |
A few field aliases (dhost→dest, suser→user) plus event tags get you CIM-compliant today; a certified Splunk Technology Add-on is a planned follow-on.
QRadar — map eventId to QID¶
LEEF is parsed natively — add a Log Source of type LEEF. A DSM / Log-Source Extension assigns each eventId a QID → category + severity so findings raise proper offenses.
Etminan eventId |
QRadar category (high / low-level) | Severity |
|---|---|---|
pcr-mismatch, template-hash-mismatch, unexpected-pcr-selection, audit-chain-invalid |
Suspicious Activity → System Integrity Violation | High (8–10) |
operator-rejected |
Policy → Host-Based Policy Violation | High |
sla-exceeded, pending-overflow, plugin-failure, check-error |
Potential Exploit / Policy → Warning | Medium |
operator-action eventIds (baseline_approved, operator_key_*, *_rotated, …) |
Audit → Configuration / Authentication Change | Low–Medium |
Deployment checklist¶
- Turn on the producer — set
ETMINAN_SIEM_FORMAT(andETMINAN_SIEM_SYSLOG_ADDRfor a remote collector), and confirm events reach the SIEM. - Splunk — set the sourcetype, apply the field aliases + CIM tags above, validate against the Change / Alerts data models.
- QRadar — add the LEEF Log Source; import the QID map / DSM extension when available — generic LEEF parsing works in the meantime.
Where it plugs into the alarm path¶
SIEM emission is one leg of the verifier's shared channel fan-out. After a run produces findings, alarm::dispatch_all calls both the notify channels and emit_siem. The two are independent: the SIEM stream and the audit log always receive findings even when notify plugins are disabled or failing, and the SIEM leg is transition-based (only findings new this cycle), so a SOC gets one structured event per new finding rather than a re-fire every cycle.
Related¶
- Notification channels — the alarm channels this runs alongside.
- Change-source correlation — the other integration category.
- Editions — what's in Standard vs Enterprise.