use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Capability {
FsRead,
FsWrite,
NetworkOut,
ProcessExec,
EnvRead,
EnvWrite,
Clock,
Random,
MemoryLimit(u64),
CpuLimit(u64),
}
#[derive(Debug, Clone, Default)]
pub struct CapabilitySet {
pub allowed: HashSet<Capability>,
pub denied: HashSet<Capability>,
}
impl CapabilitySet {
pub fn new() -> Self {
Self::default()
}
pub fn unrestricted() -> Self {
let mut allowed = HashSet::new();
allowed.insert(Capability::FsRead);
allowed.insert(Capability::FsWrite);
allowed.insert(Capability::NetworkOut);
allowed.insert(Capability::ProcessExec);
allowed.insert(Capability::EnvRead);
allowed.insert(Capability::EnvWrite);
allowed.insert(Capability::Clock);
allowed.insert(Capability::Random);
Self {
allowed,
denied: HashSet::new(),
}
}
pub fn sandboxed() -> Self {
let mut allowed = HashSet::new();
allowed.insert(Capability::Clock);
allowed.insert(Capability::Random);
allowed.insert(Capability::MemoryLimit(16 * 1024 * 1024)); allowed.insert(Capability::CpuLimit(5000)); Self {
allowed,
denied: HashSet::new(),
}
}
pub fn allow(&mut self, cap: Capability) -> &mut Self {
self.allowed.insert(cap.clone());
self.denied.remove(&cap);
self
}
pub fn deny(&mut self, cap: Capability) -> &mut Self {
self.denied.insert(cap.clone());
self.allowed.remove(&cap);
self
}
pub fn check(&self, cap: &Capability) -> bool {
self.allowed.contains(cap) && !self.denied.contains(cap)
}
pub fn merge(&mut self, other: &CapabilitySet) -> &mut Self {
for cap in &other.allowed {
if !self.denied.contains(cap) {
self.allowed.insert(cap.clone());
}
}
for cap in &other.denied {
self.allowed.remove(cap);
self.denied.insert(cap.clone());
}
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_capability_set() {
let caps = CapabilitySet::new();
assert!(!caps.check(&Capability::FsRead));
assert!(!caps.check(&Capability::NetworkOut));
}
#[test]
fn test_unrestricted_capability_set() {
let caps = CapabilitySet::unrestricted();
assert!(caps.check(&Capability::FsRead));
assert!(caps.check(&Capability::NetworkOut));
assert!(caps.check(&Capability::ProcessExec));
}
#[test]
fn test_sandboxed_capability_set() {
let caps = CapabilitySet::sandboxed();
assert!(!caps.check(&Capability::FsRead));
assert!(!caps.check(&Capability::FsWrite));
assert!(caps.check(&Capability::Clock));
assert!(caps.check(&Capability::Random));
}
#[test]
fn test_deny_overrides_allow() {
let mut caps = CapabilitySet::unrestricted();
caps.deny(Capability::FsWrite);
assert!(!caps.check(&Capability::FsWrite));
assert!(caps.check(&Capability::FsRead));
}
#[test]
fn test_capability_merge() {
let mut caps1 = CapabilitySet::new();
caps1.allow(Capability::FsRead);
let caps2 = CapabilitySet::sandboxed();
caps1.merge(&caps2);
assert!(caps1.check(&Capability::FsRead));
assert!(caps1.check(&Capability::Clock));
}
}