From 16404f1cd5dce80e6d95b515e8cd36c5bd996863 Mon Sep 17 00:00:00 2001 From: Manuel Huber Date: Wed, 3 Jun 2026 18:06:52 +0000 Subject: [PATCH] agent: format fresh block emptyDirs Teach the agent to create an ext4 filesystem for fresh block volumes when the storage request carries the block create-filesystem driver option. The create-filesystem option is the freshness and format signal, while storage.fstype remains the filesystem type source. Plain block emptyDirs are formatted directly by the agent. Encrypted block emptyDirs must carry the same create-filesystem signal together with ephemeral encryption and continue through CDH secure_mount. Reject ephemeral encryption without the create-filesystem signal so existing direct block storage cannot accidentally enter the fresh emptyDir path. Signed-off-by: Manuel Huber Assisted-by: OpenAI Codex --- src/agent/src/storage/block_handler.rs | 172 +++++++++++++++++++++++-- 1 file changed, 164 insertions(+), 8 deletions(-) diff --git a/src/agent/src/storage/block_handler.rs b/src/agent/src/storage/block_handler.rs index e4310a33b8..133d485952 100644 --- a/src/agent/src/storage/block_handler.rs +++ b/src/agent/src/storage/block_handler.rs @@ -16,7 +16,7 @@ use kata_types::device::DRIVER_BLK_CCW_TYPE; use kata_types::device::{ DRIVER_BLK_MMIO_TYPE, DRIVER_BLK_PCI_TYPE, DRIVER_NVDIMM_TYPE, DRIVER_SCSI_TYPE, }; -use kata_types::mount::StorageDevice; +use kata_types::mount::{StorageDevice, KATA_BLOCK_VOLUME_CREATE_FS}; use nix::sys::stat::{major, minor}; use protocols::agent::Storage; use tracing::instrument; @@ -37,6 +37,17 @@ use slog::Logger; #[cfg(target_arch = "s390x")] use std::str::FromStr; +const EPHEMERAL_ENCRYPTION_DRIVER_OPTION: &str = "encryption_key=ephemeral"; +const MKFS_EXT4: &str = "mkfs.ext4"; +const BLOCK_EMPTYDIR_EXT4_MKFS_OPTS: [&str; 8] = + ["-O", "^has_journal", "-m", "0", "-i", "163840", "-I", "128"]; + +#[derive(Debug, Eq, PartialEq)] +struct BlockStorageDriverOptions { + has_ephemeral_encryption: bool, + should_create_filesystem: bool, +} + fn get_device_number(dev_path: &str, metadata: Option<&fs::Metadata>) -> Result { let dev_id = match metadata { Some(m) => m.rdev(), @@ -54,27 +65,172 @@ async fn handle_block_storage( storage: &Storage, dev_num: &str, ) -> Result> { - let has_ephemeral_encryption = storage - .driver_options - .contains(&"encryption_key=ephemeral".to_string()); + let options = block_storage_driver_options(storage)?; - if has_ephemeral_encryption { + if options.has_ephemeral_encryption { + let mkfs_opts = BLOCK_EMPTYDIR_EXT4_MKFS_OPTS.join(" "); crate::rpc::cdh_secure_mount( "block-device", dev_num, "luks2", &storage.mount_point, - "-O ^has_journal -m 0 -i 163840 -I 128", + &mkfs_opts, ) .await?; set_ownership(logger, storage)?; new_device(storage.mount_point.clone()) } else { + if options.should_create_filesystem { + ensure_block_filesystem(logger, storage).await?; + } let path = common_storage_handler(logger, storage)?; new_device(path) } } +fn block_storage_driver_options(storage: &Storage) -> Result { + let has_ephemeral_encryption = storage + .driver_options + .iter() + .any(|opt| opt == EPHEMERAL_ENCRYPTION_DRIVER_OPTION); + let should_create_filesystem = should_create_block_filesystem(storage); + + if has_ephemeral_encryption && !should_create_filesystem { + return Err(anyhow!( + "{} requires {} for block storage", + EPHEMERAL_ENCRYPTION_DRIVER_OPTION, + KATA_BLOCK_VOLUME_CREATE_FS + )); + } + + Ok(BlockStorageDriverOptions { + has_ephemeral_encryption, + should_create_filesystem, + }) +} + +fn should_create_block_filesystem(storage: &Storage) -> bool { + storage + .driver_options + .iter() + .any(|opt| opt == KATA_BLOCK_VOLUME_CREATE_FS) +} + +async fn ensure_block_filesystem(logger: &Logger, storage: &Storage) -> Result<()> { + match storage.fstype.as_str() { + "ext4" => ensure_ext4_filesystem(logger, &storage.source).await, + _ => Err(anyhow!( + "creating filesystem {} for block storage is unsupported", + storage.fstype + )), + } +} + +async fn ensure_ext4_filesystem(logger: &Logger, source: &str) -> Result<()> { + // This option is emitted for block emptyDir volumes, whose backing device + // is ephemeral and freshly allocated for the pod. + info!(logger, "creating ext4 filesystem"; "source" => source); + let output = { + // Keep the agent SIGCHLD handler from reaping this child before + // tokio::process observes it. + let _locker = rustjail::container::WAIT_PID_LOCKER.lock().await; + // BLOCK_EMPTYDIR_EXT4_MKFS_OPTS mirrors CDH's EXT4_INTEGRITY_MKFS_OPTS + // from confidential-data-hub/hub/src/storage/volume_type/blockdevice/mod.rs. + // CDH's FsFormatter adds "-F" and its mapped device path separately in + // confidential-data-hub/hub/src/storage/drivers/filesystem.rs; here the + // agent invokes mkfs.ext4 directly, so add "-F" and source below. + tokio::process::Command::new(MKFS_EXT4) + .arg("-F") + .args(BLOCK_EMPTYDIR_EXT4_MKFS_OPTS) + .arg(source) + .output() + .await + .with_context(|| format!("run {MKFS_EXT4} for {source}"))? + }; + + if output.status.success() { + return Ok(()); + } + + Err(anyhow!( + "{} failed for {}: status={}, stdout={}, stderr={}", + MKFS_EXT4, + source, + output.status, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + )) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn storage_with_driver_options(options: &[&str]) -> Storage { + Storage { + driver_options: options.iter().map(|opt| opt.to_string()).collect(), + ..Default::default() + } + } + + #[test] + fn block_storage_options_allow_normal_existing_storage() { + let storage = storage_with_driver_options(&[]); + + let options = block_storage_driver_options(&storage).unwrap(); + + assert_eq!( + options, + BlockStorageDriverOptions { + has_ephemeral_encryption: false, + should_create_filesystem: false, + } + ); + } + + #[test] + fn block_storage_options_allow_plain_fresh_storage() { + let storage = storage_with_driver_options(&[KATA_BLOCK_VOLUME_CREATE_FS]); + + let options = block_storage_driver_options(&storage).unwrap(); + + assert_eq!( + options, + BlockStorageDriverOptions { + has_ephemeral_encryption: false, + should_create_filesystem: true, + } + ); + } + + #[test] + fn block_storage_options_allow_encrypted_fresh_storage() { + let storage = storage_with_driver_options(&[ + EPHEMERAL_ENCRYPTION_DRIVER_OPTION, + KATA_BLOCK_VOLUME_CREATE_FS, + ]); + + let options = block_storage_driver_options(&storage).unwrap(); + + assert_eq!( + options, + BlockStorageDriverOptions { + has_ephemeral_encryption: true, + should_create_filesystem: true, + } + ); + } + + #[test] + fn block_storage_options_reject_encryption_without_filesystem_creation() { + let storage = storage_with_driver_options(&[EPHEMERAL_ENCRYPTION_DRIVER_OPTION]); + + let err = block_storage_driver_options(&storage).unwrap_err(); + + assert!(err.to_string().contains(KATA_BLOCK_VOLUME_CREATE_FS)); + } +} + #[derive(Debug)] pub struct VirtioBlkMmioHandler {} @@ -96,8 +252,8 @@ impl StorageHandler for VirtioBlkMmioHandler { .await .context("failed to get mmio device name")?; } - let path = common_storage_handler(ctx.logger, &storage)?; - new_device(path) + let dev_num = get_device_number(&storage.source, None)?; + handle_block_storage(ctx.logger, &storage, &dev_num).await } }