Skip to main content

Audit Logging (OCSF)

When enabled, the Trust Domain Server emits an audit trail of its security-relevant operations as OCSF events, so you can ingest them into any standard OCSF-compatible security pipeline or SIEM without custom field mapping.

Events are plain OCSF 1.8.0 with no proprietary or non-standard fields added, so they pass strict OCSF validation as-is. The server writes them to its standard output, where your existing log collection can route them to your audit destination.

Audit logging is opt-in and disabled by default. Turning it on does not change SVID issuance behavior. It only adds the audit stream.

What Gets Audited

The server emits two OCSF event classes. Each operation produces an event on both success and failure — a denied or failed operation is recorded with its reason, not silently dropped.

OCSF Classclass_uidWhen it's emittedWhat it represents
Authentication3002On each X.509-SVID / JWT-SVID mint attemptThe credential issued to a workload
Entity Management3004On each signing-key lifecycle operationA change to a signing key set

Signing key lifecycle → OCSF activity

Keyset operationOCSF activityactivity_id
PreparedCreate1
ActivatedActivate10
TaintedUpdate3
UntaintedUpdate3
RemovedDelete4
note

Taint and untaint are modeled as an Update rather than a Deactivate: a tainted key set stays published (its IDs are still distributed so workloads distrust and rotate away), so the key set is never taken out of service. The event's message field disambiguates taint from untaint.

For background on the keyset lifecycle, see Signing Key Rotation.

Output Format

When enabled, the server writes one OCSF event per line to stdout as NDJSON. Each event is a single newline-terminated JSON object, with no surrounding log envelope. Multi-line content (e.g. error messages) is escaped, so every event stays on one line. The pretty-printed JSON in the Event Reference below is for readability only.

The server writes events to stdout but does not ship them anywhere itself. Routing events to your audit destination is the operator's responsibility. See Routing to a SIEM below.

Enabling Audit Logging

Audit logging is controlled by an enable switch and optional cloud context stamped onto every event.

Helm values

ocsf:
# Whether to enable OCSF audit logging. Disabled by default.
enabled: false
# Optional cloud provider (e.g. "AWS", "GCP", "Azure")
cloudProvider: ""
# Optional cloud region (e.g. "us-east-1")
cloudRegion: ""
# Optional cloud account UID (e.g. an AWS account ID)
cloudAccount: ""

Parameters

ParameterHelm keyCLI flagTypeDefaultRequired
Enableocsf.enabled--ocsf-enableboolfalseYes (to emit anything)
Cloud providerocsf.cloudProvider--ocsf-cloud-providerstring""No
Cloud regionocsf.cloudRegion--ocsf-cloud-regionstring""No
Cloud accountocsf.cloudAccount--ocsf-cloud-accountstring""No

enabled controls event emission. When false (the default), the server emits nothing, regardless of the cloud settings. Set it to true to turn on real emission.

cloudProvider / cloudRegion / cloudAccount are optional context describing the cloud environment the deployment runs in. They are stamped onto the cloud object of every event so downstream pipelines can attribute events to an environment. The cloud account is included only when you supply one.

The cloud fields are independent of the enable switch: All three are optional even when audit logging is enabled. If cloudProvider is left empty, events still emit and validate with a blank cloud.provider dimension, and the server logs a single informational warning at startup to flag it.

Example: enabled with cloud context

ocsf:
enabled: true
cloudProvider: "AWS"
cloudRegion: "us-west-2"
cloudAccount: "123456789012"

Deploy with your usual Helm process. On startup the server begins writing OCSF events to stdout; no other behavior changes.

Event Origin Tags

Every event carries OCSF metadata.tags — key/value pairs that identify where the event came from, so events from multiple trust domains and deployments landing in one SIEM can be attributed to their source.

TagOn which eventsMeaning
trust_domain_idAllThe trust domain the server belongs to
trust_domain_deployment_idAllThe trust domain deployment that emitted the event
cluster_idAuthentication (issuance)The cluster the issuance request came from
cluster_version_idAuthentication (issuance)The cluster version the issuance request came from
realmAuthentication (issuance)The realm the request's cluster belongs to
realm_idAuthentication (issuance)The ID of that realm
key_set_idAuthentication (issuance)The signing key set that produced the credential
attestor_nameAuthentication (issuance)The server-side workload attestor extension that ran.
attestor_versionAuthentication (issuance)The version of that attestor extension

trust_domain_id and trust_domain_deployment_id are constant for a given server and appear on every event. The remaining tags describe individual issuance requests and appear on Authentication events only:

  • cluster_id, cluster_version_id, realm, and realm_id are omitted when issuance fails before the caller's session is established, since no cluster or realm origin is known.
  • key_set_id is omitted on any failure path; it is only present when a credential was successfully signed.
  • attestor_name and attestor_version are recorded even when attestation fails, so a failed issuance is attributable to the extension responsible.

