Skip to content

WireGuard Overlay Transport

The WireGuard overlay is an optional encrypted data-plane tunnel between a Tarsus agent and its assigned Thorax (the controller). When enabled, the agent’s command and result gRPC traffic to Thorax travels inside a WireGuard tunnel instead of over a plain TCP socket. mTLS is still applied inside the tunnel — WireGuard is an additional layer, never a replacement for application authentication.

The feature is off by default. With both switches off, Tarsus and Thorax behave exactly as they do today (direct mTLS over TCP).

Direct (default):
Tarsus ── gRPC/mTLS ── TCP ───────────────► Thorax
WireGuard overlay (opt-in):
Tarsus ── gRPC/mTLS ── virtual TCP ── WireGuard ── UDP ──► Thorax
└────────── encrypted overlay ─────┘

Key properties:

  • mTLS is always present. The overlay carries the same authenticated gRPC services behind the same AuthInterceptor; client certificates are validated inside the tunnel.
  • Mandible stays on the direct path. Only the agent → Thorax leg is tunnelled. Registration, heartbeat, and dispatch coordination with Mandible always use direct mTLS-TCP, so the overlay can never deadlock bootstrap.
  • The controller is the hub. Thorax holds a persistent WireGuard identity (a keypair plus an overlay IP, e.g. 10.99.0.1) and serves every agent assigned to it over one smoltcp stack and one UDP socket. Each agent gets its own per-peer tunnel.
  • Agents learn the controller identity during registration. When an agent registers with a WireGuard public key, Mandible allocates it an overlay IP from the tenant’s range and returns the controller’s public key, overlay IP, and UDP endpoint in the registration response. The agent then brings the tunnel up and routes its Thorax gRPC over the overlay IP.

Set the WireGuard section in Thorax configuration (or the equivalent environment variables). The controller loads — or generates and persists — a keypair on first start, so its identity is stable across restarts.

Environment variableDefaultDescription
THORAX__WIREGUARD__ENABLEDfalseMaster switch. When false, Thorax runs no overlay and advertises no controller identity.
THORAX__WIREGUARD__OVERLAY_IP10.99.0.1The controller’s address on the overlay network.
THORAX__WIREGUARD__OVERLAY_PREFIX_LEN16Overlay subnet prefix length.
THORAX__WIREGUARD__UDP_BIND_ADDR0.0.0.0:51820host:port the WireGuard data-plane UDP socket binds to.
THORAX__WIREGUARD__UDP_ADVERTISE_ADDR(falls back to UDP_BIND_ADDR)The host:port advertised to agents. Set this to the controller’s publicly reachable UDP endpoint when the bind address is 0.0.0.0 or behind NAT.
THORAX__WIREGUARD__PRIVATE_KEY_PATH/var/lib/mantis/thorax-wireguard.keyWhere the controller’s keypair is persisted so identity survives restarts.

The overlay gRPC listener reuses the controller’s direct gRPC port — it lives inside the smoltcp stack, so there is no second OS port to open. Only the WireGuard UDP socket (UDP_BIND_ADDR) needs to be reachable by agents.

Environment variableDefaultDescription
TARSUS__TRANSPORT__ENABLEDfalseEnable the WireGuard overlay on the agent.
TARSUS__TRANSPORT__PRIVATE_KEY_PATH-WireGuard private key file. If unset, a keypair is generated under data_dir and reused across restarts.

When transport.enabled is on, the agent:

  1. Generates (or loads) its WireGuard keypair.
  2. Completes certificate enrollment and registration on the direct path first.
  3. Sends its WireGuard public key on the registration request and stores the controller identity returned in the response.
  4. Brings up the overlay and routes only its assigned Thorax gRPC over it. The Mandible endpoint and any other address stay on the direct path.

See [transport] in the Tarsus configuration reference for the full agent-side options.

By default, if the overlay cannot be established the agent falls back to the direct mTLS-TCP path. To forbid that fallback — requiring all agent → Thorax traffic to traverse the encrypted overlay — set the agent’s transport floor:

[security]
minimum_transport_level = "wireguard"

With this set:

  • TARSUS__TRANSPORT__ENABLED=false is blocked (an attacker with environment access cannot downgrade transport security; a security-audit warning is logged).
  • If the controller identity is absent or the overlay collapses mid-session, the agent refuses to dial Thorax directly, returns a hard error, and rebuilds the overlay with backoff. The session never silently degrades to plaintext-transport.

This [security] section cannot be overridden by environment variables.

Overlay IPs are allocated per tenant from the tenant’s WireGuard quota range (wireguard_tenant_quotas.ip_range), falling back to the global overlay_range (10.99.0.0/16) when a tenant has no custom range. The prefix length the agent receives follows the effective range, so a tenant with a /24 range gets a /24 overlay prefix.

Lens. Navigate to Network → WireGuard Tunnels for a tenant-scoped (or, for admins, global) dashboard of agent overlay status: allocated overlay IP, NAT type, connection mode, handshake health, and key-rotation state.

Metrics. Mandible exports mantis_wireguard_overlay_allocations_total, incremented each time an agent is issued an overlay IP plus controller identity. Thorax emits tracing spans at overlay bring-up (success/failure); Tarsus logs fail-closed-denied events to the security_audit tracing target and overlay-established events at INFO level to the standard log.

WireGuard static keys rotate over the live overlay without dropping the tunnel. Set TARSUS__TRANSPORT__KEY_ROTATION_INTERVAL_SECS (default 0 = off) and the agent periodically generates a fresh keypair and re-registers with it. Mandible keeps the agent’s previous key valid as the pending key for a short overlap window, and the controller serves both keys during the overlap (the old tunnel drains while the new becomes active), so command/result traffic is never interrupted. The overlap clears automatically once it expires, after which the controller stops accepting the old key.

Operators can also inspect/drive rotation through the agent key-rotation endpoints (/api/v1/agents/{agent_id}/wireguard/key-rotation).

The overlay ships with direct-UDP as the primary transport and an optional WebSocket relay fallback. The following are intentionally not yet part of the overlay data plane and are tracked as follow-up work:

  • Agent ↔ agent mesh connectivity (only agent ↔ controller is supported).
  • Registration with Mandible over the overlay (Mandible is always direct).

When agents cannot reach the controller’s UDP endpoint directly (e.g. behind symmetric NAT or strict firewalls), configure THORAX__WIREGUARD__RELAY_URL to enable automatic WebSocket relay fallback. Agents will prefer the direct UDP path and fall back to the relay when direct connectivity is unavailable.

If an agent’s controller (Thorax) goes offline, Mandible reassigns the agent’s target to a healthy Thorax. On its next heartbeat the agent is told to re-register (Mandible detects that the agent’s known controller differs from its current assignment); the agent re- registers, receives the new controller’s identity, and re-keys its WireGuard overlay to the new controller in place — without exiting the process.