pub mod allowlist;
pub mod audit;
pub mod authz;
pub mod idempotency;
pub mod limits;
pub mod peer_identity;
pub mod replay;
use crate::config::audit::AuditConfig;
use crate::config::ipc_security::IpcSecurityConfig;
use crate::dashboard::error::DashboardError;
use std::collections::HashMap;
use self::audit::AuditRecord;
use self::idempotency::IdempotencyCache;
use self::limits::TokenBucket;
use self::replay::ReplayWindow;
pub struct IpcSecurityPipeline {
#[allow(dead_code)]
config: IpcSecurityConfig,
audit_config: AuditConfig,
replay_window: ReplayWindow,
rate_limiters: HashMap<String, TokenBucket>,
audit: audit::AuditBackend,
idempotency_cache: IdempotencyCache,
}
pub enum CheckOutcome {
Passed,
Denied(DashboardError),
}
impl IpcSecurityPipeline {
pub fn new(config: IpcSecurityConfig, audit_config: AuditConfig) -> Self {
Self {
replay_window: ReplayWindow::from_config(&config.replay_protection),
rate_limiters: HashMap::new(),
audit: audit::AuditBackend::from_config(&audit_config),
idempotency_cache: IdempotencyCache::from_config(&config.idempotency),
config,
audit_config,
}
}
pub fn check(
&mut self,
method: &str,
request_id: &str,
raw_body_len: usize,
peer_identity: &peer_identity::PeerIdentity,
connection_id: &str,
) -> CheckOutcome {
let rate_limiter = self
.rate_limiters
.entry(connection_id.to_string())
.or_insert_with(|| TokenBucket::from_config(&self.config.rate_limit));
if let Err(err) = rate_limiter.check_rate_limit(&self.config.rate_limit) {
tracing::warn!(
target: "rust_supervisor::ipc::security::rate_limit",
%connection_id,
"rate limit exceeded"
);
return CheckOutcome::Denied(err);
}
if let Err(err) = limits::check_request_size(raw_body_len, &self.config.request_size_limit)
{
tracing::warn!(
target: "rust_supervisor::ipc::security::size_limit",
actual = raw_body_len,
limit = self.config.request_size_limit.max_bytes,
"request too large"
);
return CheckOutcome::Denied(err);
}
if let Err(err) =
peer_identity::verify_peer_identity(peer_identity, &self.config.peer_identity)
{
tracing::warn!(
target: "rust_supervisor::ipc::security::peer_credentials",
peer_uid = peer_identity.uid,
"peer credential check failed"
);
return CheckOutcome::Denied(err);
}
if self.config.replay_protection.enabled
&& let Err(err) = self.replay_window.check_and_record(request_id)
{
tracing::warn!(
target: "rust_supervisor::ipc::security::replay",
%request_id,
"replay detected"
);
return CheckOutcome::Denied(err);
}
if let Err(err) =
authz::verify_authorization(method, peer_identity.uid, &self.config.authorization)
{
tracing::warn!(
target: "rust_supervisor::ipc::security::authorization",
%method,
peer_uid = peer_identity.uid,
"command not authorized"
);
return CheckOutcome::Denied(err);
}
CheckOutcome::Passed
}
pub fn check_idempotency(&self, request_id: &str) -> Option<String> {
if self.config.idempotency.enabled {
self.idempotency_cache.get(request_id)
} else {
None
}
}
pub fn cache_result(&mut self, request_id: &str, response_json: &str) {
if self.config.idempotency.enabled {
self.idempotency_cache
.put(request_id.to_string(), response_json.to_string());
}
}
pub fn write_audit(
&mut self,
method: &str,
peer_identity: &peer_identity::PeerIdentity,
allowed: bool,
denial_error: Option<&DashboardError>,
denial_control_point: &str,
) -> Result<(), DashboardError> {
if !self.audit_config.enabled {
return Ok(());
}
let hash = format!("uid:{}:pid:{}", peer_identity.uid, peer_identity.pid);
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
.to_string();
let record = AuditRecord {
timestamp: now,
method: method.to_string(),
initiator_hash: hash,
correlation_id: None,
allowed,
denial_code: denial_error.map(|e| e.code.clone()),
denial_control_point: if allowed {
None
} else {
Some(denial_control_point.to_string())
},
};
self.audit.write(&record).map_err(|err| {
let count = audit::alerts::increment_failure_count();
tracing::error!(
target: "rust_supervisor::ipc::security::audit",
failure_count = count,
?err,
"audit write failed"
);
err
})
}
}