mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-19 16:03:29 +00:00
feat: add a no_network annotation for sandboxed jobs (#11844)
* feat: add a no_network annotation for sandboxed python, bash, bun and r jobs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: keep nodejs relative imports and dedicated workers working under no_network Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: bundle bun no_network jobs so relative imports resolve before the run Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: point ee-repo-ref at the runner group no_network fix Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor: state the no_network token rule once Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: refuse no_network jobs whose workspace imports would be fetched at run time Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: let type-only workspace imports through the no_network import check Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: update ee-repo-ref to ca4a1959a0d2257d413a57349939053ac3473c6b This commit updates the EE repository reference after PR #895 was merged in windmill-ee-private. Previous ee-repo-ref: 3841923cfd117b6b907ac3e84b26f07bde41f8ed New ee-repo-ref: ca4a1959a0d2257d413a57349939053ac3473c6b Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
windmill-internal-app[bot]
parent
b0c97b34ca
commit
feb7b1ad90
@@ -1 +1 @@
|
||||
455681e55eb7cdfcfdb7cd79cbae75b333e07943
|
||||
ca4a1959a0d2257d413a57349939053ac3473c6b
|
||||
|
||||
@@ -189,7 +189,20 @@ pub fn parse_expr_for_imports(code: &str, skip_type_only: bool) -> anyhow::Resul
|
||||
/// // Returns: ["f/utils"]
|
||||
/// ```
|
||||
pub fn parse_relative_imports(code: &str, path: &str) -> anyhow::Result<Vec<String>> {
|
||||
let imports = parse_expr_for_imports(code, false)?;
|
||||
resolve_relative_imports(code, path, false)
|
||||
}
|
||||
|
||||
/// [`parse_relative_imports`] without type-only imports, which are erased before the code runs.
|
||||
pub fn parse_runtime_relative_imports(code: &str, path: &str) -> anyhow::Result<Vec<String>> {
|
||||
resolve_relative_imports(code, path, true)
|
||||
}
|
||||
|
||||
fn resolve_relative_imports(
|
||||
code: &str,
|
||||
path: &str,
|
||||
skip_type_only: bool,
|
||||
) -> anyhow::Result<Vec<String>> {
|
||||
let imports = parse_expr_for_imports(code, skip_type_only)?;
|
||||
let script_dir = path.rsplit_once('/').map(|(dir, _)| dir).unwrap_or("");
|
||||
|
||||
let mut resolved: Vec<String> = imports
|
||||
|
||||
@@ -4,7 +4,7 @@ mod tests {
|
||||
use windmill_parser::{Arg, MainArgSignature, ObjectProperty, ObjectType, Typ};
|
||||
use windmill_parser_ts::{
|
||||
parse_deno_signature, parse_expr_for_imports, parse_relative_imports,
|
||||
remove_pinned_import_specifiers,
|
||||
parse_runtime_relative_imports, remove_pinned_import_specifiers,
|
||||
};
|
||||
|
||||
/// The server parses defaults natively, where a non-JSON literal cannot be evaluated
|
||||
@@ -944,6 +944,19 @@ export const label = "pkg@1.2.3";
|
||||
assert_eq!(result, vec!["f/folder/helper"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_runtime_relative_imports_skip_type_only() {
|
||||
let code = r#"
|
||||
import type { Shape } from "./types";
|
||||
import { helper } from "./helper";
|
||||
export async function main(s: Shape) { return helper(s); }
|
||||
"#;
|
||||
let result = parse_runtime_relative_imports(code, "f/folder/script").unwrap();
|
||||
assert_eq!(result, vec!["f/folder/helper"]);
|
||||
let all = parse_relative_imports(code, "f/folder/script").unwrap();
|
||||
assert_eq!(all, vec!["f/folder/helper", "f/folder/types"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_imports_double_dot() {
|
||||
let code = r#"
|
||||
|
||||
@@ -1199,6 +1199,7 @@ pub struct RlangAnnotations {
|
||||
pub renv_verbose: bool,
|
||||
pub renv_install_verbose: bool,
|
||||
pub sandbox: bool,
|
||||
pub no_network: bool,
|
||||
}
|
||||
|
||||
#[annotations("#")]
|
||||
@@ -1214,6 +1215,7 @@ pub struct PythonAnnotations {
|
||||
pub py314: bool,
|
||||
pub py315: bool,
|
||||
pub sandbox: bool,
|
||||
pub no_network: bool,
|
||||
}
|
||||
|
||||
#[annotations("//")]
|
||||
@@ -1324,6 +1326,7 @@ impl SqlAnnotations {
|
||||
pub struct BashAnnotations {
|
||||
pub docker: bool,
|
||||
pub sandbox: bool,
|
||||
pub no_network: bool,
|
||||
}
|
||||
|
||||
impl BashAnnotations {
|
||||
@@ -3834,6 +3837,22 @@ mod tests {
|
||||
assert!(combined.prepare);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_network_annotation() {
|
||||
assert!(PythonAnnotations::parse("# sandbox\n# no_network\ndef main(): pass").no_network);
|
||||
assert!(!PythonAnnotations::parse("# sandbox\ndef main(): pass").no_network);
|
||||
assert!(RlangAnnotations::parse("#no_network\nmain <- function() {}").no_network);
|
||||
assert!(
|
||||
TypeScriptAnnotations::parse("// no_network\nexport function main() {}").no_network
|
||||
);
|
||||
let bash = "# sandbox alpine\n# no_network\necho hi";
|
||||
assert!(BashAnnotations::parse(bash).no_network);
|
||||
assert_eq!(
|
||||
BashAnnotations::sandbox_image(bash),
|
||||
Some("alpine".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_python_sandbox_annotation_with_other_annotations() {
|
||||
let content = "# no_cache\n# sandbox\ndef main():\n pass";
|
||||
|
||||
@@ -9,7 +9,7 @@ disable_rl: true
|
||||
|
||||
cwd: "/tmp"
|
||||
|
||||
clone_newnet: false
|
||||
clone_newnet: {CLONE_NEWNET}
|
||||
clone_newuser: {CLONE_NEWUSER}
|
||||
|
||||
skip_setsid: true
|
||||
|
||||
@@ -11,7 +11,7 @@ mount_proc: true
|
||||
|
||||
cwd: "/tmp/{LANG}"
|
||||
|
||||
clone_newnet: false
|
||||
clone_newnet: {CLONE_NEWNET}
|
||||
clone_newuser: {CLONE_NEWUSER}
|
||||
clone_newcgroup: false
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ disable_rl: true
|
||||
|
||||
cwd: {WORKDIR}
|
||||
|
||||
clone_newnet: false
|
||||
clone_newnet: {CLONE_NEWNET}
|
||||
clone_newuser: {CLONE_NEWUSER}
|
||||
|
||||
skip_setsid: true
|
||||
|
||||
@@ -12,7 +12,7 @@ rlimit_nofile: 10000
|
||||
|
||||
cwd: "/tmp"
|
||||
|
||||
clone_newnet: false
|
||||
clone_newnet: {CLONE_NEWNET}
|
||||
clone_newuser: {CLONE_NEWUSER}
|
||||
|
||||
skip_setsid: true
|
||||
|
||||
@@ -8,7 +8,7 @@ disable_rl: true
|
||||
|
||||
cwd: "/tmp"
|
||||
|
||||
clone_newnet: false
|
||||
clone_newnet: {CLONE_NEWNET}
|
||||
clone_newuser: {CLONE_NEWUSER}
|
||||
|
||||
skip_setsid: true
|
||||
|
||||
@@ -39,15 +39,16 @@ const NSJAIL_CONFIG_RUN_BASH_CONTENT: &str = include_str!("../nsjail/run.bash.co
|
||||
use crate::handle_child::run_future_with_polling_update_job_poller;
|
||||
|
||||
use crate::{
|
||||
check_no_network_jailed,
|
||||
common::{
|
||||
build_args_map, build_command_with_isolation, get_reserved_variables, raw_to_string,
|
||||
read_file, read_file_content, resolve_nsjail_timeout, resolve_nsjail_tmp_mount_block,
|
||||
start_child_process, OccupancyMetrics, DEV_CONF_NSJAIL,
|
||||
},
|
||||
get_proxy_envs_for_lang,
|
||||
get_proxy_envs_for_lang, get_run_proxy_envs,
|
||||
handle_child::handle_child,
|
||||
is_sandboxing_enabled, DISABLE_NUSER, HOME_ENV, NSJAIL_AVAILABLE, NSJAIL_PATH, PATH_ENV,
|
||||
TRACING_PROXY_CA_CERT_PATH,
|
||||
is_sandboxing_enabled, nsjail_clone_newnet, DISABLE_NUSER, HOME_ENV, NSJAIL_AVAILABLE,
|
||||
NSJAIL_PATH, PATH_ENV, TRACING_PROXY_CA_CERT_PATH,
|
||||
};
|
||||
use windmill_common::client::AuthedClient;
|
||||
use windmill_common::scripts::ScriptLang;
|
||||
@@ -94,6 +95,7 @@ pub async fn handle_bash_job(
|
||||
if let Some(image) = windmill_common::worker::BashAnnotations::sandbox_image(content) {
|
||||
return crate::docker_v2::handle_docker_v2_job(
|
||||
&image,
|
||||
annotation.no_network,
|
||||
mem_peak,
|
||||
canceled_by,
|
||||
job,
|
||||
@@ -115,6 +117,13 @@ pub async fn handle_bash_job(
|
||||
// `ssh_target` resource instead of on this worker. The OSS build returns a
|
||||
// clear "enterprise feature" error from the stub.
|
||||
if let Some(ssh_path) = windmill_common::worker::BashAnnotations::ssh_target(content) {
|
||||
if annotation.no_network {
|
||||
return Err(Error::ExecutionErr(
|
||||
"Script has the `no_network` annotation, which a remote `#ssh` host cannot \
|
||||
honor. Refusing to run it with network."
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
return crate::ssh_executor_oss::handle_ssh_bash_job(
|
||||
&ssh_path,
|
||||
mem_peak,
|
||||
@@ -139,6 +148,19 @@ pub async fn handle_bash_job(
|
||||
));
|
||||
}
|
||||
|
||||
// Init/periodic scripts need full system access without isolation
|
||||
let is_regular_job = job
|
||||
.runnable_path
|
||||
.as_ref()
|
||||
.map(|x| {
|
||||
!x.starts_with(INIT_SCRIPT_PATH_PREFIX) && !x.starts_with(PERIODIC_SCRIPT_PATH_PREFIX)
|
||||
})
|
||||
.unwrap_or(true);
|
||||
|
||||
// Use nsjail if globally enabled OR if script has #sandbox annotation
|
||||
let nsjail = (is_sandboxing_enabled() || annotation.sandbox) && is_regular_job;
|
||||
check_no_network_jailed(annotation.no_network, nsjail)?;
|
||||
|
||||
let mut logs1 = "\n\n--- BASH CODE EXECUTION ---\n".to_string();
|
||||
if annotation.docker {
|
||||
logs1.push_str("docker mode\n");
|
||||
@@ -146,6 +168,9 @@ pub async fn handle_bash_job(
|
||||
if annotation.sandbox {
|
||||
logs1.push_str("sandbox mode (nsjail)\n");
|
||||
}
|
||||
if annotation.no_network {
|
||||
logs1.push_str("no network\n");
|
||||
}
|
||||
append_logs(&job.id, &job.workspace_id, logs1, &conn).await;
|
||||
|
||||
write_file(job_dir, "main.sh", &format!("set -e\n{content}"))?;
|
||||
@@ -204,6 +229,7 @@ exit $exit_status
|
||||
let mut reserved_variables =
|
||||
get_reserved_variables(job, &client.token, conn, parent_runnable_path).await?;
|
||||
reserved_variables.insert("RUST_LOG".to_string(), "info".to_string());
|
||||
crate::strip_no_network_token(&mut reserved_variables, annotation.no_network);
|
||||
|
||||
let args = build_args_map(job, client, conn).await?.map(Json);
|
||||
let job_args = if args.is_some() {
|
||||
@@ -237,18 +263,6 @@ exit $exit_status
|
||||
vec![]
|
||||
};
|
||||
|
||||
// Check if this is a regular job (not init or periodic script)
|
||||
// Init/periodic scripts need full system access without isolation
|
||||
let is_regular_job = job
|
||||
.runnable_path
|
||||
.as_ref()
|
||||
.map(|x| {
|
||||
!x.starts_with(INIT_SCRIPT_PATH_PREFIX) && !x.starts_with(PERIODIC_SCRIPT_PATH_PREFIX)
|
||||
})
|
||||
.unwrap_or(true);
|
||||
|
||||
// Use nsjail if globally enabled OR if script has #sandbox annotation
|
||||
let nsjail = (is_sandboxing_enabled() || annotation.sandbox) && is_regular_job;
|
||||
let child = if nsjail {
|
||||
let nsjail_timeout =
|
||||
resolve_nsjail_timeout(conn, &job.workspace_id, job.id, job.timeout).await;
|
||||
@@ -258,6 +272,7 @@ exit $exit_status
|
||||
&NSJAIL_CONFIG_RUN_BASH_CONTENT
|
||||
.replace("{JOB_DIR}", job_dir)
|
||||
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
|
||||
.replace("{CLONE_NEWNET}", nsjail_clone_newnet(annotation.no_network))
|
||||
.replace("{SHARED_MOUNT}", shared_mount)
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("#{DEV}", DEV_CONF_NSJAIL)
|
||||
@@ -281,12 +296,13 @@ exit $exit_status
|
||||
.env_clear()
|
||||
.envs(reserved_variables)
|
||||
.envs(
|
||||
get_proxy_envs_for_lang(
|
||||
get_run_proxy_envs(
|
||||
&ScriptLang::Bash,
|
||||
job.kind,
|
||||
&job.id,
|
||||
&job.workspace_id,
|
||||
conn,
|
||||
annotation.no_network,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
|
||||
@@ -18,18 +18,20 @@ use windmill_parser_ts::{remove_pinned_import_specifiers, remove_pinned_imports}
|
||||
use windmill_queue::{append_logs, CanceledBy, MiniPulledJob, PrecomputedAgentInfo};
|
||||
|
||||
use crate::{
|
||||
check_no_network_jailed,
|
||||
common::{
|
||||
build_command_with_isolation, create_args_and_out_file, get_reserved_variables,
|
||||
parse_npm_config, read_file, read_file_content, read_result, resolve_nsjail_timeout,
|
||||
resolve_nsjail_tmp_mount_block, script_envars, start_child_process, write_file_binary,
|
||||
MaybeLock, OccupancyMetrics, StreamNotifier, DEV_CONF_NSJAIL,
|
||||
},
|
||||
get_proxy_envs_for_lang,
|
||||
get_proxy_envs_for_lang, get_run_proxy_envs,
|
||||
handle_child::handle_child,
|
||||
is_sandboxing_enabled, read_ee_registry_with_workspace_override, BUNFIG_INSTALL_SCOPES,
|
||||
BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR, BUN_INSTALL_MIN_RELEASE_AGE, BUN_NO_CACHE, BUN_PATH,
|
||||
DISABLE_NUSER, HOME_ENV, NODE_BIN_PATH, NODE_PATH, NPMRC, NPM_CONFIG_REGISTRY, NPM_PATH,
|
||||
NSJAIL_AVAILABLE, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, TRACING_PROXY_CA_CERT_PATH, TZ_ENV,
|
||||
is_sandboxing_enabled, nsjail_clone_newnet, read_ee_registry_with_workspace_override,
|
||||
BUNFIG_INSTALL_SCOPES, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR, BUN_INSTALL_MIN_RELEASE_AGE,
|
||||
BUN_NO_CACHE, BUN_PATH, DISABLE_NUSER, HOME_ENV, NODE_BIN_PATH, NODE_PATH, NPMRC,
|
||||
NPM_CONFIG_REGISTRY, NPM_PATH, NSJAIL_AVAILABLE, NSJAIL_PATH, PATH_ENV, PROXY_ENVS,
|
||||
TRACING_PROXY_CA_CERT_PATH, TZ_ENV,
|
||||
};
|
||||
use windmill_common::{
|
||||
cache,
|
||||
@@ -1666,6 +1668,33 @@ pub async fn handle_bun_job(
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
let jailed = is_sandboxing_enabled() || annotation.sandbox;
|
||||
// Native scripts take their network away inside the isolate instead.
|
||||
let jail_no_network = annotation.no_network && !annotation.native;
|
||||
check_no_network_jailed(jail_no_network, jailed)?;
|
||||
// Unbundled, Bun's run-time loader fetches workspace imports; Node's are bundled on the host.
|
||||
if jail_no_network
|
||||
&& codebase.is_none()
|
||||
&& !annotation.nodejs
|
||||
&& (annotation.nobundling || *DISABLE_BUNDLING)
|
||||
{
|
||||
let script_path = crate::common::use_flow_root_path(job.runnable_path());
|
||||
let script_dir = script_path.rsplit_once('/').map(|(dir, _)| dir).unwrap_or("");
|
||||
let imports = std::iter::once((script_path.clone(), inner_content.as_str()))
|
||||
.chain(modules.iter().flatten().map(|(key, module)| {
|
||||
let stem = key.rsplit_once('.').map(|(stem, _)| stem).unwrap_or(key);
|
||||
(format!("{script_dir}/{stem}"), module.content.as_str())
|
||||
}))
|
||||
.flat_map(|(path, code)| {
|
||||
windmill_parser_ts::parse_runtime_relative_imports(code, &path).unwrap_or_default()
|
||||
});
|
||||
crate::check_no_network_imports(
|
||||
imports,
|
||||
&script_path,
|
||||
modules.as_ref(),
|
||||
"Bun without bundling (`//nobundling` or `DISABLE_BUNDLING`)",
|
||||
)?;
|
||||
}
|
||||
|
||||
let (mut has_bundle_cache, cache_logs, local_path, remote_path) = if let (Some(lock), true) = (
|
||||
maybe_lock.get_lock(),
|
||||
@@ -1916,6 +1945,9 @@ pub async fn handle_bun_job(
|
||||
if annotation.sandbox {
|
||||
init_logs.push_str("sandbox mode (nsjail)\n");
|
||||
}
|
||||
if jail_no_network {
|
||||
init_logs.push_str("no network\n");
|
||||
}
|
||||
|
||||
let write_wrapper_f = async {
|
||||
if !has_bundle_cache && annotation.native {
|
||||
@@ -2232,7 +2264,8 @@ try {{
|
||||
&& !annotation.nobundling
|
||||
&& !*DISABLE_BUNDLING
|
||||
&& !codebase.is_some()
|
||||
&& (maybe_lock.get_lock().is_some() || annotation.native);
|
||||
// A `no_network` run step cannot fetch relative imports, so they resolve while bundling.
|
||||
&& (maybe_lock.get_lock().is_some() || annotation.native || jail_no_network);
|
||||
|
||||
let write_loader_f = async {
|
||||
if build_cache {
|
||||
@@ -2258,7 +2291,13 @@ try {{
|
||||
build_loader(
|
||||
job_dir,
|
||||
base_internal_url,
|
||||
&client.token,
|
||||
// Bun's run step reads this loader (Node's builds on the host), and could hand
|
||||
// the token out through its result.
|
||||
if jail_no_network && !annotation.nodejs {
|
||||
""
|
||||
} else {
|
||||
&client.token
|
||||
},
|
||||
&job.workspace_id,
|
||||
job.runnable_path(),
|
||||
if annotation.nodejs {
|
||||
@@ -2274,11 +2313,12 @@ try {{
|
||||
}
|
||||
};
|
||||
|
||||
let (reserved_variables, _, _) = tokio::try_join!(
|
||||
let (mut reserved_variables, _, _) = tokio::try_join!(
|
||||
reserved_variables_args_out_f,
|
||||
write_wrapper_f,
|
||||
write_loader_f
|
||||
)?;
|
||||
crate::strip_no_network_token(&mut reserved_variables, jail_no_network);
|
||||
|
||||
// For WAC v2, write checkpoint.json before bun runs
|
||||
if is_wac_v2 {
|
||||
@@ -2491,7 +2531,7 @@ try {{
|
||||
append_logs(&job.id, &job.workspace_id, init_logs, conn).await;
|
||||
|
||||
//do not cache local dependencies
|
||||
let child = if is_sandboxing_enabled() || annotation.sandbox {
|
||||
let child = if jailed {
|
||||
let nsjail_timeout =
|
||||
resolve_nsjail_timeout(conn, &job.workspace_id, job.id, job.timeout).await;
|
||||
|
||||
@@ -2534,12 +2574,13 @@ try {{
|
||||
.env_clear()
|
||||
.envs(reserved_variables)
|
||||
.envs(
|
||||
get_proxy_envs_for_lang(
|
||||
get_run_proxy_envs(
|
||||
&ScriptLang::Bun,
|
||||
job.kind,
|
||||
&job.id,
|
||||
&job.workspace_id,
|
||||
conn,
|
||||
jail_no_network,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -2555,6 +2596,7 @@ try {{
|
||||
.replace("{LANG}", if annotation.nodejs { "nodejs" } else { "bun" })
|
||||
.replace("{JOB_DIR}", job_dir)
|
||||
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
|
||||
.replace("{CLONE_NEWNET}", nsjail_clone_newnet(jail_no_network))
|
||||
.replace("{UIDGIDMAP}", if *DISABLE_NUSER { "" } else {
|
||||
"uidmap {\n inside_id: \"1000\"\n outside_id: \"\"\n count: 1\n}\n\ngidmap {\n inside_id: \"1000\"\n outside_id: \"\"\n count: 1\n}"
|
||||
})
|
||||
|
||||
@@ -36,9 +36,9 @@ use crate::{
|
||||
render_envars, resolve_nsjail_timeout, resolve_nsjail_tmp_mount_block, start_child_process,
|
||||
OccupancyMetrics, DEV_CONF_NSJAIL,
|
||||
},
|
||||
get_proxy_envs_for_lang,
|
||||
get_run_proxy_envs,
|
||||
handle_child::handle_child,
|
||||
DISABLE_NUSER, NSJAIL_AVAILABLE, NSJAIL_PATH, SANDBOX_IMAGE_CACHE_MAX_MB,
|
||||
nsjail_clone_newnet, DISABLE_NUSER, NSJAIL_AVAILABLE, NSJAIL_PATH, SANDBOX_IMAGE_CACHE_MAX_MB,
|
||||
SANDBOX_IMAGE_DEFAULT_REGISTRY, SANDBOX_IMAGE_MAX_SIZE_MB, SANDBOX_IMAGE_PULL_POLICY,
|
||||
SANDBOX_REGISTRY_AUTH,
|
||||
};
|
||||
@@ -642,6 +642,7 @@ async fn generate_rootfs_mounts(rootfs: &str) -> Result<String, Error> {
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
pub async fn handle_docker_v2_job(
|
||||
image: &str,
|
||||
no_network: bool,
|
||||
mem_peak: &mut i32,
|
||||
canceled_by: &mut Option<CanceledBy>,
|
||||
job: &MiniPulledJob,
|
||||
@@ -671,7 +672,10 @@ pub async fn handle_docker_v2_job(
|
||||
append_logs(
|
||||
&job.id,
|
||||
&job.workspace_id,
|
||||
format!("\n\n--- SANDBOXED CONTAINER (nsjail) ---\nextracting image {image}...\n"),
|
||||
format!(
|
||||
"\n\n--- SANDBOXED CONTAINER (nsjail) ---\n{}extracting image {image}...\n",
|
||||
if no_network { "no network\n" } else { "" }
|
||||
),
|
||||
conn,
|
||||
)
|
||||
.await;
|
||||
@@ -781,6 +785,7 @@ pub async fn handle_docker_v2_job(
|
||||
&NSJAIL_CONFIG_RUN_DOCKER_CONTENT
|
||||
.replace("{TIMEOUT}", &nsjail_timeout)
|
||||
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
|
||||
.replace("{CLONE_NEWNET}", nsjail_clone_newnet(no_network))
|
||||
// proto_str-quoted: WorkingDir is image-controlled, must not break out
|
||||
// of the `cwd:` string and inject nsjail directives.
|
||||
.replace("{WORKDIR}", &proto_str(working_dir))
|
||||
@@ -814,13 +819,15 @@ pub async fn handle_docker_v2_job(
|
||||
"BASE_INTERNAL_URL".to_string(),
|
||||
base_internal_url.to_string(),
|
||||
);
|
||||
crate::strip_no_network_token(&mut reserved_variables, no_network);
|
||||
|
||||
let proxy_envs = get_proxy_envs_for_lang(
|
||||
let proxy_envs = get_run_proxy_envs(
|
||||
&ScriptLang::Bash,
|
||||
job.kind,
|
||||
&job.id,
|
||||
&job.workspace_id,
|
||||
conn,
|
||||
no_network,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -149,15 +149,16 @@ use crate::global_cache::pull_from_tar;
|
||||
use windmill_object_store::get_cache_object_store;
|
||||
|
||||
use crate::{
|
||||
check_no_network_jailed,
|
||||
common::{
|
||||
build_command_with_isolation, create_args_and_out_file, get_reserved_variables, read_file,
|
||||
read_result, render_nsjail_rlimit_as, resolve_nsjail_timeout,
|
||||
resolve_nsjail_tmp_mount_block, start_child_process, OccupancyMetrics, StreamNotifier,
|
||||
DEV_CONF_NSJAIL,
|
||||
},
|
||||
get_proxy_envs_for_lang,
|
||||
get_proxy_envs_for_lang, get_run_proxy_envs,
|
||||
handle_child::handle_child,
|
||||
is_sandboxing_enabled, read_ee_registry,
|
||||
is_sandboxing_enabled, nsjail_clone_newnet, read_ee_registry,
|
||||
worker_utils::ping_job_status,
|
||||
PyV, DISABLE_NUSER, HOME_ENV, INDEX_CERT, NATIVE_CERT, NSJAIL_AVAILABLE, NSJAIL_PATH,
|
||||
NSJAIL_PY_RLIMIT_AS_MB, PATH_ENV, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL, PROXY_ENVS,
|
||||
@@ -775,6 +776,8 @@ pub async fn handle_python_job(
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
let jailed = is_sandboxing_enabled() || annotations.sandbox;
|
||||
check_no_network_jailed(annotations.no_network, jailed)?;
|
||||
|
||||
// Preview jobs may carry _TEMP_SCRIPT_REFS so relative imports resolve from
|
||||
// not-yet-deployed local content uploaded to raw_script_temp. Gated on
|
||||
@@ -792,6 +795,19 @@ pub async fn handle_python_job(
|
||||
};
|
||||
|
||||
let module_sources = windmill_parser_py_imports::module_sources(&script_path, modules.as_ref());
|
||||
if annotations.no_network {
|
||||
let imports = std::iter::once((script_path.as_str(), inner_content.as_str()))
|
||||
.chain(
|
||||
module_sources
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(|(path, code)| (path.as_str(), code.as_str())),
|
||||
)
|
||||
.flat_map(|(path, code)| {
|
||||
windmill_parser_py_imports::parse_relative_imports(code, path).unwrap_or_default()
|
||||
});
|
||||
crate::check_no_network_imports(imports, &script_path, modules.as_ref(), "Python")?;
|
||||
}
|
||||
let (py_version, mut additional_python_paths) = handle_python_deps_with_modules(
|
||||
job_dir,
|
||||
requirements_o,
|
||||
@@ -839,6 +855,9 @@ pub async fn handle_python_job(
|
||||
if annotations.sandbox {
|
||||
logs.push_str("sandbox mode (nsjail)\n");
|
||||
}
|
||||
if annotations.no_network {
|
||||
logs.push_str("no network\n");
|
||||
}
|
||||
append_logs(&job.id, &job.workspace_id, logs, conn).await;
|
||||
}
|
||||
let (
|
||||
@@ -1080,6 +1099,7 @@ except BaseException as e:
|
||||
|
||||
let mut reserved_variables =
|
||||
get_reserved_variables(job, &client.token, conn, parent_runnable_path).await?;
|
||||
crate::strip_no_network_token(&mut reserved_variables, annotations.no_network);
|
||||
|
||||
// Add /tmp/windmill/cache/python_x_y_z/global-site-packages to PYTHONPATH.
|
||||
// Usefull if certain wheels needs to be preinstalled before execution.
|
||||
@@ -1112,7 +1132,7 @@ except BaseException as e:
|
||||
#[cfg(windows)]
|
||||
let additional_python_paths_folders = additional_python_paths_folders.replace(":", ";");
|
||||
|
||||
if is_sandboxing_enabled() || annotations.sandbox {
|
||||
if jailed {
|
||||
let shared_deps = additional_python_paths
|
||||
.into_iter()
|
||||
.map(|pp| {
|
||||
@@ -1141,6 +1161,10 @@ mount {{
|
||||
.replace("{JOB_DIR}", job_dir)
|
||||
.replace("{PY_INSTALL_DIR}", &*PY_INSTALL_DIR)
|
||||
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
|
||||
.replace(
|
||||
"{CLONE_NEWNET}",
|
||||
nsjail_clone_newnet(annotations.no_network),
|
||||
)
|
||||
.replace("{SHARED_MOUNT}", shared_mount)
|
||||
.replace("{SHARED_DEPENDENCIES}", shared_deps.as_str())
|
||||
.replace("{GLOBAL_SITE_PACKAGES}", &global_site_packages_path)
|
||||
@@ -1167,7 +1191,7 @@ mount {{
|
||||
job.id
|
||||
);
|
||||
|
||||
let child = if is_sandboxing_enabled() || annotations.sandbox {
|
||||
let child = if jailed {
|
||||
let mut nsjail_cmd = Command::new(NSJAIL_PATH.as_str());
|
||||
nsjail_cmd
|
||||
.current_dir(job_dir)
|
||||
@@ -1175,12 +1199,13 @@ mount {{
|
||||
// inject PYTHONPATH here - for some reason I had to do it in nsjail conf
|
||||
.envs(reserved_variables)
|
||||
.envs(
|
||||
get_proxy_envs_for_lang(
|
||||
get_run_proxy_envs(
|
||||
&ScriptLang::Python3,
|
||||
job.kind,
|
||||
&job.id,
|
||||
&job.workspace_id,
|
||||
conn,
|
||||
annotations.no_network,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
|
||||
@@ -18,14 +18,15 @@ use windmill_parser_r::{parse_r_requirements, parse_r_signature};
|
||||
use windmill_queue::{append_logs, CanceledBy, MiniPulledJob};
|
||||
|
||||
use crate::{
|
||||
check_no_network_jailed,
|
||||
common::{
|
||||
build_command_with_isolation, create_args_and_out_file, get_reserved_variables,
|
||||
read_result, resolve_nsjail_tmp_mount_block, script_envars, script_overrides, start_child_process,
|
||||
OccupancyMetrics, DEV_CONF_NSJAIL,
|
||||
read_result, resolve_nsjail_tmp_mount_block, script_envars, script_overrides,
|
||||
start_child_process, OccupancyMetrics, DEV_CONF_NSJAIL,
|
||||
},
|
||||
get_proxy_envs_for_lang,
|
||||
get_proxy_envs_for_lang, get_run_proxy_envs,
|
||||
handle_child::{self},
|
||||
is_sandboxing_enabled,
|
||||
is_sandboxing_enabled, nsjail_clone_newnet,
|
||||
universal_pkg_installer::{
|
||||
par_install_language_dependencies_seq, DependencyGraph, InstallDeps, RequiredDependency,
|
||||
},
|
||||
@@ -86,6 +87,8 @@ pub async fn handle_r_job<'a>(
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
let jailed = !cfg!(windows) && (is_sandboxing_enabled() || annotation.sandbox);
|
||||
check_no_network_jailed(annotation.no_network, jailed)?;
|
||||
|
||||
// --- Prepare ---
|
||||
{
|
||||
@@ -120,7 +123,13 @@ pub async fn handle_r_job<'a>(
|
||||
};
|
||||
// --- Execute ---
|
||||
{
|
||||
run(&mut args, lib_path.as_deref(), annotation.sandbox).await?;
|
||||
run(
|
||||
&mut args,
|
||||
lib_path.as_deref(),
|
||||
jailed,
|
||||
annotation.no_network,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
// --- Retrieve results ---
|
||||
{
|
||||
@@ -593,17 +602,21 @@ async fn run<'a>(
|
||||
..
|
||||
}: &mut JobHandlerInput<'a>,
|
||||
lib_path: Option<&str>,
|
||||
sandbox: bool,
|
||||
nsjail: bool,
|
||||
no_network: bool,
|
||||
) -> Result<(), Error> {
|
||||
let reserved_variables =
|
||||
let mut reserved_variables =
|
||||
get_reserved_variables(job, &client.token, conn, parent_runnable_path.clone()).await?;
|
||||
crate::strip_no_network_token(&mut reserved_variables, no_network);
|
||||
|
||||
let nsjail = !cfg!(windows) && (is_sandboxing_enabled() || sandbox);
|
||||
let child = if nsjail {
|
||||
append_logs(
|
||||
&job.id,
|
||||
&job.workspace_id,
|
||||
"\n--- R CODE EXECUTION (nsjail) ---\n".to_string(),
|
||||
format!(
|
||||
"\n--- R CODE EXECUTION (nsjail) ---\n{}",
|
||||
if no_network { "no network\n" } else { "" }
|
||||
),
|
||||
conn,
|
||||
)
|
||||
.await;
|
||||
@@ -614,14 +627,19 @@ async fn run<'a>(
|
||||
.env("PATH", PATH_ENV.as_str())
|
||||
.env("BASE_INTERNAL_URL", base_internal_url)
|
||||
.envs(&reserved_variables)
|
||||
.envs(R_PROXY_ENVS.clone())
|
||||
.envs(if no_network {
|
||||
vec![]
|
||||
} else {
|
||||
R_PROXY_ENVS.clone()
|
||||
})
|
||||
.envs(
|
||||
get_proxy_envs_for_lang(
|
||||
get_run_proxy_envs(
|
||||
&ScriptLang::Rlang,
|
||||
job.kind,
|
||||
&job.id,
|
||||
&job.workspace_id,
|
||||
conn,
|
||||
no_network,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
@@ -651,9 +669,14 @@ async fn run<'a>(
|
||||
&resolve_nsjail_tmp_mount_block(job_dir).await,
|
||||
)
|
||||
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
|
||||
.replace("{CLONE_NEWNET}", nsjail_clone_newnet(no_network))
|
||||
.replace(
|
||||
"{ENVARS}",
|
||||
&script_envars(&envs, &cmd, &script_overrides(&["PATH", "BASE_INTERNAL_URL"], &reserved_variables)),
|
||||
&script_envars(
|
||||
&envs,
|
||||
&cmd,
|
||||
&script_overrides(&["PATH", "BASE_INTERNAL_URL"], &reserved_variables),
|
||||
),
|
||||
),
|
||||
)?;
|
||||
start_child_process(cmd, NSJAIL_PATH.as_str(), false).await?
|
||||
|
||||
@@ -1022,6 +1022,189 @@ pub fn is_sandboxing_enabled() -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// A `no_network` job loses its network only through the fresh network namespace its
|
||||
/// nsjail run step is given, so a run step that would not be jailed is refused rather
|
||||
/// than run with the worker's network. `jailed` is the executor's own decision to jail.
|
||||
pub fn check_no_network_jailed(
|
||||
no_network: bool,
|
||||
jailed: bool,
|
||||
) -> windmill_common::error::Result<()> {
|
||||
no_network_refusal(no_network, jailed, NSJAIL_AVAILABLE.is_some())
|
||||
}
|
||||
|
||||
fn no_network_refusal(
|
||||
no_network: bool,
|
||||
jailed: bool,
|
||||
nsjail_available: bool,
|
||||
) -> windmill_common::error::Result<()> {
|
||||
if !no_network || (jailed && nsjail_available) {
|
||||
return Ok(());
|
||||
}
|
||||
let reason = if jailed {
|
||||
"nsjail is not available on this worker"
|
||||
} else {
|
||||
"this job would not run in an nsjail sandbox: add the `sandbox` annotation, or enable \
|
||||
nsjail sandboxing for the instance"
|
||||
};
|
||||
Err(windmill_common::error::Error::ExecutionErr(format!(
|
||||
"Script has the `no_network` annotation, which removes the network from its nsjail \
|
||||
sandbox, but {reason}. Refusing to run it with network."
|
||||
)))
|
||||
}
|
||||
|
||||
/// Whether a script asks for a run step without network, which only nsjail provides. A
|
||||
/// dedicated worker runs its script outside nsjail, so it leaves such a script to the
|
||||
/// regular executor.
|
||||
pub fn runs_no_network_in_jail(language: Option<ScriptLang>, content: &str) -> bool {
|
||||
match language {
|
||||
Some(ScriptLang::Python3) => {
|
||||
windmill_common::worker::PythonAnnotations::parse(content).no_network
|
||||
}
|
||||
Some(ScriptLang::Bun) | Some(ScriptLang::Bunnative) => {
|
||||
let annotation = windmill_common::worker::TypeScriptAnnotations::parse(content);
|
||||
annotation.no_network && !annotation.native
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Refuses a `no_network` job that imports another workspace script its run step would fetch
|
||||
/// from the API, which it cannot reach. Without this the fetch fails inside the script, where
|
||||
/// Python's loader falls back to an empty namespace module and the error names something else.
|
||||
/// `imports` are workspace paths; the job's own module files (keyed relative to
|
||||
/// `script_path`'s folder) are on disk and need no fetch.
|
||||
pub fn check_no_network_imports(
|
||||
imports: impl IntoIterator<Item = String>,
|
||||
script_path: &str,
|
||||
modules: Option<&HashMap<String, ScriptModule>>,
|
||||
fetched_by: &str,
|
||||
) -> windmill_common::error::Result<()> {
|
||||
let dir = script_path
|
||||
.rsplit_once('/')
|
||||
.map(|(dir, _)| dir)
|
||||
.unwrap_or("");
|
||||
let local: Vec<String> = modules
|
||||
.into_iter()
|
||||
.flat_map(|m| m.keys())
|
||||
.map(|key| {
|
||||
let stem = key.rsplit_once('.').map(|(stem, _)| stem).unwrap_or(key);
|
||||
if dir.is_empty() {
|
||||
stem.to_string()
|
||||
} else {
|
||||
format!("{dir}/{stem}")
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let fetched = imports.into_iter().find(|import| {
|
||||
!local
|
||||
.iter()
|
||||
.any(|l| l == import || l.starts_with(&format!("{import}/")))
|
||||
});
|
||||
match fetched {
|
||||
None => Ok(()),
|
||||
Some(import) => Err(windmill_common::error::Error::ExecutionErr(format!(
|
||||
"Script has the `no_network` annotation but imports the workspace script \
|
||||
`{import}`, which {fetched_by} fetches from the Windmill API while the script runs, \
|
||||
and the script has no network. Remove `no_network`, or copy that code into the \
|
||||
script."
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Drops `WM_TOKEN` from a `no_network` run step's env: it is useless without network, and
|
||||
/// the script could still hand it out through its result.
|
||||
pub fn strip_no_network_token(
|
||||
reserved_variables: &mut std::collections::HashMap<String, String>,
|
||||
no_network: bool,
|
||||
) {
|
||||
if no_network {
|
||||
reserved_variables.remove("WM_TOKEN");
|
||||
}
|
||||
}
|
||||
|
||||
/// `clone_newnet` for a run step's nsjail profile.
|
||||
pub fn nsjail_clone_newnet(no_network: bool) -> &'static str {
|
||||
if no_network {
|
||||
"true"
|
||||
} else {
|
||||
"false"
|
||||
}
|
||||
}
|
||||
|
||||
/// [`get_proxy_envs_for_lang`] for a run step. A `no_network` one reaches no proxy, the
|
||||
/// OTel tracing proxy included, so it gets only the trace context.
|
||||
pub async fn get_run_proxy_envs(
|
||||
lang: &ScriptLang,
|
||||
job_kind: JobKind,
|
||||
job_id: &uuid::Uuid,
|
||||
w_id: &str,
|
||||
conn: &Connection,
|
||||
no_network: bool,
|
||||
) -> anyhow::Result<Vec<(&'static str, String)>> {
|
||||
if no_network {
|
||||
return Ok(get_otel_context_envs(job_id));
|
||||
}
|
||||
get_proxy_envs_for_lang(lang, job_kind, job_id, w_id, conn).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod no_network_tests {
|
||||
use super::{check_no_network_imports, no_network_refusal, runs_no_network_in_jail};
|
||||
use windmill_common::scripts::ScriptLang;
|
||||
|
||||
#[test]
|
||||
fn no_network_runs_only_in_an_available_jail() {
|
||||
assert!(no_network_refusal(false, false, false).is_ok());
|
||||
assert!(no_network_refusal(true, true, true).is_ok());
|
||||
assert!(no_network_refusal(true, false, true).is_err());
|
||||
assert!(no_network_refusal(true, true, false).is_err());
|
||||
assert!(no_network_refusal(true, false, false).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_network_refuses_only_imports_it_would_fetch() {
|
||||
let modules = std::collections::HashMap::from([(
|
||||
"lib/util.py".to_string(),
|
||||
windmill_common::scripts::ScriptModule {
|
||||
content: String::new(),
|
||||
lock: None,
|
||||
language: ScriptLang::Python3,
|
||||
},
|
||||
)]);
|
||||
let check = |imports: &[&str]| {
|
||||
check_no_network_imports(
|
||||
imports.iter().map(|s| s.to_string()),
|
||||
"u/admin/main",
|
||||
Some(&modules),
|
||||
"Python",
|
||||
)
|
||||
};
|
||||
assert!(check(&["u/admin/lib/util", "u/admin/lib"]).is_ok());
|
||||
let err = check(&["u/admin/lib/util", "u/admin/helper"]).unwrap_err();
|
||||
assert!(err.to_string().contains("`u/admin/helper`"), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dedicated_workers_leave_jailed_no_network_scripts() {
|
||||
let py = Some(ScriptLang::Python3);
|
||||
let bun = Some(ScriptLang::Bun);
|
||||
assert!(runs_no_network_in_jail(
|
||||
py,
|
||||
"# no_network\ndef main(): pass"
|
||||
));
|
||||
assert!(!runs_no_network_in_jail(py, "def main(): pass"));
|
||||
assert!(runs_no_network_in_jail(
|
||||
bun,
|
||||
"// no_network\nexport function main() {}"
|
||||
));
|
||||
// Native scripts take their network away inside the isolate.
|
||||
assert!(!runs_no_network_in_jail(
|
||||
bun,
|
||||
"//native\n//no_network\nexport function main() {}"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if unshare PID isolation should be used (when not using nsjail).
|
||||
/// ENABLE_UNSHARE_PID forces unshare regardless of the global setting.
|
||||
/// FAVOR_UNSHARE_PID uses unshare only when the global setting is not set.
|
||||
|
||||
Generated
+1
-1
@@ -1419,7 +1419,7 @@ export async function main(url: string) {
|
||||
|
||||
## Pure computation: \`//no_network\`
|
||||
|
||||
A native script that only transforms its inputs can declare \`//no_network\` in its leading comment block (right after \`//native\`). The runtime then refuses every connection the script attempts: \`fetch\` to any host, the Windmill API (so \`windmill-client\` calls fail too), raw sockets and unix sockets, and \`WM_TOKEN\` is not set. It only takes effect on native scripts: without \`//native\` it is ignored. Use it only when the script needs no external data beyond its arguments:
|
||||
A native script that only transforms its inputs can declare \`//no_network\` in its leading comment block (right after \`//native\`). The runtime then refuses every connection the script attempts: \`fetch\` to any host, the Windmill API (so \`windmill-client\` calls fail too), raw sockets and unix sockets, and \`WM_TOKEN\` is not set. Without \`//native\` it means something else: the script must run in an nsjail sandbox (\`//sandbox\`, or instance-wide sandboxing), which then loses its network, and it fails to run otherwise. Use it only when the script needs no external data beyond its arguments:
|
||||
|
||||
\`\`\`typescript
|
||||
//native
|
||||
|
||||
@@ -4963,7 +4963,7 @@ export async function main(url: string) {
|
||||
|
||||
## Pure computation: \`//no_network\`
|
||||
|
||||
A native script that only transforms its inputs can declare \`//no_network\` in its leading comment block (right after \`//native\`). The runtime then refuses every connection the script attempts: \`fetch\` to any host, the Windmill API (so \`windmill-client\` calls fail too), raw sockets and unix sockets, and \`WM_TOKEN\` is not set. It only takes effect on native scripts: without \`//native\` it is ignored. Use it only when the script needs no external data beyond its arguments:
|
||||
A native script that only transforms its inputs can declare \`//no_network\` in its leading comment block (right after \`//native\`). The runtime then refuses every connection the script attempts: \`fetch\` to any host, the Windmill API (so \`windmill-client\` calls fail too), raw sockets and unix sockets, and \`WM_TOKEN\` is not set. Without \`//native\` it means something else: the script must run in an nsjail sandbox (\`//sandbox\`, or instance-wide sandboxing), which then loses its network, and it fails to run otherwise. Use it only when the script needs no external data beyond its arguments:
|
||||
|
||||
\`\`\`typescript
|
||||
//native
|
||||
|
||||
@@ -379,7 +379,7 @@ export async function main(url: string) {
|
||||
|
||||
## Pure computation: `//no_network`
|
||||
|
||||
A native script that only transforms its inputs can declare `//no_network` in its leading comment block (right after `//native`). The runtime then refuses every connection the script attempts: `fetch` to any host, the Windmill API (so `windmill-client` calls fail too), raw sockets and unix sockets, and `WM_TOKEN` is not set. It only takes effect on native scripts: without `//native` it is ignored. Use it only when the script needs no external data beyond its arguments:
|
||||
A native script that only transforms its inputs can declare `//no_network` in its leading comment block (right after `//native`). The runtime then refuses every connection the script attempts: `fetch` to any host, the Windmill API (so `windmill-client` calls fail too), raw sockets and unix sockets, and `WM_TOKEN` is not set. Without `//native` it means something else: the script must run in an nsjail sandbox (`//sandbox`, or instance-wide sandboxing), which then loses its network, and it fails to run otherwise. Use it only when the script needs no external data beyond its arguments:
|
||||
|
||||
```typescript
|
||||
//native
|
||||
|
||||
@@ -124,7 +124,7 @@ export async function main(url: string) {
|
||||
|
||||
## Pure computation: `//no_network`
|
||||
|
||||
A native script that only transforms its inputs can declare `//no_network` in its leading comment block (right after `//native`). The runtime then refuses every connection the script attempts: `fetch` to any host, the Windmill API (so `windmill-client` calls fail too), raw sockets and unix sockets, and `WM_TOKEN` is not set. It only takes effect on native scripts: without `//native` it is ignored. Use it only when the script needs no external data beyond its arguments:
|
||||
A native script that only transforms its inputs can declare `//no_network` in its leading comment block (right after `//native`). The runtime then refuses every connection the script attempts: `fetch` to any host, the Windmill API (so `windmill-client` calls fail too), raw sockets and unix sockets, and `WM_TOKEN` is not set. Without `//native` it means something else: the script must run in an nsjail sandbox (`//sandbox`, or instance-wide sandboxing), which then loses its network, and it fails to run otherwise. Use it only when the script needs no external data beyond its arguments:
|
||||
|
||||
```typescript
|
||||
//native
|
||||
|
||||
@@ -49,7 +49,7 @@ export async function main(url: string) {
|
||||
|
||||
## Pure computation: `//no_network`
|
||||
|
||||
A native script that only transforms its inputs can declare `//no_network` in its leading comment block (right after `//native`). The runtime then refuses every connection the script attempts: `fetch` to any host, the Windmill API (so `windmill-client` calls fail too), raw sockets and unix sockets, and `WM_TOKEN` is not set. It only takes effect on native scripts: without `//native` it is ignored. Use it only when the script needs no external data beyond its arguments:
|
||||
A native script that only transforms its inputs can declare `//no_network` in its leading comment block (right after `//native`). The runtime then refuses every connection the script attempts: `fetch` to any host, the Windmill API (so `windmill-client` calls fail too), raw sockets and unix sockets, and `WM_TOKEN` is not set. Without `//native` it means something else: the script must run in an nsjail sandbox (`//sandbox`, or instance-wide sandboxing), which then loses its network, and it fails to run otherwise. Use it only when the script needs no external data beyond its arguments:
|
||||
|
||||
```typescript
|
||||
//native
|
||||
|
||||
@@ -137,7 +137,9 @@ export async function getResource(
|
||||
return undefined;
|
||||
} else {
|
||||
throw Error(
|
||||
`Resource not found at ${path} or not visible to you: ${e.body}`
|
||||
e.status === undefined
|
||||
? `Could not reach the Windmill API to get resource ${path}: ${e.message ?? e}`
|
||||
: `Resource not found at ${path} or not visible to you: ${e.body}`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -831,7 +833,9 @@ export async function getVariable(path: string): Promise<string> {
|
||||
return await VariableService.getVariableValue({ workspace, path });
|
||||
} catch (e: any) {
|
||||
throw Error(
|
||||
`Variable not found at ${path} or not visible to you: ${e.body}`
|
||||
e.status === undefined
|
||||
? `Could not reach the Windmill API to get variable ${path}: ${e.message ?? e}`
|
||||
: `Variable not found at ${path} or not visible to you: ${e.body}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user