Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 33 additions & 11 deletions crates/lib/src/podstorage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,22 @@
//! At the current time, this is only used for Logically Bound Images.

use std::collections::HashSet;
use std::io::Seek;
use std::io::{Seek, Write};
use std::os::unix::process::CommandExt;
use std::process::{Command, Stdio};
use std::sync::Arc;

use anyhow::{Context, Result};
use bootc_utils::{AsyncCommandRunExt, CommandRunExt, ExitStatusExt};
use camino::{Utf8Path, Utf8PathBuf};
use cap_std_ext::cap_std;
use cap_std_ext::cap_std::fs::Dir;
use cap_std_ext::cap_tempfile::TempDir;
use cap_std_ext::cmdext::CapStdExtCommandExt;
use cap_std_ext::dirext::CapStdExtDirExt;
use cap_std_ext::{cap_std, cap_tempfile};
use fn_error_context::context;
use ostree_ext::ostree::{self};
use std::os::fd::OwnedFd;
use std::os::fd::{AsFd, AsRawFd, OwnedFd};
use tokio::process::Command as AsyncCommand;

// Pass only 100 args at a time just to avoid potentially overflowing argument
Expand Down Expand Up @@ -119,11 +119,38 @@ fn bind_storage_roots(cmd: &mut Command, storage_root: &Dir, run_root: &Dir) ->
Ok(())
}

fn new_podman_cmd_in(storage_root: &Dir, run_root: &Dir) -> Result<Command> {
// Initialize a `podman` subprocess with:
// - storage overridden to point to to storage_root
// - Authentication (auth.json) using the bootc/ostree owned auth
fn new_podman_cmd_in(sysroot: &Dir, storage_root: &Dir, run_root: &Dir) -> Result<Command> {
let mut cmd = Command::new("podman");
bind_storage_roots(&mut cmd, storage_root, run_root)?;
let run_root = format!("/proc/self/fd/{STORAGE_RUN_FD}");
cmd.args(["--root", STORAGE_ALIAS_DIR, "--runroot", run_root.as_str()]);

let tmpd = &cap_std::fs::Dir::open_ambient_dir("/tmp", cap_std::ambient_authority())?;
let mut tempfile = cap_tempfile::TempFile::new_anonymous(tmpd).map(std::io::BufWriter::new)?;

// Keep this in sync with https://github.com/bootc-dev/containers-image-proxy-rs/blob/b5e0861ad5065f47eaf9cda0d48da3529cc1bc43/src/imageproxy.rs#L310
// We always override the auth to match the bootc setup.
let authfile_fd = ostree_ext::globals::get_global_authfile(sysroot)?.map(|v| v.1);
if let Some(mut fd) = authfile_fd {
std::io::copy(&mut fd, &mut tempfile)?;
} else {
// Note that if there's no bootc-owned auth, then we force an empty authfile to ensure
// that podman doesn't fall back to searching the user-owned paths.
tempfile.write_all(b"{}")?;
}

let tempfile = tempfile
.into_inner()
.map_err(|e| e.into_error())?
.into_std();
let fd: Arc<OwnedFd> = std::sync::Arc::new(tempfile.into());
let target_fd = fd.as_fd().as_raw_fd();
cmd.take_fd_n(fd, target_fd);
cmd.env("REGISTRY_AUTH_FILE", format!("/proc/self/fd/{target_fd}"));

Ok(cmd)
}

Expand Down Expand Up @@ -167,7 +194,7 @@ impl CStorage {
/// Create a `podman image` Command instance prepared to operate on our alternative
/// root.
pub(crate) fn new_image_cmd(&self) -> Result<Command> {
let mut r = new_podman_cmd_in(&self.storage_root, &self.run)?;
let mut r = new_podman_cmd_in(&self.sysroot, &self.storage_root, &self.run)?;
// We want to limit things to only manipulating images by default.
r.arg("image");
Ok(r)
Expand Down Expand Up @@ -233,7 +260,7 @@ impl CStorage {
// There's no explicit API to initialize a containers-storage:
// root, simply passing a path will attempt to auto-create it.
// We run "podman images" in the new root.
new_podman_cmd_in(&storage_root, &run)?
new_podman_cmd_in(&sysroot, &storage_root, &run)?
.stdout(Stdio::null())
.arg("images")
.run_capture_stderr()
Expand Down Expand Up @@ -353,11 +380,6 @@ impl CStorage {
cmd.stdin(Stdio::null());
cmd.stdout(Stdio::null());
cmd.args(["pull", image]);
let authfile = ostree_ext::globals::get_global_authfile(&self.sysroot)?
.map(|(authfile, _fd)| authfile);
if let Some(authfile) = authfile {
cmd.args(["--authfile", authfile.as_str()]);
}
tracing::debug!("Pulling image: {image}");
let mut cmd = AsyncCommand::from(cmd);
cmd.run().await.context("Failed to pull image")?;
Expand Down
8 changes: 8 additions & 0 deletions tmt/tests/booted/readonly/010-test-bootc-container-store.nu
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ if $is_composefs {

# And verify this works
bootc image cmd list -q o>/dev/null

bootc image cmd pull busybox
podman --storage-opt=additionalimagestore=/usr/lib/bootc/storage image exists busybox

'corrupted JSON!@#%!@#' | save -f /run/ostree/auth.json
let e = bootc image cmd pull busybox | complete | get exit_code
assert not equal $e 0
rm -v /run/ostree/auth.json
}

tap ok
5 changes: 5 additions & 0 deletions tmt/tests/booted/test-logically-bound-switch.nu
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ bootc status
let st = bootc status --json | from json
let booted = $st.status.booted.image

# The tests here aren't fetching from a registry which requires auth by default,
# but we can replicate the failure in https://github.com/bootc-dev/bootc/pull/1852
# by just injecting any auth file.
echo '{}' | save -f /run/ostree/auth.json

def initial_setup [] {
bootc image copy-to-storage
podman images
Expand Down