diff --git a/backend/windmill-worker/nsjail/install.powershell.config.proto b/backend/windmill-worker/nsjail/install.powershell.config.proto new file mode 100644 index 0000000000..1ea6be4475 --- /dev/null +++ b/backend/windmill-worker/nsjail/install.powershell.config.proto @@ -0,0 +1,123 @@ +name: "powershell install" + +mode: ONCE +hostname: "powershell" +log_level: ERROR +time_limit: {TIMEOUT} + +disable_rl: true + +cwd: "/tmp" + +clone_newnet: false +clone_newuser: {CLONE_NEWUSER} + +skip_setsid: true +keep_caps: false +keep_env: true +mount_proc: true + +mount { + src: "/bin" + dst: "/bin" + is_bind: true +} + +mount { + src: "/proc/self/fd" + dst: "/dev/fd" + is_symlink: true + mandatory: false +} + +mount { + src: "/opt/microsoft" + dst: "/opt/microsoft" + is_bind: true +} + +mount { + src: "/lib" + dst: "/lib" + is_bind: true +} + + +mount { + src: "/lib64" + dst: "/lib64" + is_bind: true + mandatory: false +} + + +mount { + src: "/usr" + dst: "/usr" + is_bind: true +} + +mount { + src: "/dev/null" + dst: "/dev/null" + is_bind: true + rw: true +} + +{TMP_MOUNT_BLOCK} + +mount { + src: "/etc" + dst: "/etc" + is_bind: true +} + +# Container runtimes bind exactly these 3 files as separate submounts over +# /etc; nsjail's ro remount of /etc is non-recursive so they stay writable. +# Load-bearing -- do not remove as redundant with the /etc bind above. +mount { + src: "/etc/resolv.conf" + dst: "/etc/resolv.conf" + is_bind: true + mandatory: false +} + +mount { + src: "/etc/hosts" + dst: "/etc/hosts" + is_bind: true + mandatory: false +} + +mount { + src: "/etc/hostname" + dst: "/etc/hostname" + is_bind: true + mandatory: false +} + +mount { + src: "/dev/random" + dst: "/dev/random" + is_bind: true +} + +mount { + src: "/dev/urandom" + dst: "/dev/urandom" + is_bind: true +} + +iface_no_lo: true + +# Writable, unlike the run step: the install step downloads modules into the +# shared PowerShell cache here. +mount { + src: "{CACHE_DIR}" + dst: "{CACHE_DIR}" + is_bind: true + rw: true + mandatory: false +} + +envar: "HOME=/tmp" diff --git a/backend/windmill-worker/src/pwsh_executor.rs b/backend/windmill-worker/src/pwsh_executor.rs index 0bd02985ae..cac7fd62e2 100644 --- a/backend/windmill-worker/src/pwsh_executor.rs +++ b/backend/windmill-worker/src/pwsh_executor.rs @@ -18,6 +18,8 @@ use crate::SYSTEM_ROOT; const NSJAIL_CONFIG_RUN_POWERSHELL_CONTENT: &str = include_str!("../nsjail/run.powershell.config.proto"); +const NSJAIL_CONFIG_INSTALL_POWERSHELL_CONTENT: &str = + include_str!("../nsjail/install.powershell.config.proto"); lazy_static::lazy_static! { static ref RE_POWERSHELL_IMPORTS: Regex = Regex::new(r#"^\s*Import-Module\s+(?:-Name\s+)?"?([^\s"]+)"?(?:\s+-RequiredVersion\s+"?([^\s"]+)"?)?"#).unwrap(); @@ -563,12 +565,51 @@ pub async fn handle_powershell_job( &powershell_repo_pat.unwrap_or_default(), ) .replace("{modules}", &modules_list); - let mut cmd = Command::new(POWERSHELL_PATH.as_str()); - cmd.args(&["-Command", &install_string]) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); + // Sandbox the install step under nsjail when sandboxing is enabled, matching + // the run step. The install step previously always ran with full worker + // privileges; the install nsjail config mounts the module cache rw so + // Save-PSResource can still write to it. + let install_jailed = is_sandboxing_enabled(); + let mut cmd = if install_jailed { + let nsjail_timeout = + resolve_nsjail_timeout(db, &job.workspace_id, job.id, job.timeout).await; + let nsjail_proto = format!("{}.pwsh_install.config.proto", job.id); + let config_content = NSJAIL_CONFIG_INSTALL_POWERSHELL_CONTENT + .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()) + .replace("{CACHE_DIR}", &*POWERSHELL_CACHE_DIR) + .replace( + "{TMP_MOUNT_BLOCK}", + &resolve_nsjail_tmp_mount_block(job_dir).await, + ) + .replace("{TIMEOUT}", &nsjail_timeout); + write_file(job_dir, &nsjail_proto, &config_content)?; + let mut cmd = Command::new(NSJAIL_PATH.as_str()); + cmd.current_dir(job_dir).args(&[ + "--config", + &nsjail_proto, + "--", + POWERSHELL_PATH.as_str(), + "-Command", + &install_string, + ]); + cmd + } else { + let mut cmd = Command::new(POWERSHELL_PATH.as_str()); + cmd.args(&["-Command", &install_string]); + cmd + }; + cmd.stdout(Stdio::piped()).stderr(Stdio::piped()); - let child = start_child_process(cmd, POWERSHELL_PATH.as_str(), false).await?; + let child = start_child_process( + cmd, + if install_jailed { + NSJAIL_PATH.as_str() + } else { + POWERSHELL_PATH.as_str() + }, + false, + ) + .await?; handle_child( &job.id,