use serde::{Deserialize, Serialize};
#[derive(
Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
#[non_exhaustive]
pub enum IsolationLevel {
#[default]
None = 0,
Wasm = 1,
Process = 2,
Oci = 3,
}
impl IsolationLevel {
pub fn strength(self) -> u8 {
self as u8
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct SandboxPolicy {
pub min_isolation: IsolationLevel,
pub needs_filesystem: bool,
pub needs_network: bool,
pub max_duration_secs: u64,
pub max_memory_bytes: usize,
}
impl SandboxPolicy {
pub fn native() -> Self {
Self {
min_isolation: IsolationLevel::None,
needs_filesystem: false,
needs_network: false,
max_duration_secs: 300,
max_memory_bytes: 0,
}
}
pub fn wasm() -> Self {
Self {
min_isolation: IsolationLevel::Wasm,
needs_filesystem: false,
needs_network: false,
max_duration_secs: 30,
max_memory_bytes: 64 * 1024 * 1024,
}
}
pub fn process() -> Self {
Self {
min_isolation: IsolationLevel::Process,
needs_filesystem: true,
needs_network: false,
max_duration_secs: 60,
max_memory_bytes: 256 * 1024 * 1024,
}
}
pub fn container() -> Self {
Self {
min_isolation: IsolationLevel::Oci,
needs_filesystem: true,
needs_network: true,
max_duration_secs: 120,
max_memory_bytes: 512 * 1024 * 1024,
}
}
pub fn effective_isolation(&self) -> IsolationLevel {
let mut level = self.min_isolation;
if self.needs_network && level < IsolationLevel::Oci {
level = IsolationLevel::Oci;
} else if self.needs_filesystem && level < IsolationLevel::Process {
level = IsolationLevel::Process;
}
level
}
}
impl Default for SandboxPolicy {
fn default() -> Self {
Self::wasm()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn isolation_level_ordering() {
assert!(IsolationLevel::None < IsolationLevel::Wasm);
assert!(IsolationLevel::Wasm < IsolationLevel::Process);
assert!(IsolationLevel::Process < IsolationLevel::Oci);
}
#[test]
fn isolation_strength_values() {
assert_eq!(IsolationLevel::None.strength(), 0);
assert_eq!(IsolationLevel::Wasm.strength(), 1);
assert_eq!(IsolationLevel::Process.strength(), 2);
assert_eq!(IsolationLevel::Oci.strength(), 3);
}
#[test]
fn native_policy_no_sandbox() {
let p = SandboxPolicy::native();
assert_eq!(p.min_isolation, IsolationLevel::None);
assert_eq!(p.effective_isolation(), IsolationLevel::None);
}
#[test]
fn wasm_policy_defaults() {
let p = SandboxPolicy::wasm();
assert_eq!(p.min_isolation, IsolationLevel::Wasm);
assert!(!p.needs_filesystem);
assert!(!p.needs_network);
assert_eq!(p.effective_isolation(), IsolationLevel::Wasm);
}
#[test]
fn process_policy_needs_filesystem() {
let p = SandboxPolicy::process();
assert!(p.needs_filesystem);
assert_eq!(p.effective_isolation(), IsolationLevel::Process);
}
#[test]
fn container_policy_needs_network() {
let p = SandboxPolicy::container();
assert!(p.needs_network);
assert_eq!(p.effective_isolation(), IsolationLevel::Oci);
}
#[test]
fn effective_isolation_upgrades_for_network() {
let p = SandboxPolicy {
min_isolation: IsolationLevel::Wasm,
needs_filesystem: false,
needs_network: true,
max_duration_secs: 30,
max_memory_bytes: 0,
};
assert_eq!(p.effective_isolation(), IsolationLevel::Oci);
}
#[test]
fn effective_isolation_upgrades_for_filesystem() {
let p = SandboxPolicy {
min_isolation: IsolationLevel::Wasm,
needs_filesystem: true,
needs_network: false,
max_duration_secs: 30,
max_memory_bytes: 0,
};
assert_eq!(p.effective_isolation(), IsolationLevel::Process);
}
#[test]
fn effective_isolation_no_downgrade() {
let p = SandboxPolicy {
min_isolation: IsolationLevel::Oci,
needs_filesystem: false,
needs_network: false,
max_duration_secs: 30,
max_memory_bytes: 0,
};
assert_eq!(p.effective_isolation(), IsolationLevel::Oci);
}
#[test]
fn default_policy_is_wasm() {
let p = SandboxPolicy::default();
assert_eq!(p.min_isolation, IsolationLevel::Wasm);
}
#[test]
fn serde_roundtrip() {
let p = SandboxPolicy::container();
let json = serde_json::to_string(&p).unwrap();
let p2: SandboxPolicy = serde_json::from_str(&json).unwrap();
assert_eq!(p2.min_isolation, IsolationLevel::Oci);
assert!(p2.needs_network);
}
}