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 Class | class_uid | When it's emitted | What it represents |
|---|---|---|---|
| Authentication | 3002 | On each X.509-SVID / JWT-SVID mint attempt | The credential issued to a workload |
| Entity Management | 3004 | On each signing-key lifecycle operation | A change to a signing key set |
Signing key lifecycle → OCSF activity
| Keyset operation | OCSF activity | activity_id |
|---|---|---|
| Prepared | Create | 1 |
| Activated | Activate | 10 |
| Tainted | Update | 3 |
| Untainted | Update | 3 |
| Removed | Delete | 4 |
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
| Parameter | Helm key | CLI flag | Type | Default | Required |
|---|---|---|---|---|---|
| Enable | ocsf.enabled | --ocsf-enable | bool | false | Yes (to emit anything) |
| Cloud provider | ocsf.cloudProvider | --ocsf-cloud-provider | string | "" | No |
| Cloud region | ocsf.cloudRegion | --ocsf-cloud-region | string | "" | No |
| Cloud account | ocsf.cloudAccount | --ocsf-cloud-account | string | "" | 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.
| Tag | On which events | Meaning |
|---|---|---|
trust_domain_id | All | The trust domain the server belongs to |
trust_domain_deployment_id | All | The trust domain deployment that emitted the event |
cluster_id | Authentication (issuance) | The cluster the issuance request came from |
cluster_version_id | Authentication (issuance) | The cluster version the issuance request came from |
realm | Authentication (issuance) | The realm the request's cluster belongs to |
realm_id | Authentication (issuance) | The ID of that realm |
key_set_id | Authentication (issuance) | The signing key set that produced the credential |
attestor_name | Authentication (issuance) | The server-side workload attestor extension that ran. |
attestor_version | Authentication (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, andrealm_idare omitted when issuance fails before the caller's session is established, since no cluster or realm origin is known.key_set_idis omitted on any failure path; it is only present when a credential was successfully signed.attestor_nameandattestor_versionare 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.
| Field | Description |
|---|---|
time | The time the event occurred, as epoch milliseconds. |
metadata.version | The OCSF schema version the event conforms to ("1.8.0"). |
metadata.product | The product that reported the event — its vendor (Defakto) and name (spirl-server). |
metadata.tags | Origin tags identifying the event's source (see Event Origin Tags). |
cloud | The cloud environment the deployment runs in, from the cloud configuration. |
osint | OCSF open-source-intelligence enrichments. Required by the schema; always emitted as an empty array. |
severity_id | The normalized severity — 1 (Informational) on success, 3 (Medium) on failure. |
status_id | The normalized outcome — 1 (Success) or 2 (Failure). |
message | A human-readable summary of the outcome. |
status_code | A low-cardinality outcome code from the source. On failure, the gRPC status code; on an idempotent success that changed nothing, Unchanged. Omitted otherwise. |
status_detail | A 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.
| Field | Description |
|---|---|
class_uid | The Authentication class identifier — 3002. |
category_uid | The category identifier — 3 (Identity & Access Management). |
type_uid | The event type identifier — 300201 (class_uid * 100 + activity_id). |
activity_id | The normalized activity — Logon (1). |
auth_protocol | The credential type: "SPIFFE X.509-SVID" or "SPIFFE JWT-SVID". |
auth_protocol_id | The normalized protocol identifier — Other (99). |
user | The subject the credential was issued to — the workload, identified by its SPIFFE ID. |
service | The service that performed the authentication — the trust domain. |
actor | The 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. |
api | The API call that carried the request — operation is the gRPC full-method name. |
src_endpoint | The 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:
| Operation | activity_id | type_uid | message (success) |
|---|---|---|---|
| Prepared | Create (1) | 300401 | "prepared signing key set" |
| Activated | Activate (10) | 300410 | "activated signing key set" |
| Tainted | Update (3) | 300403 | "tainted signing key set" |
| Untainted | Update (3) | 300403 | "untainted signing key set" |
| Removed | Delete (4) | 300404 | "removed signing key set" |
| Field | Description |
|---|---|
class_uid | The Entity Management class identifier — 3004. |
category_uid | The category identifier — 3 (Identity & Access Management). |
activity_id | The normalized activity for the operation (see the table above). |
type_uid | The event type identifier — class_uid * 100 + activity_id (see the table above). |
entity | The 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.