Role-Based Access Control (RBAC)
Tellstone supports per-user authentication with role-based access control
(RBAC). Where --require-pass authenticates every client against a single
shared password, RBAC binds each user to a role that decides which commands
the user may run on which keys. RBAC supersedes --require-pass when both
are configured.
RBAC is off by default and adds no overhead when it is not enabled.
Enabling RBAC
Section titled “Enabling RBAC”Point --rbac-config at a policy file (YAML or JSON):
./bin/tellstone --rbac-config policy.yaml --enable-respThe policy file is validated at startup — unknown roles, malformed rules, and
duplicate names fail the load so a bad file can never half-apply. A validated
file is re-read on SIGHUP for hot reload (see Hot reload).
Policy file
Section titled “Policy file”roles: - name: admin rules: ["+@all", "~*"] - name: readonly rules: ["+@read", "~*"]users: - name: admin role: admin password: "$2a$10$pcaKkTfRy.KSdNUgKszYYedE7L32P9fSEG3x1phq0EbjeYkn5WpEi" - name: alice role: readonly password: "$2a$10$sslrTYVwaIaA7O1lhokY2OgnojP5bB8YJ/o2MXaFP1v49lG8fqJYK"default_role: readonly # fallback for users without an explicit role| Key | Meaning |
|---|---|
roles | Role definitions: a name and a list of rules. |
users | Principals bound to exactly one role, each with a bcrypt password hash or nopass: true. |
default_role | Optional fallback role applied to any user without an explicit assignment. |
A user without a password (nopass: true) accepts any password; a
passwordless default user lets connections start authenticated and inherit
its effective role. Every connection that cannot resolve a role is denied
everything — RBAC is fail-closed by design.
Rule syntax
Section titled “Rule syntax”A role’s rules are Redis-style tokens, applied in any order:
| Token | Effect |
|---|---|
+cmd | Grant one command (e.g. +get) |
-cmd | Revoke one command |
+@cat / -@cat | Grant / revoke a whole category |
~prefix | Whitelist a key namespace (e.g. ~users:*) |
-rules always override+rules, regardless of order.- An empty whitelist — or an explicit
~*— allows every key. - If any namespace rule exists, only matching prefixes pass (default-deny).
- A role with no rules is a valid deny-all role.
Built-in categories
Section titled “Built-in categories”+@cat expands to the category’s registered commands at load time.
| Category | Grants |
|---|---|
login | AUTH, PING, COMMAND |
read | GET, INFO |
write | SET, DEL |
readwrite | read + write + login |
operator | readwrite + FLUSH |
maintenance | FLUSH, SHUTDOWN, CONFIG, DEBUG, MONITOR |
admin | AUTH, ROLE, ACL, USER, GRANT, REVOKE |
all / none | every registered command / nothing |
Password hashing
Section titled “Password hashing”Passwords in the policy file are bcrypt hashes ($2a$10$...). Generate one
with either tool:
htpasswd -nbBC 10 "" "PASSWORD" | tr -d ':\n' # apache2-utilsmkpasswd -m bcrypt "PASSWORD" # whoisROLE SETUSER accepts a raw >password and bcrypt-hashes it server-side, so
runtime-created users need no tooling. Full hash verification happens at the
first AUTH; the hash itself is never rendered by ROLE LIST or
ROLE GETUSER.
Federated identity (OAuth / OIDC)
Section titled “Federated identity (OAuth / OIDC)”Instead of a password, a client can present an OIDC id_token at AUTH. The
token is verified against a configured identity provider, its claims resolve
to a role through the policy’s oauth.rules (first match wins), and the
token’s sub becomes the session username. See
OAuth / OIDC.
Sessions
Section titled “Sessions”- A connection’s role is resolved once at handshake and pinned for the connection’s lifetime. A later policy reload never changes an in-flight session.
- Unauthenticated data commands return
-NOAUTH; commands a user’s role does not grant return-NOPERM. - bcrypt verification runs on a dedicated worker pool off the event loop, so hashing never blocks I/O.
Runtime management
Section titled “Runtime management”Authenticated users with the ROLE permission (the admin category) can
manage roles and users without a restart:
| Command | Effect |
|---|---|
ROLE CREATE <name> <rule>... | Define a new role; fails if it already exists (update = DELETE then CREATE) |
ROLE SETUSER <user> <role> [>password] [nopass] | Create or update a user; a >password or nopass option is required |
ROLE DELUSER <user> | Remove a user |
ROLE DELETE <role> | Remove a role; users referencing it fall back to the default role |
ROLE LIST | Enumerate roles with their granted commands and namespaces |
ROLE GETUSER <user> | Show a user’s role and whether a password is set |
Auditing auth failures
Section titled “Auditing auth failures”ACL LOG (see the ACL command page) returns the recent
rejected-AUTH buffer: up to 100 entries, each a
[timestamp, username, remote address, reason] tuple. The log lives on the
RBAC store — not the policy snapshot — so it survives SIGHUP reloads, and
it is shared by both the RESP2 and the native binary protocol. The store-wide
failure counter behind it is the same one tellstone_rbac_auth_failures_total
reports in Metrics.
Example over RESP2:
> ROLE CREATE operator +get '~users:*'+OK> ROLE SETUSER bob operator '>bobpw'+OK> ROLE GETUSER bobbob / operator / 1ROLE GETUSER returns the username, its assigned role (or null when the
default role applies), and 1/0 for whether a password is set — the hash
itself is never exposed. ROLE LIST returns each role’s name, granted
commands, and namespace whitelist.
See the ROLE command reference for full details, including the native binary protocol.
Hot reload
Section titled “Hot reload”Send SIGHUP to re-read the policy file and atomically publish the new
snapshot. A rejected file (parse error, unknown role, bad hash) is ignored and
the last valid policy stays active.
Because the reload replaces the whole policy with the file’s contents, any
roles or users created at runtime via ROLE CREATE / ROLE SETUSER are
discarded — persist them in the policy file first.
Metrics
Section titled “Metrics”With --enable-metrics, RBAC counters are exposed on /metrics:
| Metric | Type | Description |
|---|---|---|
tellstone_rbac_auth_failures_total | counter | Failed AUTH attempts |
tellstone_rbac_denied_commands_total | counter | Authorization-denied command attempts (-NOPERM) |
tellstone_rbac_commands_total{role="..."} | counter | Data commands executed per role |
A SIGHUP reload swaps in freshly parsed roles, which resets per-role command
counts. Runtime ROLE CREATE does not.
Example
Section titled “Example”A ready-to-run policy file and a walkthrough client ship in the main repository
at cmd/example/role — start a server with --rbac-config, then run the
example to watch an admin create a user, a reader hit its namespace gate, and
denials surface as NOT_AUTHORIZED errors.