From d567c0eefe320635442ff041ce9e62e88dabda00 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 11 Mar 2024 15:56:00 -0400 Subject: [PATCH] Add argument --- README.md | 2 +- crates/uv-client/Cargo.toml | 2 + crates/uv-client/src/registry_client.rs | 16 +-- crates/uv-client/src/tls.rs | 138 ++++++++++++------------ crates/uv/src/commands/pip_compile.rs | 2 + crates/uv/src/commands/pip_install.rs | 2 + crates/uv/src/commands/pip_sync.rs | 2 + crates/uv/src/main.rs | 10 ++ 8 files changed, 95 insertions(+), 79 deletions(-) diff --git a/README.md b/README.md index 427d97c33218..0e81ebec0f0c 100644 --- a/README.md +++ b/README.md @@ -423,7 +423,7 @@ In addition, uv respects the following environment variables: uv supports custom CA certificates (such as those needed by corporate proxies) by utilizing the system's trust store. To ensure this works out of the box, ensure your certificates are added to the -system's trust store. +system's trust store, and run uv with the `--native-tls` command-line Flag. If a direct path to the certificate is required (e.g., in CI), set the `SSL_CERT_FILE` environment variable to the path of the certificate bundle, to instruct uv to use that file instead of the diff --git a/crates/uv-client/Cargo.toml b/crates/uv-client/Cargo.toml index 29eb2553d515..26c3b73d7f8d 100644 --- a/crates/uv-client/Cargo.toml +++ b/crates/uv-client/Cargo.toml @@ -48,6 +48,8 @@ tokio-util = { workspace = true } tracing = { workspace = true } url = { workspace = true } urlencoding = { workspace = true } + +# These must be kept in-sync with those used by `reqwest`. rustls = { version = "0.21.10" } rustls-native-certs = { version = "0.6.3" } webpki-roots = { version = "0.25.4" } diff --git a/crates/uv-client/src/registry_client.rs b/crates/uv-client/src/registry_client.rs index b0d0b26072fd..b1d6aaf5ca3e 100644 --- a/crates/uv-client/src/registry_client.rs +++ b/crates/uv-client/src/registry_client.rs @@ -33,13 +33,13 @@ use crate::middleware::{NetrcMiddleware, OfflineMiddleware}; use crate::remote_metadata::wheel_metadata_from_remote_zip; use crate::rkyvutil::OwnedArchive; use crate::tls::Roots; -use crate::{CachedClient, CachedClientError, Error, ErrorKind}; +use crate::{tls, CachedClient, CachedClientError, Error, ErrorKind}; /// A builder for an [`RegistryClient`]. #[derive(Debug, Clone)] pub struct RegistryClientBuilder { index_urls: IndexUrls, - native_roots: bool, + native_tls: bool, retries: u32, connectivity: Connectivity, cache: Cache, @@ -50,7 +50,7 @@ impl RegistryClientBuilder { pub fn new(cache: Cache) -> Self { Self { index_urls: IndexUrls::default(), - native_roots: false, + native_tls: false, cache, connectivity: Connectivity::Online, retries: 3, @@ -79,8 +79,8 @@ impl RegistryClientBuilder { } #[must_use] - pub fn native_roots(mut self, native_roots: bool) -> Self { - self.native_roots = native_roots; + pub fn native_tls(mut self, native_tls: bool) -> Self { + self.native_tls = native_tls; self } @@ -120,12 +120,12 @@ impl RegistryClientBuilder { // Initialize the base client. let client = self.client.unwrap_or_else(|| { // Load the TLS configuration. - let roots = if self.native_roots { + let tls = tls::load(if self.native_tls { Roots::Native } else { Roots::Webpki - }; - let tls = roots.load().expect("Failed to load TLS configuration."); + }) + .expect("Failed to load TLS configuration."); let client_core = ClientBuilder::new() .user_agent(user_agent_string) diff --git a/crates/uv-client/src/tls.rs b/crates/uv-client/src/tls.rs index 9c82280716d9..7118a4c05789 100644 --- a/crates/uv-client/src/tls.rs +++ b/crates/uv-client/src/tls.rs @@ -19,86 +19,84 @@ pub(crate) enum Roots { Native, } -impl Roots { - /// Initialize a TLS configuration for the client. - /// - /// This is equivalent to the TLS initialization `reqwest` when `rustls-tls` is enabled, - /// with two notable changes: - /// - /// 1. It enables _either_ the `webpki-roots` or the `native-certs` feature, but not both. - /// 2. It assumes the following builder settings (which match the defaults): - /// - `root_certs: vec![]` - /// - `min_tls_version: None` - /// - `max_tls_version: None` - /// - `identity: None` - /// - `certs_verification: false` - /// - `tls_sni: true` - /// - `http_version_pref: HttpVersionPref::All` - /// - /// See: - pub(crate) fn load(self) -> Result { - // Set root certificates. - let mut root_cert_store = rustls::RootCertStore::empty(); +/// Initialize a TLS configuration for the client. +/// +/// This is equivalent to the TLS initialization `reqwest` when `rustls-tls` is enabled, +/// with two notable changes: +/// +/// 1. It enables _either_ the `webpki-roots` or the `native-certs` feature, but not both. +/// 2. It assumes the following builder settings (which match the defaults): +/// - `root_certs: vec![]` +/// - `min_tls_version: None` +/// - `max_tls_version: None` +/// - `identity: None` +/// - `certs_verification: false` +/// - `tls_sni: true` +/// - `http_version_pref: HttpVersionPref::All` +/// +/// See: +pub(crate) fn load(roots: Roots) -> Result { + // Set root certificates. + let mut root_cert_store = rustls::RootCertStore::empty(); - match self { - Self::Webpki => { - // Use `rustls-tls-webpki-roots` - use rustls::OwnedTrustAnchor; + match roots { + Roots::Webpki => { + // Use `rustls-tls-webpki-roots` + use rustls::OwnedTrustAnchor; - let trust_anchors = webpki_roots::TLS_SERVER_ROOTS.iter().map(|trust_anchor| { - OwnedTrustAnchor::from_subject_spki_name_constraints( - trust_anchor.subject, - trust_anchor.spki, - trust_anchor.name_constraints, - ) - }); + let trust_anchors = webpki_roots::TLS_SERVER_ROOTS.iter().map(|trust_anchor| { + OwnedTrustAnchor::from_subject_spki_name_constraints( + trust_anchor.subject, + trust_anchor.spki, + trust_anchor.name_constraints, + ) + }); - root_cert_store.add_trust_anchors(trust_anchors); - } - Self::Native => { - // Use: `rustls-tls-native-roots` - let mut valid_count = 0; - let mut invalid_count = 0; - for cert in rustls_native_certs::load_native_certs() - .map_err(TlsError::NativeCertificates)? - { - let cert = rustls::Certificate(cert.0); - // Continue on parsing errors, as native stores often include ancient or syntactically - // invalid certificates, like root certificates without any X509 extensions. - // Inspiration: https://github.com/rustls/rustls/blob/633bf4ba9d9521a95f68766d04c22e2b01e68318/rustls/src/anchors.rs#L105-L112 - match root_cert_store.add(&cert) { - Ok(_) => valid_count += 1, - Err(err) => { - invalid_count += 1; - warn!( - "rustls failed to parse DER certificate {:?} {:?}", - &err, &cert - ); - } + root_cert_store.add_trust_anchors(trust_anchors); + } + Roots::Native => { + // Use: `rustls-tls-native-roots` + let mut valid_count = 0; + let mut invalid_count = 0; + for cert in + rustls_native_certs::load_native_certs().map_err(TlsError::NativeCertificates)? + { + let cert = rustls::Certificate(cert.0); + // Continue on parsing errors, as native stores often include ancient or syntactically + // invalid certificates, like root certificates without any X509 extensions. + // Inspiration: https://github.com/rustls/rustls/blob/633bf4ba9d9521a95f68766d04c22e2b01e68318/rustls/src/anchors.rs#L105-L112 + match root_cert_store.add(&cert) { + Ok(_) => valid_count += 1, + Err(err) => { + invalid_count += 1; + warn!( + "rustls failed to parse DER certificate {:?} {:?}", + &err, &cert + ); } } - if valid_count == 0 && invalid_count > 0 { - return Err(TlsError::ZeroCertificates); - } + } + if valid_count == 0 && invalid_count > 0 { + return Err(TlsError::ZeroCertificates); } } + } - // Build TLS config - let config_builder = rustls::ClientConfig::builder() - .with_safe_default_cipher_suites() - .with_safe_default_kx_groups() - .with_protocol_versions(&rustls::ALL_VERSIONS)? - .with_root_certificates(root_cert_store); + // Build TLS config + let config_builder = ClientConfig::builder() + .with_safe_default_cipher_suites() + .with_safe_default_kx_groups() + .with_protocol_versions(rustls::ALL_VERSIONS)? + .with_root_certificates(root_cert_store); - // Finalize TLS config - let mut tls = config_builder.with_no_client_auth(); + // Finalize TLS config + let mut tls = config_builder.with_no_client_auth(); - // Enable SNI - tls.enable_sni = true; + // Enable SNI + tls.enable_sni = true; - // ALPN protocol - tls.alpn_protocols = vec!["h2".into(), "http/1.1".into()]; + // ALPN protocol + tls.alpn_protocols = vec!["h2".into(), "http/1.1".into()]; - Ok(tls) - } + Ok(tls) } diff --git a/crates/uv/src/commands/pip_compile.rs b/crates/uv/src/commands/pip_compile.rs index fa255357ba50..c3696ef91691 100644 --- a/crates/uv/src/commands/pip_compile.rs +++ b/crates/uv/src/commands/pip_compile.rs @@ -67,6 +67,7 @@ pub(crate) async fn pip_compile( python_version: Option, exclude_newer: Option>, annotation_style: AnnotationStyle, + native_tls: bool, quiet: bool, cache: Cache, printer: Printer, @@ -188,6 +189,7 @@ pub(crate) async fn pip_compile( // Initialize the registry client. let client = RegistryClientBuilder::new(cache.clone()) + .native_tls(native_tls) .connectivity(connectivity) .index_urls(index_locations.index_urls()) .build(); diff --git a/crates/uv/src/commands/pip_install.rs b/crates/uv/src/commands/pip_install.rs index a122db17dd31..e8e7a9f7a536 100644 --- a/crates/uv/src/commands/pip_install.rs +++ b/crates/uv/src/commands/pip_install.rs @@ -67,6 +67,7 @@ pub(crate) async fn pip_install( python: Option, system: bool, break_system_packages: bool, + native_tls: bool, cache: Cache, printer: Printer, ) -> Result { @@ -177,6 +178,7 @@ pub(crate) async fn pip_install( // Initialize the registry client. let client = RegistryClientBuilder::new(cache.clone()) + .native_tls(native_tls) .connectivity(connectivity) .index_urls(index_locations.index_urls()) .build(); diff --git a/crates/uv/src/commands/pip_sync.rs b/crates/uv/src/commands/pip_sync.rs index 1715f08ad9aa..e4561816c465 100644 --- a/crates/uv/src/commands/pip_sync.rs +++ b/crates/uv/src/commands/pip_sync.rs @@ -45,6 +45,7 @@ pub(crate) async fn pip_sync( python: Option, system: bool, break_system_packages: bool, + native_tls: bool, cache: Cache, printer: Printer, ) -> Result { @@ -116,6 +117,7 @@ pub(crate) async fn pip_sync( // Initialize the registry client. let client = RegistryClientBuilder::new(cache.clone()) + .native_tls(native_tls) .connectivity(connectivity) .index_urls(index_locations.index_urls()) .build(); diff --git a/crates/uv/src/main.rs b/crates/uv/src/main.rs index 8271b1ff664c..9b3d6ddb8b8c 100644 --- a/crates/uv/src/main.rs +++ b/crates/uv/src/main.rs @@ -88,6 +88,13 @@ struct Cli { )] color: ColorChoice, + /// Whether to load TLS certificates from the platform's native certificate store. + /// + /// By default, `uv` loads certificates from the bundled `webpki-roots` crate, which contains + /// Mozilla's root certificates. + #[arg(global = true, long)] + native_tls: bool, + #[command(flatten)] cache_args: CacheArgs, } @@ -1384,6 +1391,7 @@ async fn run() -> Result { args.python_version, args.exclude_newer, args.annotation_style, + cli.native_tls, cli.quiet, cache, printer, @@ -1440,6 +1448,7 @@ async fn run() -> Result { args.python, args.system, args.break_system_packages, + cli.native_tls, cache, printer, ) @@ -1535,6 +1544,7 @@ async fn run() -> Result { args.python, args.system, args.break_system_packages, + cli.native_tls, cache, printer, )