RBAC, Policies and Audit Trail in the Open-Source Rendezvous

RBAC, Policies and Audit Trail in the Open-Source Rendezvous

The Pilot rendezvous (the registry and beacon that every daemon talks to) has network roles, network policies, consent-based invites, a structured audit trail, fleet enrollment, signed webhooks, key lifecycle metadata and a health endpoint. These first shipped in v1.5.0-rc1 in March 2026. The server is open source under AGPL-3.0 at github.com/pilot-protocol/rendezvous.

Updated October 2026. This post first described the v1.5 milestone. It now describes the open-source rendezvous as it works with pilotctl v1.18.0. Creating a network needs the registry's admin token. On the public backbone that token is held by its operators, so you use these controls on a rendezvous you run yourself. Nobody provisions or manages networks for you.

Where These Controls Live

Everything below is enforced by the rendezvous process. A daemon joins your registry instead of the public one when you point it there:

# Build and start your own registry + beacon (one process)
git clone https://github.com/pilot-protocol/rendezvous && cd rendezvous
go build ./cmd/rendezvous
export PILOT_ADMIN_TOKEN=$(openssl rand -hex 32)
./rendezvous -registry-addr :9000 -beacon-addr :9001 \
  -store /var/lib/pilot/registry.json -http 127.0.0.1:3000 -log-format json

# Point a node at it
pilotctl init --registry registry.example.com:9000 --beacon registry.example.com:9001
pilotctl daemon start

With an empty admin token the registry disables network creation. The admin commands in pilotctl read the token from PILOT_ADMIN_TOKEN or admin_token in ~/.pilot/config.json. The repository README describes itself as a source-transparency note, not an operations guide, so treat this as the minimum setup rather than a hardened deployment.

The Per-Network Enterprise Flag

Roles, policies and invites act on networks that carry the registry's enterprise flag. It is a per-network switch in the open-source code, not a product tier. Set it when you create the network, or later with the set_network_enterprise registry message (admin token only). Turning it on gives existing members roles; turning it off clears them.

# Create an invite-only network with the enterprise flag (admin token required)
pilotctl network create --name prod-fleet --join-rule invite --enterprise --node-id 686

The node named with --node-id becomes the network's owner. Invite-only networks require the enterprise flag; the registry rejects --join-rule invite without it.

Role-Based Access Control

A network with the enterprise flag has three roles: owner, admin, and member. Nodes that join with a token or accept an invite start as members.

The registry authorizes a management request if any one of three checks passes: the global admin token, the network's own admin token (set with --network-admin-token at creation), or the requesting node's role in that network.

# Promote a member to admin
pilotctl network promote 1 686

# Demote an admin back to member
pilotctl network demote 1 686

# Remove a node from the network
pilotctl network kick 1 687

# Show a node's role
pilotctl network role 1 686

From pilotctl, promote, demote and kick authenticate with the admin token. The registry refuses to kick the owner, to promote or demote the owner, and to let admins kick each other. Ownership moves with the transfer_ownership registry message (there is no CLI command for it): the old owner becomes an admin and the transfer is recorded in the audit trail.

Network Policies

A policy limits a network's membership and the ports its members may use:

Updates merge: setting max_members leaves allowed_ports as it was. Policies require the enterprise flag, and setting one requires an owner, an admin or an admin token.

# Show the current policy
pilotctl network policy 1

# Set a membership cap
pilotctl network policy 1 --max-members 50

# Restrict ports
pilotctl network policy 1 --allowed-ports 80,443,7

Consent-Based Invites

Invites never add a node silently. Joining a network changes who can reach you, so the invited node has to accept:

  1. An owner or admin invites a node: pilotctl network invite 1 686
  2. The invited node lists its invites: pilotctl network invites
  3. It accepts or rejects: pilotctl network accept 1 or pilotctl network reject 1
  4. On acceptance the node joins as a member.

These commands go through the local daemon, which signs each request with the node's Ed25519 key. A repeated invite returns already_invited instead of a duplicate. Each node's invite inbox holds at most 100 invites, pending invites expire after 30 days, and the inbox is saved with the registry snapshot. A plain join on an invite-only network is refused and points to the invite flow.

Structured Audit Trail