Any tag whose value is unavailable is omitted rather than emitted empty.

Event Reference

Every event shares a common set of OCSF base fields, then adds the fields specific to its class.

Common fields

Present on every event regardless of class.

FieldDescription
timeThe time the event occurred, as epoch milliseconds.
metadata.versionThe OCSF schema version the event conforms to ("1.8.0").
metadata.productThe product that reported the event — its vendor (Defakto) and name (spirl-server).
metadata.tagsOrigin tags identifying the event's source (see Event Origin Tags).
cloudThe cloud environment the deployment runs in, from the cloud configuration.
osintOCSF open-source-intelligence enrichments. Required by the schema; always emitted as an empty array.
severity_idThe normalized severity — 1 (Informational) on success, 3 (Medium) on failure.
status_idThe normalized outcome — 1 (Success) or 2 (Failure).
messageA human-readable summary of the outcome.
status_codeA low-cardinality outcome code from the source. On failure, the gRPC status code; on an idempotent success that changed nothing, Unchanged. Omitted otherwise.
status_detailA sanitized, free-text description of the outcome — the failure reason. Present on failure only.

Authentication — SVID issuance

Records a credential issued to a workload. SPIFFE has no dedicated entry in the OCSF auth_protocol_id enum, so the credential type rides on auth_protocol paired with the "Other" id (99).

A single Authentication event represents the entire issuance attempt — on both success and failure. The workload receiving the SVID is the subject (user), the calling agent is the actor, and the RPC and peer details ride on api and src_endpoint.

FieldDescription
class_uidThe Authentication class identifier — 3002.
category_uidThe category identifier — 3 (Identity & Access Management).
type_uidThe event type identifier — 300201 (class_uid * 100 + activity_id).
activity_idThe normalized activity — Logon (1).
auth_protocolThe credential type: "SPIFFE X.509-SVID" or "SPIFFE JWT-SVID".
auth_protocol_idThe normalized protocol identifier — Other (99).
userThe subject the credential was issued to — the workload, identified by its SPIFFE ID.
serviceThe service that performed the authentication — the trust domain.
actorThe principal that requested issuance on the subject's behalf — the calling agent. actor.user.uid and actor.user.name hold the agent's ID. Omitted when no authenticated agent is known.
apiThe API call that carried the request — operation is the gRPC full-method name.
src_endpointThe source endpoint of the request — the caller's IP. Omitted when no peer address is known.

When issuance fails before the workload's identity is resolved (for example, a required argument is missing, or the workload's attributes match no registered identity), the event is still emitted, attributed to the canonical Unknown subject (user.name "unknown", user.type "Unknown") — so even an unresolved attempt is audited. Issuance that fails after the identity is resolved (for example, a signing error) carries the resolved user.

Success — X.509-SVID

{
"activity_id": 1,
"category_uid": 3,
"class_uid": 3002,
"metadata": {
"version": "1.8.0",
"product": {
"vendor_name": "Defakto",
"name": "spirl-server",
"version": "0.36.1"
},
"tags": [
{ "name": "trust_domain_id", "value": "td-kscgxiupol" },
{ "name": "trust_domain_deployment_id", "value": "tdd-0c9009b3ed" },
{ "name": "cluster_id", "value": "c-55zzfbmonu" },
{ "name": "cluster_version_id", "value": "cv-kwalu86v7w" },
{ "name": "realm", "value": "production" },
{ "name": "realm_id", "value": "rlm-1zt9yzr92e" },
{ "name": "key_set_id", "value": "ks_3FBEEV8pFwANfqEWPOzGZ8RGAD5" },
{ "name": "attestor_name", "value": "custom:https://attestation-service.company.com/webhook" },
{ "name": "attestor_version", "value": "1.0" }
]
},
"severity_id": 1,
"time": 1781540126644,
"type_uid": 300201,
"cloud": {
"provider": "AWS",
"region": "us-west-2",
"account": { "uid": "123456789012" }
},
"osint": [],
"status_id": 1,
"message": "issued X.509-SVID",
"user": {
"uid": "spiffe://example.com/production/us-west-2/ns/payments/sa/checkout",
"name": "spiffe://example.com/production/us-west-2/ns/payments/sa/checkout",
"type": "Service"
},
"service": { "name": "example.com" },
"auth_protocol": "SPIFFE X.509-SVID",
"auth_protocol_id": 99,
"actor": {
"user": {
"uid": "prod-k8s-node-01",
"name": "prod-k8s-node-01",
"type": "Service"
}
},
"api": { "operation": "/com.spirl.agent.v1.agent.API/MintX509SVID" },
"src_endpoint": { "ip": "10.244.0.5" }
}

