Audit Events and Records
AlmaForge records security-relevant activity as typed JSON objects. These records cover authentication, access requests, configuration changes, and activity within proxied sessions. Interactive SSH sessions can also produce a recording that can be replayed later.
At a glance
- Every record is an
AuditEventinalmaforge.com/v1. spec.typeidentifies the event.spec.codepreserves the finer outcome code used for alerting.spec.verbandspec.objectRefdescribe the action and affected AlmaForge resource when those concepts apply.- Identity, session, destination, connection, and result fields live in
consistently named context objects under
spec. - An event may add one type-specific object, such as
spec.sessionEnd. - Audit events and session recordings have separate storage settings and retention policies.
Event records
An audit record has a Kubernetes-style envelope and a typed spec. This
SessionEnd event shows the shared fields, nested context, and type-specific
details:
{
"apiVersion": "almaforge.com/v1",
"kind": "AuditEvent",
"spec": {
"id": "da455e0f-c27d-459f-a218-4e83b3db9426",
"type": "SessionEnd",
"code": "T2004I",
"verb": "end",
"index": 20,
"timestamp": "2026-05-18T22:30:58.246000Z",
"clusterName": "almaforge.example.com",
"actor": {
"name": "alice@example.com",
"type": "user",
"login": "root"
},
"session": {
"id": "1cdcd17c-9d9f-4806-9a3c-dffe3f873b0c"
},
"server": {
"id": "051a45b0-2c6b-4ef5-babd-a7442048597e",
"hostname": "node-1.example.com",
"labels": {
"env": "prod",
"region": "us-east-1"
}
},
"connection": {
"localAddr": "10.0.0.12:3022",
"remoteAddr": "203.0.113.42:50778",
"protocol": "ssh"
},
"sessionEnd": {
"interactive": true,
"participants": ["alice@example.com"],
"startTime": "2026-05-18T22:18:32.114000Z",
"endTime": "2026-05-18T22:30:58.246000Z",
"initialCommand": [""],
"sessionRecording": "node"
}
}
}
Envelope and shared fields
| Path | Presence | Meaning |
|---|---|---|
apiVersion | Always | API group and version, always almaforge.com/v1 |
kind | Always | Object kind, always AuditEvent |
spec.id | Always | Unique audit event ID, empty for stream-only events such as SessionOutput |
spec.type | Always | Exact event type, such as SessionEnd or RoleCreate |
spec.code | Always | Outcome-sensitive event code used for alerting, empty for stream-only events such as SessionOutput |
spec.verb | Conditional | Actor action, such as create, execute, or end |
spec.index | Always | Sequence index within one session |
spec.timestamp | Always | Microsecond-precision event time |
spec.clusterName | Conditional | AlmaForge cluster that emitted the event |
spec.index orders records only within the same session. It is not unique
across sessions and does not establish a cluster-wide order.
Event codes begin with T. Codes ending in I usually describe successful or
informational activity, W usually identifies a warning or failed operation,
and E usually identifies an error. Treat the full code as the stable value.
Do not derive behavior from the suffix alone. One event type can have more than
one code, such as DatabaseSessionStart with TDB00I or TDB00W.
Query and correlation fields
Use these paths as the primary dimensions when searching or processing audit records:
| Question | Path |
|---|---|
| What happened? | spec.type |
| What action did the actor take? | spec.verb |
| What kind of AlmaForge resource changed? | spec.objectRef.kind |
| Which named resource changed? | spec.objectRef.name |
| Who or what acted? | spec.actor.name and spec.actor.type |
| Which session produced the event? | spec.session.id |
| What was the detailed outcome? | spec.code and spec.status |
spec.actor.type is user, bot, or system. It distinguishes a person from
a workload identity or AlmaForge itself. spec.objectRef carries kind,
name, and, when the referenced resource expires, expires.
Context objects
Each event type defines which context objects are required, optional, or not applicable. Shipping event families use these context paths and fields:
| Object | Fields |
|---|---|
spec.actor | name, type, login, impersonator, accessRequests |
spec.objectRef | kind, name, expires |
spec.session | id |
spec.connection | localAddr, remoteAddr, protocol |
spec.server | id, hostname, addr, labels, forwardedBy, type |
spec.status | success, error, message |
spec.client | userAgent |
spec.command | command, exitCode, exitError |
spec.database | service, protocol, uri, name, user, labels, roles, type, origin, awsRegion, awsRedshiftClusterId, gcpProjectId, gcpInstanceId |
spec.app | uri, publicAddr, labels, name |
spec.kubernetesCluster | name, users, groups, labels |
spec.kubernetesPod | name, namespace, containerName, containerImage, nodeName |
The nesting distinguishes values that would otherwise be ambiguous. For
example, spec.server.addr is the server's address, while
spec.connection.localAddr is the target address on the host.
Type-specific details
When an event has fields beyond the shared context, spec contains one object
named from spec.type in lower camel case. SessionEnd uses
spec.sessionEnd, AccessRequestReview uses spec.accessRequestReview, and
DatabaseSessionQuery uses spec.databaseSessionQuery.
Some types need no type-specific object. A RoleCreate record ends after its
shared actor and objectRef context. The selected type determines which
context and type-specific objects are allowed. Fields from another event type
do not belong in the record.
Event catalog
The following catalog lists the event types most useful for access reviews, incident investigation, and configuration auditing. It is a curated selection, not an exhaustive list.
Authentication and access
| Type | Verb | Emitted when |
|---|---|---|
UserLogin | login | A user login succeeds or fails |
AccessRequestCreate | create | A user creates an access request |
AccessRequestUpdate | update | An access request changes state |
AccessRequestReview | review | A reviewer submits a decision |
AccessRequestDelete | delete | An access request is deleted |
TokenCreate | create | A join token is created |
BotJoin | join | A workload identity bot joins the cluster |
CertificateCreate | create | A certificate is issued |
LockCreate | create | An access lock is created |
LockDelete | delete | An access lock is deleted |
SSH, application, and Kubernetes access
| Type | Verb | Emitted when |
|---|---|---|
SessionStart | start | An SSH or Kubernetes exec or attach session starts |
SessionEnd | end | An SSH or Kubernetes exec or attach session ends |
SessionReject | start | An SSH session start is rejected |
SessionExec | execute | An SSH or Kubernetes command succeeds or fails |
SessionPortForward | transfer | SSH or Kubernetes port forwarding succeeds or fails |
RecordingAccess | read | A user requests session recording data |
AppSessionStart | start | An application session starts |
AppRecordingChunk | None | An application recording chunk starts |
AppSessionRequest | execute | An HTTP application request receives a response. Stored only in the recording chunk. |
AppSessionEnd | end | A TCP application session ends |
KubernetesRequest | execute | AlmaForge proxies a Kubernetes API request |
Database access
| Type | Verb | Emitted when |
|---|---|---|
DatabaseSessionStart | start | A database connection succeeds or fails |
DatabaseSessionEnd | end | A database connection ends |
DatabaseSessionQuery | execute | A database query is submitted |
DatabaseSessionQueryFailed | execute | A database query fails |
PostgresParse | execute | A client submits a prepared statement for parsing |
PostgresBind | execute | A client binds parameters to a prepared statement |
PostgresExecute | execute | A client requests execution of a prepared statement |
PostgresClose | execute | A client requests closure of a prepared statement or portal |
PostgresFunctionCall | execute | A client requests a function call |
Resource changes
| Resource | Create | Update | Delete |
|---|---|---|---|
| Role | RoleCreate | RoleUpdate | RoleDelete |
| OIDC connector | OIDCConnectorCreate | OIDCConnectorUpdate | OIDCConnectorDelete |
| Authentication configuration | AuthConfigCreate | AuthConfigUpdate | AuthConfigDelete |
| Network configuration | NetworkConfigCreate | NetworkConfigUpdate | NetworkConfigDelete |
| Recording configuration | RecordingConfigCreate | RecordingConfigUpdate | RecordingConfigDelete |
| Application target | AppTargetCreate | AppTargetUpdate | AppTargetDelete |
| Database target | DatabaseTargetCreate | DatabaseTargetUpdate | DatabaseTargetDelete |
| Kubernetes target | KubernetesTargetCreate | KubernetesTargetUpdate | KubernetesTargetDelete |
Resource-change records use spec.verb for the operation and
spec.objectRef for the exact resource kind and name. Most need no
type-specific object.
SSH session events
For records that carry session context, use spec.session.id to correlate
records from the same session and spec.index to order them.
SessionStart adds spec.sessionStart, including the terminal size,
initialCommand, and sessionRecording mode. Actor, server, connection, and
Kubernetes context appears under the corresponding shared objects when it
applies.
SessionExec records an SSH exec request, rather than each command typed in an
interactive shell. It uses spec.command for command, exitCode, and exitError.
The event code distinguishes a successful command from a failed one.
SessionPortForward adds spec.sessionPortForward.addr and uses spec.status
for the result. SessionResize adds spec.sessionResize.size.
SessionEnd adds spec.sessionEnd, including interactive, participants,
startTime, endTime, initialCommand, and sessionRecording.
HTTP application requests
AppSessionStart records the user's identity, session, connection, and target
application. For HTTP applications, AppSessionRequest adds the method, path,
query string, and response status for each proxied request.
AppSessionRequest records are stored only in recording chunks. They are not
sent to configured audit event sinks, and the request record itself has no
spec.actor or spec.session. Find the recording's AppRecordingChunk event
in the audit log to identify the actor and application session, then use
spec.appRecordingChunk.sessionChunkId to read the structured request records
after the chunk is uploaded:
alma recording play --format=json <session-chunk-id>Application recording chunks are not included in recording ls and do not
support terminal replay. With recording mode off, HTTP request records are
discarded. Application session and chunk events still go to the audit sinks.
Database session events
Database records use spec.database for the service, protocol, destination,
database account, labels, and roles. spec.session.id correlates activity from
the same connection.
DatabaseSessionStart records actor, session, server, database, and status
context. A failed connection uses the same type and a failure event code and
status.
DatabaseSessionQuery and DatabaseSessionQueryFailed add
spec.databaseSessionQuery or spec.databaseSessionQueryFailed. Each object
contains dbQuery and, when available, dbQueryParameters.
DatabaseSessionEnd closes the sequence for the same session and database
identity.
PostgreSQL extended-query operations emit separate PostgresParse,
PostgresBind, PostgresExecute, and PostgresClose events. Function calls
use PostgresFunctionCall. This preserves the protocol sequence when a client
uses prepared statements instead of sending one text query.
Recorded sessions
An interactive SSH session can produce an ordered recording of terminal output
and timing metadata. The recording is stored separately from the general audit
event stream. SessionOutput events carry byte counts, relative delays, chunk
indexes, and offsets used during replay.
A database session produces a recording of its own, holding
DatabaseSessionStart, the operations the session ran, and DatabaseSessionEnd in
order. A simple statement is recorded as DatabaseSessionQuery. A prepared
statement records the SQL under its protocol's own event, and a
request-shaped protocol such as DynamoDB records each operation as a
request, so the generic query event is not the whole of what a recording
holds. It is listed with a
type of db and the database service as its name.
A database recording carries no terminal output, and neither does a command run without one. Replaying either prints a transcript instead: the statements a database session ran, or the command a non-interactive session executed, timed by the offsets between the events. A transcript reports what the recording holds, which is what was asked and not what came back, so a statement is followed by its recorded error or by a line saying the outcome was not recorded. Read the events in full with the structured formats below.
List recordings from the past 24 hours:
alma recording ls --last=24hReplay a recording in the terminal:
alma recording play <session-id>Read the recording as structured events for investigation or export:
alma recording play --format=json <session-id>The recording play command also accepts yaml as a structured output format.
Requesting recording data emits RecordingAccess, so access to a recording is
itself auditable.
Modes
The cluster-wide RecordingConfig resource controls where SSH session capture
happens. The resource name is cluster, and spec.mode accepts:
| Mode | Behavior |
|---|---|
node | The node agent records the session. This is the default. |
proxy | The proxy intercepts and records the session. |
node-sync | The node agent records and uploads synchronously. |
proxy-sync | The proxy records and uploads synchronously. |
off | Session recording is disabled. Events sent to configured audit sinks remain available, but recording-only events such as HTTP application requests are discarded. |
Per-role policy controls what happens when SSH recording is unavailable. Set
spec.options.recordSession.ssh to strict to prevent or terminate an SSH
session if it cannot be recorded. Set it to bestEffort to allow the session
to continue. The default is bestEffort.
For each role, AlmaForge uses spec.options.recordSession.ssh when set,
otherwise spec.options.recordSession.default, otherwise bestEffort.
It then selects the strictest result across all applicable roles. A role with
recordSession.default: strict therefore requires strict recording even if
another role sets recordSession.ssh: bestEffort.
See RecordingConfig for the resource field reference.
Storage
Audit records and session recordings use separate storage destinations:
| Data | Server option | Environment variable | Default |
|---|---|---|---|
| Audit events | --audit-uri | ALMA_AUDIT_URI | $ALMA_DATA_DIR/log |
| Session recordings | --recordings-uri | ALMA_RECORDINGS_URI | $ALMA_DATA_DIR/records |
The default audit directory contains dated log files and an events.log
symlink pointing to the current file.
Set both destinations when using external audit storage. Choose retention and access controls independently for events and recordings because they contain different data and may have different compliance requirements.
See Audit event storage and session recording storage for supported backends and configuration details.