From 15fc925d0403ac4fc6f7e7544af6a7471b368e92 Mon Sep 17 00:00:00 2001 From: John Eckersberg Date: Thu, 6 Aug 2026 16:53:07 -0400 Subject: [PATCH 1/7] feat: Add --network-isolation CLI flag (no-op) Add the --network-isolation flag to both the ephemeral (CommonVmOpts) and libvirt (LibvirtRunOpts) CLI option structs. The flag is accepted by the CLI parser but has no effect yet; the actual QEMU restrict=on wiring follows in subsequent commits. This allows the integration test to be written against the flag immediately, following TDD practice. Closes: https://github.com/bootc-dev/bcvk/issues/304 Assisted-by: AI Signed-off-by: John Eckersberg --- crates/kit/src/libvirt/run.rs | 5 +++++ crates/kit/src/run_ephemeral.rs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/crates/kit/src/libvirt/run.rs b/crates/kit/src/libvirt/run.rs index 67eaa645e..da1cebfbd 100644 --- a/crates/kit/src/libvirt/run.rs +++ b/crates/kit/src/libvirt/run.rs @@ -302,6 +302,11 @@ pub struct LibvirtRunOpts { #[clap(long)] pub graphical_console: bool, + /// Isolate the VM from the network. SSH access from the host is + /// preserved, but the VM cannot reach the internet or other hosts. + #[clap(long)] + pub network_isolation: bool, + /// Create a transient VM that disappears on shutdown/reboot #[clap(long)] pub transient: bool, diff --git a/crates/kit/src/run_ephemeral.rs b/crates/kit/src/run_ephemeral.rs index fae2511b2..1f585217c 100644 --- a/crates/kit/src/run_ephemeral.rs +++ b/crates/kit/src/run_ephemeral.rs @@ -437,6 +437,11 @@ pub struct CommonVmOpts { )] pub ssh_keygen: bool, + /// Isolate the VM from the network. SSH access from the host is + /// preserved, but the VM cannot reach the internet or other hosts. + #[clap(long)] + pub network_isolation: bool, + #[clap( long = "virtiofsd", env = "VIRTIOFSD_BIN", From 104c222c41aeb811d27ddf0c90a59c069a75202a Mon Sep 17 00:00:00 2001 From: John Eckersberg Date: Thu, 6 Aug 2026 16:53:54 -0400 Subject: [PATCH 2/7] test: Add integration tests for network isolation Add two tests for --network-isolation support: - test_run_ephemeral_network_reachable: positive control that boots a normal VM and verifies it can ping 8.8.8.8 from inside the guest. This proves run-ssh, guest networking, and external reachability all work. Without this, the isolation test could pass vacuously when SSH or networking is broken for unrelated reasons. - test_run_ephemeral_network_isolation: boots a VM with --network-isolation and verifies that pinging 8.8.8.8 from inside the guest fails. Includes the same positive control as a first step to guard against false passes. The fact that run-ssh itself succeeds proves SSH (host-to-guest via hostfwd) is preserved. Both tests skip gracefully with a loud warning when the host cannot reach 8.8.8.8 (e.g. offline environments). The isolation test is expected to fail against the current no-op flag (the positive control succeeds, then the isolation check fails because the guest can still reach the internet). This establishes the TDD baseline that the implementation commits will fix. Assisted-by: AI Signed-off-by: John Eckersberg --- crates/integration-tests/src/main.rs | 1 + .../src/tests/network_isolation.rs | 139 ++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 crates/integration-tests/src/tests/network_isolation.rs diff --git a/crates/integration-tests/src/main.rs b/crates/integration-tests/src/main.rs index 296f1ce2a..c5bc52855 100644 --- a/crates/integration-tests/src/main.rs +++ b/crates/integration-tests/src/main.rs @@ -19,6 +19,7 @@ mod tests { pub mod libvirt_upload_disk; pub mod libvirt_verb; pub mod mount_feature; + pub mod network_isolation; pub mod run_ephemeral; pub mod run_ephemeral_ignition; pub mod run_ephemeral_ssh; diff --git a/crates/integration-tests/src/tests/network_isolation.rs b/crates/integration-tests/src/tests/network_isolation.rs new file mode 100644 index 000000000..f80eb64ce --- /dev/null +++ b/crates/integration-tests/src/tests/network_isolation.rs @@ -0,0 +1,139 @@ +//! Integration tests for network isolation (--network-isolation flag) +//! +//! Verifies that VMs launched with --network-isolation cannot reach +//! external hosts, while SSH from the host into the VM still works. + +use integration_tests::integration_test; +use itest::TestResult; +use xshell::cmd; + +use crate::{get_bck_command, get_test_image, shell, INTEGRATION_TEST_LABEL}; + +/// Well-known external IP used to verify outbound connectivity. +/// Google Public DNS; chosen because it is highly reliable and +/// responds to ICMP echo. +const EXTERNAL_PROBE_IP: &str = "8.8.8.8"; + +/// Check whether the host can reach the external probe IP. +fn host_can_reach_external() -> bool { + std::process::Command::new("ping") + .args(["-c1", "-W5", EXTERNAL_PROBE_IP]) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status() + .map(|s| s.success()) + .unwrap_or(false) +} + +/// Test that a guest WITHOUT --network-isolation can reach the internet. +/// +/// This is the positive control for the isolation test: it proves that +/// run-ssh works, that the guest has a working network stack, and that +/// the external probe IP is reachable from inside the guest. +/// +/// If the host itself cannot reach the probe IP, the test prints a +/// warning and passes without booting a VM. +fn test_run_ephemeral_network_reachable() -> TestResult { + let sh = shell()?; + let bck = get_bck_command()?; + let image = get_test_image(); + let label = INTEGRATION_TEST_LABEL; + + if !host_can_reach_external() { + eprintln!(); + eprintln!("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + eprintln!("WARNING: Host cannot reach {EXTERNAL_PROBE_IP}"); + eprintln!(" Skipping guest connectivity check."); + eprintln!(" Re-run with internet access for full coverage."); + eprintln!("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + eprintln!(); + return Ok(()); + } + + // Boot a normal VM (no isolation) and verify the guest can ping + // the external probe IP. This proves run-ssh, guest networking, + // and external reachability all work. + cmd!( + sh, + "{bck} ephemeral run-ssh --label {label} {image} -- ping -c1 -W10 {EXTERNAL_PROBE_IP}" + ) + .run()?; + + eprintln!("Guest successfully reached {EXTERNAL_PROBE_IP} (positive control passed)"); + + Ok(()) +} +integration_test!(test_run_ephemeral_network_reachable); + +/// Test that --network-isolation blocks outbound traffic from the guest. +/// +/// 1. Verify the host itself can reach 8.8.8.8. If it cannot, skip with +/// a loud warning (so offline environments are not broken). +/// 2. Boot an ephemeral VM WITHOUT --network-isolation and ping the +/// probe IP. This positive control proves that run-ssh and guest +/// networking work correctly. If this step fails, the test +/// environment is broken and we bail out rather than risk a false +/// pass on the isolation check. +/// 3. Boot an ephemeral VM WITH --network-isolation and ping the same +/// IP. This must fail, proving outbound traffic is blocked. +/// 4. The fact that run-ssh itself succeeds in step 3 proves that SSH +/// (host-to-guest via hostfwd) is preserved under isolation. +fn test_run_ephemeral_network_isolation() -> TestResult { + let sh = shell()?; + let bck = get_bck_command()?; + let image = get_test_image(); + let label = INTEGRATION_TEST_LABEL; + + if !host_can_reach_external() { + eprintln!(); + eprintln!("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + eprintln!("WARNING: Host cannot reach {EXTERNAL_PROBE_IP}"); + eprintln!(" Network isolation test CANNOT verify that the"); + eprintln!(" guest is actually blocked. Skipping."); + eprintln!(" Re-run with internet access for full coverage."); + eprintln!("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + eprintln!(); + return Ok(()); + } + + // Step 1: Positive control -- verify the guest can reach the + // external probe IP without isolation. This guards against a false + // pass where the isolated test "succeeds" because networking is + // broken for an unrelated reason (e.g. SSH itself is not working). + eprintln!( + "Positive control: verifying guest can reach {EXTERNAL_PROBE_IP} without isolation..." + ); + cmd!( + sh, + "{bck} ephemeral run-ssh --label {label} {image} -- ping -c1 -W10 {EXTERNAL_PROBE_IP}" + ) + .run()?; + eprintln!("Positive control passed"); + + // Step 2: Isolation check -- the same ping must fail with + // --network-isolation. run-ssh propagates the guest command's exit + // code, so we use ignore_status() and check manually. + eprintln!("Isolation check: verifying guest CANNOT reach {EXTERNAL_PROBE_IP} with --network-isolation..."); + let output = cmd!( + sh, + "{bck} ephemeral run-ssh --network-isolation --label {label} {image} -- ping -c1 -W10 {EXTERNAL_PROBE_IP}" + ) + .ignore_status() + .output()?; + + assert!( + !output.status.success(), + "Guest ping to {EXTERNAL_PROBE_IP} succeeded despite --network-isolation; \ + expected it to be blocked. stdout: {} stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + ); + + eprintln!( + "Isolation check passed: guest ping failed as expected (exit code {:?})", + output.status.code() + ); + + Ok(()) +} +integration_test!(test_run_ephemeral_network_isolation); From db36863ab3bac434dd714aa0a1aa11108ee02060 Mon Sep 17 00:00:00 2001 From: John Eckersberg Date: Fri, 7 Aug 2026 15:24:23 -0400 Subject: [PATCH 3/7] feat(qemu): Add restrict support to NetworkMode::User Add a `restrict` field to `NetworkMode::User` that maps to QEMU's slirp `restrict=on` option. When enabled, the guest cannot initiate outbound connections except through explicit hostfwd rules (e.g. SSH port forwarding). This is the mechanism that --network-isolation will use. - Add `restrict: bool` to `NetworkMode::User`, defaulting to false - Add `set_network_restrict()` builder method on QemuConfig - Update `enable_ssh_access()` to preserve the restrict setting - Emit `restrict=on` in the -netdev argument when enabled - Add unit tests for the new functionality Assisted-by: AI Signed-off-by: John Eckersberg --- crates/bcvk-qemu/src/qemu.rs | 80 +++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/crates/bcvk-qemu/src/qemu.rs b/crates/bcvk-qemu/src/qemu.rs index 634a991e3..4b279f82f 100644 --- a/crates/bcvk-qemu/src/qemu.rs +++ b/crates/bcvk-qemu/src/qemu.rs @@ -99,12 +99,18 @@ pub enum NetworkMode { User { /// Port forwarding rules: "tcp::2222-:22" format. hostfwd: Vec, + /// When true, block all guest-initiated outbound traffic except + /// explicit hostfwd rules (QEMU slirp `restrict=on`). + restrict: bool, }, } impl Default for NetworkMode { fn default() -> Self { - NetworkMode::User { hostfwd: vec![] } + NetworkMode::User { + hostfwd: vec![], + restrict: false, + } } } @@ -449,15 +455,34 @@ impl QemuConfig { } /// Enable SSH access by configuring port forwarding. + /// + /// Preserves the current `restrict` setting so that callers can + /// enable isolation and SSH in either order. pub fn enable_ssh_access(&mut self, host_port: Option) -> &mut Self { let port = host_port.unwrap_or(2222); // Default to port 2222 on host let hostfwd = format!("tcp::{}-:22", port); // Forward host port to guest port 22 + let restrict = + matches!(&self.network_mode, NetworkMode::User { restrict, .. } if *restrict); self.network_mode = NetworkMode::User { hostfwd: vec![hostfwd], + restrict, }; self } + /// Enable or disable network isolation (QEMU slirp `restrict=on`). + /// + /// When enabled, the guest cannot initiate outbound connections + /// except through explicit `hostfwd` rules (e.g. SSH). + pub fn set_network_restrict(&mut self, restrict: bool) -> &mut Self { + match &mut self.network_mode { + NetworkMode::User { + restrict: current, .. + } => *current = restrict, + } + self + } + /// Add a fw_cfg entry to pass a file to the guest. /// The file will be accessible in the guest via the fw_cfg interface. pub fn add_fw_cfg(&mut self, name: String, file_path: Utf8PathBuf) -> &mut Self { @@ -679,9 +704,13 @@ fn spawn( // Configure network (only User mode supported now) match &config.network_mode { - NetworkMode::User { hostfwd } => { + NetworkMode::User { hostfwd, restrict } => { let mut netdev_parts = vec!["user".to_string(), "id=net0".to_string()]; + if *restrict { + netdev_parts.push("restrict=on".to_string()); + } + // Add port forwarding rules for fwd in hostfwd { netdev_parts.push(format!("hostfwd={}", fwd)); @@ -1051,4 +1080,51 @@ mod tests { assert_eq!(config.fw_cfg_entries[0].0, "opt/com.coreos/config"); assert_eq!(config.fw_cfg_entries[0].1.as_str(), "/test/ignition.json"); } + + #[test] + fn test_network_mode_default_no_restrict() { + let config = QemuConfig::default(); + assert!( + matches!(&config.network_mode, NetworkMode::User { restrict, .. } if !restrict), + "Default network mode should not restrict" + ); + } + + #[test] + fn test_network_mode_restrict() { + let mut config = QemuConfig::default(); + config.set_network_restrict(true); + assert!(matches!(&config.network_mode, NetworkMode::User { restrict, .. } if *restrict),); + + config.set_network_restrict(false); + assert!(matches!(&config.network_mode, NetworkMode::User { restrict, .. } if !restrict),); + } + + #[test] + fn test_enable_ssh_preserves_restrict() { + let mut config = QemuConfig::default(); + config.set_network_restrict(true); + config.enable_ssh_access(Some(2222)); + + match &config.network_mode { + NetworkMode::User { hostfwd, restrict } => { + assert!(restrict, "enable_ssh_access should preserve restrict=true"); + assert_eq!(hostfwd, &["tcp::2222-:22"]); + } + } + } + + #[test] + fn test_enable_ssh_then_restrict() { + let mut config = QemuConfig::default(); + config.enable_ssh_access(Some(3000)); + config.set_network_restrict(true); + + match &config.network_mode { + NetworkMode::User { hostfwd, restrict } => { + assert!(restrict); + assert_eq!(hostfwd, &["tcp::3000-:22"]); + } + } + } } From bb44d45ab44f5b0a94fd339ebf1eb83f27009eb8 Mon Sep 17 00:00:00 2001 From: John Eckersberg Date: Fri, 7 Aug 2026 15:25:20 -0400 Subject: [PATCH 4/7] feat: Wire --network-isolation to QEMU restrict for ephemeral VMs When --network-isolation is passed to `bcvk ephemeral run` or `bcvk ephemeral run-ssh`, set restrict=on on the QEMU slirp netdev. This blocks all guest-initiated outbound connections while preserving SSH access via hostfwd. DNS server injection is also skipped under isolation since external DNS servers are unreachable and the entries would only cause timeouts. Assisted-by: AI Signed-off-by: John Eckersberg --- crates/kit/src/run_ephemeral.rs | 42 ++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/crates/kit/src/run_ephemeral.rs b/crates/kit/src/run_ephemeral.rs index 1f585217c..17a06c00a 100644 --- a/crates/kit/src/run_ephemeral.rs +++ b/crates/kit/src/run_ephemeral.rs @@ -910,21 +910,28 @@ fn prepare_run_command_with_temp( ]); } - // Read host DNS servers and configure them via podman --dns flags + // Read host DNS servers and configure them via podman --dns flags. // This fixes DNS resolution issues when QEMU runs inside containers. // QEMU's slirp reads /etc/resolv.conf from the container's network namespace, // which would otherwise contain unreachable bridge DNS servers (e.g., 169.254.1.1). // Using --dns properly configures /etc/resolv.conf in the container. - let host_dns_servers = read_host_dns_servers(); - - if let Some(ref dns) = host_dns_servers { - debug!("Using DNS servers for ephemeral VM: {:?}", dns); - // Configure DNS servers for the container using --dns flags - // This properly sets up /etc/resolv.conf in the container's network namespace - for server in dns { - cmd.args(["--dns", server]); + // + // Skip DNS injection when network isolation is enabled: the guest + // cannot reach external DNS servers anyway, and the unreachable + // entries would only cause unnecessary timeouts. + let host_dns_servers = if opts.common.network_isolation { + debug!("Network isolation enabled, skipping DNS server injection"); + None + } else { + let servers = read_host_dns_servers(); + if let Some(ref dns) = servers { + debug!("Using DNS servers for ephemeral VM: {:?}", dns); + for server in dns { + cmd.args(["--dns", server]); + } } - } + servers + }; // Pass configuration as JSON via BCK_CONFIG environment variable // Include host DNS servers in the config so they're available inside the container @@ -1996,10 +2003,12 @@ Options= // This fixes DNS resolution issues when QEMU runs inside containers. // QEMU's slirp reads /etc/resolv.conf from the container's network namespace, // and podman properly sets it up using --dns instead of relying on bridge DNS. - if let Some(ref dns_servers) = opts.host_dns_servers { - debug!("DNS servers configured for QEMU slirp: {:?}", dns_servers); - } else { - warn!("No host DNS servers available, QEMU slirp will use container's resolv.conf which may not work"); + if !opts.common.network_isolation { + if let Some(ref dns_servers) = opts.host_dns_servers { + debug!("DNS servers configured for QEMU slirp: {:?}", dns_servers); + } else { + warn!("No host DNS servers available, QEMU slirp will use container's resolv.conf which may not work"); + } } if opts.common.ssh_keygen { @@ -2011,6 +2020,11 @@ Options= // TODO: Add proper SMBIOS credential injection if needed } + if opts.common.network_isolation { + qemu_config.set_network_restrict(true); + debug!("Network isolation enabled: guest outbound traffic blocked (QEMU restrict=on)"); + } + // Set main virtiofs configuration for root filesystem (will be spawned by QEMU) qemu_config.set_main_virtiofs(main_virtiofsd_config.clone()); From 32f0ffef97deebfa1219e4f201ed71a64f7ec320 Mon Sep 17 00:00:00 2001 From: John Eckersberg Date: Fri, 7 Aug 2026 15:25:46 -0400 Subject: [PATCH 5/7] feat: Wire --network-isolation to QEMU restrict for libvirt VMs When --network-isolation is passed to `bcvk libvirt run`, add restrict=on to the QEMU user-mode netdev arguments injected via qemu:commandline. This blocks all guest-initiated outbound connections while preserving SSH access via hostfwd, matching the ephemeral path behavior. Assisted-by: AI Signed-off-by: John Eckersberg --- crates/kit/src/libvirt/run.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/kit/src/libvirt/run.rs b/crates/kit/src/libvirt/run.rs index da1cebfbd..7eadda869 100644 --- a/crates/kit/src/libvirt/run.rs +++ b/crates/kit/src/libvirt/run.rs @@ -1555,8 +1555,13 @@ fn create_libvirt_domain_from_disk( )); } + let restrict_opt = if opts.network_isolation { + ",restrict=on" + } else { + "" + }; let netdev_config = format!( - "user,id=ssh0,{}", + "user,id=ssh0{restrict_opt},{}", hostfwd_args .iter() .map(|fwd| format!("hostfwd={}", fwd)) From 0b7ccfe6222974313852ed272af61a7824a3c3ac Mon Sep 17 00:00:00 2001 From: John Eckersberg Date: Fri, 7 Aug 2026 15:42:50 -0400 Subject: [PATCH 6/7] docs: Sync manpages with --network-isolation option Run `cargo xtask sync-manpages` to regenerate the OPTIONS sections, picking up the new --network-isolation flag in: - bcvk-ephemeral-run(8) - bcvk-ephemeral-run-ssh(8) - bcvk-libvirt-run(8) - bcvk-to-disk(8) Also picks up a previously missing --virtiofsd option in bcvk-libvirt-run(8). Assisted-by: AI Signed-off-by: John Eckersberg --- docs/src/man/bcvk-ephemeral-run-ssh.md | 4 ++++ docs/src/man/bcvk-ephemeral-run.md | 4 ++++ docs/src/man/bcvk-libvirt-run.md | 8 ++++++++ docs/src/man/bcvk-to-disk.md | 4 ++++ 4 files changed, 20 insertions(+) diff --git a/docs/src/man/bcvk-ephemeral-run-ssh.md b/docs/src/man/bcvk-ephemeral-run-ssh.md index 51680ae6d..68604eb99 100644 --- a/docs/src/man/bcvk-ephemeral-run-ssh.md +++ b/docs/src/man/bcvk-ephemeral-run-ssh.md @@ -82,6 +82,10 @@ For longer-running VMs where you need to reconnect multiple times, use Generate SSH keypair and inject via systemd credentials +**--network-isolation** + + Isolate the VM from the network. SSH access from the host is preserved, but the VM cannot reach the internet or other hosts + **--virtiofsd**=*VIRTIOFSD_BINARY* Path to virtiofsd binary (overrides auto-detection) diff --git a/docs/src/man/bcvk-ephemeral-run.md b/docs/src/man/bcvk-ephemeral-run.md index 7de1c3e04..2a4e37251 100644 --- a/docs/src/man/bcvk-ephemeral-run.md +++ b/docs/src/man/bcvk-ephemeral-run.md @@ -84,6 +84,10 @@ This design allows bcvk to provide VM-like isolation and boot behavior while lev Generate SSH keypair and inject via systemd credentials +**--network-isolation** + + Isolate the VM from the network. SSH access from the host is preserved, but the VM cannot reach the internet or other hosts + **--virtiofsd**=*VIRTIOFSD_BINARY* Path to virtiofsd binary (overrides auto-detection) diff --git a/docs/src/man/bcvk-libvirt-run.md b/docs/src/man/bcvk-libvirt-run.md index b9cbbe847..3de01bbc0 100644 --- a/docs/src/man/bcvk-libvirt-run.md +++ b/docs/src/man/bcvk-libvirt-run.md @@ -154,6 +154,10 @@ Run a bootable container as a persistent VM Enable graphical console (SPICE) for virt-manager access +**--network-isolation** + + Isolate the VM from the network. SSH access from the host is preserved, but the VM cannot reach the internet or other hosts + **--transient** Create a transient VM that disappears on shutdown/reboot @@ -162,6 +166,10 @@ Run a bootable container as a persistent VM Path to Ignition config file (JSON format) for first-boot provisioning +**--virtiofsd**=*VIRTIOFSD_BINARY* + + Path to virtiofsd binary (overrides auto-detection for disk creation) + **--console-log**=*CONSOLE_LOG* Log virtio console (OS/journald on hvc0) to this file (created if absent) diff --git a/docs/src/man/bcvk-to-disk.md b/docs/src/man/bcvk-to-disk.md index f552c5440..e892280f2 100644 --- a/docs/src/man/bcvk-to-disk.md +++ b/docs/src/man/bcvk-to-disk.md @@ -115,6 +115,10 @@ The installation process: Generate SSH keypair and inject via systemd credentials +**--network-isolation** + + Isolate the VM from the network. SSH access from the host is preserved, but the VM cannot reach the internet or other hosts + **--virtiofsd**=*VIRTIOFSD_BINARY* Path to virtiofsd binary (overrides auto-detection) From 93225ddc62c663f18397b36c9bf2b8ff6dc492d3 Mon Sep 17 00:00:00 2001 From: John Eckersberg Date: Fri, 7 Aug 2026 15:43:57 -0400 Subject: [PATCH 7/7] docs: Add network isolation examples and prose Add hand-written documentation for --network-isolation to: Manpages (EXAMPLES sections, outside auto-generated markers): - bcvk-ephemeral-run(8): network isolation example with bind-storage-ro - bcvk-ephemeral-run-ssh(8): quick CI testing example - bcvk-libvirt-run(8): hermetic testing example mdBook conceptual docs: - ephemeral-run.md: new Network Isolation section with example - libvirt-advanced.md: network isolation subsection under Network Configuration All hand-written sections are outside the markers and will be preserved by future sync-manpages runs. Assisted-by: AI Signed-off-by: John Eckersberg --- docs/src/ephemeral-run.md | 14 +++++++++++++- docs/src/libvirt-advanced.md | 17 +++++++++++++++++ docs/src/man/bcvk-ephemeral-run-ssh.md | 9 +++++++++ docs/src/man/bcvk-ephemeral-run.md | 16 ++++++++++++++++ docs/src/man/bcvk-libvirt-run.md | 10 ++++++++++ 5 files changed, 65 insertions(+), 1 deletion(-) diff --git a/docs/src/ephemeral-run.md b/docs/src/ephemeral-run.md index 5eaea3a08..3f2c85621 100644 --- a/docs/src/ephemeral-run.md +++ b/docs/src/ephemeral-run.md @@ -40,11 +40,23 @@ even though there won't be any block devices (by default). At the current time there is not a dedicated way to detect `bcvk ephemeral`, but `ConditionKernelCommandLine=!rootfstype=virtiofs` should work reliably in the future. +## Network Isolation + +Use `--network-isolation` to block all outbound network access from the VM. +SSH from the host is preserved, but processes inside the guest cannot reach +the internet. This is useful for hermetic CI/CD testing where all inputs +should be pre-staged: + +```bash +bcvk ephemeral run-ssh --network-isolation --bind-storage-ro \ + localhost/mybootc -- make test +``` + ## Use Cases - Quick testing of bootc images - Development environments - CI/CD integration -- Isolated experimentation +- Hermetic testing with `--network-isolation` See [ephemeral-ssh](./ephemeral-ssh.md) for SSH access details. \ No newline at end of file diff --git a/docs/src/libvirt-advanced.md b/docs/src/libvirt-advanced.md index dc109635f..bb7716274 100644 --- a/docs/src/libvirt-advanced.md +++ b/docs/src/libvirt-advanced.md @@ -21,6 +21,23 @@ Create custom libvirt networks for VM isolation: For direct host network access, use bridged networking or macvtap interfaces. +### Network Isolation + +Use `--network-isolation` to block all outbound traffic from the VM while +preserving SSH access from the host: + +```bash +bcvk libvirt run --name hermetic-test \ + --network-isolation \ + --bind-storage-ro \ + quay.io/centos-bootc/centos-bootc:stream10 +``` + +This is particularly useful for CI/CD pipelines where test execution should +not depend on external network resources. Pre-stage any required container +images or packages on the host and share them into the VM via `--bind`, +`--bind-ro`, or `--bind-storage-ro`. + ## Automation with Scripts Use shell scripts or configuration management tools to automate VM provisioning and management with bcvk libvirt commands. diff --git a/docs/src/man/bcvk-ephemeral-run-ssh.md b/docs/src/man/bcvk-ephemeral-run-ssh.md index 68604eb99..8f9bb40fb 100644 --- a/docs/src/man/bcvk-ephemeral-run-ssh.md +++ b/docs/src/man/bcvk-ephemeral-run-ssh.md @@ -231,6 +231,15 @@ Mount source code for testing: bcvk ephemeral run-ssh --bind /home/user/project:src localhost/mybootc # Inside VM: ls /run/virtiofs-mnt-src +## Network Isolation + +Run tests in a network-isolated VM to avoid flakes from external services: + + bcvk ephemeral run-ssh --network-isolation localhost/mybootc -- make test + +SSH connectivity from the host is preserved; only outbound traffic from +within the guest is blocked. + ## Debugging Enable console output to see boot messages: diff --git a/docs/src/man/bcvk-ephemeral-run.md b/docs/src/man/bcvk-ephemeral-run.md index 2a4e37251..d3c4b74b2 100644 --- a/docs/src/man/bcvk-ephemeral-run.md +++ b/docs/src/man/bcvk-ephemeral-run.md @@ -276,6 +276,22 @@ For network-level port forwarding via podman, configure slirp4netns: --network slirp4netns:port_handler=slirp4netns,allow_host_loopback=true \ --name webvm localhost/mybootc +## Network Isolation + +Block all outbound network access from the VM while preserving SSH +connectivity from the host. This is useful for CI/CD pipelines where test +execution should not depend on external network resources: + + bcvk ephemeral run -d --rm -K \ + --network-isolation \ + --bind-storage-ro \ + --name testvm localhost/mybootc + +The VM can still be reached via **bcvk ephemeral ssh**, but processes inside +the guest cannot connect to the internet or other external hosts. Pre-stage +any required resources (container images, packages) on the host and share +them into the VM using **--bind**, **--ro-bind**, or **--bind-storage-ro**. + ## Instance Types Use predefined instance types for consistent resource allocation: diff --git a/docs/src/man/bcvk-libvirt-run.md b/docs/src/man/bcvk-libvirt-run.md index 3de01bbc0..0379ae469 100644 --- a/docs/src/man/bcvk-libvirt-run.md +++ b/docs/src/man/bcvk-libvirt-run.md @@ -227,6 +227,16 @@ Capture the platform console (UEFI/GRUB/serial) separately: --platform-console-log /var/home/user/vm-serial.log \ quay.io/fedora/fedora-bootc:42 +Create a network-isolated VM for hermetic testing: + + bcvk libvirt run --name test-runner \ + --network-isolation \ + --bind-storage-ro \ + quay.io/centos-bootc/centos-bootc:stream10 + +SSH access from the host is preserved, but processes inside the guest +cannot reach the internet or other external hosts. + Server management workflow: # Create a persistent server VM