diff --git a/Cargo.toml b/Cargo.toml index 9bf7386d3..5473aae40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } tracing-appender = "0.2" # Unix/Process -nix = { version = "0.29", features = ["signal", "process", "user"] } +nix = { version = "0.29", features = ["signal", "process", "user", "fs"] } # Serialization serde = { version = "1", features = ["derive"] } diff --git a/crates/navigator-sandbox/src/lib.rs b/crates/navigator-sandbox/src/lib.rs index 6c2d25cb1..01896e8a7 100644 --- a/crates/navigator-sandbox/src/lib.rs +++ b/crates/navigator-sandbox/src/lib.rs @@ -11,7 +11,7 @@ mod sandbox; use miette::{IntoDiagnostic, Result}; use std::time::Duration; use tokio::time::timeout; -use tracing::{error, info}; +use tracing::{debug, error, info}; use crate::policy::NetworkMode; use crate::policy::SandboxPolicy; @@ -42,6 +42,9 @@ pub async fn run_sandbox( // Load policy - either via gRPC or from local file let policy = load_policy(policy_path, sandbox_id, navigator_endpoint).await?; + // Prepare filesystem: create and chown read_write directories + prepare_filesystem(&policy)?; + let _proxy = if matches!(policy.network.mode, NetworkMode::Proxy) { let proxy_policy = policy.network.proxy.as_ref().ok_or_else(|| { miette::miette!("Network mode is set to proxy but no proxy configuration was provided") @@ -112,3 +115,68 @@ async fn load_policy( - --policy (or NAVIGATOR_SANDBOX_POLICY env var)" )) } + +/// Prepare filesystem for the sandboxed process. +/// +/// Creates `read_write` directories if they don't exist and sets ownership +/// to the configured sandbox user/group. This runs as the supervisor (root) +/// before forking the child process. +#[cfg(unix)] +fn prepare_filesystem(policy: &SandboxPolicy) -> Result<()> { + use nix::unistd::{Group, User, chown}; + + let user_name = match policy.process.run_as_user.as_deref() { + Some(name) if !name.is_empty() => Some(name), + _ => None, + }; + let group_name = match policy.process.run_as_group.as_deref() { + Some(name) if !name.is_empty() => Some(name), + _ => None, + }; + + // If no user/group configured, nothing to do + if user_name.is_none() && group_name.is_none() { + return Ok(()); + } + + // Resolve user and group + let uid = if let Some(name) = user_name { + Some( + User::from_name(name) + .into_diagnostic()? + .ok_or_else(|| miette::miette!("Sandbox user not found: {name}"))? + .uid, + ) + } else { + None + }; + + let gid = if let Some(name) = group_name { + Some( + Group::from_name(name) + .into_diagnostic()? + .ok_or_else(|| miette::miette!("Sandbox group not found: {name}"))? + .gid, + ) + } else { + None + }; + + // Create and chown each read_write path + for path in &policy.filesystem.read_write { + if !path.exists() { + debug!(path = %path.display(), "Creating read_write directory"); + std::fs::create_dir_all(path).into_diagnostic()?; + } + + debug!(path = %path.display(), ?uid, ?gid, "Setting ownership on read_write directory"); + chown(path, uid, gid).into_diagnostic()?; + } + + Ok(()) +} + +#[cfg(not(unix))] +fn prepare_filesystem(_policy: &SandboxPolicy) -> Result<()> { + Ok(()) +} diff --git a/docker/Dockerfile.factory b/docker/Dockerfile.factory index 6bd393425..a06b4324a 100644 --- a/docker/Dockerfile.factory +++ b/docker/Dockerfile.factory @@ -75,7 +75,8 @@ ENV PATH="/app/.venv/bin:$PATH" RUN mkdir -p /var/navigator /sandbox /var/log && \ touch /var/log/navigator.log && \ chown supervisor:supervisor /var/log/navigator.log && \ - chmod 0664 /var/log/navigator.log + chmod 0664 /var/log/navigator.log && \ + chown sandbox:sandbox /sandbox # Set navigator-sandbox as the entrypoint ENTRYPOINT ["navigator-sandbox"]