Skip to main content

Audit Logs

Wirety emits structured audit logs for every security-relevant action across the server and every agent. Audit logs are separate from the application's operational logs: they are always JSON, always on stdout, and designed for ingestion by log aggregation systems (Loki, Elasticsearch, Splunk, etc.).

Enabling Audit Logs

Set the AUDIT_LOG=true environment variable on the server and/or each agent instance.

# Server
AUDIT_LOG=true ./wirety-server

# Agent
AUDIT_LOG=true wirety-agent -server https://wirety.example.com -token <TOKEN>

When AUDIT_LOG is false (the default) the audit logger is a true no-op — no allocations occur, so there is zero performance impact.

Log Format

Every audit event is a single JSON line on stdout:

{
"level": "info",
"log_type": "audit",
"time": 1748000000,
"actor_id": "alice",
"actor_email": "alice@example.com",
"remote_ip": "10.0.0.5",
"action": "peer.create",
"network_id": "net-abc123",
"peer_id": "peer-xyz789",
"peer_name": "office-laptop",
"message": "audit"
}
FieldAlways presentDescription
log_typeAlways "audit" — use this to filter audit lines from application logs
timeUnix timestamp
actionWhat happened (see tables below)
messageAlways "audit"
actor_idServer onlyID of the authenticated user who triggered the action
actor_emailServer onlyEmail of the authenticated user
remote_ipServer onlyClient IP address
peer_id / network_idAgent onlyIdentity of the agent that performed the action

Server Events

Server audit events are emitted after every successful mutating API call.

Authentication

actionTrigger
auth.loginSuccessful simple-auth login (POST /auth/login)

Networks

actionFieldsTrigger
network.createnetwork_id, network_nameNetwork created
network.updatenetwork_id, network_nameNetwork configuration updated
network.deletenetwork_idNetwork deleted
acl.updatenetwork_idACL block list updated

Peers

actionFieldsTrigger
peer.createnetwork_id, peer_id, peer_namePeer created
peer.updatenetwork_id, peer_id, peer_namePeer updated
peer.deletenetwork_id, peer_idPeer deleted

Groups

actionFieldsTrigger
group.createnetwork_id, group_id, group_nameGroup created
group.updatenetwork_id, group_id, group_nameGroup updated
group.deletenetwork_id, group_idGroup deleted
group.peer.addnetwork_id, group_id, peer_idPeer added to group
group.peer.removenetwork_id, group_id, peer_idPeer removed from group

Policies

actionFieldsTrigger
policy.createnetwork_id, policy_id, policy_namePolicy created
policy.updatenetwork_id, policy_id, policy_namePolicy updated
policy.deletenetwork_id, policy_idPolicy deleted
policy.rule.addnetwork_id, policy_id, rule_idRule added to policy
policy.rule.removenetwork_id, policy_id, rule_idRule removed from policy
policy.group.attachnetwork_id, group_id, policy_idPolicy attached to group
policy.group.detachnetwork_id, group_id, policy_idPolicy detached from group
policy.group.reordernetwork_id, group_idPolicy priority order changed

Routes

actionFieldsTrigger
route.createnetwork_id, route_id, route_nameRoute created
route.updatenetwork_id, route_id, route_nameRoute updated
route.deletenetwork_id, route_idRoute deleted
route.group.attachnetwork_id, group_id, route_idRoute attached to group
route.group.detachnetwork_id, group_id, route_idRoute detached from group

Users

actionFieldsTrigger
user.updatetarget_user_idUser role or networks updated
user.deletetarget_user_idUser deleted
user.defaults.updateDefault network permissions changed

Agent Events

Agent audit events are emitted by each agent process. The peer_id and network_id fields identify which agent produced the event.

Configuration & Firewall

actionFieldsTrigger
config.syncWireGuard configuration successfully written and applied
firewall.syncrule_countiptables rules successfully applied
dns.updatedomain, peer_countDNS server peer table updated
peer.renameold_name, new_name, new_interfacePeer renamed, interface migrated

Tunnel Sessions

The agent monitors WireGuard handshake timestamps every 15 seconds. A peer is considered connected when its latest handshake is within the last 180 seconds (WireGuard's standard inactivity threshold).

actionFieldsTrigger
tunnel.connectedpeer_name, peer_pubkey, endpoint, handshake_atPeer established a WireGuard handshake
tunnel.disconnectedpeer_name, peer_pubkey, endpoint, last_handshake, session_durationPeer stopped sending handshakes

Example tunnel events:

{"level":"info","log_type":"audit","time":1748000100,"actor_type":"agent","peer_id":"peer-jump-1","network_id":"net-abc123","action":"tunnel.connected","peer_name":"office-laptop","peer_pubkey":"abc123...","endpoint":"203.0.113.42:51820","handshake_at":1748000098,"message":"audit"}
{"level":"info","log_type":"audit","time":1748003700,"actor_type":"agent","peer_id":"peer-jump-1","network_id":"net-abc123","action":"tunnel.disconnected","peer_name":"office-laptop","peer_pubkey":"abc123...","endpoint":"203.0.113.42:51820","last_handshake":1748003520,"session_duration":3600000000000,"message":"audit"}

Note: tunnel events are also emitted as regular (non-audit) log lines with "event":"tunnel.connected" / "event":"tunnel.disconnected" for human-readable monitoring.


Operational Log (non-audit)

The server and agent also emit human-readable operational logs to stderr via zerolog's ConsoleWriter. These are not audit logs — do not rely on them for compliance. Use AUDIT_LOG=true + stdout for that purpose.


Integration Examples

Docker / docker-compose

services:
wirety-server:
image: rg.fr-par.scw.cloud/wirety/server:latest
environment:
AUDIT_LOG: "true"
logging:
driver: json-file

Redirect stdout to your log shipper; stderr carries the operational console log.

Kubernetes

env:
- name: AUDIT_LOG
value: "true"

With a sidecar log shipper (Fluent Bit, Vector) configured to collect stdout from the wirety pods.

Filtering with jq

# All audit events from the last run
docker logs wirety-server 2>/dev/null | jq 'select(.log_type == "audit")'

# All peer creations
docker logs wirety-server 2>/dev/null | jq 'select(.action == "peer.create")'

# All tunnel sessions from a specific agent
cat agent.log | jq 'select(.log_type == "audit" and .peer_id == "peer-jump-1" and (.action | startswith("tunnel.")))'

Loki / Grafana

Label selector example (assuming Docker log driver):

{container="wirety-server"} | json | log_type="audit"

Filter by action:

{container="wirety-server"} | json | log_type="audit" | action=~"peer\\..*"