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.loginUser authenticated (local password, SSO, or certificate)
user.login.failedFailed authentication attempt
authz.deniedAuthenticated user refused a webapi action by an RBAC gate (e.g. non-admin POSTing to /v1/webapi/users)
user.user.cert.issuedUser or host certificate issued by the CA
session.startSSH session started
session.endSSH session ended (includes duration and bytes transferred)
node.joinedAgent registered with the cluster
node.leftAgent disconnected or was removed
user.createUser account created
user.updateUser account modified (roles changed, locked, etc.)
role.createRBAC role created
role.updateRBAC role modified
role.deleteRBAC role deleted
access_request.createdAccess request submitted
access_request.approvedAccess request approved
access_request.deniedAccess request denied
access_request.expiredAccess 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.

Example: session.start event json
{
  "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"
  }
}
Example: user.login event json
{
  "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.

Audit log queries bash
# 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:

Audit log output text
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.

Emitter architecture text
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.login and user.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.

External sink configuration yaml
# 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 events in real time bash
# 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.

Retention configuration yaml
# 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
Manage retention bash
# 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

Audit export bash
# 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 mezhub logs 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