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 {
/// 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

View File

@ -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

View File

@ -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()

View File

@ -303,6 +303,7 @@ impl GitRemote {
reference: &GitReference,
locked_rev: Option<GitOid>,
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<Result<ProcessBuilder>> = 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())

View File

@ -106,6 +106,7 @@ impl GitResolver {
&self,
url: &GitUrl,
client: ClientWithMiddleware,
disable_ssl: bool,
cache: PathBuf,
reporter: Option<Arc<dyn Reporter>>,
) -> Result<Fetch, GitResolverError> {
@ -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)?;

View File

@ -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<dyn Reporter>) -> Self {
@ -96,6 +108,7 @@ impl GitSource {
&self.git.reference,
locked_rev.map(GitOid::from),
&self.client,
self.disable_ssl,
)?;
(db, actual_rev, task)

View File

@ -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.