Wheelhouse

Accounts your security team will accept#

This is the section that usually stops a network team buying a GUI firewall: one shared admin password, no roles worth the name, and no way to say who made the change. It is built here, and the limits are stated with it.

Accounts#

Argon2id, 64 MiB over three passes with a 16-byte random salt per account — about 60 ms on an N100-class CPU. Only the hash and the salt are stored.

Any non-empty password is accepted. There is no minimum length and no complexity rule, deliberately: a rule that pushes people toward a season, a year and a punctuation mark buys nothing. What the product does instead is advise — the change-password field says a passphrase beats a short scramble, and the installer, given fewer than eight characters, says so once and offers Use it or Change it. Neither refuses.

What protects the login is rate limiting, and there are three limiters because each stops a different attack: eight failures in five minutes per source address; twenty in thirty minutes per account name, so guessing one password costs attempts rather than addresses; and site-wide, past 200 failures in five minutes every attempt is slowed by 250 ms. The site-wide one never refuses, because a real operator has to be able to sign in during an attack.

Changing a password signs out that account's other sessions.

Two-factor#

TOTP (RFC 6238), enrolled per account and verified with a live code before it is switched on, so a mis-scanned QR cannot lock anyone out. A setting makes it mandatory for everyone.

Roles#

Three, enforced server-side on every route. The interface greys out what your role cannot do, but that is a courtesy — the gate is in the agent, and a denied request is recorded in the audit log as denied <method> <path> with the actor's name.

RoleMay do
viewerRead operational and configuration state. Configuration reads are redacted: private keys, pre-shared secrets and password hashes come back as <redacted>.
operatorEverything a viewer can, plus stage, commit, commit-confirm, roll back, reconcile and install apps.
adminEverything an operator can, plus accounts, API tokens, agent settings, the licence, power and boot images, and the unredacted configuration download.

Per-object permissions do not exist. Three roles is the whole model.

Sessions#

Opaque random identifiers in a cookie: HttpOnly, SameSite=Strict, and Secure whenever the request arrived over TLS. The identifier means nothing on its own; the session record lives on the router. Default lifetime is 12 hours.

A cookie alone can never change configuration — every mutating request must also echo that session's CSRF token in X-Wheelhouse-CSRF. You can see and revoke your own live sessions from the Account page.

API tokens#

For scripts, Terraform and CI. Created from Administration → API tokens, they carry a role, may be given an expiry, and only their SHA-256 is stored — the plaintext is shown exactly once.

Single sign-on#

The agent is an OIDC relying party: authorization code with PKCE, ID tokens verified through go-oidc, identities linked by the provider's sub claim, and optional group-to-role mapping (ADR-002).

Two decisions are worth knowing. It is configured by flags on the systemd unit rather than through the interface, because an agent whose identity-provider settings can be edited through the interface that the identity provider gates has a bootstrapping problem. And password, TOTP, token and break-glass logins all keep working when SSO is on, because a router that can only be entered through an identity provider is one provider outage away from being unmanageable.

Identities are never linked by email address unless you explicitly turn that on, and even then only when the provider asserts email_verified.

The audit log#

Every mutation records the actor, their role, their source address, the exact commands and whether the router accepted them. Denied requests are recorded too. It is retained per policy — 2000 entries by default, adjustable.

Break-glass#

A file can hold a token that authenticates as admin without an account. The shipped unit points at a path and nothing creates the file, so a default install has no break-glass token; the agent logs that it is absent and carries on. Create one deliberately if you want one, and treat it as what it is: a full admin credential sitting in a file.

Read on#

Updated 2026-09-02