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 } }