Temporal HBAC Rules
A contractor needs SSH access to development servers for three months. The ops team needs access to all hosts during a four-hour maintenance window on Saturday night. An on-call engineer needs emergency access for 24 hours after an incident.
TemporalHbacRule wraps a standard HBAC rule with a validity window. The
rule participates in evaluation only when the current time falls within that
window.
Creating a temporal rule
A TemporalHbacRule takes an HbacRule and optional start/end timestamps
(milliseconds since the Unix epoch):
#![allow(unused)]
fn main() {
use hbac_rs::prelude::*;
let now = current_timestamp_millis();
let three_months = 90 * 24 * 60 * 60 * 1000_u64;
let rule = HbacRule::builder("contractor_access")
.user("contractor-jane")
.host_group("dev-servers")
.service_category_all()
.enabled(true)
.build()
.unwrap();
let temporal = TemporalHbacRule::new(
rule,
Some(now), // active immediately
Some(now + three_months), // expires in 3 months
);
assert!(temporal.is_currently_valid());
}
Convenience constructors
For common patterns, named constructors save boilerplate:
#![allow(unused)]
fn main() {
use hbac_rs::prelude::*;
let rule = HbacRule::builder("emergency_admin")
.user("alice")
.host_category_all()
.service_category_all()
.enabled(true)
.build()
.unwrap();
// Valid for the next 24 hours from now
let emergency = TemporalHbacRule::valid_for_duration(
rule,
24 * 60 * 60 * 1000, // 24 hours in milliseconds
);
}
All constructors:
| Constructor | Description |
|---|---|
new(rule, from, until) | Full control over start and end |
valid_for_duration(rule, ms) | Starts now, expires after the given duration |
valid_from(rule, timestamp) | Starts at the given time, never expires |
valid_until(rule, timestamp) | Starts immediately, expires at the given time |
Adding to a policy
Use add_temporal_rule on the policy:
#![allow(unused)]
fn main() {
use hbac_rs::prelude::*;
let mut policy = HbacPolicy::new();
// ... create temporal rule as above ...
policy.add_temporal_rule(temporal);
}
Temporal rules coexist with permanent rules in the same policy.
Evaluating at a specific time
Use evaluate_at to evaluate against a specific timestamp instead of “now”:
#![allow(unused)]
fn main() {
use hbac_rs::prelude::*;
let now = current_timestamp_millis();
let request = HbacRequest::new(
Subject::new("contractor-jane"),
"dev-01.example.com",
"sshd",
);
// Evaluate at the current time
let result = policy.evaluate_at(&request, now);
assert!(result.is_allowed());
// Evaluate after the rule has expired
let four_months = 120 * 24 * 60 * 60 * 1000_u64;
let result = policy.evaluate_at(&request, now + four_months);
assert!(result.is_denied());
}
There is also evaluate_detailed_at for debugging.
Checking validity
Query whether a temporal rule is active at a given time:
#![allow(unused)]
fn main() {
use hbac_rs::prelude::*;
let now = current_timestamp_millis();
// Is the rule active right now?
assert!(temporal.is_currently_valid());
// Is it active at a specific time?
assert!(temporal.is_valid_at(now + 1000));
// Get the underlying HbacRule only if currently valid
if let Some(rule) = temporal.current_rule() {
println!("Active rule: {}", rule.name);
}
// Get the rule at a specific time
if let Some(rule) = temporal.rule_at(now + 1000) {
println!("Rule active at time: {}", rule.name);
}
}
Maintenance window example
Grant the ops team access to all hosts for a four-hour maintenance window:
#![allow(unused)]
fn main() {
use hbac_rs::prelude::*;
let window_start = 1737338400_000_u64; // 2025-01-20 02:00 UTC
let window_end = 1737352800_000_u64; // 2025-01-20 06:00 UTC
let rule = HbacRule::builder("maintenance_window")
.user_group("ops-team")
.host_category_all()
.service_category_all()
.enabled(true)
.build()
.unwrap();
let temporal = TemporalHbacRule::new(rule, Some(window_start), Some(window_end));
let mut policy = HbacPolicy::new();
policy.add_temporal_rule(temporal);
let ops_user = Subject::new("ops-alice");
let request = HbacRequest::new(ops_user, "prod-db-01.example.com", "sshd");
// During the window: allowed
let mid_window = window_start + 2 * 60 * 60 * 1000;
assert!(policy.evaluate_at(&request, mid_window).is_allowed());
// After the window: denied
assert!(policy.evaluate_at(&request, window_end + 1).is_denied());
}
Emergency access example
Grant immediate access that auto-expires after 24 hours:
#![allow(unused)]
fn main() {
use hbac_rs::prelude::*;
let rule = HbacRule::builder("incident_response")
.user("oncall-bob")
.host_category_all()
.service_category_all()
.enabled(true)
.build()
.unwrap();
let temporal = TemporalHbacRule::valid_for_duration(
rule,
24 * 60 * 60 * 1000, // 24 hours
);
policy.add_temporal_rule(temporal);
}
Recurring schedules
TemporalHbacRule models a single time window. Recurring schedules (e.g.,
“business hours only, Monday through Friday”) are not built in. To implement
recurring access, manage the temporal rules yourself:
#![allow(unused)]
fn main() {
use hbac_rs::prelude::*;
fn create_business_hours_rule(date_start_ms: u64, date_end_ms: u64) -> TemporalHbacRule {
let rule = HbacRule::builder("business_hours")
.user_category_all()
.host_category_all()
.service("internal-app")
.enabled(true)
.build()
.unwrap();
TemporalHbacRule::new(rule, Some(date_start_ms), Some(date_end_ms))
}
// Generate rules for each business day and add them to the policy
}
Best practices
- Always set an expiration. Open-ended temporary access defeats the purpose.
- Use UTC timestamps internally. Convert to local time only for display.
- Build in a small grace period to account for clock skew between hosts.
- Log temporal grants and expirations for your audit trail.
- Periodically remove expired temporal rules to keep the policy lean.
Next steps
Your organization now has HBAC for host access and RBAC for application permissions. The final HBAC chapter shows how to combine them into a unified policy where both must agree before granting access.
See Policy Composition.