RBAC, Groups & Roles
Elara uses Casbin RBAC with one hard invariant: users get permissions only
through group membership. There is no API to grant a permission directly to a
user. The rationale is documented in
ADR 0002 — Groups-only RBAC; this page is
the practical model and how-to (internal/domain/rbac.go).
Permissions are tuples, not canned roles
A group holds a set of Permission{Object, Action, Domain} grants, assigned
explicitly through the API or UI. There is no fixed "reader gets X, writer gets
Y" expansion table on the web side — you compose the exact objects/actions/scopes
a group needs.
- Objects:
namespace,token,client,dashboard,user,group,policy,webhook, and*(all). Note thatnamespacecovers all namespace-scoped content — configs, schemas, and export/import included; there is no separate object for those. - Actions:
read,write,create,delete, and*(all).writeimpliesread(you can't edit what you can't read), and*matches any action. - Domain (scope): the scope a grant applies to — a namespace
(
namespace:<name>), a group (group:<name>), or*(global / every namespace).
Namespace-scoped vs global. A grant like namespace / write / namespace:prod
lets the group write configs in prod only. The same grant with domain *
applies to every namespace. Group membership itself is always global.
The role names
admin, writer, and reader exist as constants. admin is the wildcard
role: the seeded system superadmin group is granted * / * / * (all objects,
all actions, all namespaces) as a break-glass policy. reader and writer are
used primarily as etcd service-token roles — a token carries a role plus a
namespace list, where reader is read-only and writer additionally permits
writes within its namespaces (internal/handler/etcdv3/access.go). Service
tokens are independent credentials — see
Sessions & Tokens.
For the Casbin-facing role/action semantics used by groups specifically:
| Role | Object / Action semantics |
|---|---|
admin |
All actions (*) on all objects (*) — full control |
writer |
write on content (write implies read) |
reader |
read on content |
The system superadmin group carries the single break-glass rule
(group:superadmin, *, *, *) — all objects, all actions, all namespaces — and
its members are global admins.
Practical flow: grant someone admin over a namespace (or globally)
- Create a group (Web UI Groups → Create, or
GroupService.Create). You can attach initial permissions and members atomically at creation time. - Assign the group a permission in a domain: pick the object/action and set
the domain to a specific namespace (
namespace:prod) or*for all namespaces. For a namespace admin, grant all-actions-on-all-objects scoped to that namespace; for a platform admin, scope it to*(or just add them to the built-in superadmin group). - Add the user to the group. Membership is what actually confers the permissions.
Anti-escalation is enforced throughout: you cannot grant a group (or its
members) any permission you do not yourself already hold. The bootstrap admin
(basic-auth local user, or the OIDC adminEmail identity) starts in the
superadmin group and can therefore set up all other groups.