Audit Logging
Mezite records a structured audit event for every security-relevant action in the cluster: logins, certificate issuance, SSH session lifecycle, access requests, and more. Events are written to the configured backing database (PostgreSQL or SQLite) via the audit Emitter. This guide covers event types, event structure, querying, the Emitter architecture, and external sinks.
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.cert.issued | User certificate issued by the CA |
session.start | SSH session started |
session.end | SSH session ended (records duration; recording metadata when the session was recorded) |
node.joined | Agent registered with the cluster |
node.left | Agent disconnected or was removed |
user.created | User account created |
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": "f3a1...uuid",
"event_type": "session.start",
"event_code": "T2000I",
"user_name": "alice",
"resource_type": "node",
"resource_name": "web-server-01",
"server_hostname": "web-server-01",
"client_ip": "203.0.113.10",
"timestamp": "2026-03-24T10:16:01.234Z",
"session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"success": true
}
The full AuditEvent schema (defined in server/audit/events.go) also includes cluster_name, user_roles,
resource_labels, server_id, impersonator, error_message, and a free-form details map. Most
fields use omitempty, so events only carry the columns the
emitter explicitly populates. The exact set varies by event type —
inspect server/proxy, server/auth, and server/web emit sites to see which fields are set for each event.
{
"id": "9b2c...uuid",
"event_type": "user.login",
"event_code": "T1000I",
"user_name": "alice",
"client_ip": "203.0.113.10",
"timestamp": "2026-03-24T10:15:32.567Z",
"success": true
}
Today the user.login event does not record which authentication
method (password, SSO connector, certificate) was used — clients distinguish
that by the surrounding flow, not by a field on the event itself.
Querying Audit Events
Use mezctl audit ls to query the audit log with flexible filtering.
# List events from the last hour (default --since=1h)
mezctl audit ls
# Show events from the last 24 hours
mezctl audit ls --since=24h
# Filter by event type
mezctl audit ls --type=user.login --since=24h
# Filter by user
mezctl audit ls --user=alice --since=7d
# Combine filters
mezctl audit ls --type=session.start --user=alice --since=1h Example output:
TIME TYPE USER RESOURCE CLIENT_IP STATUS
2026-03-24 10:15:32 user.login alice 203.0.113.10 ok
2026-03-24 10:15:45 user.cert.issued alice 203.0.113.10 ok
2026-03-24 10:16:01 session.start alice node/web-server-01 203.0.113.10 ok
2026-03-24 10:28:35 session.end alice node/web-server-01 203.0.113.10 ok The Emitter Architecture
Mezite's audit system uses a producer/consumer split between event emit sites and the database writer, so 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 the configured database in batches.
goroutine A (session.start) ──┐
goroutine B (session.command) ──┼──> [buffered channel] ──> background writer ──> database
goroutine C (node.joined) ──┘
capacity: 4096 events
batch size: up to 100 events
flush ticker: 500ms This design means that SSH session establishment is not blocked by database write latency. When the buffer is full, additional async events are dropped with a warning log line — so this path is reserved for high-volume informational events. Security-relevant events use the sync path below.
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:
-
Authentication outcomes:
user.login,user.login.failed,user.cert.issued -
Identity mutations:
user.created,user.totp_reset,user.webauthn_reset, lock and connector CRUD -
Every denial event (any
*.denied/*.failed) -
CA lifecycle:
ca.cert.issued,ca.rotate.*
Sync writes ensure that if a sensitive change is acknowledged to the
admin, it is already recorded in the audit log. See
server/audit/CLAUDE.md for the full classification.
External Sinks
In addition to the database, audit events can be mirrored to external
sinks for SIEM integration, alerting, or long-term archival. Two sink
types are supported today: webhook (POSTs each event as JSON
to a URL) and file (appends to a JSONL file on disk). Sinks receive
every event after it has been written to the database — there is no per-sink
event-type filter yet, so filter downstream if you only want a subset.
type SinkConfig struct {
Type string // "webhook" or "file"
WebhookURL string // for type="webhook"
WebhookTimeout time.Duration // defaults to 5s
FilePath string // for type="file"
} Storage and Retention
Audit events are stored in the configured database (PostgreSQL or
SQLite). There is no automatic retention or purge mechanism — events
accumulate until you prune them out of band (for example, with a
scheduled SQL delete against audit_events).
An S3-compatible archiver exists in server/audit/s3_archiver.go and a mezctl audit archive CLI is wired up, but the server-side
ArchiveAuditEvents RPC currently returns
Unimplemented. Plan around external retention until the
server handler ships.
Troubleshooting
Audit events missing
- Verify the auth service is connected to its database and migrations are up to date.
-
Check
mezhublogs foraudit buffer full, dropping eventwarnings — high-volume informational events use a 4096-event channel and are dropped when the buffer is saturated. Sync events (logins, denials, identity mutations) are never dropped.
Slow audit queries
- Use filters (type, user, time range) to narrow the query scope.
-
For large deployments, ensure the database has appropriate indexes
(applied by migrations under
server/migrate/migrations/).
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.