RBAC
Per-user roles and data profiles in the dbtrail EE web console. Local users, SSO claim-to-role mapping, and the enforcement boundary
The rbac feature turns the dbtrail-console-ee web console from a
single-operator tool into a multi-user one: each person logs in with
their own credentials, holds a role deciding what they may do, and
optionally a data profile deciding what they may see. Enforcement
is server-side, per session.
Requires a license with the rbac feature claim, and an EE build embedding
core v0.44.0 or newer.
What RBAC covers, and what it deliberately does not
RBAC is enforced in the web console only. Stated up front, because the boundary is structural, not an oversight:
- The CLI is permanently out of scope. Anyone holding the index DSN can query MySQL directly, so CLI-side "enforcement" would be security theater. Treat the index DSN like the credential it is.
- Not covered in v1 (planned): the console's
/mcpendpoint and the embedded flashback MySQL port (both authenticate by token today), and the standalonebintrail shim. - Users are managed via the CLI in v1; there is no user-management UI.
The two axes
Roles: what a session may do
Four fixed roles, each a strict superset of the previous:
| Role | May do |
|---|---|
viewer | Health/status surface only: status, server list. No row data. |
analyst | viewer + browse events, time-travel reconstruction, extension views (e.g. Forensics). Reads data, changes nothing. |
operator | analyst + generate recovery SQL, trigger baselines and verify runs. Incident response. |
admin | operator + server registry create/edit/delete, monitor start/stop, settings, rotation. Everything. |
A denied request gets a 403 naming the missing permission, never a
silently empty result. The UI also hides surfaces the session cannot
use, but the server-side check is the gate.
Data profiles: what a session may see
A user may carry the name of an OSS RBAC profile (the
bintrail flag / profile / access machinery; see the
core docs
for authoring them). A profiled session:
- sees denied tables withheld and flagged columns redacted in the events and recover views, resolved against the selected server's index at request time;
- never sees the originating SQL statements (
query_text); - queries without Parquet archives (archive reads bypass redaction);
- is refused (403) on the surfaces whose reads can't be redacted: time-travel, baseline listings, recover-cascade, verify, and extension views.
A profile name that doesn't exist on the selected server is a 403,
never "enforce nothing" on a typo. The two axes are orthogonal: an
admin with an assigned profile is redacted like anyone else.
Local users
Users live in ~/.config/bintrail/console-users.yaml (0600, written
atomically), managed with the user verbs of dbtrail-console-ee:
$ dbtrail-console-ee user add ana --role admin
New password: ********
Repeat password: ********
user "ana" added (role admin) in /home/op/.config/bintrail/console-users.yaml
$ dbtrail-console-ee user add sam --role analyst --profile support_redact
$ dbtrail-console-ee user list
ana admin
sam analyst profile=support_redact
$ dbtrail-console-ee user set-role sam --role operator
$ dbtrail-console-ee user set-password sam
$ dbtrail-console-ee user remove samNotes that matter in practice:
- The username comes right after the verb, flags after it. The
flag-only forms (
user set-password,user remove --yes,user status) keep managing the built-in single-user credential from the OSS console, unchanged. - Passwords are read from a prompt or
--password-stdin, never a flag or environment variable. - A running console picks up store edits per login attempt (no restart for add/rotate/remove/role changes) but activates the store itself at startup: restart the console after creating the store's first user.
- Zero-admin guardrail: while the built-in credential file is absent, the CLI refuses any change that would leave the store without an admin (including creating a first non-admin user, and emptying the store; delete the file to tear it down).
- Precedence: the built-in single-user credential keeps working and logs in as a full-access (admin-equivalent) session. If the store defines the same username, the store wins: the built-in password stops matching for that name. Keep the operator's built-in username distinct from store usernames.
- An empty store is inert: the console behaves as if it didn't exist (including browser first-run password setup).
SSO claim→role mapping
With both sso and rbac licensed, admitted SSO identities can mint
scoped sessions instead of full-power ones. Environment-only, alongside
the existing DBTRAIL_SSO_* variables:
| Variable | Meaning |
|---|---|
DBTRAIL_SSO_ROLE_CLAIM | ID-token claim holding the user's group/role values (e.g. groups). String and string-array values both work. |
DBTRAIL_SSO_ROLE_MAP | Comma-separated claimValue=role[:profile]. First match in declaration order wins when a user carries several mapped values. |
DBTRAIL_SSO_DEFAULT_ROLE | Role for admitted identities matching no entry. Default viewer. |
A worked Okta/Keycloak-style example, with groups dba, ops, support:
export DBTRAIL_SSO_ROLE_CLAIM=groups
export DBTRAIL_SSO_ROLE_MAP="dba=admin,ops=operator:ops_redact,support=viewer"
export DBTRAIL_SSO_DEFAULT_ROLE=viewerSemantics worth knowing:
- An unmapped identity is admitted read-only (
viewer), never silently admin, and never rejected outright: who may log in stays with the allowlists and the IdP application assignment; the mapping only decides what the session may do. - No
ROLE_*variables set → SSO sessions stay full-power (upgrading to anrbaclicense changes nothing until you configure the mapping). - A broken mapping (bad entry, unknown role,
ROLE_MAPwithoutROLE_CLAIM) prints one warning at startup and falls back to full-power sessions; the console never refuses to boot over it. - A local store user and an SSO identity mapped to the same role+profile get identical enforcement.
License lapse: what stops, what never stops
The gate follows the sso precedent, in the safe direction:
- Gate closed (expired past grace, claim removed, license missing or
invalid): the console behaves exactly like the OSS build. Users
defined only in the multi-user store cannot log in; if
ssois still licensed, SSO sessions go back to full-power. Nobody's session is ever escalated by a lapse, and the operator's built-in credentials (password file, static token) always keep working: a lapse never locks you out of your recovery tooling. - Store or
DBTRAIL_SSO_ROLE_*config present while the gate is closed → one warning at startup naming the reason (no file / status / missing claim).
Interaction notes
- Profiled sessions and archives/baselines: a session carrying a data profile queries live MySQL only and cannot use time-travel or baseline listings: Parquet archive and baseline snapshot reads bypass the redaction pass, so withholding them is what makes the redaction real.
- Audit records on denials (when
auditis licensed alongside): every role denial (authz.denied, with the verified identity, route, and missing permission) and profile-gate refusal (profile.denied, with the gate named) lands in the audit trail, as does every refused store login (auth.login_denied, deliberately without the attempted username, which too often is a password typed into the wrong field). See Audit trail. - The Forensics view (feature
forensics) requiresanalystor above (extview:read); aviewerneither sees the tab nor can reach its data routes.