Skip to content

Commit

Permalink
install: Guide user towards the correct podman flags
Browse files Browse the repository at this point in the history
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 full privileges (e.g. --privileged flag)

[omer@hal9000 ~]$ 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 ~]$ podman run -it --pid=host --privileged quay.io/otuchfel/bootc:comfy bootc install to-existing-root
ERROR Installing to filesystem: /proc/1 is owned by 65534, not zero; this command must be run in the root user namespace (e.g. not rootless podman)

[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>
omertuc committed Dec 9, 2024
1 parent 33a361c commit ba9cfb6
Showing 3 changed files with 34 additions and 22 deletions.
32 changes: 22 additions & 10 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ use std::io::Seek;
use std::os::unix::process::CommandExt;
use std::process::Command;

use anyhow::{Context, Result};
use anyhow::{ensure, Context, Result};
use camino::Utf8PathBuf;
use cap_std_ext::cap_std;
use cap_std_ext::cap_std::fs::Dir;
@@ -576,15 +576,27 @@ pub(crate) async fn get_storage() -> Result<crate::store::Storage> {
}

#[context("Querying root privilege")]
pub(crate) fn require_root() -> Result<()> {
let uid = rustix::process::getuid();
if !uid.is_root() {
anyhow::bail!("This command requires root privileges");
}
if !rustix::thread::capability_is_in_bounding_set(rustix::thread::Capability::SystemAdmin)? {
anyhow::bail!("This command requires full root privileges (CAP_SYS_ADMIN)");
}
pub(crate) fn require_root(is_container: bool) -> Result<()> {
ensure!(
rustix::process::getuid().is_root(),
if is_container {
"The user inside the container from which you are running this command must be root"
} else {
"This command must be executed as the root user"
}
);

ensure!(
rustix::thread::capability_is_in_bounding_set(rustix::thread::Capability::SystemAdmin)?,
if is_container {
"The container must be executed with full privileges (e.g. --privileged flag)"
} else {
"This command requires full root privileges (CAP_SYS_ADMIN)"
}
);

tracing::trace!("Verified uid 0 with CAP_SYS_ADMIN");

Ok(())
}

@@ -616,7 +628,7 @@ fn prepare_for_write() -> Result<()> {
ostree_booted()?,
"This command requires an ostree-booted host system"
);
crate::cli::require_root()?;
crate::cli::require_root(false)?;
ensure_self_unshared_mount_namespace()?;
if crate::lsm::selinux_enabled()? && !crate::lsm::selinux_ensure_install()? {
tracing::warn!("Do not have install_t capabilities");
20 changes: 10 additions & 10 deletions lib/src/install.rs
Original file line number Diff line number Diff line change
@@ -1003,7 +1003,7 @@ pub(crate) fn finalize_filesystem(
/// A heuristic check that we were invoked with --pid=host
fn require_host_pidns() -> Result<()> {
if rustix::process::getpid().is_init() {
anyhow::bail!("This command must be run with --pid=host")
anyhow::bail!("This command must be run with the podman --pid=host flag")
}
tracing::trace!("OK: we're not pid 1");
Ok(())
@@ -1019,9 +1019,7 @@ fn require_host_userns() -> Result<()> {
.uid();
// We must really be in a rootless container, or in some way
// we're not part of the host user namespace.
if pid1_uid != 0 {
anyhow::bail!("{proc1} is owned by {pid1_uid}, not zero; this command must be run in the root user namespace (e.g. not rootless podman)");
}
ensure!(pid1_uid == 0, "{proc1} is owned by {pid1_uid}, not zero; this command must be run in the root user namespace (e.g. not rootless podman)");
tracing::trace!("OK: we're in a matching user namespace with pid1");
Ok(())
}
@@ -1154,18 +1152,17 @@ async fn prepare_install(
target_opts: InstallTargetOpts,
) -> Result<Arc<State>> {
tracing::trace!("Preparing install");
// We need full root privileges, i.e. --privileged in podman
crate::cli::require_root()?;
let rootfs = cap_std::fs::Dir::open_ambient_dir("/", cap_std::ambient_authority())
.context("Opening /")?;

let host_is_container = crate::containerenv::is_container(&rootfs);
let external_source = source_opts.source_imgref.is_some();
let source = match source_opts.source_imgref {
None => {
if !host_is_container {
anyhow::bail!("Either --source-imgref must be defined or this command must be executed inside a podman container.")
}
ensure!(host_is_container, "Either --source-imgref must be defined or this command must be executed inside a podman container.");

crate::cli::require_root(true)?;

require_host_pidns()?;
// Out of conservatism we only verify the host userns path when we're expecting
// to do a self-install (e.g. not bootc-image-builder or equivalent).
@@ -1187,7 +1184,10 @@ async fn prepare_install(

SourceInfo::from_container(&rootfs, &container_info)?
}
Some(source) => SourceInfo::from_imageref(&source, &rootfs)?,
Some(source) => {
crate::cli::require_root(false)?;
SourceInfo::from_imageref(&source, &rootfs)?
}
};

// Parse the target CLI image reference options and create the *target* image
4 changes: 2 additions & 2 deletions lib/src/install/completion.rs
Original file line number Diff line number Diff line change
@@ -197,7 +197,7 @@ pub(crate) async fn run_from_anaconda(rootfs: &Dir) -> Result<()> {
// unshare our mount namespace, so any *further* mounts aren't leaked.
// Note that because this does a re-exec, anything *before* this point
// should be idempotent.
crate::cli::require_root()?;
crate::cli::require_root(false)?;
crate::cli::ensure_self_unshared_mount_namespace()?;

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

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

0 comments on commit ba9cfb6

Please sign in to comment.