Roles & Permissions
Managing roles, permissions, and role assignments for RBAC.
Every config example on this page comes in two flavours: Go (programmatic — call the store directly) and DSL (declarative — write .warden source and apply it via warden apply or dsl.ApplyFS). They produce identical state.
Roles
A role is a named collection of permissions that can be assigned to subjects.
Role Fields
| Field | Type | Description |
|---|---|---|
ID | id.RoleID | TypeID (role_...) — auto-assigned on Create when unset |
Name | string | Display name |
Slug | string | URL-safe identifier (unique per tenant) |
Description | string | Human-readable description |
ParentSlug | string | Parent role slug for inheritance — empty means no parent |
IsSystem | bool | System roles cannot be deleted |
IsDefault | bool | Auto-assigned to new subjects |
MaxMembers | int | Maximum assignments (0 = unlimited) |
Metadata | map[string]any | Custom key-value data |
Create a Role
// IDs and CreatedAt/UpdatedAt timestamps are auto-assigned by the
// store on Create — set them only when you need a specific value
// (idempotent inserts, fixtures, etc.).
r := &role.Role{
Name: "Editor",
Slug: "editor",
Description: "Can read and write documents",
}
err := store.CreateRole(ctx, r)
// r.ID is now populated.warden config 1
tenant t1
role editor {
name = "Editor"
description = "Can read and write documents"
}Apply with warden apply -f config/ --store sqlite:./warden.db or programmatically via dsl.ApplyFS.
Role Hierarchy
Roles can inherit permissions from parent roles via slug:
admin := &role.Role{Name: "Admin", Slug: "admin"}
editor := &role.Role{Name: "Editor", Slug: "editor", ParentSlug: "admin"}
viewer := &role.Role{Name: "Viewer", Slug: "viewer", ParentSlug: "editor"}warden config 1
tenant t1
role admin { name = "Admin" }
role editor : admin { name = "Editor" }
role viewer : editor { name = "Viewer" }The : syntax declares the parent slug. Cross-namespace inheritance uses an absolute path (role x : /other-ns/admin).
When checking permissions, Warden walks up the role hierarchy to collect all inherited permissions.
Permissions
A permission represents a single (resource, action) authorization grant.
Permission Fields
| Field | Type | Description |
|---|---|---|
ID | id.PermissionID | TypeID (perm_...) — auto-assigned on Create when unset |
Name | string | <resource>:<action> natural key |
Resource | string | Resource type (e.g., "document") |
Action | string | Action verb (e.g., "read", "write") |
Description | string | Human-readable description |
IsSystem | bool | System permissions cannot be deleted |
Metadata | map[string]any | Custom key-value data |
Create a Permission
p := &permission.Permission{
Name: "doc:read",
Resource: "document",
Action: "read",
}
err := store.CreatePermission(ctx, p)warden config 1
tenant t1
// Shorthand form — link to a resource-type permission expression.
permission "doc:read" (document : read)
// Long form with metadata.
permission "doc:write" {
description = "Edit a document"
resource = "document"
action = "write"
}Attach to Role
Attach by name (the permission's natural key) — no permission ID lookup needed:
err := store.AttachPermission(ctx, roleID, permission.Ref{Name: "doc:read"})In .warden source, attachments live inside the role's grants field:
warden config 1
tenant t1
permission "doc:read" (document : read)
permission "doc:write" (document : edit)
role viewer {
grants = ["doc:read"]
}
role editor : viewer {
grants += ["doc:write"] // appends to inherited grants
}The += form appends to the parent's grants; = replaces. Glob patterns (doc:*) work in either form.
Glob Matching
Permission matching supports wildcards:
| Pattern | Matches |
|---|---|
document:read | Exactly document:read |
document:* | document:read, document:write, document:delete |
*:read | document:read, user:read, project:read |
*:* | Everything |
Assignments
An assignment links a subject (user, API key, service) to a role.
Runtime-only. Assignments are not expressible in
.wardensource — that file describes the configuration (roles, permissions, policies, resource types, relations) but not the runtime data of who has which role. Assignments are created via the store API or HTTP endpoints when subjects are provisioned.
Assignment Fields
| Field | Type | Description |
|---|---|---|
ID | id.AssignmentID | TypeID (asgn_...) — auto-assigned on Create when unset |
RoleID | id.RoleID | The role to assign |
SubjectKind | string | Subject type ("user", "api_key", "service") |
SubjectID | string | Subject identifier |
ResourceType | string | Optional: scope to resource type |
ResourceID | string | Optional: scope to specific resource |
ExpiresAt | *time.Time | Optional: auto-expire assignment |
Global Assignment
// User is an editor everywhere
ass := &assignment.Assignment{
RoleID: editorRole.ID,
SubjectKind: "user",
SubjectID: "user-42",
}
_ = store.CreateAssignment(ctx, ass)Resource-Scoped Assignment
// User is an editor only for project-123
ass := &assignment.Assignment{
RoleID: editorRole.ID,
SubjectKind: "user",
SubjectID: "user-42",
ResourceType: "project",
ResourceID: "project-123",
}Time-Limited Assignment
expires := time.Now().Add(24 * time.Hour)
ass := &assignment.Assignment{
RoleID: adminRole.ID,
SubjectKind: "user",
SubjectID: "user-42",
ExpiresAt: &expires,
}Listing
// List roles for a subject
roles, _ := store.ListRolesForSubject(ctx, "", "user", "user-42")
// List assignments with filters
assignments, _ := store.ListAssignments(ctx, &assignment.ListFilter{
SubjectKind: "user",
SubjectID: "user-42",
Limit: 50,
})
// List permissions for a role
perms, _ := store.ListRolePermissions(ctx, roleID)