acls-rs: Algebraic Access Control
Every application that serves more than one user eventually needs access control.
You could scatter if user == "admin" checks throughout your code, but those
checks multiply, contradict each other, and resist refactoring. acls-rs provides
a foundation of composable building blocks that make permission decisions
predictable by construction.
This chapter uses a running example: you are building an internal developer platform where employees deploy services, access servers, and manage infrastructure. The platform starts small and grows, and the access control requirements grow with it.
What acls-rs gives you
Four layers of access control, each building on the previous:
-
Permissions and subjects – define what actions exist (
file:read,database:write) and which users hold them. Permissions form algebraic sets with well-defined union, intersection, and difference operations. Denials explicitly override grants. -
RBAC policies – group permissions into named roles (
developer,admin,ops) with inheritance hierarchies. Assign roles to users instead of managing permissions individually. -
ABAC policies – make access decisions based on runtime attributes (department, network location, clearance level). Useful when static roles cannot capture the policy you need.
-
Temporal permissions – grant access that activates at a future time or expires automatically. Contractor access, maintenance windows, and emergency overrides all fit this model.
Quick start
Add acls-rs to your Cargo.toml:
[dependencies]
acls-rs = "0.1"
With serde support:
[dependencies]
acls-rs = { version = "0.1", features = ["serde"] }
A minimal example that creates a user, grants permissions, and checks access against a protected resource:
use acls_rs::prelude::*;
fn main() {
// Define two permissions for a file-sharing service
let read = AtomicPermission::new("file", "read");
let write = AtomicPermission::new("file", "write");
// Create a subject (user) with those permissions
let alice = Subject::builder()
.id("alice")
.grant(read.clone())
.grant(write.clone())
.build()
.unwrap();
// Define a resource that requires read access
let document = Resource::new("quarterly-report.pdf")
.requires(read.clone())
.resource_type("file");
// Check whether Alice can access the document
assert!(document.can_access(&alice));
}
Algebraic guarantees
All permission operations satisfy formal mathematical properties:
- Associativity: combining three permission sets produces the same result regardless of grouping.
- Commutativity: the order of combination does not matter.
- Idempotence: combining a set with itself is a no-op.
- Identity: combining with the empty set returns the original.
These properties mean you can compose permission sets freely – merge
permissions from multiple roles, intersect them with context-based
restrictions, subtract revocations – and the result is always well-defined.
The library encodes these as Rust traits (Semigroup, Monoid,
MeetSemilattice, JoinSemilattice) with comprehensive property-based tests.
What to read next
- Permissions and Subjects – the fundamental building blocks: atomic permissions, permission sets, grant/denial pairs, subjects, and resources.
- RBAC Policies – role-based access control with inheritance and cycle detection.
- ABAC Policies – attribute-based access control for context-sensitive decisions.
- Temporal Permissions – time-limited access with automatic expiration.