Audit log export to SIEM¶
secsy-pki can stream its tamper-evident audit event log (see RBAC, audit logging & config) to external Security Information and Event Management (SIEM) systems, and provides CLI tools to verify the hash chain and export it for offline batch delivery (Task 23).
There are three ways to get audit events out of the system:
| Mechanism | Transport | Use case |
|---|---|---|
| Streaming export | RFC 5424 syslog (TCP/TLS), CEF, NDJSON webhook | Continuous, near-real-time forwarding to a SOC/SIEM |
audit export CLI |
A file / stdout in any format | Scheduled batch jobs, air-gapped transfer, back-fill |
audit verify CLI |
— | Independent tamper detection over the whole chain |
Every mechanism reads the same append-only, hash-chained event_log. Nothing
here mutates the log.
Streaming export¶
When audit.export.enabled is true, the server starts one background worker per
configured sink. Each worker streams events forward from a durable per-sink
cursor and only advances that cursor after the sink acknowledges a batch.
Delivery guarantees¶
- At-least-once, lossless across restarts. The cursor is persisted (in the
siem_export_cursortable) only after a successful delivery. If the process crashes between delivery and cursor commit, the batch is redelivered on restart — never dropped. Downstreams should treat records as idempotent (dedup on the eventid/seq, both of which are exported). - Backpressure. Each worker reads at most
batch_sizeevents per iteration, so a slow or down sink can never accumulate an unbounded in-flight set, and a large backlog is drained in bounded chunks. - Independent sinks. A failing sink retries with exponential backoff
(
retry_backoff_secondsdoubling up tomax_backoff_seconds) without advancing its cursor, and never blocks or stalls a healthy sink — each has its own worker and cursor.
Formats¶
| Format | Value | Notes |
|---|---|---|
| RFC 5424 syslog | rfc5424 |
Fields carried in a [secsyAudit@<PEN> …] STRUCTURED-DATA element; severity derives from the result (success→info, denied→warning, error→error). |
| ArcSight CEF | cef |
CEF:0|secsy|secsy-pki|<ver>|<action>|<action>|<sev>|<ext>; standard keys (rt, suser, act, outcome, src) plus cs*/cn1 for target, hashes, and seq. |
| Newline-delimited JSON | json |
The full audit.Event object per line, including prev_hash/hash, so a downstream can re-verify chain integrity. Webhook only. |
Transports¶
- syslog over
tcp(cleartext) ortls. TLS supports a custom CA bundle, SNI/server-name override, and mutual TLS (client cert). Stream framing is RFC 6587 octet-counting by default (unambiguous even with embedded newlines) or trailing-lf for collectors that require it. - webhook: each batch is POSTed as
application/x-ndjson(one record per line); custom headers (e.g.Authorization) are forwarded. Delivery is acknowledged only on a2xxresponse.
Configuration¶
audit:
export:
enabled: true
poll_interval_seconds: 5 # how often a caught-up worker re-checks
batch_size: 256 # backpressure knob (events per delivery)
retry_backoff_seconds: 1
max_backoff_seconds: 30
sinks:
- name: soc-syslog # unique & stable — it keys the durable cursor
type: syslog
format: rfc5424
network: tls
address: siem.example.com:6514
framing: octet-counting
tls:
ca_file: /etc/secsy/siem-ca.pem
# server_name: siem.example.com
# client_cert_file: /etc/secsy/siem-client.pem # mutual TLS
# client_key_file: /etc/secsy/siem-client.key
- name: soc-webhook
type: webhook
format: json
url: https://collector.example.com/ingest
headers:
Authorization: "Bearer <token>"
Sink names are load-bearing. A sink's
namekeys both its durable cursor and its metrics. Renaming a sink resets its cursor (it re-exports from the genesis); keep names stable.
Metrics¶
The exporter publishes these Prometheus series (see
observability), all labelled by sink:
| Metric | Meaning |
|---|---|
secsy_audit_export_lag_events |
Events sealed but not yet delivered (head seq − cursor). Primary alert signal. |
secsy_audit_export_cursor_seq |
Highest sequence number durably delivered. |
secsy_audit_export_events_total{result} |
Events delivered / failed. |
secsy_audit_export_batch_failures_total |
Failed delivery attempts (retried). |
secsy_audit_export_last_success_timestamp_seconds |
Wall-clock of the last acknowledged batch. |
Example alert — a sink falling behind or stalled:
max by (sink) (secsy_audit_export_lag_events) > 1000
or (time() - max by (sink) (secsy_audit_export_last_success_timestamp_seconds)) > 900
CLI: secsy-ca audit verify¶
Re-walks the entire hash chain from the genesis and reports the first broken link, detecting content tampering, hash forgery, deletion, reordering, and head deletion. Exits non-zero on any break, so it drops into cron/monitoring cleanly.
$ secsy-ca audit verify
audit chain OK: 12048 event(s) verified, hash chain intact.
$ secsy-ca audit verify # after a row was tampered with
audit chain BROKEN at seq 5177: content hash mismatch (entry was modified)
verified 5176 event(s) before the break.
$ echo $?
1
-json emits the machine-readable VerifyResult for pipelines.
This complements the online GET /api/events/verify endpoint: the CLI needs
only the database (not the HSM or a running server), so an auditor can run it
independently and out-of-band.
CLI: secsy-ca audit export¶
Batch-exports events over a time range for offline delivery to a SIEM — a scheduled shipper, an air-gapped export, or a back-fill after a sink outage.
# Everything, as NDJSON, to a file:
$ secsy-ca audit export -out audit.ndjson
# A single day, in CEF, to stdout:
$ secsy-ca audit export -from 2026-07-01T00:00:00Z -to 2026-07-02T00:00:00Z \
-format cef
# RFC 5424 syslog records:
$ secsy-ca audit export -format rfc5424 -out audit.log
Records are written one per line using the same formatters as the streaming exporter, so offline and streaming output are byte-compatible.
See also¶
- RBAC, audit logging & config — the event log and its hash-chain design.
- Observability — the metrics endpoint the export series are published on.