From b9c7ff6c7d82de537908be1418da7ee856ff521a Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Fri, 31 May 2024 14:15:26 -0400 Subject: [PATCH 1/2] Make authfile API public Prep for having bootc directly read these as part of using podman for pulls. While we're here: - Use cap-std on general principle - Change the usage to just honoring the file content directly instead of re-reading the file - Add unit tests using that too - Switch to using camino for paths --- lib/src/container/mod.rs | 5 +- lib/src/globals.rs | 125 +++++++++++++++++++++++++++++---------- lib/src/lib.rs | 2 +- 3 files changed, 98 insertions(+), 34 deletions(-) diff --git a/lib/src/container/mod.rs b/lib/src/container/mod.rs index ac0c2f8f..c6a7faf0 100644 --- a/lib/src/container/mod.rs +++ b/lib/src/container/mod.rs @@ -26,6 +26,8 @@ //! for this is [planned but not implemented](https://github.com/ostreedev/ostree-rs-ext/issues/12). use anyhow::anyhow; +use cap_std_ext::cap_std; +use cap_std_ext::cap_std::fs::Dir; use containers_image_proxy::oci_spec; use ostree::glib; use serde::Serialize; @@ -423,7 +425,8 @@ pub fn merge_default_container_proxy_opts_with_isolation( let auth_specified = config.auth_anonymous || config.authfile.is_some() || config.auth_data.is_some(); if !auth_specified { - config.authfile = crate::globals::get_global_authfile_path()?; + let root = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?; + config.auth_data = crate::globals::get_global_authfile(root)?.map(|a| a.1); // If there's no authfile, then force on anonymous pulls to ensure // that the container stack doesn't try to find it in the standard // container paths. diff --git a/lib/src/globals.rs b/lib/src/globals.rs index 0f2dbc22..3b03d6f9 100644 --- a/lib/src/globals.rs +++ b/lib/src/globals.rs @@ -1,35 +1,47 @@ -//! Global functions. +//! Module containing access to global state. use super::Result; +use camino::{Utf8Path, Utf8PathBuf}; +use cap_std_ext::cap_std::fs::Dir; +use cap_std_ext::dirext::CapStdExtDirExt as _; use once_cell::sync::OnceCell; use ostree::glib; use std::fs::File; -use std::path::{Path, PathBuf}; struct ConfigPaths { - persistent: PathBuf, - runtime: PathBuf, - system: Option, + persistent: Utf8PathBuf, + runtime: Utf8PathBuf, + system: Option, } /// Get the runtime and persistent config directories. In the system (root) case, these /// system(root) case: /run/ostree /etc/ostree /usr/lib/ostree /// user(nonroot) case: /run/user/$uid/ostree ~/.config/ostree -fn get_config_paths() -> &'static ConfigPaths { - static PATHS: OnceCell = OnceCell::new(); - PATHS.get_or_init(|| { - let mut r = if rustix::process::getuid() == rustix::process::Uid::ROOT { - ConfigPaths { - persistent: PathBuf::from("/etc"), - runtime: PathBuf::from("/run"), - system: PathBuf::from("/usr/lib").into(), - } - } else { - ConfigPaths { - persistent: glib::user_config_dir(), - runtime: glib::user_runtime_dir(), - system: None, - } +fn get_config_paths(root: bool) -> &'static ConfigPaths { + if root { + static PATHS_ROOT: OnceCell = OnceCell::new(); + PATHS_ROOT.get_or_init(|| ConfigPaths::new("etc", "run", Some("usr/lib"))) + } else { + static PATHS_USER: OnceCell = OnceCell::new(); + PATHS_USER.get_or_init(|| { + ConfigPaths::new( + Utf8PathBuf::try_from(glib::user_config_dir()).unwrap(), + Utf8PathBuf::try_from(glib::user_runtime_dir()).unwrap(), + None, + ) + }) + } +} + +impl ConfigPaths { + fn new>(persistent: P, runtime: P, system: Option

) -> Self { + fn relative_owned(p: &Utf8Path) -> Utf8PathBuf { + p.as_str().trim_start_matches('/').into() + } + let mut r = ConfigPaths { + persistent: relative_owned(persistent.as_ref()), + runtime: relative_owned(runtime.as_ref()), + system: system.as_ref().map(|s| relative_owned(s.as_ref())), }; let path = "ostree"; r.persistent.push(path); @@ -38,26 +50,28 @@ fn get_config_paths() -> &'static ConfigPaths { system.push(path); } r - }) -} + } -impl ConfigPaths { /// Return the path and an open fd for a config file, if it exists. - pub(crate) fn open_file(&self, p: impl AsRef) -> Result> { + pub(crate) fn open_file( + &self, + root: &Dir, + p: impl AsRef, + ) -> Result> { let p = p.as_ref(); let mut runtime = self.runtime.clone(); runtime.push(p); - if let Some(f) = crate::container_utils::open_optional(&runtime)? { + if let Some(f) = root.open_optional(&runtime)?.map(|f| f.into_std()) { return Ok(Some((runtime, f))); } let mut persistent = self.persistent.clone(); persistent.push(p); - if let Some(f) = crate::container_utils::open_optional(&persistent)? { + if let Some(f) = root.open_optional(&persistent)?.map(|f| f.into_std()) { return Ok(Some((persistent, f))); } if let Some(mut system) = self.system.clone() { system.push(p); - if let Some(f) = crate::container_utils::open_optional(&system)? { + if let Some(f) = root.open_optional(&system)?.map(|f| f.into_std()) { return Ok(Some((system, f))); } } @@ -66,9 +80,56 @@ impl ConfigPaths { } /// Return the path to the global container authentication file, if it exists. -pub(crate) fn get_global_authfile_path() -> Result> { - let paths = get_config_paths(); - let r = paths.open_file("auth.json")?; - // TODO pass the file descriptor to the proxy, not a global path - Ok(r.map(|v| v.0)) +pub fn get_global_authfile(root: &Dir) -> Result> { + let am_uid0 = rustix::process::getuid() == rustix::process::Uid::ROOT; + get_global_authfile_impl(root, am_uid0) +} + +/// Return the path to the global container authentication file, if it exists. +fn get_global_authfile_impl(root: &Dir, am_uid0: bool) -> Result> { + let paths = get_config_paths(am_uid0); + paths.open_file(root, "auth.json") +} + +#[cfg(test)] +mod tests { + use std::io::Read; + + use super::*; + use camino::Utf8PathBuf; + use cap_std_ext::{cap_std, cap_tempfile}; + + fn read_authfile(root: &Dir) -> Result> { + let r = get_global_authfile_impl(root, true)?; + if let Some((path, mut f)) = r { + let mut s = String::new(); + f.read_to_string(&mut s)?; + Ok(Some((path.try_into()?, s))) + } else { + Ok(None) + } + } + + #[test] + fn test_config_paths() -> Result<()> { + let root = &cap_tempfile::TempDir::new(cap_std::ambient_authority())?; + assert!(read_authfile(root).unwrap().is_none()); + root.create_dir_all("etc/ostree")?; + root.write("etc/ostree/auth.json", "etc ostree auth")?; + let (p, authdata) = read_authfile(root).unwrap().unwrap(); + assert_eq!(p, "etc/ostree/auth.json"); + assert_eq!(authdata, "etc ostree auth"); + root.create_dir_all("usr/lib/ostree")?; + root.write("usr/lib/ostree/auth.json", "usrlib ostree auth")?; + // We should see /etc content still + let (p, authdata) = read_authfile(root).unwrap().unwrap(); + assert_eq!(p, "etc/ostree/auth.json"); + assert_eq!(authdata, "etc ostree auth"); + // Now remove the /etc content, unveiling the /usr content + root.remove_file("etc/ostree/auth.json")?; + let (p, authdata) = read_authfile(root).unwrap().unwrap(); + assert_eq!(p, "usr/lib/ostree/auth.json"); + assert_eq!(authdata, "usrlib ostree auth"); + Ok(()) + } } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 3fbb5e4f..f303129c 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -27,7 +27,7 @@ pub use ostree::gio::glib; type Result = anyhow::Result; // Import global functions. -mod globals; +pub mod globals; mod isolation; From 6f4e8c721f21ff8b9d210f0d4b06194cab0b05ac Mon Sep 17 00:00:00 2001 From: John Eckersberg Date: Tue, 4 Jun 2024 13:29:07 -0400 Subject: [PATCH 2/2] authfile: add tests for non-root case Signed-off-by: John Eckersberg --- lib/src/globals.rs | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/lib/src/globals.rs b/lib/src/globals.rs index 3b03d6f9..56ab3b33 100644 --- a/lib/src/globals.rs +++ b/lib/src/globals.rs @@ -99,8 +99,8 @@ mod tests { use camino::Utf8PathBuf; use cap_std_ext::{cap_std, cap_tempfile}; - fn read_authfile(root: &Dir) -> Result> { - let r = get_global_authfile_impl(root, true)?; + fn read_authfile(root: &Dir, am_uid0: bool) -> Result> { + let r = get_global_authfile_impl(root, am_uid0)?; if let Some((path, mut f)) = r { let mut s = String::new(); f.read_to_string(&mut s)?; @@ -113,23 +113,54 @@ mod tests { #[test] fn test_config_paths() -> Result<()> { let root = &cap_tempfile::TempDir::new(cap_std::ambient_authority())?; - assert!(read_authfile(root).unwrap().is_none()); + assert!(read_authfile(root, true).unwrap().is_none()); root.create_dir_all("etc/ostree")?; root.write("etc/ostree/auth.json", "etc ostree auth")?; - let (p, authdata) = read_authfile(root).unwrap().unwrap(); + let (p, authdata) = read_authfile(root, true).unwrap().unwrap(); assert_eq!(p, "etc/ostree/auth.json"); assert_eq!(authdata, "etc ostree auth"); root.create_dir_all("usr/lib/ostree")?; root.write("usr/lib/ostree/auth.json", "usrlib ostree auth")?; // We should see /etc content still - let (p, authdata) = read_authfile(root).unwrap().unwrap(); + let (p, authdata) = read_authfile(root, true).unwrap().unwrap(); assert_eq!(p, "etc/ostree/auth.json"); assert_eq!(authdata, "etc ostree auth"); // Now remove the /etc content, unveiling the /usr content root.remove_file("etc/ostree/auth.json")?; - let (p, authdata) = read_authfile(root).unwrap().unwrap(); + let (p, authdata) = read_authfile(root, true).unwrap().unwrap(); assert_eq!(p, "usr/lib/ostree/auth.json"); assert_eq!(authdata, "usrlib ostree auth"); + + // Non-root + let mut user_runtime_dir = + Utf8Path::from_path(glib::user_runtime_dir().strip_prefix("/").unwrap()) + .unwrap() + .to_path_buf(); + user_runtime_dir.push("ostree"); + root.create_dir_all(&user_runtime_dir)?; + user_runtime_dir.push("auth.json"); + root.write(&user_runtime_dir, "usr_runtime_dir ostree auth")?; + + let mut user_config_dir = + Utf8Path::from_path(glib::user_config_dir().strip_prefix("/").unwrap()) + .unwrap() + .to_path_buf(); + user_config_dir.push("ostree"); + root.create_dir_all(&user_config_dir)?; + user_config_dir.push("auth.json"); + root.write(&user_config_dir, "usr_config_dir ostree auth")?; + + // We should see runtime_dir content still + let (p, authdata) = read_authfile(root, false).unwrap().unwrap(); + assert_eq!(p, user_runtime_dir); + assert_eq!(authdata, "usr_runtime_dir ostree auth"); + + // Now remove the runtime_dir content, unveiling the config_dir content + root.remove_file(&user_runtime_dir)?; + let (p, authdata) = read_authfile(root, false).unwrap().unwrap(); + assert_eq!(p, user_config_dir); + assert_eq!(authdata, "usr_config_dir ostree auth"); + Ok(()) } }