use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::time::Duration;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct HealthCheckOptions {
#[serde(default = "default_health_interval")]
pub interval: Duration,
#[serde(default = "default_health_timeout")]
pub timeout: Duration,
#[serde(default = "default_health_retries")]
pub retries: u32,
#[serde(default = "default_health_start_period")]
pub start_period: Duration,
}
fn default_health_interval() -> Duration {
Duration::from_secs(30)
}
fn default_health_timeout() -> Duration {
Duration::from_secs(10)
}
fn default_health_retries() -> u32 {
3
}
fn default_health_start_period() -> Duration {
Duration::from_secs(60)
}
impl Default for HealthCheckOptions {
fn default() -> Self {
Self {
interval: default_health_interval(),
timeout: default_health_timeout(),
retries: default_health_retries(),
start_period: default_health_start_period(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct SecurityOptions {
pub jailer_enabled: bool,
pub seccomp_enabled: bool,
pub uid: Option<u32>,
pub gid: Option<u32>,
pub new_pid_ns: bool,
pub new_net_ns: bool,
pub chroot_base: PathBuf,
pub chroot_enabled: bool,
pub close_fds: bool,
pub sanitize_env: bool,
pub env_allowlist: Vec<String>,
pub resource_limits: ResourceLimits,
pub sandbox_profile: Option<PathBuf>,
pub network_enabled: bool,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ResourceLimits {
#[serde(default)]
pub max_open_files: Option<u64>,
#[serde(default)]
pub max_file_size: Option<u64>,
#[serde(default)]
pub max_processes: Option<u64>,
#[serde(default)]
pub max_memory: Option<u64>,
#[serde(default)]
pub max_cpu_time: Option<u64>,
}
fn default_chroot_base() -> PathBuf {
PathBuf::from("/srv/boxlite")
}
fn default_network_enabled() -> bool {
true
}
impl Default for SecurityOptions {
fn default() -> Self {
Self {
jailer_enabled: true,
seccomp_enabled: cfg!(target_os = "linux"),
uid: Some(65534), gid: Some(65534), new_pid_ns: cfg!(target_os = "linux"),
new_net_ns: false, chroot_base: default_chroot_base(),
chroot_enabled: cfg!(target_os = "linux"),
close_fds: true,
sanitize_env: true,
env_allowlist: vec!["RUST_LOG".to_string()],
resource_limits: ResourceLimits {
max_open_files: Some(1024),
max_file_size: Some(1024 * 1024 * 1024), max_processes: Some(1024),
max_memory: None, max_cpu_time: None, },
sandbox_profile: None,
network_enabled: default_network_enabled(),
}
}
}
impl SecurityOptions {
pub fn enabled() -> Self {
Self::default()
}
pub fn disabled() -> Self {
Self {
jailer_enabled: false,
seccomp_enabled: false,
uid: None,
gid: None,
new_pid_ns: false,
new_net_ns: false,
chroot_base: default_chroot_base(),
chroot_enabled: false,
close_fds: false,
sanitize_env: false,
env_allowlist: Vec::new(),
resource_limits: ResourceLimits::default(),
sandbox_profile: None,
network_enabled: default_network_enabled(),
}
}
pub fn from_preset(name: &str) -> boxlite_shared::errors::BoxliteResult<Self> {
match name.trim().to_ascii_lowercase().as_str() {
"enable" | "enabled" | "on" => Ok(Self::enabled()),
"disable" | "disabled" | "off" => Ok(Self::disabled()),
other => Err(boxlite_shared::errors::BoxliteError::InvalidArgument(
format!("unknown security setting {other:?}; expected one of enable|disable"),
)),
}
}
pub fn is_full_isolation_available() -> bool {
cfg!(target_os = "linux")
}
pub fn warn_inert_fields(&self) {
#[cfg(not(target_os = "linux"))]
{
let mut ignored = Vec::new();
if self.seccomp_enabled {
ignored.push("seccomp_enabled");
}
if self.new_pid_ns {
ignored.push("new_pid_ns");
}
if self.new_net_ns {
ignored.push("new_net_ns");
}
if self.chroot_enabled {
ignored.push("chroot_enabled");
}
if !ignored.is_empty() {
tracing::warn!(
?ignored,
"SecurityOptions: Linux-only isolation requested but ignored on this non-Linux platform"
);
}
}
#[cfg(not(target_os = "macos"))]
{
if self.sandbox_profile.is_some() {
tracing::warn!(
"SecurityOptions: sandbox_profile is macOS-only and ignored on this platform"
);
}
}
}
pub fn builder() -> SecurityOptionsBuilder {
SecurityOptionsBuilder::new()
}
}
#[derive(Debug, Clone)]
pub struct SecurityOptionsBuilder {
inner: SecurityOptions,
}
impl Default for SecurityOptionsBuilder {
fn default() -> Self {
Self::new()
}
}
impl SecurityOptionsBuilder {
pub fn new() -> Self {
Self {
inner: SecurityOptions::default(),
}
}
pub fn enabled() -> Self {
Self {
inner: SecurityOptions::enabled(),
}
}
pub fn disabled() -> Self {
Self {
inner: SecurityOptions::disabled(),
}
}
pub fn jailer_enabled(&mut self, enabled: bool) -> &mut Self {
self.inner.jailer_enabled = enabled;
self
}
pub fn seccomp_enabled(&mut self, enabled: bool) -> &mut Self {
self.inner.seccomp_enabled = enabled;
self
}
pub fn uid(&mut self, uid: u32) -> &mut Self {
self.inner.uid = Some(uid);
self
}
pub fn gid(&mut self, gid: u32) -> &mut Self {
self.inner.gid = Some(gid);
self
}
pub fn new_pid_ns(&mut self, enabled: bool) -> &mut Self {
self.inner.new_pid_ns = enabled;
self
}
pub fn new_net_ns(&mut self, enabled: bool) -> &mut Self {
self.inner.new_net_ns = enabled;
self
}
pub fn chroot_base(&mut self, path: impl Into<PathBuf>) -> &mut Self {
self.inner.chroot_base = path.into();
self
}
pub fn chroot_enabled(&mut self, enabled: bool) -> &mut Self {
self.inner.chroot_enabled = enabled;
self
}
pub fn close_fds(&mut self, enabled: bool) -> &mut Self {
self.inner.close_fds = enabled;
self
}
pub fn sanitize_env(&mut self, enabled: bool) -> &mut Self {
self.inner.sanitize_env = enabled;
self
}
pub fn env_allowlist(&mut self, vars: Vec<String>) -> &mut Self {
self.inner.env_allowlist = vars;
self
}
pub fn allow_env(&mut self, var: impl Into<String>) -> &mut Self {
self.inner.env_allowlist.push(var.into());
self
}
pub fn resource_limits(&mut self, limits: ResourceLimits) -> &mut Self {
self.inner.resource_limits = limits;
self
}
pub fn max_open_files(&mut self, limit: u64) -> &mut Self {
self.inner.resource_limits.max_open_files = Some(limit);
self
}
pub fn max_file_size_bytes(&mut self, bytes: u64) -> &mut Self {
self.inner.resource_limits.max_file_size = Some(bytes);
self
}
pub fn max_processes(&mut self, limit: u64) -> &mut Self {
self.inner.resource_limits.max_processes = Some(limit);
self
}
pub fn max_memory_bytes(&mut self, bytes: u64) -> &mut Self {
self.inner.resource_limits.max_memory = Some(bytes);
self
}
pub fn max_cpu_time_seconds(&mut self, seconds: u64) -> &mut Self {
self.inner.resource_limits.max_cpu_time = Some(seconds);
self
}
pub fn sandbox_profile(&mut self, path: impl Into<PathBuf>) -> &mut Self {
self.inner.sandbox_profile = Some(path.into());
self
}
pub fn network_enabled(&mut self, enabled: bool) -> &mut Self {
self.inner.network_enabled = enabled;
self
}
pub fn build(&self) -> SecurityOptions {
self.inner.clone()
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct AdvancedBoxOptions {
#[serde(default)]
pub security: SecurityOptions,
#[serde(default)]
pub isolate_mounts: bool,
#[serde(default)]
pub health_check: Option<HealthCheckOptions>,
}