Skip to main content

Crate gatehouse

Crate gatehouse 

Source
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.try_filter(resources).await?;
let authorized_rows = bound.try_filter_by(rows, |row| &row.authz_resource).await?;
let page = bound.try_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 loads facts — via ctx.fact(...) (which also records provenance) or the raw session — such as RebacPolicy or a custom fact-backed policy.

BoundEvaluator::evaluate preserves input order and retains every item and decision. BoundEvaluator::try_filter excludes definite denials but fails on indeterminate decisions; BoundEvaluator::try_filter_by applies the same rule to projected caller-owned rows. Errors retain all input items and evaluations. The explicit filter_lossy / filter_by_lossy helpers deliberately omit outages. The original filter / filter_by names are deprecated aliases.

BoundEvaluator::try_lookup_page enumerates candidate IDs, hydrates them, and strictly authorizes one page. LookupSource must cover every grant path, including admin overrides. On evaluation failure, no next cursor is returned: retry the same cursor with a fresh session after recovery.

§Decision Semantics

Policy returns GrantResult: grant, abstention, or indeterminate. VetoPolicy returns VetoResult: veto, pass, or indeterminate. Register them with PermissionChecker::add_policy and PermissionChecker::add_veto. These types prevent grant policies from returning veto authority.

A definite veto denies access. An unresolved veto blocks grants. After vetoes pass, any grant authorizes, including a grant after another grant policy failed. With no grant, uncertainty yields AccessEvaluation::Indeterminate; otherwise access is denied. An empty checker denies access.

AndPolicy, OrPolicy, and NotPolicy compose grants only. A definite abstention settles AND; a definite grant settles OR; NOT retains uncertainty. Use grant.and(blocked.not()) for a local exclusion. Vetoes use AllOfVeto or AnyOfVeto; a pass settles all-of and a veto settles any-of, otherwise uncertainty remains. Veto negation is not supported.

PolicyBuilder::build produces a grant policy; PolicyBuilder::build_veto produces a veto when its predicate matches. Repeated predicate calls accumulate: every predicate on every axis must hold, so a later call narrows the match and never widens it. A policy with no predicate must be spelled out with PolicyBuilder::allow_all or PolicyBuilder::forbid_all. Register DelegatingPolicy once with PermissionChecker::add_delegate to preserve both child capabilities and their distinct failure semantics.

PolicyEvalResult is the inspectable audit tree, not a policy return type. Typed result constructors compute aggregate decisions; arbitrary raw trees cannot be promoted into typed authority. GrantResult::all and GrantResult::any support custom grant aggregates.

Use AccessEvaluation::into_result to distinguish AccessError::Denied from AccessError::Indeterminate without parsing reasons. Both errors retain the full trace and classified fact errors. The original to_result combines both outcomes into one reason-only callback.

Recorded successful, missing, and failed facts are retained on every node, including aggregates. Recorded errors upgrade abstention/pass to indeterminate; decisive grants and vetoes remain decisive. Attaching evidence has the same behavior for leaves and aggregate nodes. Call ctx.finish(result) when evaluating a policy directly; the checker and built-in combinators do this automatically.

§Cargo Features

tracing is enabled by default. Disable default features to remove its instrumentation and dependency; decisions, returned EvalTrace, and fact provenance remain available. No telemetry events or contract warnings are emitted without tracing. serde independently enables audit serialization.

§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. A Found(false) or Missing fact is an ordinary non-grant; missing sources, backend errors, and fact-source contract violations fail closed as an indeterminate GrantResult — never a grant, and structurally distinguishable from a policy denial.

§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::try_filter or BoundEvaluator::try_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

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, or custom telemetry metadata. Implement VetoPolicy for custom vetoes. A grant policy looks like this:

struct OwnerPolicy;

#[async_trait]
impl Policy<Documents> for OwnerPolicy {
    async fn evaluate(&self, ctx: &EvalCtx<'_, Documents>) -> GrantResult {
        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§

AllOfVeto
Forbids when all veto children forbid.
AndPolicy
Requires every child policy to grant.
AnyOfVeto
Forbids when any veto child forbids.
BatchEvalCtx
Batch policy evaluation context.
BoundEvaluator
A request-bound evaluator for one checker, subject, action, context, and evaluation session.
Conditional
Builder state: at least one predicate has been supplied.
DelegatingPolicy
Maps inputs into a child checker. Install with PermissionChecker::add_delegate.
EmptyPoliciesError
Error returned when no policies are provided to a combinator.
EvalCtx
Per-item policy evaluation context.
EvalTrace
A tree of PolicyEvalResult nodes capturing every policy decision made during an access evaluation.
EvaluationSession
Request-scoped fact loading and caching state.
FactProvenance
A record that a policy consulted a fact while reaching its decision.
FactRegistry
Reusable fact-source registry for creating request-scoped sessions.
FactRegistryBuilder
Builder for declaring fact sources once at application setup.
FilterError
An authorization batch containing at least one indeterminate decision.
GrantResult
A grant policy’s authority-bearing result. Its audit tree is read-only.
LookupAuthorizedPage
One page of authorized resources, paired with the next candidate-page cursor.
LookupPage
One page of enumerated candidate IDs.
NotPolicy
Inverts a grant policy, preserving uncertainty.
OrPolicy
Grants when any child policy grants.
PermissionChecker
Grant and veto policies for one authorization domain.
PolicyBatchItem
A borrowed resource passed to batch policy evaluators.
PolicyBuilder
Fluent builder for synchronous predicate policies.
RbacPolicy
Role-based access control policy.
RebacPolicy
Relationship-based access control backed by request-scoped fact loading.
RelationshipQuery
Canonical fact key for relationship (ReBAC) lookups.
SecurityRuleMetadata
Metadata describing the security rule associated with a crate::Policy.
Unconditional
Builder state: no predicate has been supplied yet.
VetoResult
A veto policy’s authority-bearing result. It cannot grant access.

Enums§

AccessError
A non-grant decision with its full audit evidence.
AccessEvaluation
The complete result of a permission evaluation. Contains both the final decision and a detailed trace for debugging.
CombineOp
The type of boolean combining operation a policy might represent.
Decision
The decision carried by one node of a PolicyEvalResult tree.
FactLoadError
Error raised while loading a fact.
FactLoadErrorKind
Stable, value-erased classification of a FactLoadError.
FactLoadResult
Result of loading one fact.
FactOutcome
How a fact load that informed a policy decision resolved.
LookupAuthorizedError
Failure modes for lookup, hydration, and authorization of one candidate page.
PolicyEvalResult
The inspectable audit representation of a policy decision or aggregate.

Traits§

BuilderState
Marker trait for the PolicyBuilder type-state parameter.
FactKey
A typed fact key that can be loaded through an crate::EvaluationSession.
FactSource
A batched source for one fact key type.
Hydrator
Resolves enumerated IDs to caller-owned resources.
LookupSource
Enumerates a candidate superset of resources for a subject.
Policy
A grant policy for one PolicyDomain.
PolicyDomain
Names the four Rust types that make up one authorization domain.
PolicyExt
Boolean composition for grant policies only.
PolicyResult
Results accepted by recording contexts. Implementations are sealed.
VetoPolicy
A policy that can veto or pass, but cannot grant.
VetoPolicyExt
Composition of veto predicates. Passing never grants permission.