Skip to content

Commit

Permalink
Add argument
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Mar 11, 2024
1 parent e18fb29 commit d567c0e
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 79 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions crates/uv-client/Cargo.toml
Expand Up @@ -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" }
Expand Down
16 changes: 8 additions & 8 deletions crates/uv-client/src/registry_client.rs
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
Expand Down
138 changes: 68 additions & 70 deletions crates/uv-client/src/tls.rs
Expand Up @@ -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: <https://github.com/seanmonstar/reqwest/blob/e3192638518d577759dd89da489175b8f992b12f/src/async_impl/client.rs#L498>
pub(crate) fn load(self) -> Result<ClientConfig, TlsError> {
// 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: <https://github.com/seanmonstar/reqwest/blob/e3192638518d577759dd89da489175b8f992b12f/src/async_impl/client.rs#L498>
pub(crate) fn load(roots: Roots) -> Result<ClientConfig, TlsError> {
// 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)
}
2 changes: 2 additions & 0 deletions crates/uv/src/commands/pip_compile.rs
Expand Up @@ -67,6 +67,7 @@ pub(crate) async fn pip_compile(
python_version: Option<PythonVersion>,
exclude_newer: Option<DateTime<Utc>>,
annotation_style: AnnotationStyle,
native_tls: bool,
quiet: bool,
cache: Cache,
printer: Printer,
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions crates/uv/src/commands/pip_install.rs
Expand Up @@ -67,6 +67,7 @@ pub(crate) async fn pip_install(
python: Option<String>,
system: bool,
break_system_packages: bool,
native_tls: bool,
cache: Cache,
printer: Printer,
) -> Result<ExitStatus> {
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions crates/uv/src/commands/pip_sync.rs
Expand Up @@ -45,6 +45,7 @@ pub(crate) async fn pip_sync(
python: Option<String>,
system: bool,
break_system_packages: bool,
native_tls: bool,
cache: Cache,
printer: Printer,
) -> Result<ExitStatus> {
Expand Down Expand Up @@ -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();
Expand Down
10 changes: 10 additions & 0 deletions crates/uv/src/main.rs
Expand Up @@ -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,
}
Expand Down Expand Up @@ -1384,6 +1391,7 @@ async fn run() -> Result<ExitStatus> {
args.python_version,
args.exclude_newer,
args.annotation_style,
cli.native_tls,
cli.quiet,
cache,
printer,
Expand Down Expand Up @@ -1440,6 +1448,7 @@ async fn run() -> Result<ExitStatus> {
args.python,
args.system,
args.break_system_packages,
cli.native_tls,
cache,
printer,
)
Expand Down Expand Up @@ -1535,6 +1544,7 @@ async fn run() -> Result<ExitStatus> {
args.python,
args.system,
args.break_system_packages,
cli.native_tls,
cache,
printer,
)
Expand Down

0 comments on commit d567c0e

Please sign in to comment.