use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::{env, fs};
use crate::AgentdResult;
const CA_TRUST_DIRS: &[&str] = &[
"/usr/local/share/ca-certificates", "/etc/pki/ca-trust/source/anchors", ];
const CA_BUNDLE_PATHS: &[&str] = &[
"/etc/ssl/certs/ca-certificates.crt", "/etc/pki/tls/certs/ca-bundle.crt", "/etc/ssl/cert.pem", ];
const FALLBACK_BUNDLE_PATH: &str = "/etc/ssl/certs/ca-certificates.crt";
const CA_CERT_FILENAMES: &[&str] = &["microsandbox-ca.pem", "microsandbox-ca.crt"];
const HOST_CAS_FILENAMES: &[&str] = &["microsandbox-host-cas.pem", "microsandbox-host-cas.crt"];
const CA_TRUST_DIR_MODE: u32 = 0o755;
pub fn install_ca_cert() -> AgentdResult<()> {
let ca_path = Path::new(microsandbox_protocol::GUEST_TLS_CA_PATH);
if !ca_path.exists() {
return Ok(());
}
let ca_pem = fs::read_to_string(ca_path)?;
eprintln!(
"tls: CA cert found at {}, installing into guest trust store",
ca_path.display()
);
copy_to_trust_dirs(&ca_pem, CA_CERT_FILENAMES);
let bundle_path = append_to_bundle(&ca_pem)?;
unsafe {
env::set_var("SSL_CERT_FILE", &bundle_path);
env::set_var("REQUESTS_CA_BUNDLE", &bundle_path);
env::set_var("CURL_CA_BUNDLE", &bundle_path);
env::set_var(
"NODE_EXTRA_CA_CERTS",
microsandbox_protocol::GUEST_TLS_CA_PATH,
);
}
eprintln!("tls: CA cert installed, bundle={bundle_path}");
Ok(())
}
pub fn install_host_cas() -> AgentdResult<()> {
let path = Path::new(microsandbox_protocol::GUEST_TLS_HOST_CAS_PATH);
if !path.exists() {
return Ok(());
}
let pem = fs::read_to_string(path)?;
eprintln!(
"tls: host CA bundle found at {}, installing into guest trust store",
path.display()
);
copy_to_trust_dirs(&pem, HOST_CAS_FILENAMES);
let bundle_path = append_to_bundle(&pem)?;
eprintln!("tls: host CA bundle installed, bundle={bundle_path}");
Ok(())
}
fn copy_to_trust_dirs(pem: &str, filenames: &[&str]) {
for &dir in CA_TRUST_DIRS {
copy_to_trust_dir(Path::new(dir), pem, filenames);
}
}
fn copy_to_trust_dir(dir_path: &Path, pem: &str, filenames: &[&str]) {
let missing_dirs = missing_ancestors(dir_path);
if let Err(e) = fs::create_dir_all(dir_path) {
eprintln!(
"tls: failed to create CA trust directory {}: {e}",
dir_path.display()
);
return;
}
for dir in missing_dirs {
if let Err(e) = fs::set_permissions(&dir, fs::Permissions::from_mode(CA_TRUST_DIR_MODE)) {
eprintln!(
"tls: failed to set CA trust directory permissions on {}: {e}",
dir.display()
);
return;
}
}
for &filename in filenames {
let dest = dir_path.join(filename);
match fs::write(&dest, pem) {
Ok(()) => eprintln!("tls: copied CA cert to {}", dest.display()),
Err(e) => eprintln!("tls: failed to copy CA cert to {}: {e}", dest.display()),
}
}
}
fn missing_ancestors(path: &Path) -> Vec<PathBuf> {
let mut missing = Vec::new();
let mut current = path;
while !current.exists() {
missing.push(current.to_path_buf());
match current.parent() {
Some(parent) => current = parent,
None => break,
}
}
missing.reverse();
missing
}
fn append_to_bundle(ca_pem: &str) -> AgentdResult<String> {
for &path in CA_BUNDLE_PATHS {
if Path::new(path).exists() {
let mut contents = fs::read_to_string(path)?;
if !contents.ends_with('\n') {
contents.push('\n');
}
contents.push_str(ca_pem);
fs::write(path, contents)?;
return Ok(path.to_string());
}
}
if let Some(parent) = Path::new(FALLBACK_BUNDLE_PATH).parent() {
fs::create_dir_all(parent)?;
}
fs::write(FALLBACK_BUNDLE_PATH, ca_pem)?;
Ok(FALLBACK_BUNDLE_PATH.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ca_cert_filenames_cover_both_extensions() {
assert!(CA_CERT_FILENAMES.contains(&"microsandbox-ca.pem"));
assert!(CA_CERT_FILENAMES.contains(&"microsandbox-ca.crt"));
}
#[test]
fn copy_to_trust_dir_creates_missing_dir_with_mode() {
let root = std::env::temp_dir().join(format!(
"microsandbox-agentd-ca-test-{}",
std::process::id()
));
let dir = root.join("usr/local/share/ca-certificates");
let _ = fs::remove_dir_all(&root);
copy_to_trust_dir(&dir, "test pem", &["microsandbox-ca.crt"]);
assert_eq!(
fs::read_to_string(dir.join("microsandbox-ca.crt")).unwrap(),
"test pem"
);
assert_eq!(
fs::metadata(&dir).unwrap().permissions().mode() & 0o777,
CA_TRUST_DIR_MODE
);
let _ = fs::remove_dir_all(&root);
}
}