Warden

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

FieldTypeDescription
IDid.RoleIDTypeID (role_...) — auto-assigned on Create when unset
NamestringDisplay name
SlugstringURL-safe identifier (unique per tenant)
DescriptionstringHuman-readable description
ParentSlugstringParent role slug for inheritance — empty means no parent
IsSystemboolSystem roles cannot be deleted
IsDefaultboolAuto-assigned to new subjects
MaxMembersintMaximum assignments (0 = unlimited)
Metadatamap[string]anyCustom 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

FieldTypeDescription
IDid.PermissionIDTypeID (perm_...) — auto-assigned on Create when unset
Namestring<resource>:<action> natural key
ResourcestringResource type (e.g., "document")
ActionstringAction verb (e.g., "read", "write")
DescriptionstringHuman-readable description
IsSystemboolSystem permissions cannot be deleted
Metadatamap[string]anyCustom 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:

PatternMatches
document:readExactly document:read
document:*document:read, document:write, document:delete
*:readdocument: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 .warden source — 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

FieldTypeDescription
IDid.AssignmentIDTypeID (asgn_...) — auto-assigned on Create when unset
RoleIDid.RoleIDThe role to assign
SubjectKindstringSubject type ("user", "api_key", "service")
SubjectIDstringSubject identifier
ResourceTypestringOptional: scope to resource type
ResourceIDstringOptional: scope to specific resource
ExpiresAt*time.TimeOptional: 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)

On this page