ABAC Policies
Your platform’s security team mandates a new policy: database write access is only allowed from the corporate network during business hours. Roles cannot express this – a “database-writer” role either grants the permission or it does not, regardless of where or when the request originates.
Attribute-Based Access Control (ABAC) solves this by making access decisions based on runtime attributes: who the user is, what they are trying to access, and the environment in which the request occurs.
Attribute context
An AttributeContext is a HashMap<String, String> that carries the runtime
attributes available at decision time. You populate it from whatever sources
your application has – request headers, environment variables, identity
provider claims:
#![allow(unused)]
fn main() {
use std::collections::HashMap;
use acls_rs::policy::AttributeContext;
let mut context = AttributeContext::new();
context.insert("department".into(), "engineering".into());
context.insert("network".into(), "corporate".into());
context.insert("time_of_day".into(), "business-hours".into());
context.insert("clearance".into(), "standard".into());
}
Attribute rules
An AttributeRule maps a set of required attributes to permissions that are
granted (or denied) when those attributes are present and match. The rule
checks whether every key-value pair in the rule’s attribute map appears in the
context.
#![allow(unused)]
fn main() {
use std::collections::HashMap;
use acls_rs::prelude::*;
// Grant database:write when network=corporate AND time_of_day=business-hours
let rule = AttributeRule::new()
.grant(
AtomicPermission::new("database", "write"),
HashMap::from([
("network".into(), "corporate".into()),
("time_of_day".into(), "business-hours".into()),
]),
);
}
Rules can also carry denials. A denial matches when its attribute requirements
are met and overrides any corresponding grant, just like GrantDenialPair:
#![allow(unused)]
fn main() {
use std::collections::HashMap;
use acls_rs::prelude::*;
// Deny production:deploy when environment=staging
let rule = AttributeRule::new()
.deny(
AtomicPermission::new("production", "deploy"),
HashMap::from([
("environment".into(), "staging".into()),
]),
);
}
You can chain multiple grants and denials on a single rule:
#![allow(unused)]
fn main() {
use std::collections::HashMap;
use acls_rs::prelude::*;
let rule = AttributeRule::new()
.grant(
AtomicPermission::new("api", "read"),
HashMap::from([("authenticated".into(), "true".into())]),
)
.grant(
AtomicPermission::new("api", "write"),
HashMap::from([
("authenticated".into(), "true".into()),
("role".into(), "editor".into()),
]),
)
.deny(
AtomicPermission::new("api", "admin"),
HashMap::from([("role".into(), "guest".into())]),
);
}
Building a policy
An AbacPolicy collects rules and evaluates them against a context. By
default, when multiple rules produce conflicting results, the policy uses
MeetResolver (intersection – the most restrictive outcome wins):
#![allow(unused)]
fn main() {
use std::collections::HashMap;
use acls_rs::prelude::*;
let corp_db_rule = AttributeRule::new()
.grant(
AtomicPermission::new("database", "write"),
HashMap::from([
("network".into(), "corporate".into()),
("time_of_day".into(), "business-hours".into()),
]),
);
let read_anywhere = AttributeRule::new()
.grant(
AtomicPermission::new("database", "read"),
HashMap::from([("authenticated".into(), "true".into())]),
);
let mut policy = AbacPolicy::new();
policy.add_rule(corp_db_rule);
policy.add_rule(read_anywhere);
// Context: corporate network, business hours, authenticated
let mut context: AttributeContext = HashMap::new();
context.insert("network".into(), "corporate".into());
context.insert("time_of_day".into(), "business-hours".into());
context.insert("authenticated".into(), "true".into());
let result = policy.evaluate(&context);
assert!(result.has_permission(&AtomicPermission::new("database", "write")));
assert!(result.has_permission(&AtomicPermission::new("database", "read")));
// Context: home network -- database:write is not granted
let mut home_context: AttributeContext = HashMap::new();
home_context.insert("network".into(), "home".into());
home_context.insert("authenticated".into(), "true".into());
let result = policy.evaluate(&home_context);
assert!(!result.has_permission(&AtomicPermission::new("database", "write")));
assert!(result.has_permission(&AtomicPermission::new("database", "read")));
}
Conflict resolution
When multiple rules grant and deny the same permission, the conflict resolver determines the outcome. Two resolvers are available:
MeetResolver(default): intersection of all rule results. The most restrictive interpretation wins. Use this for high-security environments.JoinResolver: union of all rule results. The most permissive interpretation wins. Use this for convenience-first environments.
#![allow(unused)]
fn main() {
use acls_rs::prelude::*;
// Use JoinResolver for a more permissive policy
let policy = AbacPolicy::with_resolver(JoinResolver);
}
Combining ABAC with RBAC
ABAC and RBAC address different concerns. A practical pattern is to use RBAC for static role assignments and ABAC for dynamic refinement:
#![allow(unused)]
fn main() {
use std::collections::HashMap;
use acls_rs::prelude::*;
// RBAC: resolve base permissions from roles
let mut rbac = RbacPolicy::new();
// ... add roles ...
let roles = vec!["developer".to_string()];
let role_perms = rbac.effective_permissions(&roles).unwrap();
// ABAC: evaluate context-dependent permissions
let mut abac = AbacPolicy::new();
// ... add rules ...
let context: AttributeContext = HashMap::new();
// ... populate context ...
let context_perms = abac.evaluate(&context).effective_permissions();
// Intersect: user gets only what both RBAC and ABAC agree on
let effective = role_perms.meet(context_perms);
}
Next steps
ABAC handles context at decision time, but it does not handle time itself. Your platform hires contractors who need access for exactly three months, and on-call engineers who need emergency admin access for 24 hours. The next chapter introduces temporal permissions – access that activates and expires automatically.
See Temporal Permissions.