ABAC & Cedar Policies
On top of role-based access control, Tetrapus runs an attribute-based check on
every authenticated request using Cedar.
The ABAC layer exposes an
Authorizer trait and a
CedarAuthorizer implementation backed by a bounded
LRU decision cache. Cedar runs after RBAC; if either layer denies, the request is denied.
Decision pipeline
graph LR
REQ["WS / REST request"] --> RBAC["Permission check<br/>(ws_authz::check)"]
RBAC -->|deny| DENY["403 + audit"]
RBAC -->|allow| ABAC["Cedar check<br/>(ws_authz::check_abac)"]
ABAC -->|cache hit| FAST["Decision<br/>(no eval)"]
ABAC -->|cache miss| EVAL["CedarAuthorizer.is_authorized"]
EVAL --> CACHE["DecisionCache<br/>(LRU + TTL)"]
CACHE --> FAST
FAST -->|Allow| OK["dispatch"]
FAST -->|Deny| DENY
EVAL -.->|engine error| FAILCLOSED["fail-closed<br/>+ warn!"]
FAILCLOSED --> DENY
Sample policy
From policies/base.cedar:
Text
// 1. System-wide same-Org scoping — every action must be in the same Org.
forbid (
principal,
action,
resource
) when {
principal.org != resource.org
};
// 2. Org admins pass every action inside their Org.
permit (
principal is User,
action,
resource
) when {
principal.org == resource.org &&
(principal.role == "org_owner" || principal.role == "org_admin")
};
// 3. Stream-read requires every required marking on the stream.
permit (
principal,
action == Action::"stream_read",
resource is Stream
) when {
principal.org == resource.org &&
principal.markings.containsAll(resource.required_markings)
}; Schema
Text
entity Org;
entity User in Org = {
"org": Org,
"role": String,
"markings": Set<String>
};
entity Stream in Org = {
"org": Org,
"required_markings": Set<String>
};
entity AuditLog in Org = { "org": Org };
entity Workspace in Org = { "org": Org };
entity Device in Org = { "org": Org };
entity Service in Org = { "org": Org };
action stream_read appliesTo { principal: [User, Device, Service], resource: Stream };
action command_issue appliesTo { principal: [User], resource: Command };
action audit_read appliesTo { principal: [User], resource: AuditLog }; Decision cache
- Capacity — 4096 entries (bounded LRU, eviction on insert).
- TTL — 30 seconds. Expired entries are recomputed lazily on next lookup.
- Key —
(principal_id, action, resource, markings_fingerprint). Markings are hashed in declaration order for stability. - Hit-rate — ~98% on realistic stream-read traffic; per-message authz cost drops to a single hashmap lookup.
- Invalidation — principal-scoped flush on permission grants, marking changes, or role updates.
Status: the trait + Passthrough authorizer ship today; CedarAuthorizer with the bundled base policies is wired into the WS hot-path. Per-Org policy bundles (uploadable Cedar files) are on the roadmap.
Related
- Permissions — the RBAC layer Cedar runs after.
- Markings — the labels Cedar consumes for stream-read gating.
- Audit Trail — every Cedar deny is recorded.
Questions?
Reach out for help with integration, deployment, or custom domain codecs.