A JWT-SVID issuance is identical in shape; only three fields differ: auth_protocol is "SPIFFE JWT-SVID", message is "issued JWT-SVID", and api.operation is /com.spirl.agent.v1.agent.API/MintJWTSVID.

Failure — after the identity is resolved

The signing or key-manager step failed after the workload's SPIFFE ID was known, so the event carries the resolved user, status_id 2, severity_id 3, and the failure reason.

{
"activity_id": 1,
"category_uid": 3,
"class_uid": 3002,
"metadata": {
"version": "1.8.0",
"product": {
"vendor_name": "Defakto",
"name": "spirl-server",
"version": "0.36.1"
},
"tags": [
{ "name": "trust_domain_id", "value": "td-kscgxiupol" },
{ "name": "trust_domain_deployment_id", "value": "tdd-0c9009b3ed" },
{ "name": "cluster_id", "value": "c-55zzfbmonu" },
{ "name": "cluster_version_id", "value": "cv-kwalu86v7w" },
{ "name": "realm", "value": "production" },
{ "name": "realm_id", "value": "rlm-1zt9yzr92e" },
{ "name": "attestor_name", "value": "custom:https://attestation-service.company.com/webhook" },
{ "name": "attestor_version", "value": "1.0" }
]
},
"severity_id": 3,
"time": 1781541841296,
"type_uid": 300201,
"cloud": {
"provider": "AWS",
"region": "us-west-2",
"account": { "uid": "123456789012" }
},
"osint": [],
"status_id": 2,
"message": "X.509-SVID issuance failed",
"status_code": "Internal",
"status_detail": "failed to sign X.509-SVID",
"user": {
"uid": "spiffe://example.com/production/us-west-2/ns/payments/sa/checkout",
"name": "spiffe://example.com/production/us-west-2/ns/payments/sa/checkout",
"type": "Service"
},
"service": { "name": "example.com" },
"auth_protocol": "SPIFFE X.509-SVID",
"auth_protocol_id": 99,
"actor": {
"user": {
"uid": "prod-k8s-node-01",
"name": "prod-k8s-node-01",
"type": "Service"
}
},
"api": { "operation": "/com.spirl.agent.v1.agent.API/MintX509SVID" },
"src_endpoint": { "ip": "10.244.0.5" }
}

Failure — before the identity is resolved

The request was rejected before the workload's SPIFFE ID could be resolved, so the subject is the canonical Unknown placeholder. Here a JWT-SVID issuance was rejected up front for an invalid argument, before attestation ran. There is no key_set_id or attestor tag, but the agent's session was already established, so the cluster and realm origin tags are present. The authenticated agent is still recorded as the actor.

{
"activity_id": 1,
"category_uid": 3,
"class_uid": 3002,
"metadata": {
"version": "1.8.0",
"product": {
"vendor_name": "Defakto",
"name": "spirl-server",
"version": "0.36.1"
},
"tags": [
{ "name": "trust_domain_id", "value": "td-kscgxiupol" },
{ "name": "trust_domain_deployment_id", "value": "tdd-0c9009b3ed" },
{ "name": "cluster_id", "value": "c-55zzfbmonu" },
{ "name": "cluster_version_id", "value": "cv-kwalu86v7w" },
{ "name": "realm", "value": "production" },
{ "name": "realm_id", "value": "rlm-1zt9yzr92e" }
]
},
"severity_id": 3,
"time": 1781541364308,
"type_uid": 300201,
"cloud": {
"provider": "AWS",
"region": "us-west-2",
"account": { "uid": "123456789012" }
},
"osint": [],
"status_id": 2,
"message": "JWT-SVID issuance failed",
"status_code": "InvalidArgument",
"status_detail": "audience cannot be empty",
"user": {
"name": "unknown",
"type": "Unknown",
"type_id": 0
},
"service": { "name": "example.com" },
"auth_protocol": "SPIFFE JWT-SVID",
"auth_protocol_id": 99,
"actor": {
"user": {
"uid": "prod-k8s-node-01",
"name": "prod-k8s-node-01",
"type": "Service"
}
},
"api": { "operation": "/com.spirl.agent.v1.agent.API/MintJWTSVID" },
"src_endpoint": { "ip": "10.244.0.5" }
}

Entity Management — signing key lifecycle

Records a change to a signing key set. The affected key set is the entity. The activity_id, type_uid, and message vary by operation:

