Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

install: Add prominent warning+timeout when targeting host root #505

Merged
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
16 changes: 8 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ jobs:
- name: Install
run: sudo tar -C / -xvf bootc.tar.zst
- name: Integration tests
run: sudo podman run --rm -ti --privileged -v /run/systemd:/run/systemd -v /:/run/host -v /usr/bin/bootc:/usr/bin/bootc --pid=host quay.io/fedora/fedora-coreos:testing-devel bootc internal-tests run-privileged-integration
run: sudo podman run --rm --privileged -v /run/systemd:/run/systemd -v /:/run/host -v /usr/bin/bootc:/usr/bin/bootc --pid=host quay.io/fedora/fedora-coreos:testing-devel bootc internal-tests run-privileged-integration
container-tests:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'control/skip-ci') }}
name: "Container testing"
Expand Down Expand Up @@ -146,18 +146,18 @@ jobs:
set -xeuo pipefail
image=quay.io/centos-bootc/centos-bootc-dev:stream9
echo 'ssh-ed25519 ABC0123 testcase@example.com' > test_authorized_keys
sudo podman run --rm -ti --privileged -v ./test_authorized_keys:/test_authorized_keys --env RUST_LOG=debug -v /:/target -v /var/lib/containers:/var/lib/containers -v ./usr/bin/bootc:/usr/bin/bootc --pid=host --security-opt label=disable \
${image} bootc install to-filesystem \
sudo podman run --rm --privileged -v ./test_authorized_keys:/test_authorized_keys --env RUST_LOG=debug -v /:/target -v /var/lib/containers:/var/lib/containers -v ./usr/bin/bootc:/usr/bin/bootc --pid=host --security-opt label=disable \
${image} bootc install to-filesystem --acknowledge-destructive \
--karg=foo=bar --disable-selinux --replace=alongside --root-ssh-authorized-keys=/test_authorized_keys /target
ls -al /boot/loader/
sudo grep foo=bar /boot/loader/entries/*.conf
grep authorized_keys /ostree/deploy/default/deploy/*/etc/tmpfiles.d/bootc-root-ssh.conf
# TODO fix https://github.com/containers/bootc/pull/137
sudo chattr -i /ostree/deploy/default/deploy/*
sudo rm /ostree/deploy/default -rf
sudo podman run --rm -ti --privileged --env RUST_LOG=debug -v /:/target -v /var/lib/containers:/var/lib/containers -v ./usr/bin/bootc:/usr/bin/bootc --pid=host --security-opt label=disable \
${image} bootc install to-existing-root
sudo podman run --rm -ti --privileged -v /:/target -v ./usr/bin/bootc:/usr/bin/bootc --pid=host --security-opt label=disable ${image} bootc internal-tests verify-selinux /target/ostree --warn
sudo podman run --rm --privileged --env RUST_LOG=debug -v /:/target -v /var/lib/containers:/var/lib/containers -v ./usr/bin/bootc:/usr/bin/bootc --pid=host --security-opt label=disable \
${image} bootc install to-existing-root --acknowledge-destructive
sudo podman run --rm --privileged -v /:/target -v ./usr/bin/bootc:/usr/bin/bootc --pid=host --security-opt label=disable ${image} bootc internal-tests verify-selinux /target/ostree --warn
install-to-existing-root:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'control/skip-ci') }}
name: "Test install-to-existing-root"
Expand All @@ -177,7 +177,7 @@ jobs:
# so we bind mount an empty directory over /usr/lib/bootc/install.
empty=$(mktemp -d)
image=quay.io/centos-bootc/centos-bootc-dev:stream9
sudo podman run --rm -ti --privileged --env RUST_LOG=debug -v /:/target -v /var/lib/containers:/var/lib/containers -v ./usr/bin/bootc:/usr/bin/bootc -v ${empty}:/usr/lib/bootc/install --pid=host --security-opt label=disable \
sudo podman run --rm --privileged --env RUST_LOG=debug -v /:/target -v /var/lib/containers:/var/lib/containers -v ./usr/bin/bootc:/usr/bin/bootc -v ${empty}:/usr/lib/bootc/install --pid=host --security-opt label=disable \
${image} bootc install to-existing-root
install-to-loopback:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'control/skip-ci') }}
Expand All @@ -197,5 +197,5 @@ jobs:
image=quay.io/centos-bootc/centos-bootc-dev:stream9
tmpdisk=$(mktemp -p /var/tmp)
truncate -s 20G ${tmpdisk}
sudo podman run --rm -ti --privileged --env RUST_LOG=debug -v /dev:/dev -v /:/target -v /var/lib/containers:/var/lib/containers -v ./usr/bin/bootc:/usr/bin/bootc --pid=host --security-opt label=disable \
sudo podman run --rm --privileged --env RUST_LOG=debug -v /dev:/dev -v /:/target -v /var/lib/containers:/var/lib/containers -v ./usr/bin/bootc:/usr/bin/bootc --pid=host --security-opt label=disable \
-v ${tmpdisk}:/disk ${image} bootc install to-disk --via-loopback /disk
45 changes: 44 additions & 1 deletion lib/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use std::path::Path;
use std::process::Command;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;

use anyhow::Ok;
use anyhow::{anyhow, Context, Result};
Expand Down Expand Up @@ -230,6 +231,10 @@ pub(crate) struct InstallTargetFilesystemOpts {
#[clap(long)]
pub(crate) replace: Option<ReplaceMode>,

/// If the target is the running system's root filesystem, this will skip any warnings.
#[clap(long)]
pub(crate) acknowledge_destructive: bool,

/// The default mode is to "finalize" the target filesystem by invoking `fstrim` and similar
/// operations, and finally mounting it readonly. This option skips those operations. It
/// is then the responsibility of the invoking code to perform those operations.
Expand Down Expand Up @@ -269,6 +274,10 @@ pub(crate) struct InstallToExistingRootOpts {
#[clap(flatten)]
pub(crate) config_opts: InstallConfigOpts,

/// Accept that this is a destructive action and skip a warning timer.
#[clap(long)]
pub(crate) acknowledge_destructive: bool,

/// Path to the mounted root; it's expected to invoke podman with
/// `-v /:/target`, then supplying this argument is unnecessary.
#[clap(default_value = "/target")]
Expand Down Expand Up @@ -1373,6 +1382,34 @@ fn find_root_args_to_inherit(cmdline: &[&str], root_info: &Filesystem) -> Result
Ok(RootMountInfo { mount_spec, kargs })
}

fn warn_on_host_root(rootfs_fd: &Dir) -> Result<()> {
// Seconds for which we wait while warning
const DELAY_SECONDS: u64 = 20;

let host_root_dfd = &Dir::open_ambient_dir("/proc/1/root", cap_std::ambient_authority())?;
let host_root_devstat = rustix::fs::fstatvfs(host_root_dfd)?;
let target_devstat = rustix::fs::fstatvfs(rootfs_fd)?;
if host_root_devstat.f_fsid != target_devstat.f_fsid {
tracing::debug!("Not the host root");
return Ok(());
}
let dashes = "----------------------------";
let timeout = Duration::from_secs(DELAY_SECONDS);
eprintln!("{dashes}");
crate::utils::medium_visibility_warning(
"WARNING: This operation will OVERWRITE THE BOOTED HOST ROOT FILESYSTEM and is NOT REVERSIBLE.",
);
eprintln!("Waiting {timeout:?} to continue; interrupt (Control-C) to cancel.");
eprintln!("{dashes}");

let bar = indicatif::ProgressBar::new_spinner();
bar.enable_steady_tick(Duration::from_millis(100));
std::thread::sleep(timeout);
bar.finish();

Ok(())
}

/// Implementation of the `bootc install to-filsystem` CLI command.
#[context("Installing to filesystem")]
pub(crate) async fn install_to_filesystem(
Expand All @@ -1391,7 +1428,12 @@ pub(crate) async fn install_to_filesystem(
let rootfs_fd = Dir::open_ambient_dir(root_path, cap_std::ambient_authority())
.with_context(|| format!("Opening target root directory {root_path}"))?;
if let Some(false) = ostree_ext::mountutil::is_mountpoint(&rootfs_fd, ".")? {
anyhow::bail!("Not a root mountpoint: {root_path}");
anyhow::bail!("Not a mountpoint: {root_path}");
}

// Check to see if this happens to be the real host root
if !fsopts.acknowledge_destructive {
warn_on_host_root(&rootfs_fd)?;
}

// Gather global state, destructuring the provided options
Expand Down Expand Up @@ -1554,6 +1596,7 @@ pub(crate) async fn install_to_existing_root(opts: InstallToExistingRootOpts) ->
boot_mount_spec: None,
replace: opts.replace,
skip_finalize: true,
acknowledge_destructive: opts.acknowledge_destructive,
},
source_opts: opts.source_opts,
target_opts: opts.target_opts,
Expand Down
2 changes: 1 addition & 1 deletion tests/kolainst/install
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ case "${AUTOPKGTEST_REBOOT_MARK:-}" in
COPY usr usr
EOF
podman build -t localhost/testimage .
podman run --rm -ti --privileged --pid=host --env RUST_LOG=error,bootc_lib::install=debug \
podman run --rm --privileged --pid=host --env RUST_LOG=error,bootc_lib::install=debug \
localhost/testimage bootc install to-disk --skip-fetch-check --karg=foo=bar ${DEV}
# In theory we could e.g. wipe the bootloader setup on the primary disk, then reboot;
# but for now let's just sanity test that the install command executes.
Expand Down