use std::path::{Path, PathBuf};
use anyhow::{Context as _, Result};
use crate::home::write_private;
const REPLACING: &[&str] = &[
"SSL_CERT_FILE",
"REQUESTS_CA_BUNDLE",
"PIP_CERT",
"CURL_CA_BUNDLE",
"GIT_SSL_CAINFO",
"CARGO_HTTP_CAINFO",
];
const ADDING: &[&str] = &["NODE_EXTRA_CA_CERTS"];
const SYSTEM_BUNDLES: &[&str] = &[
"/etc/ssl/cert.pem",
"/etc/ssl/certs/ca-certificates.crt",
"/etc/pki/tls/certs/ca-bundle.crt",
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
"/etc/ssl/ca-bundle.pem",
];
#[derive(Debug)]
pub struct Trust {
dir: PathBuf,
ca: PathBuf,
bundle: Option<PathBuf>,
}
impl Trust {
pub fn write(cert_pem: &str) -> Result<Self> {
let temp =
std::fs::canonicalize(std::env::temp_dir()).context("resolving the temp directory")?;
let dir = temp.join(format!("moat-run-{}-ca", std::process::id()));
let ca = dir.join("ca.pem");
write_private(&ca, cert_pem.as_bytes())?;
let mut trust = Self {
dir,
ca,
bundle: None,
};
let person = std::env::var_os("SSL_CERT_FILE").map(PathBuf::from);
let base = person
.into_iter()
.chain(SYSTEM_BUNDLES.iter().map(PathBuf::from))
.find_map(|path| std::fs::read_to_string(path).ok());
if let Some(mut roots) = base {
if !roots.ends_with('\n') {
roots.push('\n');
}
roots.push_str(cert_pem);
let bundle = trust.dir.join("bundle.pem");
write_private(&bundle, roots.as_bytes())?;
trust.bundle = Some(bundle);
}
Ok(trust)
}
pub fn dir(&self) -> &Path {
&self.dir
}
pub fn env(&self) -> Vec<(&'static str, &Path)> {
let mut vars: Vec<(&'static str, &Path)> =
ADDING.iter().map(|v| (*v, self.ca.as_path())).collect();
if let Some(bundle) = &self.bundle {
vars.extend(REPLACING.iter().map(|v| (*v, bundle.as_path())));
}
vars
}
pub fn notice(&self) -> Option<String> {
self.bundle.is_none().then(|| {
format!(
"moat run: no system root bundle found ({}), so only Node.js clients \
(`NODE_EXTRA_CA_CERTS`) trust the proxy CA; others refuse the hosts \
`tls: terminate` names",
SYSTEM_BUNDLES.join(", ")
)
})
}
}
impl Drop for Trust {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.ca);
if let Some(bundle) = &self.bundle {
let _ = std::fs::remove_file(bundle);
}
let _ = std::fs::remove_dir(&self.dir);
}
}