Expand description
§axum-acl
Flexible Access Control List (ACL) middleware for axum 0.8.
This crate provides a configurable ACL system using a 5-tuple rule system:
- Endpoint: Request path (HashMap key for O(1) lookup, or prefix/glob patterns)
- Role:
u32bitmask for up to 32 roles per user - Time: Time windows when rules are active (business hours, weekdays, etc.)
- IP: Client IP address (single IP, CIDR ranges, or lists)
- ID: User/session ID matching (exact or wildcard)
§Features
- Fast endpoint lookup - HashMap for exact matches (O(1)), patterns for prefix/glob
- Efficient role matching -
u32bitmask with single AND operation - Pluggable role extraction - Headers, extensions, or custom extractors
- Time-based access control - Business hours, specific days
- IP-based filtering - Single IP, CIDR notation, lists
- ID matching - Exact match or wildcard for user/session IDs
§Quick Start
use axum::{Router, routing::get};
use axum_acl::{AclLayer, AclTable, AclRuleFilter, AclAction, TimeWindow};
use std::net::SocketAddr;
// Define role bits
const ROLE_ADMIN: u32 = 0b001;
const ROLE_USER: u32 = 0b010;
async fn public_handler() -> &'static str {
"Public content"
}
async fn admin_handler() -> &'static str {
"Admin only"
}
#[tokio::main]
async fn main() {
// Define ACL rules
let acl_table = AclTable::builder()
// Default action when no rules match
.default_action(AclAction::Deny)
// Allow admins to access everything
.add_any(AclRuleFilter::new()
.role_mask(ROLE_ADMIN)
.action(AclAction::Allow)
.description("Admins can access everything"))
// Allow users to access /api/** during business hours
.add_prefix("/api/", AclRuleFilter::new()
.role_mask(ROLE_USER)
.time(TimeWindow::hours_on_days(9, 17, vec![0, 1, 2, 3, 4])) // Mon-Fri 9-5
.action(AclAction::Allow)
.description("Users can access API during business hours"))
// Allow anyone to access /public/** (all roles)
.add_prefix("/public/", AclRuleFilter::new()
.role_mask(u32::MAX)
.action(AclAction::Allow)
.description("Public endpoints"))
.build();
// Build the router with ACL middleware
let app = Router::new()
.route("/public/info", get(public_handler))
.route("/admin/dashboard", get(admin_handler))
.layer(AclLayer::new(acl_table));
// Important: Use into_make_service_with_connect_info for IP extraction
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(
listener,
app.into_make_service_with_connect_info::<SocketAddr>()
).await.unwrap();
}§Rule Evaluation
- Endpoint lookup: Exact matches checked first (O(1) HashMap), then patterns
- Filter matching: For each endpoint match, filters are checked in order:
- ID match:
rule.id == "*"ORrule.id == ctx.id - Role match:
(rule.role_mask & ctx.roles) != 0 - IP match: CIDR-style network matching
- Time match: Current time within window
- ID match:
First matching rule determines the action. Default action used if no match.
§Role Extraction
By default, roles are extracted from the X-Roles header as a u32 bitmask.
The header can contain decimal (e.g., 5) or hex (e.g., 0x1F) values.
use axum_acl::{AclLayer, AclTable, HeaderRoleExtractor};
let table = AclTable::new();
// Use a different header with default roles for anonymous users
let layer = AclLayer::new(table)
.with_extractor(HeaderRoleExtractor::new("X-User-Roles").with_default_roles(0b100));For more complex scenarios, implement the RoleExtractor trait:
use axum_acl::{RoleExtractor, RoleExtractionResult};
use http::Request;
const ROLE_ADMIN: u32 = 0b001;
const ROLE_USER: u32 = 0b010;
struct JwtRoleExtractor;
impl<B> RoleExtractor<B> for JwtRoleExtractor {
fn extract_roles(&self, request: &Request<B>) -> RoleExtractionResult {
// Extract and validate JWT, return the roles bitmask
if let Some(auth) = request.headers().get("Authorization") {
// Parse JWT and extract roles...
RoleExtractionResult::Roles(ROLE_USER)
} else {
RoleExtractionResult::Anonymous
}
}
}§Endpoint Patterns
- Exact:
EndpointPattern::exact("/api/users")- matches only/api/users - Prefix:
EndpointPattern::prefix("/api/")- matches/api/users,/api/posts, etc. - Glob:
EndpointPattern::glob("/api/*/users")- matches/api/v1/users,/api/v2/users*matches exactly one path segment**matches zero or more path segments
- Any:
EndpointPattern::any()- matches all paths
§Time Windows
use axum_acl::TimeWindow;
// Any time (default)
let always = TimeWindow::any();
// 9 AM to 5 PM UTC
let business_hours = TimeWindow::hours(9, 17);
// Monday to Friday, 9 AM to 5 PM UTC
let weekday_hours = TimeWindow::hours_on_days(9, 17, vec![0, 1, 2, 3, 4]);§IP Matching
use axum_acl::IpMatcher;
// Any IP
let any = IpMatcher::any();
// Single IP
let single = IpMatcher::parse("192.168.1.1").unwrap();
// CIDR range
let network = IpMatcher::parse("10.0.0.0/8").unwrap();§Behind a Reverse Proxy
When running behind a reverse proxy, configure the middleware to read the client IP from a header:
use axum_acl::{AclLayer, AclTable};
let table = AclTable::new();
let layer = AclLayer::new(table)
.with_forwarded_ip_header("X-Forwarded-For");§Custom Denied Response
use axum_acl::{AclLayer, AclTable, AccessDeniedHandler, AccessDenied, JsonDeniedHandler};
use axum::response::{Response, IntoResponse};
use http::StatusCode;
// Use the built-in JSON handler
let layer = AclLayer::new(AclTable::new())
.with_denied_handler(JsonDeniedHandler::new());
// Or implement your own
struct CustomHandler;
impl AccessDeniedHandler for CustomHandler {
fn handle(&self, denied: &AccessDenied) -> Response {
(StatusCode::FORBIDDEN, "Custom denied message").into_response()
}
}§Dynamic Rules
Implement AclRuleProvider to load rules from external sources:
use axum_acl::{AclRuleProvider, RuleEntry, AclRuleFilter, AclTable, AclAction, EndpointPattern};
const ROLE_ADMIN: u32 = 0b001;
struct DatabaseRuleProvider {
// connection pool, etc.
}
impl DatabaseRuleProvider {
fn load_rules(&self) -> Result<Vec<RuleEntry>, std::io::Error> {
// Query database for rules
Ok(vec![
RuleEntry::any(AclRuleFilter::new()
.role_mask(ROLE_ADMIN)
.action(AclAction::Allow))
])
}
}
// Use with the table builder
fn build_table(provider: &DatabaseRuleProvider) -> AclTable {
let rules = provider.load_rules().unwrap();
let mut builder = AclTable::builder();
for entry in rules {
builder = builder.add_pattern(entry.pattern, entry.filter);
}
builder.build()
}Modules§
- prelude
- Prelude module for convenient imports.
Structs§
- Access
Denied - Error returned when access is denied by the ACL middleware.
- AclConfig
- Configuration for the ACL middleware.
- AclLayer
- A Tower layer that adds ACL middleware to a service.
- AclMiddleware
- The ACL middleware service.
- AclRule
Filter - ACL rule filter for the 5-tuple matching system.
- AclTable
- A table containing ACL rules for evaluation.
- AclTable
Builder - Builder for constructing an
AclTable. - Anonymous
IdExtractor - An ID extractor that always returns anonymous (no ID).
- Anonymous
Role Extractor - A role extractor that always returns anonymous (no roles).
- Chained
Role Extractor - A composite extractor that tries multiple extractors in order.
- Config
Settings - Global configuration settings.
- Default
Denied Handler - Default handler that returns a plain text 403 response.
- Extension
IdExtractor - Extract ID from a request extension.
- Extension
Role Extractor - Extract roles from a request extension.
- Fixed
IdExtractor - An ID extractor that always returns a fixed ID.
- Fixed
Role Extractor - A roles extractor that always returns fixed roles.
- Header
IdExtractor - Extract ID from an HTTP header.
- Header
Role Extractor - Extract roles bitmask from an HTTP header.
- Json
Denied Handler - Handler that returns a JSON error response.
- Request
Context - Request context for ACL evaluation.
- Rule
Config - A single rule configuration.
- Rule
Entry - Rule entry for providers: endpoint pattern + filter.
- Static
Rule Provider - A simple rule provider that returns a static list of rules.
- Time
Window - Time window specification for rule matching.
- Toml
Config - Configuration file structure.
Enums§
- AclAction
- Action to take when a rule matches.
- AclError
- Error type for ACL operations.
- Config
Error - Error type for configuration parsing.
- Endpoint
Pattern - Endpoint pattern for rule matching.
- IdExtraction
Result - Result of ID extraction.
- IpMatcher
- IP address specification for rule matching.
- Role
Extraction Result - Result of role extraction.
Constants§
- ROLE_
ANONYMOUS - Role bitmask constant for anonymous/unauthenticated users.
Traits§
- Access
Denied Handler - Custom response handler for access denied errors.
- AclRule
Provider - Trait for types that can provide ACL rules.
- IdExtractor
- Trait for extracting user/resource ID from HTTP requests.
- Role
Extractor - Trait for extracting roles from HTTP requests.