Expand description
An in-process authorization engine for Rust.
Gatehouse keeps authorization logic in Rust while giving policy code a
request-scoped fact session for relationship and backend-loaded data. The
public API is centered on one PolicyDomain marker per authorization
domain, a PermissionChecker that owns that domain’s policy stack, and a
BoundEvaluator created for one request/session/subject/action/context.
§Overview
A Policy is an asynchronous decision unit for one PolicyDomain. The
domain names the four Rust types involved in a decision:
Subject: the caller.Action: the operation being attempted.Resource: the target resource or scope resource.Context: request-scoped inputs such as current time, MFA freshness, network zone, tenant config, or feature flags.
Relationship data and other backend-loaded authorization facts do not
belong in Context; expose them as FactKey values loaded by an
EvaluationSession. The session batches, deduplicates, caches, and
coalesces fact loads for one request.
§Quick Start
The fastest way to define a synchronous predicate policy is
PolicyBuilder:
#[derive(Debug, Clone)]
struct User {
id: u64,
roles: Vec<&'static str>,
}
#[derive(Debug, Clone)]
struct Document {
owner_id: u64,
}
#[derive(Debug, Clone)]
struct ReadAction;
struct Documents;
impl PolicyDomain for Documents {
type Subject = User;
type Action = ReadAction;
type Resource = Document;
type Context = ();
}
let admin_policy = PolicyBuilder::<Documents>::new("AdminOnly")
.subjects(|user: &User| user.roles.contains(&"admin"))
.build();
let owner_policy = PolicyBuilder::<Documents>::new("OwnerOnly")
.when(|user: &User, _action: &ReadAction, document: &Document, _ctx: &()| {
user.id == document.owner_id
})
.build();
let mut checker = PermissionChecker::<Documents>::new();
checker.add_policy(admin_policy);
checker.add_policy(owner_policy);
let session = EvaluationSession::empty();
let document = Document { owner_id: 7 };
let admin = User { id: 1, roles: vec!["admin"] };
let owner = User { id: 7, roles: vec!["user"] };
let guest = User { id: 2, roles: vec!["user"] };
assert!(checker.bind(&session, &admin, &ReadAction, &()).check(&document).await.is_granted());
assert!(checker.bind(&session, &owner, &ReadAction, &()).check(&document).await.is_granted());
assert!(!checker.bind(&session, &guest, &ReadAction, &()).check(&document).await.is_granted());§Core Flows
Bind request-wide inputs once, then evaluate resources through the bound evaluator:
let session = registry.session();
let bound = checker.bind(&session, &subject, &action, &request_context);
let decision = bound.check(&resource).await;
let decisions = bound.evaluate(resources.clone()).await;
let authorized = bound.filter(resources).await;
let authorized_rows = bound.filter_by(rows, |row| &row.authz_resource).await;
let page = bound.lookup_page(&lookup, &hydrator, cursor.as_deref(), limit).await?;Use EvaluationSession::empty for fact-free decisions. Use a session from
FactRegistry::session when any policy calls ctx.session.get(...), such
as RebacPolicy or a custom fact-backed policy.
BoundEvaluator::evaluate preserves input order and returns one
AccessEvaluation per input resource. BoundEvaluator::filter keeps
only granted resources. BoundEvaluator::evaluate_by and
BoundEvaluator::filter_by are for wide caller-owned rows where
authorization uses a projected resource. BoundEvaluator::lookup_page is
for list endpoints where the application cannot load every possible
candidate first; the LookupSource enumerates candidate IDs, a
Hydrator resolves them, and the full policy stack authorizes the
hydrated resources.
§Decision Semantics
Gatehouse deliberately keeps combining semantics fixed:
PermissionCheckerapplies deny-overrides. Any evaluated result containingPolicyEvalResult::Forbiddendenies the request and overrides grants.- Policies declaring
Effect::ForbidorEffect::AllowOrForbidare evaluated before allow-only policies so a veto cannot be skipped by grant short-circuiting. - If no policy forbids, the first grant wins.
- If nothing grants, the checker denies with
"All policies denied access". - An empty checker denies with
"No policies configured". PolicyEvalResult::NotApplicablemeans the policy did not grant.PolicyEvalResult::Forbiddenmeans the policy actively vetoed.PolicyBuildercombines configured predicates with AND logic.PolicyBuilder::forbidmakes a matching built policy forbid; a non-match remains not applicable and does not block.AndPolicyandOrPolicyevaluate veto-capable children before allow-only children, then short-circuit normally.NotPolicyinverts grants and non-grants, but never turnsForbiddeninto a grant.Forbiddenpropagates throughAndPolicy,OrPolicy,NotPolicy, andDelegatingPolicy.NotPolicydoes not neutralize a veto.admin.or(blocked.not())still denies whenblockedreturnsForbidden. For “grant unless blocked”, use an allow-onlyblockedpredicate undernot(), or register an explicit forbid policy when the block should be global.grant.and(forbid_only)can never grant: a forbid-only child does not satisfy AND’s “all children grant” rule. Usegrant.and(blocked_allow_predicate.not())for a local exclusion.
Denials from AccessEvaluation are summary-level. Use
AccessEvaluation::display_trace or the attached EvalTrace to inspect
individual policy reasons and fact provenance.
§Fact-Loaded Authorization
FactSource::load_many receives unique fact keys and must return exactly
one result per key in the same order. EvaluationSession expands
duplicate caller inputs, preserves caller order, caches results for the
request, chunks loads according to FactSource::max_batch_size, and joins
concurrent in-flight loads for the same key.
RebacPolicy is the built-in fact-backed policy. It extracts flat
subject/resource IDs, builds RelationshipQuery keys, and grants only
when the request session loads a Found(true) relationship fact. Missing
sources, missing facts, backend errors, and fact-source contract violations
fail closed to denied ReBAC decisions.
§Long-Lived Streams
EvaluationSession caches are scoped to one authorization pass. For SSE,
WebSocket, and other long-lived streams, do not hold one fact-backed session
for the stream lifetime.
If your product contract authorizes once at stream open, create a fresh
session, compute the visible ID set with BoundEvaluator::filter or
BoundEvaluator::filter_by, drop the session, and only emit frames for
that set. If the stream must observe mid-stream permission revocation, run
periodic reauthorization with a fresh FactRegistry::session and re-bind
the checker for that pass.
§Built-In Policies
RbacPolicy: role-based access control from caller roles and required roles for the(action, resource)pair.RebacPolicy: relationship-based access control backed byFactSourceandEvaluationSession.DelegatingPolicy: maps the current inputs into anotherPolicyDomainand delegates to a childPermissionChecker.
Use PolicyBuilder::when for attribute-style predicates that compare
subject, action, resource, and context in one synchronous closure.
§Custom Policies
Implement Policy directly when a rule needs async work, custom batching,
custom telemetry metadata, or hand-written forbid behavior:
struct OwnerPolicy;
#[async_trait]
impl Policy<Documents> for OwnerPolicy {
async fn evaluate(&self, ctx: &EvalCtx<'_, Documents>) -> PolicyEvalResult {
if ctx.subject.id == ctx.resource.owner_id {
ctx.grant("subject owns the document")
} else {
ctx.not_applicable("subject does not own the document")
}
}
fn policy_type(&self) -> Cow<'static, str> {
Cow::Borrowed("OwnerPolicy")
}
}§Tracing
When trace-level events are enabled, checker evaluation records spans for
single-resource and batch evaluation, and each evaluated policy records a
trace! event on the gatehouse::security target. Batch evaluation also
records per-policy counts on nested gatehouse.batch_policy spans.
Structs§
- AndPolicy
- Combines multiple policies with logical AND semantics.
- Batch
Eval Ctx - Batch policy evaluation context.
- Bound
Evaluator - A request-bound evaluator for one checker, subject, action, context, and evaluation session.
- Delegating
Policy - A policy that maps the current domain into a child domain and delegates to
another
PermissionChecker. - Empty
Policies Error - Error returned when no policies are provided to a combinator policy.
- EvalCtx
- Per-item policy evaluation context.
- Eval
Trace - A tree of
PolicyEvalResultnodes capturing every policy decision made during an access evaluation. - Evaluation
Session - Request-scoped fact loading and caching state.
- Fact
Provenance - A record that a policy consulted a fact while reaching its decision.
- Fact
Registry - Reusable fact-source registry for creating request-scoped sessions.
- Fact
Registry Builder - Builder for declaring fact sources once at application setup.
- Lookup
Authorized Page - One page of authorized resources, paired with the next candidate-page cursor.
- Lookup
Page - One page of enumerated candidate IDs.
- NotPolicy
- Inverts the decision of an inner policy.
- OrPolicy
- Combines multiple policies with logical OR semantics.
- Permission
Checker - A policy stack for one
PolicyDomain. - Policy
Batch Item - A borrowed resource passed to batch policy evaluators.
- Policy
Builder - Fluent builder for synchronous predicate policies.
- Rbac
Policy - Role-based access control policy.
- Rebac
Policy - Relationship-based access control backed by request-scoped fact loading.
- Relationship
Query - Canonical fact key for relationship (ReBAC) lookups.
- Security
Rule Metadata - Metadata describing the security rule associated with a
crate::Policy.
Enums§
- Access
Evaluation - The complete result of a permission evaluation. Contains both the final decision and a detailed trace for debugging.
- Combine
Op - The type of boolean combining operation a policy might represent.
- Effect
- The declared effect of a policy: whether it can grant, forbid, or both.
- Fact
Load Error - Error raised while loading a fact.
- Fact
Load Result - Result of loading one fact.
- Fact
Outcome - How a fact load that informed a policy decision resolved.
- Lookup
Authorized Error - Failure modes for
crate::BoundEvaluator::lookup_page. - Policy
Eval Result - The result of evaluating a single policy (or a combination).
Traits§
- FactKey
- A typed fact key that can be loaded through an
crate::EvaluationSession. - Fact
Source - A batched source for one fact key type.
- Hydrator
- Resolves enumerated IDs to caller-owned resources.
- Lookup
Source - Enumerates a candidate superset of resources for a subject.
- Policy
- A generic async trait representing a single authorization policy for one
PolicyDomain. - Policy
Domain - Names the four Rust types that make up one authorization domain.
- Policy
Ext - Fluent combinator helpers for policies.