Skip to content

Commit 03ab934

Browse files
committed
podstorage: Fallback to running system's auth.json
This fixes a bug where running bootc upgrade/switch to an image that does not have an auth.json results in failing to pull new LBIs that are stored in an authenticated registry. Signed-off-by: ckyrouac <ckyrouac@redhat.com>
1 parent fece2e0 commit 03ab934

File tree

3 files changed

+118
-20
lines changed

3 files changed

+118
-20
lines changed

crates/lib/src/install/completion.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,9 @@ pub(crate) async fn impl_completion(
315315

316316
// When we're run through ostree, we only lazily initialize the podman storage to avoid
317317
// having a hard dependency on it.
318-
let imgstorage = &CStorage::create(&sysroot_dir, &rundir, sepolicy.as_ref())?;
318+
// Note: We pass None for booted_root since during install completion there's no
319+
// booted deployment to fall back to for auth file lookup.
320+
let imgstorage = &CStorage::create(&sysroot_dir, None, &rundir, sepolicy.as_ref())?;
319321
crate::boundimage::pull_images_impl(imgstorage, bound_images)
320322
.await
321323
.context("pulling bound images")?;

crates/lib/src/podstorage.rs

Lines changed: 99 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,41 @@ pub(crate) const SUBPATH: &str = "storage";
4848
const RUNROOT: &str = "bootc/storage";
4949

5050
/// A bootc-owned instance of `containers-storage:`.
51+
///
52+
/// This struct manages bootc's container image storage, used for:
53+
/// - Logically bound images (LBIs)
54+
/// - Unified image pulls (pulling the host image into bootc storage)
55+
/// - Other container image operations
56+
///
57+
/// ## Auth file lookup
58+
///
59+
/// When pulling images that require authentication, we need to locate auth.json.
60+
/// This struct maintains two root directories to handle auth lookup correctly:
61+
///
62+
/// - `sysroot`: The ostree sysroot directory. This is checked first for auth.json.
63+
/// Depending on the operation, this may be the staged deployment's sysroot (during
64+
/// LBI pulls for an upgrade) or the current sysroot.
65+
///
66+
/// - `booted_root`: The currently running deployment's root filesystem, obtained via
67+
/// `deployment_fd()`. This is used as a fallback when auth.json is not found in
68+
/// the sysroot. This handles the upgrade scenario where the user has auth.json on
69+
/// their running system but is upgrading to an image that doesn't have it baked in.
70+
///
71+
/// This fallback is essential for LBI pulls during upgrades: the LBIs are defined
72+
/// in the *new* image, but we may need to authenticate using credentials from the
73+
/// *running* system.
5174
pub(crate) struct CStorage {
52-
/// The root directory
75+
/// The ostree sysroot directory. This is also checked first for auth.json.
5376
sysroot: Dir,
54-
/// The location of container storage
77+
/// The booted (currently running) deployment's root directory, obtained via
78+
/// `deployment_fd()`. Used as a fallback for auth file lookup when the sysroot
79+
/// doesn't contain auth.json. This is `None` during fresh installs where there
80+
/// is no booted deployment.
81+
booted_root: Option<Dir>,
82+
/// The location of container storage, relative to the sysroot.
5583
storage_root: Dir,
5684
#[allow(dead_code)]
57-
/// Our runtime state
85+
/// Our runtime state directory.
5886
run: Dir,
5987
/// Disallow using this across multiple threads concurrently; while we
6088
/// have internal locking in podman, in the future we may change how
@@ -119,10 +147,50 @@ fn bind_storage_roots(cmd: &mut Command, storage_root: &Dir, run_root: &Dir) ->
119147
Ok(())
120148
}
121149

122-
// Initialize a `podman` subprocess with:
123-
// - storage overridden to point to to storage_root
124-
// - Authentication (auth.json) using the bootc/ostree owned auth
125-
fn new_podman_cmd_in(sysroot: &Dir, storage_root: &Dir, run_root: &Dir) -> Result<Command> {
150+
/// Get the global authfile from the booted deployment's root filesystem.
151+
///
152+
/// This is used as a fallback when the authfile is not found in the sysroot.
153+
/// The booted deployment's root is obtained via `deployment_fd()`, which gives us
154+
/// a Dir handle to the on-disk deployment directory.
155+
///
156+
/// This fallback handles the upgrade scenario where:
157+
/// 1. The user's running system has auth.json (manually added or from the current image)
158+
/// 2. They upgrade to a new image that does NOT have auth.json baked in
159+
/// 3. The new image has LBIs that require authentication
160+
/// 4. We need to use the running system's auth.json to pull those LBIs
161+
fn get_booted_authfile(
162+
booted_root: Option<&Dir>,
163+
) -> Result<Option<(camino::Utf8PathBuf, std::fs::File)>> {
164+
let Some(booted_root) = booted_root else {
165+
return Ok(None);
166+
};
167+
ostree_ext::globals::get_global_authfile(booted_root)
168+
}
169+
170+
/// Initialize a `podman` subprocess configured for bootc's container storage.
171+
///
172+
/// This sets up podman with:
173+
/// - `--root` pointing to bootc's container storage
174+
/// - `--runroot` pointing to runtime state
175+
/// - `REGISTRY_AUTH_FILE` set to an auth.json for authenticated registry access
176+
///
177+
/// # Auth file lookup order
178+
///
179+
/// The auth.json is resolved with the following priority:
180+
/// 1. **Sysroot** (`sysroot` param): Check the ostree sysroot for auth.json.
181+
/// This finds credentials in the sysroot, which depending on the operation
182+
/// may be the staged deployment or the current deployment.
183+
/// 2. **Booted deployment** (`booted_root` param): Fall back to the currently running
184+
/// deployment's root. This finds credentials from the user's running system,
185+
/// which is essential during upgrades where the new image lacks auth.json.
186+
/// 3. **Empty auth**: If neither has auth.json, use an empty `{}` to prevent podman
187+
/// from searching user-owned paths.
188+
fn new_podman_cmd_in(
189+
sysroot: &Dir,
190+
booted_root: Option<&Dir>,
191+
storage_root: &Dir,
192+
run_root: &Dir,
193+
) -> Result<Command> {
126194
let mut cmd = Command::new("podman");
127195
bind_storage_roots(&mut cmd, storage_root, run_root)?;
128196
let run_root = format!("/proc/self/fd/{STORAGE_RUN_FD}");
@@ -132,11 +200,21 @@ fn new_podman_cmd_in(sysroot: &Dir, storage_root: &Dir, run_root: &Dir) -> Resul
132200
let mut tempfile = cap_tempfile::TempFile::new_anonymous(tmpd).map(std::io::BufWriter::new)?;
133201

134202
// Keep this in sync with https://github.com/bootc-dev/containers-image-proxy-rs/blob/b5e0861ad5065f47eaf9cda0d48da3529cc1bc43/src/imageproxy.rs#L310
135-
// We always override the auth to match the bootc setup.
136-
let authfile_fd = ostree_ext::globals::get_global_authfile(sysroot)?.map(|v| v.1);
137-
if let Some(mut fd) = authfile_fd {
203+
// We always override the auth to match the bootc setup. See the function doc comment
204+
// for the full auth lookup order explanation.
205+
let authfile = if let Some((path, file)) = ostree_ext::globals::get_global_authfile(sysroot)? {
206+
tracing::debug!("Using authfile from staged sysroot: {path}");
207+
Some(file)
208+
} else if let Some((path, file)) = get_booted_authfile(booted_root)? {
209+
tracing::debug!("Using authfile from booted deployment: {path}");
210+
Some(file)
211+
} else {
212+
None
213+
};
214+
if let Some(mut fd) = authfile {
138215
std::io::copy(&mut fd, &mut tempfile)?;
139216
} else {
217+
tracing::debug!("No authfile found, using empty auth");
140218
// Note that if there's no bootc-owned auth, then we force an empty authfile to ensure
141219
// that podman doesn't fall back to searching the user-owned paths.
142220
tempfile.write_all(b"{}")?;
@@ -194,7 +272,12 @@ impl CStorage {
194272
/// Create a `podman image` Command instance prepared to operate on our alternative
195273
/// root.
196274
pub(crate) fn new_image_cmd(&self) -> Result<Command> {
197-
let mut r = new_podman_cmd_in(&self.sysroot, &self.storage_root, &self.run)?;
275+
let mut r = new_podman_cmd_in(
276+
&self.sysroot,
277+
self.booted_root.as_ref(),
278+
&self.storage_root,
279+
&self.run,
280+
)?;
198281
// We want to limit things to only manipulating images by default.
199282
r.arg("image");
200283
Ok(r)
@@ -237,6 +320,7 @@ impl CStorage {
237320
#[context("Creating imgstorage")]
238321
pub(crate) fn create(
239322
sysroot: &Dir,
323+
booted_root: Option<&Dir>,
240324
run: &Dir,
241325
sepolicy: Option<&ostree::SePolicy>,
242326
) -> Result<Self> {
@@ -260,7 +344,7 @@ impl CStorage {
260344
// There's no explicit API to initialize a containers-storage:
261345
// root, simply passing a path will attempt to auto-create it.
262346
// We run "podman images" in the new root.
263-
new_podman_cmd_in(&sysroot, &storage_root, &run)?
347+
new_podman_cmd_in(&sysroot, booted_root, &storage_root, &run)?
264348
.stdout(Stdio::null())
265349
.arg("images")
266350
.run_capture_stderr()
@@ -277,11 +361,11 @@ impl CStorage {
277361
Self::ensure_labeled(&storage_root, sepolicy)?;
278362
}
279363

280-
Self::open(sysroot, run)
364+
Self::open(sysroot, booted_root, run)
281365
}
282366

283367
#[context("Opening imgstorage")]
284-
pub(crate) fn open(sysroot: &Dir, run: &Dir) -> Result<Self> {
368+
pub(crate) fn open(sysroot: &Dir, booted_root: Option<&Dir>, run: &Dir) -> Result<Self> {
285369
tracing::trace!("Opening container image store");
286370
Self::init_globals()?;
287371
let subpath = &Self::subpath();
@@ -294,6 +378,7 @@ impl CStorage {
294378
let run = run.open_dir(RUNROOT)?;
295379
Ok(Self {
296380
sysroot: sysroot.try_clone()?,
381+
booted_root: booted_root.map(|d| d.try_clone()).transpose()?,
297382
storage_root,
298383
run,
299384
_unsync: Default::default(),

crates/lib/src/store/mod.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,15 @@ impl Storage {
369369
let ostree = self.get_ostree()?;
370370
let sysroot_dir = crate::utils::sysroot_dir(ostree)?;
371371

372-
let sepolicy = if ostree.booted_deployment().is_none() {
372+
// Get the booted deployment's root filesystem if available.
373+
// This is used for auth file lookup during upgrades.
374+
let booted_root = if let Some(dep) = ostree.booted_deployment() {
375+
Some(deployment_fd(ostree, &dep)?)
376+
} else {
377+
None
378+
};
379+
380+
let sepolicy = if booted_root.is_none() {
373381
// fallback to policy from container root
374382
// this should only happen during cleanup of a broken install
375383
tracing::trace!("falling back to container root's selinux policy");
@@ -379,14 +387,17 @@ impl Storage {
379387
// load the sepolicy from the booted ostree deployment so the imgstorage can be
380388
// properly labeled with /var/lib/container/storage labels
381389
tracing::trace!("loading sepolicy from booted ostree deployment");
382-
let dep = ostree.booted_deployment().unwrap();
383-
let dep_fs = deployment_fd(ostree, &dep)?;
384-
lsm::new_sepolicy_at(&dep_fs)?
390+
lsm::new_sepolicy_at(booted_root.as_ref().unwrap())?
385391
};
386392

387393
tracing::trace!("sepolicy in get_ensure_imgstore: {sepolicy:?}");
388394

389-
let imgstore = CStorage::create(&sysroot_dir, &self.run, sepolicy.as_ref())?;
395+
let imgstore = CStorage::create(
396+
&sysroot_dir,
397+
booted_root.as_ref(),
398+
&self.run,
399+
sepolicy.as_ref(),
400+
)?;
390401
Ok(self.imgstore.get_or_init(|| imgstore))
391402
}
392403

0 commit comments

Comments
 (0)