RBAC Core Concepts

What is RBAC?

Role-Based Access Control (RBAC) is an authorization model that assigns permissions to roles, and roles to users (subjects). This indirection simplifies permission management at scale — instead of granting permissions to each user individually, you assign them to a role.

Core Entities

Subject

A Subject is any entity that can be granted permissions — typically a user, service account, or automated agent. In kirino, subjects implement the Subject trait:

TraitPurpose
SubjectBase trait for any authorizable entity
DelegatableA subject that can delegate its permissions to another subject

Permission

A Permission is the atomic unit of authorization — a named action on a resource domain:

TraitPurpose
Permissionname() -> &str for serialization, domain() -> &str for grouping

Role

A Role is a named collection of permissions:

TraitPurpose
Role<P>Base role: holds a set of permissions
HierarchicalRole<P>Extends Role<P> with parent_roles() for inheritance

RBAC Levels

Kirino implements the ANSI INCITS 359-2004 standard across three levels:

graph TD
    subgraph RBAC0["RBAC0 — Base Model"]
        S0["Subject"] --> A0["Assignment"]
        A0 --> R0["Role"]
        R0 --> P0["Permission"]
    end
    subgraph RBAC1["RBAC1 — Hierarchy"]
        R1["Parent Role"] -->|inherits| R1C["Child Role"]
        R1C --> P1["Permissions (union)"]
    end
    subgraph RBAC2["RBAC2 — Constraints"]
        SSD["SSD: Static Separation of Duty"]
        DSD["DSD: Dynamic Separation of Duty"]
        CARD["Cardinality Constraints"]
        PREQ["Prerequisite Constraints"]
        TEMP["Temporal Constraints"]
    end
    RBAC0 --> RBAC1
    RBAC1 --> RBAC2

RBAC0 — Base Model

The foundation: users are assigned to roles, roles hold permissions.

Subject ──assigned──→ Role ──contains──→ Permission

RBAC1 — Hierarchical Model

Roles can inherit from parent roles, forming a permission tree:

graph TD
    ADMIN["admin<br/>(all permissions)"] --> OPERATOR["operator<br/>(read + write + deploy)"]
    ADMIN --> AUDITOR["auditor<br/>(read + audit)"]
    OPERATOR --> VIEWER["viewer<br/>(read only)"]

RBAC2 — Constraint Model

Constraints enforce separation of duty and other business rules:

Static Separation of Duty (SSD)

Conflicting roles cannot be assigned to the same user.

SsdPolicy { roles: {"billing", "auditor"}, cardinality: 2 }
→ A user cannot hold both "billing" and "auditor" simultaneously.

Dynamic Separation of Duty (DSD)

Conflicting roles can be assigned but cannot be active in the same session.

DsdPolicy { roles: {"author", "reviewer"}, cardinality: 2 }
→ A user can be both author and reviewer, but only activate one per session.

Cardinality Constraint

Limits how many users can hold a given role.

CardinalityConstraint { role: "admin", max: 3 }
→ At most 3 users can be administrators.

Prerequisite Constraint

A user must hold role A before being assigned role B.

PrerequisiteConstraint { role: "operator", requires: "viewer" }
→ Only existing viewers can be promoted to operator.

Temporal Constraint

A role is only valid within a time window.

TemporalConstraint { role: "temp-admin", valid_from: ..., valid_until: ... }
→ Auto-expires; automatically revoked after valid_until.

Decision Flow

When RbacEngine::check(subject, permission) is called:

flowchart TD
    START(["check(subject, permission)"]) --> CACHE{"Cache hit?"}
    CACHE -->|yes| RETURN_CACHED(["Return cached result"])
    CACHE -->|no| DENIED{"In denied_permissions?"}
    DENIED -->|yes| RETURN_DENY(["DENY — persist to cache"])
    DENIED -->|no| EXTRA{"In extra_permissions?"}
    EXTRA -->|yes| RETURN_ALLOW(["ALLOW — persist to cache"])
    EXTRA -->|no| ROLES["Resolve assigned roles"]
    ROLES --> HIER["Expand role hierarchy"]
    HIER --> CHECK["Check if permission ∈ role permissions"]
    CHECK -->|yes| RETURN_ALLOW2(["ALLOW — persist to cache"])
    CHECK -->|no| RETURN_DENY2(["DENY — persist to cache"])

Key semantic: Deny takes precedence. A denied permission cannot be granted by roles or extra permissions.

Key Traits Summary

classDiagram
    class Subject {
        +subject_id() &str
        +subject_type() &str
    }
    class Permission {
        +name() &str
        +domain() &str
    }
    class Role~P~ {
        +role_name() &str
        +permissions() HashSet~P~
    }
    class HierarchicalRole~P~ {
        +parent_roles() Vec~String~
    }
    class AssignmentStore~S,R,P~ {
        +assign_role() Result
        +revoke_role() Result
        +roles_of() Vec~String~
        +extra_permissions() HashSet~P~
        +denied_permissions() HashSet~P~
    }
    class ConstraintStore {
        +list_ssd_policies() Vec~SsdPolicy~
        +list_dsd_policies() Vec~DsdPolicy~
        +list_cardinality_constraints() Vec~CardinalityConstraint~
    }
    class RbacEngine~S,P,R,A~ {
        +check() bool
        +check_batch() HashMap~P,bool~
        +effective_permissions() HashSet~P~
        +check_hierarchical() bool
    }

    Subject --> AssignmentStore : assigned via
    Role~P~ <|-- HierarchicalRole~P~
    AssignmentStore --> Role~P~ : references
    RbacEngine~S,P,R,A~ --> AssignmentStore
    RbacEngine~S,P,R,A~ --> ConstraintStore