dbtrail
dbtrail EE

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 /mcp endpoint and the embedded flashback MySQL port (both authenticate by token today), and the standalone bintrail 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:

RoleMay do
viewerHealth/status surface only: status, server list. No row data.
analystviewer + browse events, time-travel reconstruction, extension views (e.g. Forensics). Reads data, changes nothing.
operatoranalyst + generate recovery SQL, trigger baselines and verify runs. Incident response.
adminoperator + 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 sam

Notes 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:

VariableMeaning
DBTRAIL_SSO_ROLE_CLAIMID-token claim holding the user's group/role values (e.g. groups). String and string-array values both work.
DBTRAIL_SSO_ROLE_MAPComma-separated claimValue=role[:profile]. First match in declaration order wins when a user carries several mapped values.
DBTRAIL_SSO_DEFAULT_ROLERole 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=viewer

Semantics 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 an rbac license changes nothing until you configure the mapping).
  • A broken mapping (bad entry, unknown role, ROLE_MAP without ROLE_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 sso is 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 audit is 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) requires analyst or above (extview:read); a viewer neither sees the tab nor can reach its data routes.

On this page