Read plane, write plane, admin plane#
Wheelhouse has four roles — viewer, approver, operator, admin — and they nest: an admin can do everything an operator can, an operator everything an approver can, an approver everything a viewer can. Every route in the agent names the minimum role it requires, and the check runs server-side on every request. The web UI's greying-out of what your role cannot do sits on top of that; it is not the gate.
The planes#
Four roles, three planes: the write plane is split, because promoting a change and authoring one are different jobs.
| Plane | Role | Contains |
|---|---|---|
| Read | viewer | Every GET: system, interfaces, routes, firewall, NAT, QoS, sessions, DHCP, DNS, VPN, containers, certificates, logs, the audit log, the commit history and its diffs, the staged working set, the telemetry stream, licence state. |
| Write, promoting | approver | Commit, commit-confirm and rollback — locally and per fleet router. Nothing that authors a change. |
| Write | operator | Stage, un-stage, discard, direct configure, save and load a configuration, reconcile, app install and lifecycle, traceroute, the inline-IPS plan. |
| Admin | admin | Accounts, API tokens, agent settings, the licence key, power and boot images, the single-sign-on view, the git-backed configuration export, and the unredacted configuration download. |
A few routes need no authentication, because something has to work before anyone is signed
in: GET /health (liveness, used by the installer's health check and by a load balancer),
GET /api/auth/status (what the login screen needs to draw itself), and the three
single-sign-on routes, where the state parameter authenticates the callback and no session
exists until the ID token verifies.
Four rules that are not obvious from the table#
A write also needs a licence. The write plane is wrapped twice:
writeable(requireLicense(h)). Reads never need one. See
What the licence gates.
A configuration read is redacted below admin. GET /api/config,
/api/config/commands, /api/history/diff, /api/drift and the VPN and PKI reads blank
private keys, pre-shared secrets and password hashes for anyone below admin.
The staged working set is redacted below operator, not admin. An operator sees the values they staged; a viewer does not.
Two routes sit off the obvious grid.
GET /api/reconcile/fileneeds operator, not viewer: it reads a file out of the agent's data directory and renders it as operations.POST /api/diagnostics/tracerouteneeds operator but no licence: it runs a command on the router rather than changing configuration.POST /api/ids/ips/planis the same shape — it renders the operations to enable inline IPS without applying them.
GET /metrics requires the viewer role by default and is open to anyone when the
agent was started with --metrics-public.
What happens when a role is not enough#
The request is refused with 403 and a message naming the role it needed — this action
requires the operator role — and the refusal is recorded as denied <method> <path> with
the actor, their role and their address. A denial is often the first sign of a credential
in the wrong hands.
An unauthenticated or expired request is 401 instead, and the browser client handles a 401 globally by returning to the sign-in screen rather than leaving half-loaded pages showing stale data.
What the UI does with your role#
Write actions use a gated button that disables with a reason rather than hiding, so an operator can see that a capability exists and that their role is what is withholding it. The Commit Bar does the same, replacing its buttons with Your role cannot commit changes.
None of this is security. The agent enforces the same gate on every route, and a client that skips the UI entirely is refused identically.
What roles do not do#
Roles also do not cover the router's own accounts. system login on the router is a
separate set of users with separate credentials; a Wheelhouse operator is not a shell
user, and vice versa.
Assigning a role#
An admin sets a role when creating an account, or changes it with
PATCH /api/admin/users/{name}. An API token carries a role of its own, chosen when
it is created. With single sign-on, a role can be mapped from the identity provider's
group claim — --oidc-admin-groups and --oidc-operator-groups, with
--oidc-default-role (default viewer) for an identity in neither. Group mapping is
opt-in; with no groups configured, provisioned accounts get the default role.
See also#
- Accounts, sessions and tokens — who the principal is.
- What the licence gates — the second gate on every write.
- The audit log — where a denial is recorded.
- Roles — the screen that shows them.
- Roles in the API and agent endpoints — every route with its role.
- Users — assigning one.