SIEM export
Your SOC watches the SIEM, not this console. SIEM export sends Attestkeep's security events there as they happen — refused admissions, deny-list hits, runtime events, policy and triage changes, evidence downloads, failed sign-ins — so they sit on the same timeline as everything else you collect.
Destinations are set up in the console, Settings › SIEM export. Everyone who signs in can see them and how each one is doing; adding, changing, testing and removing one is an administrator's.
Nothing leaves unless you add a destination. Events go only where you point them, from your cluster to your SIEM. Without a destination nothing is exported, and adding one changes no admission decision.
The event
Every event is one JSON object, schema attestkeep.event/v1. Each is a record the console already shows, and carries a reference to it.
| Field | Meaning |
|---|---|
time | When it happened, RFC 3339 UTC with milliseconds. For a runtime event, when the detector saw it. Always present. |
type | One of the types below. Always present. |
severity | info, low, medium, high or critical. Always present. |
cluster | The chart's clusterName. Always present. |
namespace, pod | Where it happened; empty for a cluster-wide event such as a policy change. |
workload_kind, workload_name | The workload an admission was counted against: the Deployment above a Deployment's ReplicaSet, the CronJob above a Job, Pod for a pod nobody owns. |
image, digest | The image reference and its digest. |
policy | The policy that decided an admission, the runtime rule that decided a Falco event, or the policy a change was made to. |
actor | Who: the console account's name as the audit log shows it, or the Kubernetes username as the admission ledger holds it; a runtime policy is policy:<rule>. A destination set to pseudonymous actor names gets user:<account id> and, for a Kubernetes username that is not a system: identity, k8s-user:<first 16 hex characters of its SHA-256>, as in the evidence package; a record written before account ids were kept reads user:unresolved. |
outcome | denied, allowed, warned, success, failure, stopped, taken_down, blocked, pending, restored, found, expired, or a runtime rule's action. |
reason | The sentence Attestkeep recorded, at most 1,000 characters. |
reference | The record it came from, kind:id. Unique per event and the same when an event is sent again: drop duplicates by it. Always present. |
schema | attestkeep.event/v1. Always present. |
An event never carries an SBOM, a list of findings, a credential, licence data, or what was typed in a failed sign-in.
Event types
| Type | When | Severity |
|---|---|---|
admission.denied | A pod was refused. | high |
admission.allowed | A pod was admitted. Off unless you tick it: it is most of the ledger. | info |
admission.break_glass | A pod was admitted under an open break-glass. | high |
denylist.denied | A deny-list entry matched a pod: denied where the policy enforces the list, warned (medium) where it audits it. | high |
denylist.entry_added, denylist.entry_removed | An entry was added or lifted. | medium |
denylist.whatif_run | Somebody ran What if for a deny-list entry. | info |
denylist.takedown | An administrator took down a workload a deny-list entry catches. Outcome taken_down, blocked (a PodDisruptionBudget kept the pods), failed, or pending. A refused request (the chart key off, no admin role, a namespace admission does not review, a changed replica count) is not sent. | high |
denylist.restored | A workload scaled to zero by a take down was put back. A refused restore is not sent. | medium |
scan.finding_new | A scan reports a CRITICAL finding. | critical |
scan.kev | A scan first sees a finding on CISA's Known Exploited Vulnerabilities list, on the image's first scan or a later one. | critical |
runtime.event | Your Falco reported an event (severity from its priority). | info to critical |
runtime.pod_stopped | A runtime policy stopped a pod. | high |
policy.changed | A policy was created, changed or deleted, or break-glass was closed. | medium |
policy.break_glass_opened | Break-glass was opened. | high |
triage.decided, triage.expired | A finding was triaged or a triage withdrawn; an accepted risk lapsed. | low |
evidence.generated, evidence.downloaded | An evidence package was generated or downloaded. | info |
auth.login_failed, auth.role_changed | A sign-in failed; a user's role changed. | medium |
A change the server refused is sent too, with outcome failure: an attempt to change a control is worth seeing.
Findings are sent once, when first seen. Attestkeep records the first scan that reported each finding of an image (digest, advisory, package and version). scan.finding_new is sent for that scan only; a rescan that reports the same finding sends nothing. scan.kev is sent once, by the first scan that sees the finding on the KEV list — often a later scan, since a CVE usually joins the list after the image was scanned. Findings that existed before 1.4.0 count as already seen.
Example
{"time":"2026-10-25T09:30:00.123Z","type":"admission.denied","severity":"high",
"cluster":"prod-eu-1","namespace":"prod","pod":"api-7c9f5-x2k",
"workload_kind":"Deployment","workload_name":"api",
"image":"ghcr.io/acme/api:1.24.3","digest":"sha256:9f2a…","policy":"baseline",
"actor":"system:serviceaccount:kube-system:replicaset-controller","outcome":"denied",
"reason":"denied by deny list entry 30: digest sha256:9f2a…",
"reference":"admission:812","schema":"attestkeep.event/v1"}
What each destination sends
| Destination | For | How |
|---|---|---|
| HTTP (JSON) | Google SecOps, Sumo Logic, Exabeam, any HTTP collector | An HTTPS POST per batch: a JSON array, or one event per line. Optional gzip, two optional request headers for the collector's key, and an optional HMAC signature. |
| Splunk HEC | Splunk; CrowdStrike Falcon LogScale | HEC events with time, host (the cluster), source attestkeep, sourcetype attestkeep:event and an optional index. |
| Syslog | QRadar, ArcSight, Wazuh, Graylog, Securonix, Logsign | RFC 5424 over TLS (plain TCP for a receiver without TLS), with a CEF, LEEF or JSON body. |
| Elasticsearch | Elastic Security | The bulk API into an index or data stream, with an API key. Every document carries @timestamp. |
| Microsoft Sentinel | Microsoft Sentinel | The Logs Ingestion API through a data collection rule, with an Entra application. |
Every destination must be reachable from the operator. Loopback and link-local addresses — the cloud metadata service among them — are refused; addresses inside your cluster and network are allowed. HTTP destinations must use https://; a receiver with a certificate from your own CA takes that CA in the form, added to the system roots. A redirect is followed only to the same scheme and host, at most three times, and never to a loopback or link-local address. Tokens, keys and secrets are stored encrypted with the installation's data key and never shown again: leave a field empty to keep it, or choose Replace.
Changing where a destination points removes its credentials. When an edit changes the URL, the endpoint, the syslog receiver, or for Sentinel the tenant, rule or stream, the stored token, key or secret is removed, so it is never sent to the new place unless you enter it there. The destination then reads Credentials required and sends nothing until they are entered; the audit log records which fields changed and the old and new host.
The HMAC signature, when you set a secret, is the header X-Attestkeep-Signature: sha256=<hex>: HMAC-SHA256 of the request body exactly as sent (after gzip when gzip is on). Compute it over the bytes you received.
Setting up your SIEM
Splunk
Enable HTTP Event Collector (Splunk Enterprise: Settings › Data Inputs › HTTP Event Collector › Global Settings; it is on by default on Splunk Cloud Platform) and create a token with the index it may write to. In Attestkeep: Splunk HEC, the endpoint https://<splunk>:8088/services/collector/event, the token, sent as Splunk. Search with sourcetype="attestkeep:event". Splunk's reference: Format events for HTTP Event Collector.
CrowdStrike Falcon LogScale
Create an ingest token for the repository. In Attestkeep: Splunk HEC, the endpoint https://<logscale>/api/v1/ingest/hec, the token, sent as Bearer. The event arrives as a JSON object, so its fields are searchable without a parser. Reference: Ingesting with HTTP Event Collector.
Microsoft Sentinel
Create a custom table Attestkeep_CL and a data collection rule whose stream (for example Custom-Attestkeep_CL) declares these columns: TimeGenerated (datetime) and, as strings, event_type, time, severity, cluster, namespace, pod, workload_kind, workload_name, image, digest, policy, actor, outcome, reason, reference, schema, with the transformation source. Register an Entra application with a client secret and give it the Monitoring Metrics Publisher role on the DCR. In Attestkeep: Microsoft Sentinel, the DCR's logs ingestion endpoint (or the data collection endpoint), the DCR's immutable id, the stream, the tenant id, the client id and the client secret. The event's type is sent as event_type because Type is a reserved column name. The Azure public cloud is supported. Reference: Logs Ingestion API in Azure Monitor.
IBM QRadar
Add a log source that receives syslog over TLS (not verified against IBM's documentation; confirm the log source setup on your side). In Attestkeep: Syslog, the receiver's host:port, body LEEF. Events arrive as LEEF:1.0|Attestkeep|Attestkeep|<version>|<type>| with tab-separated attributes: devTime (epoch milliseconds), sev (1–10), cat (the type), usrName (the actor), policy, and cluster, namespace, pod, workloadKind, workloadName, image, digest, outcome, reason, reference. IBM's guide defines no escaping for attribute values; Attestkeep writes a tab, CR or LF inside a value as a space (not verified against QRadar; confirm on your side). Reference: IBM's Log Event Extended Format (LEEF) guide.
ArcSight, Logsign, Securonix, Graylog
A syslog receiver over TLS. In Attestkeep: Syslog, body CEF. Events arrive as CEF:0|Attestkeep|Attestkeep|<version>|<type>|<name>|<severity 0–10>| with rt, cat, outcome, reason, suser, deviceExternalId (the cluster), flexString1 (the workload) and cs1–cs6 labelled namespace, pod, image, digest, policy and reference. Reference: Implementing ArcSight Common Event Format.
Wazuh
Add a syslog listener to the manager's ossec.conf, with the operator's address in allowed-ips:
<remote>
<connection>syslog</connection>
<port>514</port>
<protocol>tcp</protocol>
<allowed-ips>10.0.0.0/8</allowed-ips>
</remote>
The listener does not speak TLS, so in Attestkeep choose Syslog, Plain TCP on, framing one message per line, body JSON: Wazuh's JSON decoder reads the event's fields. Use it inside your own network only. Wazuh's documentation does not state the TCP framing it expects; one message per line is our choice for a line reader, not verified against Wazuh — if events do not arrive, check the framing first. Reference: Wazuh syslog collection.
Without a destination, a Wazuh agent on the nodes can also read the operator pods' container log files, where each line is an attestkeep.log/v1 JSON object behind the container runtime's own prefix. Wazuh's JSON decoder skips such a prefix with offset="after_prematch" (JSON decoder); test a line from your nodes with wazuh-logtest before writing rules.
Elastic Security
Create an API key with create_doc (or create, index, write) on the target, and auto_configure or create_index if the index or data stream should be created on the first write; a data stream needs a matching index template. In Attestkeep: Elasticsearch, the cluster's base URL, the key's encoded value, and the index or data stream (default attestkeep-events). Reference: Bulk API.
Google SecOps
Create a webhook feed (Settings › Feeds › Add new, source type Webhook, split delimiter \n), generate its secret key, and create an API key. In Attestkeep: HTTP (JSON), the feed URL, body one event per line, header 1 X-goog-api-key with the API key, header 2 X-Webhook-Access-Key with the feed's secret. Reference: Set up a SIEM webhook.
Sumo Logic
Create an HTTP Logs and Metrics source. In Attestkeep: HTTP (JSON), body one event per line, gzip on, and either the source URL or the base URL with header x-sumo-token. The source URL carries its own access code; the token header keeps it out of the address. Reference: Upload logs to an HTTP source.
Exabeam
Create a Webhook Cloud Collector in JSON mode. In Attestkeep: HTTP (JSON), the collector's …/cloud-collectors/v1/logs/json URL, body JSON array, header Authorization with Bearer <token>. Reference: Exabeam's Webhook Cloud Collector.
What is sent, per destination
- Events: the default set is every type except
admission.allowed; or tick the ones you want. - Namespaces: empty means all. An event without a namespace is cluster-wide and always goes.
- Lowest severity: events below it are not sent.
- Send the last 24 hours first: only for a new destination. Without it, it starts with what happens from the moment it is added.
Test sends one event of type test through the destination as it is saved, whatever its filters and even when it is disabled, and shows the answer.
Delivery
- At least once, for the HTTP destinations (HTTP JSON, Splunk HEC, Elasticsearch, Sentinel): a batch the destination did not accept is sent again, whole. Drop repeats by
reference; Elasticsearch does it for you, because every document is written with the reference as its id. A document Elasticsearch refuses for good (a mapping conflict, say) is counted as not delivered instead of holding back the events behind it. - Best effort, for syslog. Syslog has no delivery receipt. A batch counts as sent once the connection has taken it, over TLS with octet counting as over plain TCP; events written to a connection when it drops can be lost. The page marks syslog destinations Best effort.
- Every few seconds, at most 100 events per request, from the replica running the operator's periodic work.
- One destination never holds back another. Each has its own place in the stream.
- Retries start after 5 seconds and double up to 5 minutes while a destination fails, and never come sooner than a
Retry-Afterthe destination sent with a 429 or 503 (still at most 5 minutes). - One replica. Only the replica holding the operator's lock exports; when it loses the lock it stops on its next tick, and the replica that takes the lock carries on from the same place.
- In commit order. A record still being written when the export reads is left for the next pass, so one committed late is not skipped. A transaction left open on the database server holds the export back until it ends; meanwhile the destination reads Waiting and the lag counts from the oldest record waiting. This needs PostgreSQL 13 or later: on an older server no destination can be switched on, and the page reads Database too old.
- 24 hours. An event still not delivered 24 hours after it happened is given up and counted on the page as "N events not delivered". The evidence package's section 13, External export, lists the destinations, the last batch each accepted, and the events given up in the period.
Metrics: attestkeep_siem_events_total{target,outcome} with outcome delivered, failed or gave_up, and attestkeep_siem_lag_seconds{target}, the age of the oldest event a destination has not received. target is the destination's id.
Without a destination: OpenTelemetry
The operator's own logs (attestkeep.log/v1), traces and metrics can go to an OpenTelemetry collector with observability.otlp.endpoint (see Configuration), and from there to any SIEM the collector exports to. That stream is the operator's log, not the event schema above: the same decisions, written as log fields.
Configuration bundle
Settings › Backup exports SIEM destinations with the rest of the configuration, without their credentials and disabled. An HTTP (JSON) destination's URL is exported as scheme and host only, since the rest of it can be the collector's key; after an import, enter the full URL again. An import adds the ones not yet present, disabled; those that need a credential read Credentials required until it is entered. See backup and restore.
Who can do what
Every role that signs in can read the destinations and their status. Adding, changing, testing and removing one are administrator actions, filed in the audit log as siem_target_add, siem_target_update, siem_target_test and siem_target_remove.
GET /api/v1/siem/targets
POST /api/v1/siem/targets {"kind":"splunkhec","name":"SOC Splunk","config":{"url":"https://…/services/collector/event","token":"…"},"types":[],"namespaces":[],"min_severity":"info","backfill_24h":true}
PUT /api/v1/siem/targets/{id} fields to change; config keys merge one by one (a key left out is kept, null clears it); an empty credential keeps the stored one, "clear_secrets":["hmac_secret"] drops one
DELETE /api/v1/siem/targets/{id}
POST /api/v1/siem/targets/{id}/test
A field the server refuses comes back as 400 with {"error":"<field>: …","field":"<field>"}; a test the destination refused is 502 with its own words.