Skip to content

Commit 223606d

Browse files
committed
install: Guide user towards the correct podman flags
Modified the error / root checking code a bit to better guide the user towards the correct bootc invocation. Issue BIFROST-552 [1] ``` [omer@hal9000 ~]$ podman run -it quay.io/otuchfel/bootc:comfy bootc install to-existing-root ERROR Installing to filesystem: Querying root privilege: The container must be executed with the podman --privileged flag [omer@hal9000 ~]$ podman run -it --privileged quay.io/otuchfel/bootc:comfy bootc install to-existing-root ERROR Installing to filesystem: Querying root podman: rootless podman unsupported, please run podman as root [omer@hal9000 ~]$ sudo podman run -it --privileged quay.io/otuchfel/bootc:comfy bootc install to-existing-root ERROR Installing to filesystem: This command must be run with the podman --pid=host flag [omer@hal9000 ~]$ sudo podman run -it --privileged --pid=host quay.io/otuchfel/bootc:comfy bootc install to-existing-root Installing image: docker://quay.io/otuchfel/bootc:comfy ... ``` [1] https://issues.redhat.com/browse/BIFROST-552 Signed-off-by: Omer Tuchfeld <omer@tuchfeld.dev>
1 parent af4d43f commit 223606d

File tree

3 files changed

+57
-20
lines changed

3 files changed

+57
-20
lines changed

lib/src/cli.rs

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
//! Command line tool to manage bootable ostree-based containers.
44
55
use std::ffi::{CString, OsStr, OsString};
6-
use std::io::Seek;
6+
use std::io::{BufRead, Seek};
77
use std::os::unix::process::CommandExt;
88
use std::process::Command;
9+
use std::sync::OnceLock;
910

10-
use anyhow::{Context, Result};
11+
use anyhow::{ensure, Context, Result};
1112
use camino::Utf8PathBuf;
1213
use cap_std_ext::cap_std;
1314
use cap_std_ext::cap_std::fs::Dir;
@@ -20,6 +21,7 @@ use ostree_ext::container as ostree_container;
2021
use ostree_ext::container_utils::ostree_booted;
2122
use ostree_ext::keyfileext::KeyFileExt;
2223
use ostree_ext::ostree;
24+
use regex::Regex;
2325
use schemars::schema_for;
2426
use serde::{Deserialize, Serialize};
2527

@@ -576,15 +578,46 @@ pub(crate) async fn get_storage() -> Result<crate::store::Storage> {
576578
}
577579