Operationactivity_idtype_uidmessage (success)
PreparedCreate (1)300401"prepared signing key set"
ActivatedActivate (10)300410"activated signing key set"
TaintedUpdate (3)300403"tainted signing key set"
UntaintedUpdate (3)300403"untainted signing key set"
RemovedDelete (4)300404"removed signing key set"
FieldDescription
class_uidThe Entity Management class identifier — 3004.
category_uidThe category identifier — 3 (Identity & Access Management).
activity_idThe normalized activity for the operation (see the table above).
type_uidThe event type identifier — class_uid * 100 + activity_id (see the table above).
entityThe managed entity acted upon — the signing key set, identified by its key set ID (uid) and trust domain (name). The uid may be empty if a Prepare failed before a key set was generated.

Activate, taint, untaint, and remove are idempotent. When such an operation succeeds but the key set was already in the requested state, the event records status_id 1 with status_code Unchanged and a distinct message (for example, "signing key set was already absent"), so a consumer can tell a real change from a no-op without parsing the message.

Success

All lifecycle successes share this shape; only activity_id, type_uid, and message change per operation (see the table above). The Activate event below is representative.

{
"activity_id": 10,
"category_uid": 3,
"class_uid": 3004,
"metadata": {
"version": "1.8.0",
"product": {
"vendor_name": "Defakto",
"name": "spirl-server",
"version": "0.36.1"
},
"tags": [
{ "name": "trust_domain_id", "value": "td-kscgxiupol" },
{ "name": "trust_domain_deployment_id", "value": "tdd-0c9009b3ed" }
]
},
"severity_id": 1,
"time": 1781540534363,
"type_uid": 300410,
"cloud": {
"provider": "AWS",
"region": "us-west-2",
"account": { "uid": "123456789012" }
},
"osint": [],
"status_id": 1,
"message": "activated signing key set",
"entity": {
"uid": "ks_3FBFPCV2TJlodtTLg97Splmq331",
"name": "example.com",
"type": "Signing Key Set"
}
}

Failure — Taint (key set still active)

A taint was rejected because the key set is still the active signing key. The event carries the resolved key set as the entity, status_id 2, severity_id 3, and the failure reason.

{
"activity_id": 3,
"category_uid": 3,
"class_uid": 3004,
"metadata": {
"version": "1.8.0",
"product": {
"vendor_name": "Defakto",
"name": "spirl-server",
"version": "0.36.1"
},
"tags": [
{ "name": "trust_domain_id", "value": "td-kscgxiupol" },
{ "name": "trust_domain_deployment_id", "value": "tdd-0c9009b3ed" }
]
},
"severity_id": 3,
"time": 1781540531702,
"type_uid": 300403,
"cloud": {
"provider": "AWS",
"region": "us-west-2",
"account": { "uid": "123456789012" }
},
"osint": [],
"status_id": 2,
"message": "signing key set taint failed",
"status_code": "FailedPrecondition",
"status_detail": "cannot taint active key set \"ks_3FBEEV8pFwANfqEWPOzGZ8RGAD5\"",
"entity": {
"uid": "ks_3FBEEV8pFwANfqEWPOzGZ8RGAD5",
"name": "example.com",
"type": "Signing Key Set"
}
}

Success — Remove (no-op, already absent)

The key set was already gone, so the operation changed nothing. It succeeds with status_code Unchanged and the no-op message.

{
"activity_id": 4,
"category_uid": 3,
"class_uid": 3004,
"metadata": {
"version": "1.8.0",
"product": {
"vendor_name": "Defakto",
"name": "spirl-server",
"version": "0.36.1"
},
"tags": [
{ "name": "trust_domain_id", "value": "td-kscgxiupol" },
{ "name": "trust_domain_deployment_id", "value": "tdd-0c9009b3ed" }
]
},
"severity_id": 1,
"time": 1781540866002,
"type_uid": 300404,
"cloud": {
"provider": "AWS",
"region": "us-west-2",
"account": { "uid": "123456789012" }
},
"osint": [],
"status_id": 1,
"message": "signing key set was already absent",
"status_code": "Unchanged",
"entity": {
"uid": "ks_3FBEEV8pFwANfqEWPOzGZ8RGAD5",
"name": "example.com",
"type": "Signing Key Set"
}
}

Routing to a SIEM

The server only writes events to stdout. To get them into your audit pipeline, pick the events up with the log collector you already run in your cluster and forward them to your OCSF-compatible destination.

Because each event is already valid OCSF 1.8.0, no field mapping or transformation is required. Use the metadata.tags, cloud, and class_uid fields to route or partition events by source and type.

For how the server's other logs are structured, see Grouping Logs by Request. For broader monitoring guidance, see the Operations section.