use std::fmt;
use std::sync::atomic::{AtomicBool, Ordering};
use reqwest::{Client, ClientBuilder};
use tracing::warn;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum TrustFallback {
PublicCaOnly,
NoRoots,
}
#[derive(Debug, Clone)]
pub(crate) struct Degradation {
pub(crate) fallback: TrustFallback,
pub(crate) source: String,
pub(crate) recovery: &'static str,
}
impl fmt::Display for Degradation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.fallback {
TrustFallback::PublicCaOnly => write!(
f,
"the OS certificate store failed to load ({}), so this HTTP client now trusts \
only the built-in public CA bundle — calls to endpoints signed by a private or \
corporate CA will fail. {}",
self.source, self.recovery
),
TrustFallback::NoRoots => write!(
f,
"the OS certificate store failed to load ({}) and the built-in public CA bundle \
could not be used either, so every TLS request from this HTTP client will fail. \
{}",
self.source, self.recovery
),
}
}
}
pub(crate) struct Site {
name: &'static str,
recovery: &'static str,
warned: AtomicBool,
}
impl Site {
const fn new(name: &'static str, recovery: &'static str) -> Self {
Self {
name,
recovery,
warned: AtomicBool::new(false),
}
}
pub(crate) fn name(&self) -> &'static str {
self.name
}
}
pub(crate) static REMOTE_BACKEND: Site = Site::new(
"remote inference backend",
"Inference does not re-read the store while the daemon is running: repair the certificate \
store and restart the daemon to recover.",
);
pub(crate) static HUGGINGFACE_PROBE: Site = Site::new(
"hugging face upgrade probe",
"This client is rebuilt on every probe, so repairing the certificate store restores it with \
no restart.",
);
pub(crate) static VLLM_HEALTH_CHECK: Site = Site::new(
"vllm-mlx health check",
"This client is rebuilt on every health check, so repairing the certificate store restores it \
with no restart.",
);
pub(crate) static MODEL_DOWNLOAD: Site = Site::new(
"model download",
"This client is rebuilt on every download, so repairing the certificate store restores it \
with no restart.",
);
pub(crate) static CATALOG_REFRESH: Site = Site::new(
"model catalog refresh",
"This client is rebuilt on every refresh, so repairing the certificate store restores it with \
no restart.",
);
pub(crate) fn model_download_client() -> Client {
build_client_with_degradation(&MODEL_DOWNLOAD, ClientBuilder::new).0
}
pub(crate) fn catalog_refresh_client() -> Client {
build_client_with_degradation(&CATALOG_REFRESH, ClientBuilder::new).0
}
pub(crate) fn build_client_with_degradation(
site: &'static Site,
make: impl Fn() -> ClientBuilder,
) -> (Client, Option<Degradation>) {
let source = match make().build() {
Ok(client) => return (client, None),
Err(e) => e.to_string(),
};
if let Ok(client) = make().tls_built_in_native_certs(false).build() {
let degradation = Degradation {
fallback: TrustFallback::PublicCaOnly,
source,
recovery: site.recovery,
};
report(site, °radation);
return (client, Some(degradation));
}
let degradation = Degradation {
fallback: TrustFallback::NoRoots,
source,
recovery: site.recovery,
};
report(site, °radation);
match make().tls_built_in_root_certs(false).build() {
Ok(client) => (client, Some(degradation)),
Err(floor) => {
panic!(
"reqwest could not build an HTTP client even with every built-in TLS root \
disabled (not a certificate failure): {floor}"
)
}
}
}
fn report(site: &'static Site, degradation: &Degradation) {
#[cfg(test)]
record_degradation_for_test(site, degradation);
if !site.warned.swap(true, Ordering::SeqCst) {
warn!(site = site.name(), "{degradation}");
}
}
#[cfg(test)]
fn test_records() -> &'static std::sync::Mutex<std::collections::HashMap<&'static str, Degradation>>
{
static RECORDS: std::sync::OnceLock<
std::sync::Mutex<std::collections::HashMap<&'static str, Degradation>>,
> = std::sync::OnceLock::new();
RECORDS.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()))
}
#[cfg(test)]
fn record_degradation_for_test(site: &'static Site, degradation: &Degradation) {
test_records()
.lock()
.unwrap_or_else(|p| p.into_inner())
.insert(site.name(), degradation.clone());
}
#[cfg(test)]
pub(crate) fn last_degradation(site: &'static Site) -> Option<Degradation> {
test_records()
.lock()
.unwrap_or_else(|p| p.into_inner())
.get(site.name())
.cloned()
}
#[cfg(test)]
pub(crate) fn reset_sites_for_test() {
test_records()
.lock()
.unwrap_or_else(|p| p.into_inner())
.clear();
for site in [
&REMOTE_BACKEND,
&HUGGINGFACE_PROBE,
&VLLM_HEALTH_CHECK,
&MODEL_DOWNLOAD,
&CATALOG_REFRESH,
] {
site.warned.store(false, Ordering::SeqCst);
}
}
#[cfg(test)]
pub(crate) mod test_seam {
use std::ffi::OsString;
use std::io::Write;
const SSL_CERT_FILE: &str = "SSL_CERT_FILE";
pub(crate) const UNPARSEABLE_CERT_PEM: &str =
"-----BEGIN CERTIFICATE-----\naGVsbG8gd29ybGQ=\n-----END CERTIFICATE-----\n";
pub(crate) const VALID_CERT_PEM: &str = "\
-----BEGIN CERTIFICATE-----
MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5
MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg
Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl
ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j
QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr
ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr
BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM
YyRIHN8wfdVoOw==
-----END CERTIFICATE-----
";
pub(crate) fn mixed_cert_pem() -> String {
format!("{VALID_CERT_PEM}{UNPARSEABLE_CERT_PEM}")
}
pub(crate) struct TrustStoreScope {
_lock: tokio::sync::MutexGuard<'static, ()>,
previous_file: Option<OsString>,
previous_dir: Option<OsString>,
file: tempfile::NamedTempFile,
}
impl TrustStoreScope {
pub(crate) async fn acquire_async(pem: &str) -> Self {
let lock = crate::openrouter::test_environment_scope_async().await;
Self::install(lock, pem)
}
pub(crate) fn acquire_blocking(pem: &str) -> Self {
let lock = crate::openrouter::test_environment_scope();
Self::install(lock, pem)
}
pub(crate) fn with_lock(lock: tokio::sync::MutexGuard<'static, ()>, pem: &str) -> Self {
Self::install(lock, pem)
}
fn install(lock: tokio::sync::MutexGuard<'static, ()>, pem: &str) -> Self {
let previous_file = std::env::var_os(SSL_CERT_FILE);
let previous_dir = std::env::var_os("SSL_CERT_DIR");
let file = tempfile::NamedTempFile::new().expect("temp cert file");
let mut scope = Self {
_lock: lock,
previous_file,
previous_dir,
file,
};
std::env::remove_var("SSL_CERT_DIR");
scope.repoint(pem);
scope
}
pub(crate) fn repoint(&mut self, pem: &str) {
self.file = tempfile::NamedTempFile::new().expect("temp cert file");
self.file
.write_all(pem.as_bytes())
.expect("write cert fixture");
self.file.flush().expect("flush cert fixture");
std::env::set_var(SSL_CERT_FILE, self.file.path());
}
pub(crate) fn assert_breaks_client_construction(&self) {
assert!(
reqwest::Client::builder().build().is_err(),
"SSL_CERT_FILE fixture did not break certificate loading — a bare \
reqwest client still built. Check the PEM fixture: exactly five hyphens each \
side of BEGIN/END, and a valid-base64 body that is not valid X.509 DER."
);
}
}
impl Drop for TrustStoreScope {
fn drop(&mut self) {
match self.previous_file.take() {
Some(value) => std::env::set_var(SSL_CERT_FILE, value),
None => std::env::remove_var(SSL_CERT_FILE),
}
match self.previous_dir.take() {
Some(value) => std::env::set_var("SSL_CERT_DIR", value),
None => std::env::remove_var("SSL_CERT_DIR"),
}
}
}
}
#[cfg(test)]
mod tests {
use super::test_seam::{TrustStoreScope, UNPARSEABLE_CERT_PEM, VALID_CERT_PEM};
use super::*;
#[tokio::test]
async fn download_and_catalog_clients_degrade_instead_of_panicking() {
let mut scope = TrustStoreScope::acquire_async(UNPARSEABLE_CERT_PEM).await;
scope.assert_breaks_client_construction();
reset_sites_for_test();
let _download = model_download_client();
let _catalog = catalog_refresh_client();
for site in [&MODEL_DOWNLOAD, &CATALOG_REFRESH] {
let record = last_degradation(site)
.unwrap_or_else(|| panic!("{} must keep its own record", site.name()));
assert_eq!(record.fallback, TrustFallback::PublicCaOnly);
assert!(!record.source.is_empty());
assert!(
record.recovery.contains("with no restart"),
"{} rebuilds its client per call and must not demand a restart: {}",
site.name(),
record.recovery
);
}
assert!(
last_degradation(&REMOTE_BACKEND).is_none(),
"records are per site — neither of these may be attributed to inference"
);
scope.repoint(VALID_CERT_PEM);
reset_sites_for_test();
let _download = model_download_client();
let _catalog = catalog_refresh_client();
for site in [&MODEL_DOWNLOAD, &CATALOG_REFRESH] {
assert!(
last_degradation(site).is_none(),
"{} degraded against a healthy trust store",
site.name()
);
}
}
}