578580
#[context("Querying root privilege")]
579-
pub(crate) fn require_root() -> Result<()> {
580-
let uid = rustix::process::getuid();
581-
if !uid.is_root() {
582-
anyhow::bail!("This command requires root privileges");
583-
}
584-
if !rustix::thread::capability_is_in_bounding_set(rustix::thread::Capability::SystemAdmin)? {
585-
anyhow::bail!("This command requires full root privileges (CAP_SYS_ADMIN)");
586-
}
581+
pub(crate) fn require_root(is_container: bool) -> Result<()> {
582+
ensure!(
583+
rustix::process::getuid().is_root(),
584+
match is_container {
585+
true =>
586+
"The user inside the container from which you are running this command must be root",
587+
false => "This command must be executed as the root user",
588+
}
589+
);
590+
591+
ensure!(
592+
rustix::thread::capability_is_in_bounding_set(rustix::thread::Capability::SystemAdmin)?,
593+
match is_container {
594+
true => "The container must be executed with the podman --privileged flag",
595+
false => "This command requires full root privileges (CAP_SYS_ADMIN)",
596+
}
597+
);
598+
587599
tracing::trace!("Verified uid 0 with CAP_SYS_ADMIN");
600+
601+
Ok(())
602+
}
603+
604+
#[context("Querying root podman")]
605+
pub(crate) fn require_root_podman() -> Result<()> {
606+
let proc_self = Dir::open_ambient_dir("/proc/self", cap_std::ambient_authority())?;
607+
let uid_map = proc_self.open("uid_map")?;
608+
let uid_map = std::io::BufReader::new(uid_map);
609+
610+
let map_lines = uid_map.lines().collect::<std::io::Result<Vec<_>>>()?;
611+
612+
// TODO: I'm unaware of more official channels to check for rootless podman, we should
613+
// probably move to these if and when they exist
614+
static REGEX: OnceLock<Regex> = OnceLock::new();
615+
let root_pattern = REGEX.get_or_init(|| Regex::new(r#"^\s+0\s+0\s+\d+"#).expect("regex"));
616+
ensure!(
617+
map_lines.into_iter().any(|l| root_pattern.is_match(&l)),
618+
"rootless podman unsupported, please run podman as root"
619+
);
620+
588621
Ok(())
589622
}
590623

@@ -616,7 +649,7 @@ fn prepare_for_write() -> Result<()> {
616649
ostree_booted()?,
617650
"This command requires an ostree-booted host system"
618651
);
619-
crate::cli::require_root()?;
652+
crate::cli::require_root(false)?;
620653
ensure_self_unshared_mount_namespace()?;
621654
if crate::lsm::selinux_enabled()? && !crate::lsm::selinux_ensure_install()? {
622655
tracing::warn!("Do not have install_t capabilities");

lib/src/install.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,7 +1003,7 @@ pub(crate) fn finalize_filesystem(
10031003
/// A heuristic check that we were invoked with --pid=host
10041004
fn require_host_pidns() -> Result<()> {
10051005
if rustix::process::getpid().is_init() {
1006-
anyhow::bail!("This command must be run with --pid=host")
1006+
anyhow::bail!("This command must be run with the podman --pid=host flag")
10071007
}
10081008
tracing::trace!("OK: we're not pid 1");
10091009
Ok(())
@@ -1154,18 +1154,19 @@ async fn prepare_install(
11541154
target_opts: InstallTargetOpts,
11551155
) -> Result<Arc<State>> {
11561156
tracing::trace!("Preparing install");
1157-
// We need full root privileges, i.e. --privileged in podman
1158-
crate::cli::require_root()?;
11591157
let rootfs = cap_std::fs::Dir::open_ambient_dir("/", cap_std::ambient_authority())
11601158
.context("Opening /")?;
11611159

11621160
let host_is_container = crate::containerenv::is_container(&rootfs);
11631161
let external_source = source_opts.source_imgref.is_some();
11641162
let source = match source_opts.source_imgref {
11651163
None => {
1166-
if !host_is_container {
1167-
anyhow::bail!("Either --source-imgref must be defined or this command must be executed inside a podman container.")
1168-
}
1164+
ensure!(host_is_container, "Either --source-imgref must be defined or this command must be executed inside a podman container.");
1165+
1166+
crate::cli::require_root(true)?;
1167+
1168+
crate::cli::require_root_podman()?;
1169+
11691170
require_host_pidns()?;
11701171
// Out of conservatism we only verify the host userns path when we're expecting
11711172
// to do a self-install (e.g. not bootc-image-builder or equivalent).
@@ -1187,7 +1188,10 @@ async fn prepare_install(
11871188

11881189
SourceInfo::from_container(&rootfs, &container_info)?
11891190
}
1190-
Some(source) => SourceInfo::from_imageref(&source, &rootfs)?,
1191+
Some(source) => {
1192+
crate::cli::require_root(false)?;
1193+
SourceInfo::from_imageref(&source, &rootfs)?
1194+
}
11911195
};
11921196

11931197
// Parse the target CLI image reference options and create the *target* image

lib/src/install/completion.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ pub(crate) async fn run_from_anaconda(rootfs: &Dir) -> Result<()> {
197197
// unshare our mount namespace, so any *further* mounts aren't leaked.
198198
// Note that because this does a re-exec, anything *before* this point
199199
// should be idempotent.
200-
crate::cli::require_root()?;
200+
crate::cli::require_root(false)?;
201201
crate::cli::ensure_self_unshared_mount_namespace()?;
202202

203203
if std::env::var_os(ANACONDA_ENV_HINT).is_none() {
@@ -245,7 +245,7 @@ pub(crate) async fn run_from_anaconda(rootfs: &Dir) -> Result<()> {
245245

246246
/// From ostree-rs-ext, run through the rest of bootc install functionality
247247
pub async fn run_from_ostree(rootfs: &Dir, sysroot: &Utf8Path, stateroot: &str) -> Result<()> {
248-
crate::cli::require_root()?;
248+
crate::cli::require_root(false)?;
249249
// Load sysroot from the provided path
250250
let sysroot = ostree::Sysroot::new(Some(&gio::File::for_path(sysroot)));
251251
sysroot.load(gio::Cancellable::NONE)?;

0 commit comments

Comments
 (0)