Audit Logging
Mezite records a structured audit event for every security-relevant action in the cluster: logins, certificate issuance, SSH session lifecycle, role changes, access requests, and more. Events are written to PostgreSQL and are immutable once stored. This guide covers event types, event structure, querying, the Emitter architecture, external sinks, and retention.
Event Types
Every action in Mezite generates a typed audit event. The following table lists the core event types:
| Event Type | Description |
|---|---|
user.login | User authenticated (local password, SSO, or certificate) |
user.login.failed | Failed authentication attempt |
authz.denied | Authenticated user refused a webapi action by an RBAC gate
(e.g. non-admin POSTing to /v1/webapi/users) |
user.user.cert.issued | User or host certificate issued by the CA |
session.start | SSH session started |
session.end | SSH session ended (includes duration and bytes transferred) |
node.joined | Agent registered with the cluster |
node.left | Agent disconnected or was removed |
user.create | User account created |
user.update | User account modified (roles changed, locked, etc.) |
role.create | RBAC role created |
role.update | RBAC role modified |
role.delete | RBAC role deleted |
access_request.created | Access request submitted |
access_request.approved | Access request approved |
access_request.denied | Access request denied |
access_request.expired | Access request or granted access expired |
Event Structure
Each audit event is a structured JSON document with a consistent schema. All events share common fields, with type-specific fields nested under the relevant key.
{
"id": "evt_a1b2c3d4e5f6",
"type": "session.start",
"time": "2026-03-24T10:16:01.234Z",
"user": "alice",
"roles": ["access", "ssh-production"],
"resource": {
"kind": "node",
"name": "web-server-01",
"labels": {
"env": "production",
"team": "platform"
}
},
"session": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"login": "ubuntu",
"server_addr": "10.0.1.50:22"
},
"connection": {
"client_addr": "203.0.113.10:54321",
"proxy_addr": "10.0.0.5:3023"
}
} {
"id": "evt_b2c3d4e5f6a7",
"type": "user.login",
"time": "2026-03-24T10:15:32.567Z",
"user": "alice",
"method": "oidc",
"connector": "corporate-sso",
"client_addr": "203.0.113.10:54321",
"traits": {
"email": "alice@example.com",
"team": "platform"
},
"roles_assigned": ["access", "ssh-production"]
} Querying Audit Events
Use mezctl audit ls to query the audit log with flexible filtering.
# List all events from the last hour
mezctl audit ls --last=1h
# Filter by event type
mezctl audit ls --type=user.login --last=24h
# Filter by user
mezctl audit ls --user=alice --last=7d
# Filter by multiple types
mezctl audit ls --type=session.start,session.end --last=1h
# Filter by time range
mezctl audit ls --from=2026-03-24T00:00:00Z --to=2026-03-24T12:00:00Z
# Filter by resource
mezctl audit ls --resource=web-server-01 --last=24h
# Combine filters
mezctl audit ls --type=session.start --user=alice --resource=web-server-01 --last=1h
# Output as JSON for scripting
mezctl audit ls --type=user.login --last=24h --format=json Example output:
TIME TYPE USER RESOURCE DETAILS
2026-03-24T10:15:32Z user.login alice proxy method=oidc, connector=corporate-sso
2026-03-24T10:15:45Z user.cert.issued alice user-ca ttl=12h, logins=[ubuntu,deploy]
2026-03-24T10:16:01Z session.start alice web-server-01 login=ubuntu, sid=a1b2c3d4...
2026-03-24T10:28:35Z session.end alice web-server-01 duration=12m34s, bytes=45678 The Emitter Architecture
Mezite's audit system uses an Emitter pattern to decouple event generation from event storage. This ensures that audit writes do not block the critical path of SSH session establishment.
Buffered Async Writes
Most audit events are written asynchronously via a buffered channel. The Emitter accepts events from any goroutine, queues them in a channel buffer, and a background writer flushes them to PostgreSQL in batches.
goroutine A (session.start) ──┐
goroutine B (user.login) ──┼──> [buffered channel] ──> background writer ──> PostgreSQL
goroutine C (user.cert.issued) ──┘
capacity: 4096 events
flush interval: 1s or batch full This design means that SSH session establishment is not blocked by database write latency. Events are guaranteed to be written as long as the buffer does not overflow.
Sync Writes for Critical Events
Certain high-priority events bypass the buffer and are written synchronously to guarantee they are persisted before the operation completes:
-
user.loginanduser.login.failed— security-critical -
role.create,role.update,role.delete— policy changes -
user.create,user.update— identity changes
Sync writes ensure that if a policy change is acknowledged to the admin, it is already recorded in the audit log.
External Sinks
In addition to PostgreSQL, audit events can be forwarded to external systems for SIEM integration, alerting, or long-term archival.
# In mezhub's mezite.yaml
audit:
# Primary storage (always PostgreSQL)
storage:
type: postgres
# Optional: stream events to external sinks
external_sinks:
- type: webhook
name: siem-forwarder
url: https://siem.example.com/api/events
headers:
Authorization: "Bearer YOUR_TOKEN"
# Which event types to forward (default: all)
event_types:
- user.login
- user.login.failed
- session.start
- session.end # Stream audit events to stdout (for piping to external tools)
mezctl audit stream --format=json
# Stream only auth events
mezctl audit stream --type=user.login,user.login.failed --format=json Retention and Storage
Audit events are stored in the database (SQLite or PostgreSQL). Configure retention policies to automatically purge old events and manage database size.
# In mezhub's mezite.yaml
audit:
# Event retention period (events older than this are purged)
event_retention: 365d
# Session recording retention period
session_retention: 90d # View current retention settings
mezctl audit config
# Manually purge old events (dry run)
mezctl audit purge --older-than=365d --dry-run
# Would delete 12,345 events and 456 session recordings
# Execute purge
mezctl audit purge --older-than=365d --confirm Export for Archival
# Export to JSON (line-delimited)
mezctl audit ls --last=30d --format=json > audit-export.jsonl
# Export to CSV
mezctl audit ls --last=30d --format=csv > audit-export.csv Troubleshooting
Audit events missing
- Verify the auth service is connected to PostgreSQL and migrations are up to date.
-
Check
mezhublogs for audit write errors or buffer overflow warnings. - Ensure the queried time range is within the retention period.
Slow audit queries
- Use filters (type, user, time range) to narrow the query scope.
- For large deployments, ensure PostgreSQL has appropriate indexes (applied by migrations).
-
Consider archiving old events with
mezctl audit purge.
Next Steps
- Session Recording — Learn about SSH session recording and playback.
- RBAC Configuration — Control who can view audit logs.
- SSH Access — Understand the events generated by SSH sessions.
- Access Requests — Audit trail for approval workflows.