diff --git a/crates/uv-client/src/base_client.rs b/crates/uv-client/src/base_client.rs index f78c03cea..3c7a5eb71 100644 --- a/crates/uv-client/src/base_client.rs +++ b/crates/uv-client/src/base_client.rs @@ -387,17 +387,20 @@ enum Security { impl BaseClient { /// Selects the appropriate client based on the host's trustworthiness. pub fn for_host(&self, url: &Url) -> &ClientWithMiddleware { - if self - .allow_insecure_host - .iter() - .any(|allow_insecure_host| allow_insecure_host.matches(url)) - { + if self.disable_ssl(url) { &self.dangerous_client } else { &self.client } } + /// Returns `true` if the host is trusted to use the insecure client. + pub fn disable_ssl(&self, url: &Url) -> bool { + self.allow_insecure_host + .iter() + .any(|allow_insecure_host| allow_insecure_host.matches(url)) + } + /// The configured client timeout, in seconds. pub fn timeout(&self) -> Duration { self.timeout diff --git a/crates/uv-client/src/registry_client.rs b/crates/uv-client/src/registry_client.rs index 02a0a9949..1dc268864 100644 --- a/crates/uv-client/src/registry_client.rs +++ b/crates/uv-client/src/registry_client.rs @@ -215,6 +215,11 @@ impl RegistryClient { self.client.uncached().for_host(url) } + /// Returns `true` if SSL verification is disabled for the given URL. + pub fn disable_ssl(&self, url: &Url) -> bool { + self.client.uncached().disable_ssl(url) + } + /// Return the [`Connectivity`] mode used by this client. pub fn connectivity(&self) -> Connectivity { self.connectivity diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index 8ba3edd63..949c2c06a 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -1430,7 +1430,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .git() .fetch( resource.git, - client.unmanaged.uncached_client(resource.url).clone(), + client + .unmanaged + .uncached_client(resource.git.repository()) + .clone(), + client.unmanaged.disable_ssl(resource.git.repository()), self.build_context.cache().bucket(CacheBucket::Git), self.reporter .clone() @@ -1530,7 +1534,10 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .git() .github_fast_path( resource.git, - client.unmanaged.uncached_client(resource.url).clone(), + client + .unmanaged + .uncached_client(resource.git.repository()) + .clone(), ) .await { @@ -1582,7 +1589,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .git() .fetch( resource.git, - client.unmanaged.uncached_client(resource.url).clone(), + client + .unmanaged + .uncached_client(resource.git.repository()) + .clone(), + client.unmanaged.disable_ssl(resource.git.repository()), self.build_context.cache().bucket(CacheBucket::Git), self.reporter .clone() @@ -1812,6 +1823,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .fetch( git, client.unmanaged.uncached_client(git.repository()).clone(), + client.unmanaged.disable_ssl(git.repository()), self.build_context.cache().bucket(CacheBucket::Git), self.reporter .clone() diff --git a/crates/uv-git/src/git.rs b/crates/uv-git/src/git.rs index 68f7c0277..cf3e0ed17 100644 --- a/crates/uv-git/src/git.rs +++ b/crates/uv-git/src/git.rs @@ -303,6 +303,7 @@ impl GitRemote { reference: &GitReference, locked_rev: Option, client: &ClientWithMiddleware, + disable_ssl: bool, ) -> Result<(GitDatabase, GitOid)> { let reference = locked_rev .map(ReferenceOrOid::Oid) @@ -310,7 +311,7 @@ impl GitRemote { let enable_lfs_fetch = env::var(EnvVars::UV_GIT_LFS).is_ok(); if let Some(mut db) = db { - fetch(&mut db.repo, &self.url, reference, client) + fetch(&mut db.repo, &self.url, reference, client, disable_ssl) .with_context(|| format!("failed to fetch into: {}", into.user_display()))?; let resolved_commit_hash = match locked_rev { @@ -320,7 +321,7 @@ impl GitRemote { if let Some(rev) = resolved_commit_hash { if enable_lfs_fetch { - fetch_lfs(&mut db.repo, &self.url, &rev) + fetch_lfs(&mut db.repo, &self.url, &rev, disable_ssl) .with_context(|| format!("failed to fetch LFS objects at {rev}"))?; } return Ok((db, rev)); @@ -338,14 +339,14 @@ impl GitRemote { fs_err::create_dir_all(into)?; let mut repo = GitRepository::init(into)?; - fetch(&mut repo, &self.url, reference, client) + fetch(&mut repo, &self.url, reference, client, disable_ssl) .with_context(|| format!("failed to clone into: {}", into.user_display()))?; let rev = match locked_rev { Some(rev) => rev, None => reference.resolve(&repo)?, }; if enable_lfs_fetch { - fetch_lfs(&mut repo, &self.url, &rev) + fetch_lfs(&mut repo, &self.url, &rev, disable_ssl) .with_context(|| format!("failed to fetch LFS objects at {rev}"))?; } @@ -502,6 +503,7 @@ fn fetch( remote_url: &Url, reference: ReferenceOrOid<'_>, client: &ClientWithMiddleware, + disable_ssl: bool, ) -> Result<()> { let oid_to_fetch = match github_fast_path(repo, remote_url, reference, client) { Ok(FastPathRev::UpToDate) => return Ok(()), @@ -577,14 +579,21 @@ fn fetch( debug!("Performing a Git fetch for: {remote_url}"); let result = match refspec_strategy { - RefspecStrategy::All => fetch_with_cli(repo, remote_url, refspecs.as_slice(), tags), + RefspecStrategy::All => { + fetch_with_cli(repo, remote_url, refspecs.as_slice(), tags, disable_ssl) + } RefspecStrategy::First => { // Try each refspec let mut errors = refspecs .iter() .map_while(|refspec| { - let fetch_result = - fetch_with_cli(repo, remote_url, std::slice::from_ref(refspec), tags); + let fetch_result = fetch_with_cli( + repo, + remote_url, + std::slice::from_ref(refspec), + tags, + disable_ssl, + ); // Stop after the first success and log failures match fetch_result { @@ -629,12 +638,17 @@ fn fetch_with_cli( url: &Url, refspecs: &[String], tags: bool, + disable_ssl: bool, ) -> Result<()> { let mut cmd = ProcessBuilder::new(GIT.as_ref()?); cmd.arg("fetch"); if tags { cmd.arg("--tags"); } + if disable_ssl { + debug!("Disabling SSL verification for Git fetch"); + cmd.env(EnvVars::GIT_SSL_NO_VERIFY, "true"); + } cmd.arg("--force") // handle force pushes .arg("--update-head-ok") // see discussion in #2078 .arg(url.as_str()) @@ -674,7 +688,12 @@ static GIT_LFS: LazyLock> = LazyLock::new(|| { }); /// Attempts to use `git-lfs` CLI to fetch required LFS objects for a given revision. -fn fetch_lfs(repo: &mut GitRepository, url: &Url, revision: &GitOid) -> Result<()> { +fn fetch_lfs( + repo: &mut GitRepository, + url: &Url, + revision: &GitOid, + disable_ssl: bool, +) -> Result<()> { let mut cmd = if let Ok(lfs) = GIT_LFS.as_ref() { debug!("Fetching Git LFS objects"); lfs.clone() @@ -684,6 +703,11 @@ fn fetch_lfs(repo: &mut GitRepository, url: &Url, revision: &GitOid) -> Result<( return Ok(()); }; + if disable_ssl { + debug!("Disabling SSL verification for Git LFS"); + cmd.env(EnvVars::GIT_SSL_NO_VERIFY, "true"); + } + cmd.arg("fetch") .arg(url.as_str()) .arg(revision.as_str()) diff --git a/crates/uv-git/src/resolver.rs b/crates/uv-git/src/resolver.rs index 4ef519b22..5e5be3d85 100644 --- a/crates/uv-git/src/resolver.rs +++ b/crates/uv-git/src/resolver.rs @@ -106,6 +106,7 @@ impl GitResolver { &self, url: &GitUrl, client: ClientWithMiddleware, + disable_ssl: bool, cache: PathBuf, reporter: Option>, ) -> Result { @@ -139,6 +140,14 @@ impl GitResolver { } else { GitSource::new(url.as_ref().clone(), client, cache) }; + + // If necessary, disable SSL. + let source = if disable_ssl { + source.dangerous() + } else { + source + }; + let fetch = tokio::task::spawn_blocking(move || source.fetch()) .await? .map_err(GitResolverError::Git)?; diff --git a/crates/uv-git/src/source.rs b/crates/uv-git/src/source.rs index efbea29b7..58599f651 100644 --- a/crates/uv-git/src/source.rs +++ b/crates/uv-git/src/source.rs @@ -22,6 +22,8 @@ pub struct GitSource { git: GitUrl, /// The HTTP client to use for fetching. client: ClientWithMiddleware, + /// Whether to disable SSL verification. + disable_ssl: bool, /// The path to the Git source database. cache: PathBuf, /// The reporter to use for this source. @@ -37,12 +39,22 @@ impl GitSource { ) -> Self { Self { git, + disable_ssl: false, client: client.into(), cache: cache.into(), reporter: None, } } + /// Disable SSL verification for this [`GitSource`]. + #[must_use] + pub fn dangerous(self) -> Self { + Self { + disable_ssl: true, + ..self + } + } + /// Set the [`Reporter`] to use for the [`GitSource`]. #[must_use] pub fn with_reporter(self, reporter: Arc) -> Self { @@ -96,6 +108,7 @@ impl GitSource { &self.git.reference, locked_rev.map(GitOid::from), &self.client, + self.disable_ssl, )?; (db, actual_rev, task) diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index 8db9a3595..6a5c90c3e 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -449,6 +449,10 @@ impl EnvVars { #[attr_hidden] pub const GIT_ALTERNATE_OBJECT_DIRECTORIES: &'static str = "GIT_ALTERNATE_OBJECT_DIRECTORIES"; + /// Disables SSL verification for git operations. + #[attr_hidden] + pub const GIT_SSL_NO_VERIFY: &'static str = "GIT_SSL_NO_VERIFY"; + /// Used in tests for better git isolation. /// /// For example, we run some tests in ~/.local/share/uv/tests.