From dd9cc07285643ccc70aa1b0437af07cac21e96da Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Tue, 20 Jan 2026 19:31:07 +0100 Subject: [PATCH] deb: make sure to have the right apt keyrings --- distro_info.yml | 4 ++- src/apt/keyring.rs | 54 +++++++++++++++++++++++++++++++ src/apt/mod.rs | 1 + src/deb/ephemeral.rs | 5 +++ src/distro_info.rs | 76 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 139 insertions(+), 1 deletion(-) diff --git a/distro_info.yml b/distro_info.yml index 87db24e..c73575b 100644 --- a/distro_info.yml +++ b/distro_info.yml @@ -10,6 +10,7 @@ dist_info: dist: debian: base_url: http://deb.debian.org/debian + archive_keyring: https://ftp-master.debian.org/keys/archive-key-{series_num}.asc pockets: - proposed-updates - updates @@ -18,9 +19,10 @@ dist: network: https://salsa.debian.org/debian/distro-info-data/-/raw/main/debian.csv ubuntu: base_url: http://archive.ubuntu.com/ubuntu + archive_keyring: http://archive.ubuntu.com/ubuntu/project/ubuntu-archive-keyring.gpg pockets: - proposed - updates series: local: /usr/share/distro-info/ubuntu.csv - network: https://salsa.debian.org/debian/distro-info-data/-/raw/main/ubuntu.csv \ No newline at end of file + network: https://salsa.debian.org/debian/distro-info-data/-/raw/main/ubuntu.csv diff --git a/src/apt/keyring.rs b/src/apt/keyring.rs index e69de29..600287c 100644 --- a/src/apt/keyring.rs +++ b/src/apt/keyring.rs @@ -0,0 +1,54 @@ +//! APT keyring management for mmdebstrap +//! +//! Provides a simple function to ensure that archive keyrings are available +//! for mmdebstrap operations by downloading them from specified URLs. + +use crate::context; +use crate::distro_info; +use std::error::Error; +use std::path::Path; +use std::sync::Arc; + +/// Download a keyring into apt trusted.gpg.d directory, trusting that keyring +pub async fn download_trust_keyring( + ctx: Option>, + series: &str, +) -> Result<(), Box> { + let ctx = ctx.unwrap_or_else(context::current); + + // Obtain keyring URL from distro_info + let keyring_url = distro_info::get_keyring_url(series).await?; + log::debug!("Downloading keyring from: {}", keyring_url); + + // Create trusted.gpg.d directory if it doesn't exist + let trusted_gpg_d = "/etc/apt/trusted.gpg.d"; + if !ctx.exists(Path::new(trusted_gpg_d))? { + ctx.command("mkdir").arg("-p").arg(trusted_gpg_d).status()?; + } + + // Generate a filename for the keyring + let filename = format!("pkh-{}.gpg", series); + let keyring_path = format!("{}/{}", trusted_gpg_d, filename); + + // Download the keyring directly to the final location using curl + let mut curl_cmd = ctx.command("curl"); + curl_cmd + .arg("-s") + .arg("-f") + .arg("-L") + .arg(&keyring_url) + .arg("--output") + .arg(&keyring_path); + + let status = curl_cmd.status()?; + if !status.success() { + return Err(format!("Failed to download keyring from {}", keyring_url).into()); + } + + log::info!( + "Successfully downloaded and installed keyring for {} to {}", + series, + keyring_path + ); + Ok(()) +} diff --git a/src/apt/mod.rs b/src/apt/mod.rs index ed814b8..c05834c 100644 --- a/src/apt/mod.rs +++ b/src/apt/mod.rs @@ -1 +1,2 @@ +pub mod keyring; pub mod sources; diff --git a/src/deb/ephemeral.rs b/src/deb/ephemeral.rs index 5f52a63..65c3440 100644 --- a/src/deb/ephemeral.rs +++ b/src/deb/ephemeral.rs @@ -120,6 +120,11 @@ impl EphemeralContextGuard { .arg(lockfile_path.to_string_lossy().to_string()) .status()?; + // Make sure we have the right apt keyrings to mmdebstrap the chroot + tokio::runtime::Runtime::new().unwrap().block_on( + crate::apt::keyring::download_trust_keyring(Some(ctx.clone()), series), + )?; + // Use mmdebstrap to download the tarball to the cache directory let status = ctx .command("mmdebstrap") diff --git a/src/distro_info.rs b/src/distro_info.rs index 397a4f3..7665e5a 100644 --- a/src/distro_info.rs +++ b/src/distro_info.rs @@ -13,6 +13,7 @@ struct SeriesInfo { #[derive(Debug, Deserialize)] struct DistData { base_url: String, + archive_keyring: String, pockets: Vec, series: SeriesInfo, } @@ -124,6 +125,27 @@ pub fn get_base_url(dist: &str) -> String { DATA.dist.get(dist).unwrap().base_url.clone() } +/// Obtain the URL for the archive keyring of a distribution series +pub async fn get_keyring_url(series: &str) -> Result> { + let dist = get_dist_from_series(series).await?; + let dist_data = DATA + .dist + .get(&dist) + .ok_or(format!("Unsupported distribution: {}", dist))?; + + // For Debian, we need the series number to form the keyring URL + if dist == "debian" { + let series_num = get_debian_series_number(series).await?.unwrap(); + // Replace {series_num} placeholder with the actual series number + Ok(dist_data + .archive_keyring + .replace("{series_num}", &series_num)) + } else { + // For other distributions like Ubuntu, use the keyring directly + Ok(dist_data.archive_keyring.clone()) + } +} + /// Obtain the URL for the 'Release' file of a distribution series fn get_release_url(base_url: &str, series: &str, pocket: &str) -> String { let pocket_full = if pocket.is_empty() { @@ -159,6 +181,44 @@ pub async fn get_components( Err("Components not found.".into()) } +/// Map a Debian series name to its version number +pub async fn get_debian_series_number(series: &str) -> Result, Box> { + let series_info = &DATA.dist.get("debian").unwrap().series; + let content = if Path::new(series_info.local.as_str()).exists() { + std::fs::read_to_string(series_info.local.as_str())? + } else { + reqwest::get(series_info.network.as_str()) + .await? + .text() + .await? + }; + + let mut rdr = csv::ReaderBuilder::new() + .flexible(true) + .from_reader(content.as_bytes()); + + let headers = rdr.headers()?.clone(); + let series_idx = headers + .iter() + .position(|h| h == "series") + .ok_or("Column 'series' not found")?; + let version_idx = headers + .iter() + .position(|h| h == "version") + .ok_or("Column 'version' not found")?; + + for result in rdr.records() { + let record = result?; + if let (Some(s), Some(v)) = (record.get(series_idx), record.get(version_idx)) + && s.to_lowercase() == series.to_lowercase() + { + return Ok(Some(v.to_string())); + } + } + + Ok(None) +} + #[cfg(test)] mod tests { use super::*; @@ -182,4 +242,20 @@ mod tests { assert_eq!(get_dist_from_series("sid").await.unwrap(), "debian"); assert_eq!(get_dist_from_series("noble").await.unwrap(), "ubuntu"); } + + #[tokio::test] + async fn test_get_debian_series_number() { + // Test with known Debian series + let bookworm_number = get_debian_series_number("bookworm").await.unwrap(); + assert!(bookworm_number.is_some()); + assert_eq!(bookworm_number.unwrap(), "12"); + + let trixie_number = get_debian_series_number("trixie").await.unwrap(); + assert!(trixie_number.is_some()); + assert_eq!(trixie_number.unwrap(), "13"); + + // Test with unknown series + let unknown_number = get_debian_series_number("unknown").await.unwrap(); + assert!(unknown_number.is_none()); + } }