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 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 <[email protected]>
  • Loading branch information
omertuc committed Dec 9, 2024
1 parent af4d43f commit 223606d
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 20 deletions.
55 changes: 44 additions & 11 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
//! Command line tool to manage bootable ostree-based containers.
use std::ffi::{CString, OsStr, OsString};
use std::io::Seek;
use std::io::{BufRead, Seek};
use std::os::unix::process::CommandExt;
use std::process::Command;
use std::sync::OnceLock;

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;
Expand All @@ -20,6 +21,7 @@ use ostree_ext::container as ostree_container;
use ostree_ext::container_utils::ostree_booted;
use ostree_ext::keyfileext::KeyFileExt;
use ostree_ext::ostree;
use regex::Regex;
use schemars::schema_for;
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -576,15 +578,46 @@ 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(),
match is_container {
true =>
"The user inside the container from which you are running this command must be root",
false => "This command must be executed as the root user",
}
);

ensure!(
rustix::thread::capability_is_in_bounding_set(rustix::thread::Capability::SystemAdmin)?,
match is_container {
true => "The container must be executed with the podman --privileged flag",
false => "This command requires full root privileges (CAP_SYS_ADMIN)",
}
);

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

Ok(())
}

#[context("Querying root podman")]
pub(crate) fn require_root_podman() -> Result<()> {
let proc_self = Dir::open_ambient_dir("/proc/self", cap_std::ambient_authority())?;
let uid_map = proc_self.open("uid_map")?;
let uid_map = std::io::BufReader::new(uid_map);

let map_lines = uid_map.lines().collect::<std::io::Result<Vec<_>>>()?;

// TODO: I'm unaware of more official channels to check for rootless podman, we should
// probably move to these if and when they exist
static REGEX: OnceLock<Regex> = OnceLock::new();
let root_pattern = REGEX.get_or_init(|| Regex::new(r#"^\s+0\s+0\s+\d+"#).expect("regex"));
ensure!(
map_lines.into_iter().any(|l| root_pattern.is_match(&l)),
"rootless podman unsupported, please run podman as root"
);

Ok(())
}

Expand Down Expand Up @@ -616,7 +649,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");
Expand Down
18 changes: 11 additions & 7 deletions lib/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
Expand Down Expand Up @@ -1154,18 +1154,19 @@ 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)?;

crate::cli::require_root_podman()?;

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).
Expand All @@ -1187,7 +1188,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
Expand Down
4 changes: 2 additions & 2 deletions lib/src/install/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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)?;
Expand Down

0 comments on commit 223606d

Please sign in to comment.