Disable SSL in Git commands for `--allow-insecure-host` (#11210)

## Summary

Closes https://github.com/astral-sh/uv/issues/11176.

## Test Plan

- Created a self-signed certificate.
- Ran `openssl s_server -cert cert.pem -key key.pem -WWW -port 8443`.
- Verified that `cargo run pip install
git+https://localhost:8443/repo.git` failed with:

```
error: Git operation failed
  Caused by: failed to fetch into: /Users/crmarsh/.cache/uv/git-v0/db/0773914b3ec4a56e
  Caused by: process didn't exit successfully: `/usr/bin/git fetch --force --update-head-ok 'https://localhost:8443/repo.git' '+HEAD:refs/remotes/origin/HEAD'` (exit status: 128)
--- stderr
fatal: unable to access 'https://localhost:8443/repo.git/': SSL certificate problem: self signed certificate
```

- Verified that `cargo run pip install
git+https://localhost:8443/repo.git --allow-insecure-host
https://localhost:8443` continued further.
This commit is contained in:
Charlie Marsh 2025-02-04 10:57:57 -05:00 committed by GitHub
parent d9907f6fda
commit 748582ee6f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 86 additions and 16 deletions

View File

@ -387,17 +387,20 @@ enum Security {
impl BaseClient { impl BaseClient {
/// Selects the appropriate client based on the host's trustworthiness. /// Selects the appropriate client based on the host's trustworthiness.
pub fn for_host(&self, url: &Url) -> &ClientWithMiddleware { pub fn for_host(&self, url: &Url) -> &ClientWithMiddleware {
if self if self.disable_ssl(url) {
.allow_insecure_host
.iter()
.any(|allow_insecure_host| allow_insecure_host.matches(url))
{
&self.dangerous_client &self.dangerous_client
} else { } else {
&self.client &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. /// The configured client timeout, in seconds.
pub fn timeout(&self) -> Duration { pub fn timeout(&self) -> Duration {
self.timeout self.timeout

View File

@ -215,6 +215,11 @@ impl RegistryClient {
self.client.uncached().for_host(url) 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. /// Return the [`Connectivity`] mode used by this client.
pub fn connectivity(&self) -> Connectivity { pub fn connectivity(&self) -> Connectivity {
self.connectivity self.connectivity

View File

@ -1430,7 +1430,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
.git() .git()
.fetch( .fetch(
resource.git, 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.build_context.cache().bucket(CacheBucket::Git),
self.reporter self.reporter
.clone() .clone()
@ -1530,7 +1534,10 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
.git() .git()
.github_fast_path( .github_fast_path(
resource.git, resource.git,
client.unmanaged.uncached_client(resource.url).clone(), client
.unmanaged
.uncached_client(resource.git.repository())
.clone(),
) )
.await .await
{ {
@ -1582,7 +1589,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
.git() .git()
.fetch( .fetch(
resource.git, 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.build_context.cache().bucket(CacheBucket::Git),
self.reporter self.reporter
.clone() .clone()
@ -1812,6 +1823,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
.fetch( .fetch(
git, git,
client.unmanaged.uncached_client(git.repository()).clone(), client.unmanaged.uncached_client(git.repository()).clone(),
client.unmanaged.disable_ssl(git.repository()),
self.build_context.cache().bucket(CacheBucket::Git), self.build_context.cache().bucket(CacheBucket::Git),
self.reporter self.reporter
.clone() .clone()

View File

@ -303,6 +303,7 @@ impl GitRemote {
reference: &GitReference, reference: &GitReference,
locked_rev: Option<GitOid>, locked_rev: Option<GitOid>,
client: &ClientWithMiddleware, client: &ClientWithMiddleware,
disable_ssl: bool,
) -> Result<(GitDatabase, GitOid)> { ) -> Result<(GitDatabase, GitOid)> {
let reference = locked_rev let reference = locked_rev
.map(ReferenceOrOid::Oid) .map(ReferenceOrOid::Oid)
@ -310,7 +311,7 @@ impl GitRemote {
let enable_lfs_fetch = env::var(EnvVars::UV_GIT_LFS).is_ok(); let enable_lfs_fetch = env::var(EnvVars::UV_GIT_LFS).is_ok();
if let Some(mut db) = db { 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()))?; .with_context(|| format!("failed to fetch into: {}", into.user_display()))?;
let resolved_commit_hash = match locked_rev { let resolved_commit_hash = match locked_rev {
@ -320,7 +321,7 @@ impl GitRemote {
if let Some(rev) = resolved_commit_hash { if let Some(rev) = resolved_commit_hash {
if enable_lfs_fetch { 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}"))?; .with_context(|| format!("failed to fetch LFS objects at {rev}"))?;
} }
return Ok((db, rev)); return Ok((db, rev));
@ -338,14 +339,14 @@ impl GitRemote {
fs_err::create_dir_all(into)?; fs_err::create_dir_all(into)?;
let mut repo = GitRepository::init(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()))?; .with_context(|| format!("failed to clone into: {}", into.user_display()))?;
let rev = match locked_rev { let rev = match locked_rev {
Some(rev) => rev, Some(rev) => rev,
None => reference.resolve(&repo)?, None => reference.resolve(&repo)?,
}; };
if enable_lfs_fetch { 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}"))?; .with_context(|| format!("failed to fetch LFS objects at {rev}"))?;
} }
@ -502,6 +503,7 @@ fn fetch(
remote_url: &Url, remote_url: &Url,
reference: ReferenceOrOid<'_>, reference: ReferenceOrOid<'_>,
client: &ClientWithMiddleware, client: &ClientWithMiddleware,
disable_ssl: bool,
) -> Result<()> { ) -> Result<()> {
let oid_to_fetch = match github_fast_path(repo, remote_url, reference, client) { let oid_to_fetch = match github_fast_path(repo, remote_url, reference, client) {
Ok(FastPathRev::UpToDate) => return Ok(()), Ok(FastPathRev::UpToDate) => return Ok(()),
@ -577,14 +579,21 @@ fn fetch(
debug!("Performing a Git fetch for: {remote_url}"); debug!("Performing a Git fetch for: {remote_url}");
let result = match refspec_strategy { 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 => { RefspecStrategy::First => {
// Try each refspec // Try each refspec
let mut errors = refspecs let mut errors = refspecs
.iter() .iter()
.map_while(|refspec| { .map_while(|refspec| {
let fetch_result = let fetch_result = fetch_with_cli(
fetch_with_cli(repo, remote_url, std::slice::from_ref(refspec), tags); repo,
remote_url,
std::slice::from_ref(refspec),
tags,
disable_ssl,
);
// Stop after the first success and log failures // Stop after the first success and log failures
match fetch_result { match fetch_result {
@ -629,12 +638,17 @@ fn fetch_with_cli(
url: &Url, url: &Url,
refspecs: &[String], refspecs: &[String],
tags: bool, tags: bool,
disable_ssl: bool,
) -> Result<()> { ) -> Result<()> {
let mut cmd = ProcessBuilder::new(GIT.as_ref()?); let mut cmd = ProcessBuilder::new(GIT.as_ref()?);
cmd.arg("fetch"); cmd.arg("fetch");
if tags { if tags {
cmd.arg("--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 cmd.arg("--force") // handle force pushes
.arg("--update-head-ok") // see discussion in #2078 .arg("--update-head-ok") // see discussion in #2078
.arg(url.as_str()) .arg(url.as_str())
@ -674,7 +688,12 @@ static GIT_LFS: LazyLock<Result<ProcessBuilder>> = LazyLock::new(|| {
}); });
/// Attempts to use `git-lfs` CLI to fetch required LFS objects for a given revision. /// 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() { let mut cmd = if let Ok(lfs) = GIT_LFS.as_ref() {
debug!("Fetching Git LFS objects"); debug!("Fetching Git LFS objects");
lfs.clone() lfs.clone()
@ -684,6 +703,11 @@ fn fetch_lfs(repo: &mut GitRepository, url: &Url, revision: &GitOid) -> Result<(
return Ok(()); return Ok(());
}; };
if disable_ssl {
debug!("Disabling SSL verification for Git LFS");
cmd.env(EnvVars::GIT_SSL_NO_VERIFY, "true");
}
cmd.arg("fetch") cmd.arg("fetch")
.arg(url.as_str()) .arg(url.as_str())
.arg(revision.as_str()) .arg(revision.as_str())

View File

@ -106,6 +106,7 @@ impl GitResolver {
&self, &self,
url: &GitUrl, url: &GitUrl,
client: ClientWithMiddleware, client: ClientWithMiddleware,
disable_ssl: bool,
cache: PathBuf, cache: PathBuf,
reporter: Option<Arc<dyn Reporter>>, reporter: Option<Arc<dyn Reporter>>,
) -> Result<Fetch, GitResolverError> { ) -> Result<Fetch, GitResolverError> {
@ -139,6 +140,14 @@ impl GitResolver {
} else { } else {
GitSource::new(url.as_ref().clone(), client, cache) 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()) let fetch = tokio::task::spawn_blocking(move || source.fetch())
.await? .await?
.map_err(GitResolverError::Git)?; .map_err(GitResolverError::Git)?;

View File

@ -22,6 +22,8 @@ pub struct GitSource {
git: GitUrl, git: GitUrl,
/// The HTTP client to use for fetching. /// The HTTP client to use for fetching.
client: ClientWithMiddleware, client: ClientWithMiddleware,
/// Whether to disable SSL verification.
disable_ssl: bool,
/// The path to the Git source database. /// The path to the Git source database.
cache: PathBuf, cache: PathBuf,
/// The reporter to use for this source. /// The reporter to use for this source.
@ -37,12 +39,22 @@ impl GitSource {
) -> Self { ) -> Self {
Self { Self {
git, git,
disable_ssl: false,
client: client.into(), client: client.into(),
cache: cache.into(), cache: cache.into(),
reporter: None, 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`]. /// Set the [`Reporter`] to use for the [`GitSource`].
#[must_use] #[must_use]
pub fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self { pub fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self {
@ -96,6 +108,7 @@ impl GitSource {
&self.git.reference, &self.git.reference,
locked_rev.map(GitOid::from), locked_rev.map(GitOid::from),
&self.client, &self.client,
self.disable_ssl,
)?; )?;
(db, actual_rev, task) (db, actual_rev, task)

View File

@ -449,6 +449,10 @@ impl EnvVars {
#[attr_hidden] #[attr_hidden]
pub const GIT_ALTERNATE_OBJECT_DIRECTORIES: &'static str = "GIT_ALTERNATE_OBJECT_DIRECTORIES"; 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. /// Used in tests for better git isolation.
/// ///
/// For example, we run some tests in ~/.local/share/uv/tests. /// For example, we run some tests in ~/.local/share/uv/tests.