Registry mutations emit an audit event through slog. Run the registry with -log-format json and the log is ready for a SIEM or jq:

# Stream audit events from wherever the registry's log goes
tail -f /var/log/pilot-registry.log | jq 'select(.msg=="audit")'

# Or read the registry's in-memory audit log
pilotctl extras audit --network 1

Each event carries an audit_action and the IDs involved as structured attributes, so no string parsing is needed. The in-memory log keeps the latest 1,000 entries; pilotctl extras audit returns up to 100 of them, newest first, and needs the admin token. Actions include:

CategoryActions
Nodesnode.registered, node.re_registered, node.deregistered, node.reaped
Networksnetwork.created, network.deleted, network.renamed, network.policy_changed, network.enterprise_changed
Membershipnetwork.joined, network.left, member.promoted, member.demoted, member.kicked, network.ownership_transferred
Invitesinvite.created, invite.responded
Trusttrust.created, trust.revoked, handshake.relayed, handshake.responded
Settingsvisibility.changed, hostname.changed, tags.changed
Keyskey.rotated, key.expiry_set, key.expiry_cleared

To send these events to Splunk, a syslog collector or any HTTP endpoint, see identity, directory sync and audit export.

Fleet Enrollment

Putting many agents into the same networks is a configuration change rather than one command per node. The daemon takes two flags for it:

# Start a daemon that joins networks 1 and 3 at startup
pilot-daemon -registry registry.example.com:9000 -beacon registry.example.com:9001 \
  -admin-token $PILOT_ADMIN_TOKEN -networks 1,3

At startup the daemon joins each listed network with the admin token. A failed join, including "already a member", is logged as a warning and does not stop the daemon. Each successful join publishes a network.auto_joined webhook event. Because the flag carries the registry admin token, use it only on machines you would trust with that token.

Webhook Reliability

The daemon's webhook client (pilotctl extras set-webhook <url>, or --webhook on daemon start) delivers events with a few guarantees:

On shutdown the client waits up to 5 seconds for its queue to drain, then abandons what is left.

Key Lifecycle

The registry keeps metadata for each node's Ed25519 identity key, returned by the get_key_info registry message:

A node sets its own expiry with the signed set_key_expiry message, which requires membership of an enterprise-flagged network. Once a key has expired, the registry rejects its heartbeats and records key.expired_heartbeat_blocked. That lets you enforce a rule such as "rotate every 90 days" at the registry.

Health Endpoint and Metrics

When the registry runs with -http, it serves /healthz on that listener:

curl http://127.0.0.1:3000/healthz
{"nodes_online": 12, "status": "ok", "uptime_seconds": 86412, "version": "1.0"}

The same listener serves Prometheus metrics at /metrics, to localhost only: request counts and durations per message type, audit actions, and per-network gauges such as pilot_network_members. On a node, pilotctl health reports the local daemon's health over IPC.

Registry Hardening

Secure Channel Authentication

Encrypted tunnels verify identity during the key-agreement handshake. Both sides present Ed25519 signatures, and the tunnel records the authenticated PeerNodeID. Current releases derive peer-specific secrets from daemon-lifetime X25519 keys; this authenticates the peer but does not provide per-session forward secrecy.

The v1.5 rollout included a compatibility transition for older peers. Current guidance is to require authenticated peers and apply the controls described in the security documentation.

Network Commands in pilotctl

Membership commands go through the local daemon and are signed with the node's identity. Administrative commands go straight to the registry with the admin token.

# Networks this node belongs to
pilotctl network list

# Join a token-gated network, or leave one
pilotctl network join 1 --token my-secret
pilotctl network leave 1

# List members
pilotctl network members 1

# Invites
pilotctl network invite 1 686
pilotctl network invites
pilotctl network accept 1
pilotctl network reject 1

# Admin token: create, rename, delete
pilotctl network create --name staging --join-rule token --token my-secret
pilotctl network rename 2 staging-eu
pilotctl network delete 2

Run pilotctl network --help for the full list. The same admin commands are also listed under pilotctl extras.

Run the Controls on Your Own Registry

Build the open-source rendezvous, point your daemons at it, and create a network with roles and a policy.

Network Docs  ·  Rendezvous Source  ·  Get Started