feat: gate private registry configuration behind enterprise feature (#7953)

Add read_ee_registry<T> helper that returns None and emits a job log
in CE builds when a private registry is configured. Applied to all
language executors: pip, npm/bun, deno, cargo, go, maven, nuget,
powershell, and ruby. Maven settings.xml file write in monitor.rs
is also gated.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-02-15 14:02:14 +00:00
committed by GitHub
co-authored by Claude Opus 4.6
parent ab81bddb91
commit a0f6243ba9
11 changed files with 278 additions and 82 deletions
+4
View File
@@ -1346,6 +1346,10 @@ pub async fn reload_maven_settings_xml_setting(conn: &Connection) {
)
.await;
if !cfg!(feature = "enterprise") {
return;
}
let settings_xml = MAVEN_SETTINGS_XML.read().await.clone();
match settings_xml {
Some(ref content) if !content.trim().is_empty() => {
+50 -10
View File
@@ -20,9 +20,10 @@ use crate::{
},
get_proxy_envs_for_lang,
handle_child::handle_child,
BUNFIG_INSTALL_SCOPES, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR, BUN_NO_CACHE, BUN_PATH,
is_sandboxing_enabled, DISABLE_NUSER, HOME_ENV, NODE_BIN_PATH, NODE_PATH, NPM_CONFIG_REGISTRY,
NPM_PATH, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, TRACING_PROXY_CA_CERT_PATH, TZ_ENV,
is_sandboxing_enabled, read_ee_registry, BUNFIG_INSTALL_SCOPES, BUN_BUNDLE_CACHE_DIR,
BUN_CACHE_DIR, BUN_NO_CACHE, BUN_PATH, DISABLE_NUSER, HOME_ENV, NODE_BIN_PATH, NODE_PATH,
NPM_CONFIG_REGISTRY, NPM_PATH, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, TRACING_PROXY_CA_CERT_PATH,
TZ_ENV,
};
use windmill_common::{
client::AuthedClient,
@@ -168,7 +169,7 @@ pub async fn gen_bun_lockfile(
let mut empty_deps = false;
if let Some(package_json_content) = workspace_dependencies.get_bun()? {
gen_bunfig(job_dir).await?;
gen_bunfig(job_dir, job_id, w_id, db).await?;
write_file(job_dir, "package.json", package_json_content.as_str())?;
} else {
let loader = RELATIVE_BUN_LOADER
@@ -193,7 +194,7 @@ pub async fn gen_bun_lockfile(
),
)?;
gen_bunfig(job_dir).await?;
gen_bunfig(job_dir, job_id, w_id, db).await?;
let mut child_cmd = Command::new(&*BUN_PATH);
child_cmd
@@ -291,9 +292,37 @@ pub async fn gen_bun_lockfile(
}
}
async fn gen_bunfig(job_dir: &str) -> Result<()> {
let registry = NPM_CONFIG_REGISTRY.read().await.clone();
let bunfig_install_scopes = BUNFIG_INSTALL_SCOPES.read().await.clone();
async fn gen_bunfig(
job_dir: &str,
job_id: &Uuid,
w_id: &str,
db: Option<&Connection>,
) -> Result<()> {
let (registry, bunfig_install_scopes) = if let Some(conn) = db {
(
read_ee_registry(
NPM_CONFIG_REGISTRY.read().await.clone(),
"npm registry",
job_id,
w_id,
conn,
)
.await,
read_ee_registry(
BUNFIG_INSTALL_SCOPES.read().await.clone(),
"bunfig install scopes",
job_id,
w_id,
conn,
)
.await,
)
} else {
(
NPM_CONFIG_REGISTRY.read().await.clone(),
BUNFIG_INSTALL_SCOPES.read().await.clone(),
)
};
if registry.is_some() || bunfig_install_scopes.is_some() {
let (url, token_opt) = if let Some(ref s) = registry {
let url = s.trim();
@@ -372,7 +401,18 @@ pub async fn install_bun_lockfile(
};
let has_file = if npm_mode {
let registry = NPM_CONFIG_REGISTRY.read().await.clone();
let registry = if let Some(conn) = db {
read_ee_registry(
NPM_CONFIG_REGISTRY.read().await.clone(),
"npm registry",
job_id,
w_id,
conn,
)
.await
} else {
NPM_CONFIG_REGISTRY.read().await.clone()
};
if let Some(registry) = registry {
let content = registry
.trim_start_matches("https:")
@@ -407,7 +447,7 @@ pub async fn install_bun_lockfile(
let mut child_process = start_child_process(child_cmd, &*BUN_PATH, false).await?;
gen_bunfig(job_dir).await?;
gen_bunfig(job_dir, job_id, w_id, db).await?;
if let Some(db) = db {
handle_child(
job_id,
+20 -4
View File
@@ -32,8 +32,8 @@ use crate::{
},
get_proxy_envs_for_lang,
handle_child::handle_child,
CSHARP_CACHE_DIR, is_sandboxing_enabled, DISABLE_NUSER, DOTNET_PATH, HOME_ENV, NSJAIL_PATH,
NUGET_CONFIG, PATH_ENV, TRACING_PROXY_CA_CERT_PATH, TZ_ENV,
is_sandboxing_enabled, read_ee_registry, CSHARP_CACHE_DIR, DISABLE_NUSER, DOTNET_PATH,
HOME_ENV, NSJAIL_PATH, NUGET_CONFIG, PATH_ENV, TRACING_PROXY_CA_CERT_PATH, TZ_ENV,
};
#[cfg(feature = "csharp")]
use windmill_common::scripts::ScriptLang;
@@ -82,7 +82,15 @@ pub async fn generate_nuget_lockfile(
) -> error::Result<String> {
check_executor_binary_exists("dotnet", DOTNET_PATH.as_str(), "C#")?;
if let Some(nuget_config) = NUGET_CONFIG.read().await.clone() {
if let Some(nuget_config) = read_ee_registry(
NUGET_CONFIG.read().await.clone(),
"nuget config",
job_id,
w_id,
conn,
)
.await
{
if !nuget_config.trim().is_empty() {
write_file(job_dir, "nuget.config", &nuget_config)?;
}
@@ -336,7 +344,15 @@ async fn build_cs_proj(
hash: &str,
occupancy_metrics: &mut OccupancyMetrics,
) -> error::Result<String> {
if let Some(nuget_config) = NUGET_CONFIG.read().await.clone() {
if let Some(nuget_config) = read_ee_registry(
NUGET_CONFIG.read().await.clone(),
"nuget config",
job_id,
w_id,
conn,
)
.await
{
if !nuget_config.trim().is_empty() {
write_file(job_dir, "nuget.config", &nuget_config)?;
}
+35 -6
View File
@@ -13,7 +13,8 @@ use crate::{
},
get_proxy_envs_for_lang,
handle_child::handle_child,
is_sandboxing_enabled, DENO_CACHE_DIR, DENO_PATH, HOME_ENV, NPM_CONFIG_REGISTRY, PATH_ENV, TZ_ENV,
is_sandboxing_enabled, read_ee_registry, DENO_CACHE_DIR, DENO_PATH, HOME_ENV,
NPM_CONFIG_REGISTRY, PATH_ENV, TZ_ENV,
};
use windmill_common::client::AuthedClient;
@@ -54,6 +55,9 @@ lazy_static::lazy_static! {
async fn get_common_deno_proc_envs(
token: &str,
base_internal_url: &str,
job_id: &Uuid,
w_id: &str,
conn: Option<&Connection>,
) -> HashMap<String, String> {
let hostname = BASE_URL.read().await.clone();
let hostname_base = hostname.split("://").last().unwrap_or("localhost");
@@ -75,7 +79,19 @@ async fn get_common_deno_proc_envs(
),
]);
if let Some(ref s) = NPM_CONFIG_REGISTRY.read().await.clone() {
let registry = if let Some(conn) = conn {
read_ee_registry(
NPM_CONFIG_REGISTRY.read().await.clone(),
"npm registry",
job_id,
w_id,
conn,
)
.await
} else {
NPM_CONFIG_REGISTRY.read().await.clone()
};
if let Some(ref s) = registry {
let (url, _token_opt) = parse_npm_config(s);
deno_envs.insert(String::from("NPM_CONFIG_REGISTRY"), url);
}
@@ -131,7 +147,7 @@ pub async fn generate_deno_lock(
write_file(job_dir, "import_map.json", &import_map)?;
write_file(job_dir, "empty.ts", "")?;
let deno_envs = get_common_deno_proc_envs("", base_internal_url).await;
let deno_envs = get_common_deno_proc_envs("", base_internal_url, job_id, w_id, db).await;
let mut child_cmd = Command::new(DENO_PATH.as_str());
child_cmd
@@ -362,8 +378,14 @@ try {{
write_import_map_f
)?;
let mut common_deno_proc_envs =
get_common_deno_proc_envs(&client.token, base_internal_url).await;
let mut common_deno_proc_envs = get_common_deno_proc_envs(
&client.token,
base_internal_url,
&job.id,
&job.workspace_id,
Some(conn),
)
.await;
if is_sandboxing_enabled() {
common_deno_proc_envs.insert("HOME".to_string(), job_dir.to_string());
}
@@ -553,7 +575,14 @@ pub async fn start_worker(
use crate::common::build_envs_map;
let _ = write_file(job_dir, "main.ts", inner_content)?;
let common_deno_proc_envs = get_common_deno_proc_envs(&token, base_internal_url).await;
let common_deno_proc_envs = get_common_deno_proc_envs(
&token,
base_internal_url,
&Uuid::nil(),
w_id,
Some(&db.into()),
)
.await;
let context = variables::get_reserved_variables(
&db.into(),
+26 -6
View File
@@ -24,8 +24,8 @@ use crate::{
read_result, start_child_process, OccupancyMetrics, DEV_CONF_NSJAIL,
},
handle_child::handle_child,
is_sandboxing_enabled, DISABLE_NUSER, GOPRIVATE, GOPROXY, GO_BIN_CACHE_DIR, GO_CACHE_DIR, HOME_ENV,
NSJAIL_PATH, PATH_ENV, PROXY_ENVS, TRACING_PROXY_CA_CERT_PATH, TZ_ENV,
is_sandboxing_enabled, read_ee_registry, DISABLE_NUSER, GOPRIVATE, GOPROXY, GO_BIN_CACHE_DIR,
GO_CACHE_DIR, HOME_ENV, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, TRACING_PROXY_CA_CERT_PATH, TZ_ENV,
};
use windmill_common::client::AuthedClient;
@@ -392,10 +392,26 @@ func Run(req Req) (interface{{}}, error){{
})
.env("HOME", HOME_ENV.as_str());
if let Some(ref goprivate) = *GOPRIVATE {
if let Some(ref goprivate) = read_ee_registry(
GOPRIVATE.clone(),
"go private",
&job.id,
&job.workspace_id,
conn,
)
.await
{
run_go.env("GOPRIVATE", goprivate);
}
if let Some(ref goproxy) = *GOPROXY {
if let Some(ref goproxy) = read_ee_registry(
GOPROXY.clone(),
"go proxy",
&job.id,
&job.workspace_id,
conn,
)
.await
{
run_go.env("GOPROXY", goproxy);
}
@@ -585,13 +601,17 @@ pub async fn install_go_dependencies(
.stdout(Stdio::piped())
.stderr(Stdio::piped());
if let Some(ref goprivate) = *GOPRIVATE {
if let Some(ref goprivate) =
read_ee_registry(GOPRIVATE.clone(), "go private", job_id, w_id, conn).await
{
child_cmd.env("GOPRIVATE", goprivate);
}
// TODO: Remove if no incidents reported
if !std::env::var("WMDEBUG_NO_GOPROXY_ON_TIDY").ok().is_some() {
if let Some(ref goproxy) = *GOPROXY {
if let Some(ref goproxy) =
read_ee_registry(GOPROXY.clone(), "go proxy", job_id, w_id, conn).await
{
child_cmd.env("GOPROXY", goproxy);
}
}
+25 -20
View File
@@ -24,10 +24,10 @@ use crate::{
build_command_with_isolation, create_args_and_out_file, get_reserved_variables,
read_result, start_child_process, OccupancyMetrics,
},
handle_child,
handle_child, is_sandboxing_enabled, read_ee_registry,
universal_pkg_installer::{par_install_language_dependencies_all_at_once, RequiredDependency},
is_sandboxing_enabled, COURSIER_CACHE_DIR, DISABLE_NUSER, JAVA_CACHE_DIR, JAVA_HOME_DIR,
JAVA_REPOSITORY_DIR, MAVEN_REPOS, NO_DEFAULT_MAVEN, NSJAIL_PATH, PATH_ENV, PROXY_ENVS,
COURSIER_CACHE_DIR, DISABLE_NUSER, JAVA_CACHE_DIR, JAVA_HOME_DIR, JAVA_REPOSITORY_DIR,
MAVEN_REPOS, NO_DEFAULT_MAVEN, NSJAIL_PATH, PATH_ENV, PROXY_ENVS,
};
use windmill_common::client::AuthedClient;
@@ -224,7 +224,7 @@ pub async fn resolve<'a>(
"--cache",
COURSIER_CACHE_DIR,
])
.args(&get_repos().await)
.args(&get_repos(job_id, w_id, conn).await)
.args(&deps.split("\n").collect_vec())
.stderr(Stdio::piped());
@@ -306,7 +306,7 @@ async fn install<'a>(
"JAVA classpath: {}", &classpath
);
let (repos, no_default, trust_store_metadata) = (
get_repos().await,
get_repos(&job.id, &job.workspace_id, conn).await,
get_no_default(),
metadata(TRUST_STORE_PATH.clone()).await,
);
@@ -788,21 +788,26 @@ fn parse_proxy() -> anyhow::Result<JavaProxySettings> {
Ok(jps)
}
async fn get_repos() -> Vec<String> {
MAVEN_REPOS
.read()
.await
.as_ref()
.map(|repos| {
repos
.trim()
.split_whitespace()
.into_iter()
.map(|el| vec!["--repository".to_owned(), el.to_owned()])
.collect_vec()
})
.unwrap_or_default()
.concat()
async fn get_repos(job_id: &Uuid, w_id: &str, conn: &Connection) -> Vec<String> {
read_ee_registry(
MAVEN_REPOS.read().await.clone(),
"maven repos",
job_id,
w_id,
conn,
)
.await
.as_ref()
.map(|repos| {
repos
.trim()
.split_whitespace()
.into_iter()
.map(|el| vec!["--repository".to_owned(), el.to_owned()])
.collect_vec()
})
.unwrap_or_default()
.concat()
}
fn get_no_default() -> String {
+19 -4
View File
@@ -27,8 +27,9 @@ use crate::{
read_file_content, start_child_process, OccupancyMetrics,
},
handle_child::handle_child,
is_sandboxing_enabled, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV, POWERSHELL_CACHE_DIR,
POWERSHELL_PATH, POWERSHELL_REPO_PAT, POWERSHELL_REPO_URL, PROXY_ENVS, TZ_ENV,
is_sandboxing_enabled, read_ee_registry, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV,
POWERSHELL_CACHE_DIR, POWERSHELL_PATH, POWERSHELL_REPO_PAT, POWERSHELL_REPO_URL, PROXY_ENVS,
TZ_ENV,
};
fn val_to_pwsh_param(v: serde_json::Value) -> String {
@@ -355,8 +356,22 @@ pub async fn handle_powershell_job(
}
if !modules_to_install.is_empty() {
let powershell_repo_url = POWERSHELL_REPO_URL.read().await.clone();
let powershell_repo_pat = POWERSHELL_REPO_PAT.read().await.clone();
let powershell_repo_url = read_ee_registry(
POWERSHELL_REPO_URL.read().await.clone(),
"powershell repo url",
&job.id,
&job.workspace_id,
db,
)
.await;
let powershell_repo_pat = read_ee_registry(
POWERSHELL_REPO_PAT.read().await.clone(),
"powershell repo pat",
&job.id,
&job.workspace_id,
db,
)
.await;
let has_private_repo = powershell_repo_url.is_some();
let has_credentials = powershell_repo_pat.is_some();
+37 -21
View File
@@ -132,7 +132,7 @@ use crate::{
},
get_proxy_envs_for_lang,
handle_child::handle_child,
is_sandboxing_enabled,
is_sandboxing_enabled, read_ee_registry,
worker_utils::ping_job_status,
PyV, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL,
PROXY_ENVS, PY_INSTALL_DIR, TRACING_PROXY_CA_CERT_PATH, TZ_ENV, UV_CACHE_DIR,
@@ -286,21 +286,29 @@ pub async fn uv_pip_compile(
if no_cache {
args.extend(["--no-cache"]);
}
let pip_extra_index_url = PIP_EXTRA_INDEX_URL
.read()
.await
.clone()
.map(handle_ephemeral_token);
let pip_extra_index_url = read_ee_registry(
PIP_EXTRA_INDEX_URL.read().await.clone(),
"pip extra index url",
job_id,
w_id,
conn,
)
.await
.map(handle_ephemeral_token);
if let Some(url) = pip_extra_index_url.as_ref() {
url.split(",").for_each(|url| {
args.extend(["--extra-index-url", url]);
});
}
let pip_index_url = PIP_INDEX_URL
.read()
.await
.clone()
.map(handle_ephemeral_token);
let pip_index_url = read_ee_registry(
PIP_INDEX_URL.read().await.clone(),
"pip index url",
job_id,
w_id,
conn,
)
.await
.map(handle_ephemeral_token);
if let Some(url) = pip_index_url.as_ref() {
args.extend(["--index-url", url]);
}
@@ -1635,16 +1643,24 @@ pub async fn handle_python_reqs(
);
let pip_indexes = (
PIP_EXTRA_INDEX_URL
.read()
.await
.clone()
.map(handle_ephemeral_token),
PIP_INDEX_URL
.read()
.await
.clone()
.map(handle_ephemeral_token),
read_ee_registry(
PIP_EXTRA_INDEX_URL.read().await.clone(),
"pip extra index url",
job_id,
w_id,
conn,
)
.await
.map(handle_ephemeral_token),
read_ee_registry(
PIP_INDEX_URL.read().await.clone(),
"pip index url",
job_id,
w_id,
conn,
)
.await
.map(handle_ephemeral_token),
);
// Cached paths
+21 -3
View File
@@ -28,8 +28,9 @@ use crate::{
},
get_proxy_envs_for_lang,
handle_child::{self},
is_sandboxing_enabled, read_ee_registry,
universal_pkg_installer::{par_install_language_dependencies_seq, RequiredDependency},
is_sandboxing_enabled, DISABLE_NUSER, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, RUBY_CACHE_DIR, RUBY_REPOS,
DISABLE_NUSER, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, RUBY_CACHE_DIR, RUBY_REPOS,
TRACING_PROXY_CA_CERT_PATH,
};
use windmill_common::scripts::ScriptLang;
@@ -360,7 +361,16 @@ Your Gemfile syntax will continue to work as-is."
])
.envs(RUBY_PROXY_ENVS.clone());
for repo in RUBY_REPOS.read().await.clone().unwrap_or_default() {
for repo in read_ee_registry(
RUBY_REPOS.read().await.clone(),
"ruby repos",
job_id,
w_id,
conn,
)
.await
.unwrap_or_default()
{
if let (Some(url), usr, Some(passwd)) =
(repo.domain(), repo.username(), repo.password())
{
@@ -591,7 +601,15 @@ async fn install<'a>(
let job_dir = job_dir.to_owned();
let jailed = !cfg!(windows) && is_sandboxing_enabled();
let RubyAnnotations { verbose } = RubyAnnotations::parse(&inner_content);
let repos = RUBY_REPOS.read().await.clone().unwrap_or_default();
let repos = read_ee_registry(
RUBY_REPOS.read().await.clone(),
"ruby repos",
&job.id,
&job.workspace_id,
conn,
)
.await
.unwrap_or_default();
let (envs, reserved_variables) = (
envs.clone(),
get_reserved_variables(job, &client.token, conn, parent_runnable_path.clone()).await?,
+20 -7
View File
@@ -1,7 +1,7 @@
use serde_json::value::RawValue;
use std::{collections::HashMap, process::Stdio};
#[cfg(not(windows))]
use std::sync::Once;
use std::{collections::HashMap, process::Stdio};
use uuid::Uuid;
use windmill_parser_rust::parse_rust_deps_into_manifest;
@@ -27,8 +27,8 @@ use crate::{
},
get_proxy_envs_for_lang,
handle_child::handle_child,
is_sandboxing_enabled, CARGO_REGISTRIES, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV,
PROXY_ENVS, RUST_CACHE_DIR, TRACING_PROXY_CA_CERT_PATH, TZ_ENV,
is_sandboxing_enabled, read_ee_registry, CARGO_REGISTRIES, DISABLE_NUSER, HOME_ENV,
NSJAIL_PATH, PATH_ENV, PROXY_ENVS, RUST_CACHE_DIR, TRACING_PROXY_CA_CERT_PATH, TZ_ENV,
};
use windmill_common::client::AuthedClient;
use windmill_common::scripts::ScriptLang;
@@ -230,8 +230,21 @@ pub fn __WINDMILL_RUN__(_args: __WINDMILL_ARGS__) -> Result<String, Box<dyn std:
Ok(())
}
async fn write_cargo_config(job_dir: &str) -> anyhow::Result<()> {
if let Some(cargo_registries) = CARGO_REGISTRIES.read().await.clone() {
async fn write_cargo_config(
job_dir: &str,
job_id: &Uuid,
w_id: &str,
conn: &Connection,
) -> anyhow::Result<()> {
if let Some(cargo_registries) = read_ee_registry(
CARGO_REGISTRIES.read().await.clone(),
"cargo registries",
job_id,
w_id,
conn,
)
.await
{
if !cargo_registries.trim().is_empty() {
let cargo_dir = format!("{job_dir}/.cargo");
create_dir_all(&cargo_dir).await?;
@@ -256,7 +269,7 @@ pub async fn generate_cargo_lockfile(
check_executor_binary_exists("cargo", CARGO_PATH.as_str(), "rust")?;
gen_cargo_crate(code, job_dir)?;
write_cargo_config(job_dir).await?;
write_cargo_config(job_dir, job_id, w_id, conn).await?;
let mut gen_lockfile_cmd = Command::new(CARGO_PATH.as_str());
gen_lockfile_cmd
@@ -621,7 +634,7 @@ pub async fn handle_rust_job(
append_logs(&job.id, &job.workspace_id, logs1, conn).await;
gen_cargo_crate(inner_content, job_dir)?;
write_cargo_config(job_dir).await?;
write_cargo_config(job_dir, &job.id, &job.workspace_id, conn).await?;
if let Some(reqs) = requirements_o {
if !reqs.is_empty() {
+21 -1
View File
@@ -59,7 +59,7 @@ use std::{
collections::{HashMap, HashSet},
fmt::Display,
sync::{
atomic::{AtomicBool, AtomicU8, AtomicU16, Ordering},
atomic::{AtomicBool, AtomicU16, AtomicU8, Ordering},
Arc,
},
time::Duration,
@@ -673,6 +673,26 @@ pub fn get_job_isolation() -> JobIsolationLevel {
/// Returns true if nsjail sandboxing should be used for job execution.
/// DISABLE_NSJAIL=false forces nsjail regardless of the global setting.
pub async fn read_ee_registry<T>(
value: Option<T>,
name: &str,
job_id: &uuid::Uuid,
w_id: &str,
conn: &Connection,
) -> Option<T> {
if !cfg!(feature = "enterprise") && value.is_some() {
append_logs(
job_id,
w_id,
format!("Private registry ({name}) configuration ignored: this feature requires Windmill Enterprise Edition\n"),
conn,
)
.await;
return None;
}
value
}
pub fn is_sandboxing_enabled() -> bool {
if !*DISABLE_NSJAIL {
return true;