diff --git a/src/agent/src/cdh.rs b/src/agent/src/cdh.rs index c8301ae18f..b4a8824fce 100644 --- a/src/agent/src/cdh.rs +++ b/src/agent/src/cdh.rs @@ -8,23 +8,41 @@ // https://github.com/confidential-containers/guest-components/tree/main/confidential-data-hub use anyhow::{anyhow, Result}; +use oci::{Mount, Spec}; use protocols::{ sealed_secret, sealed_secret_ttrpc_async, sealed_secret_ttrpc_async::SealedSecretServiceClient, }; +use std::fs; +use std::os::unix::fs::symlink; +use std::path::Path; const CDH_ADDR: &str = "unix:///run/confidential-containers/cdh.sock"; +const SECRETS_DIR: &str = "/run/secrets/"; +const SEALED_SECRET_TIMEOUT: i64 = 50 * 1000 * 1000 * 1000; + +// Convenience function to obtain the scope logger. +fn sl() -> slog::Logger { + slog_scope::logger() +} #[derive(Clone)] pub struct CDHClient { - sealed_secret_client: SealedSecretServiceClient, + sealed_secret_client: Option, } impl CDHClient { pub fn new() -> Result { - let c = ttrpc::asynchronous::Client::connect(CDH_ADDR)?; - let ssclient = sealed_secret_ttrpc_async::SealedSecretServiceClient::new(c); - Ok(CDHClient { - sealed_secret_client: ssclient, - }) + let c = ttrpc::asynchronous::Client::connect(CDH_ADDR); + match c { + Ok(v) => { + let ssclient = sealed_secret_ttrpc_async::SealedSecretServiceClient::new(v); + Ok(CDHClient { + sealed_secret_client: Some(ssclient), + }) + } + Err(_) => Ok(CDHClient { + sealed_secret_client: None, + }), + } } pub async fn unseal_secret_async( @@ -33,27 +51,25 @@ impl CDHClient { ) -> Result { let secret = sealed .strip_prefix("sealed.") - .ok_or(anyhow!("strip_prefix sealed. failed"))?; + .ok_or(anyhow!("strip_prefix \"sealed.\" failed"))?; let mut input = sealed_secret::UnsealSecretInput::new(); input.set_secret(secret.into()); let unseal = self .sealed_secret_client - .unseal_secret( - ttrpc::context::with_timeout(50 * 1000 * 1000 * 1000), - &input, - ) + .as_ref() + .ok_or(anyhow!("unwrap sealed_secret_client failed"))? + .unseal_secret(ttrpc::context::with_timeout(SEALED_SECRET_TIMEOUT), &input) .await?; Ok(unseal) } pub async fn unseal_env(&self, env: &str) -> Result { - let (key, value) = env.split_once('=').unwrap(); + let (key, value) = env.split_once('=').unwrap_or(("", "")); if value.starts_with("sealed.") { let unsealed_value = self.unseal_secret_async(value).await; match unsealed_value { Ok(v) => { - let plain_env = - format!("{}={}", key, std::str::from_utf8(&v.plaintext).unwrap()); + let plain_env = format!("{}={}", key, std::str::from_utf8(&v.plaintext)?); return Ok(plain_env); } Err(e) => { @@ -63,6 +79,81 @@ impl CDHClient { } Ok((*env.to_owned()).to_string()) } + + pub async fn unseal_file(&self, sealed_source_path: &String) -> Result<()> { + if !Path::new(sealed_source_path).exists() { + info!( + sl(), + "sealed source path {:?} does not exist", sealed_source_path + ); + return Ok(()); + } + + for entry in fs::read_dir(sealed_source_path)? { + let entry = entry?; + + if !entry.file_type()?.is_symlink() + && !fs::metadata(entry.path())?.file_type().is_file() + { + info!( + sl(), + "skipping sealed source entry {:?} because its file type is {:?}", + entry, + entry.file_type()? + ); + continue; + } + + let target_path = fs::canonicalize(&entry.path())?; + info!(sl(), "sealed source entry target path: {:?}", target_path); + if !target_path.is_file() { + info!(sl(), "sealed source is not a file: {:?}", target_path); + continue; + } + + let secret_name = entry.file_name(); + let contents = fs::read_to_string(&target_path)?; + if contents.starts_with("sealed.") { + info!(sl(), "sealed source entry found: {:?}", target_path); + let unsealed_filename = SECRETS_DIR.to_string() + + secret_name + .as_os_str() + .to_str() + .ok_or(anyhow!("create unsealed_filename failed"))?; + let unsealed_value = self.unseal_secret_async(&contents).await?; + fs::write(&unsealed_filename, unsealed_value.plaintext)?; + fs::remove_file(&entry.path())?; + symlink(unsealed_filename, &entry.path())?; + } + } + Ok(()) + } + + pub fn create_sealed_secret_mounts(&self, spec: &mut Spec) -> Result> { + let mut sealed_source_path: Vec = vec![]; + for m in spec.mounts.iter_mut() { + if let Some(unsealed_mount_point) = m.destination.strip_prefix("/sealed") { + info!( + sl(), + "sealed mount destination: {:?} source: {:?}", m.destination, m.source + ); + sealed_source_path.push(m.source.clone()); + m.destination = unsealed_mount_point.to_string(); + } + } + + if sealed_source_path.len() > 0 { + let sealed_mounts = Mount { + destination: SECRETS_DIR.to_string(), + r#type: "bind".to_string(), + source: SECRETS_DIR.to_string(), + options: vec!["bind".to_string()], + }; + spec.mounts.push(sealed_mounts); + } + fs::create_dir_all(SECRETS_DIR)?; + Ok(sealed_source_path) + } } /* end of impl CDHClient */ #[cfg(test)] @@ -145,5 +236,6 @@ mod tests { assert_eq!(unchanged_env, String::from("key=testdata")); rt.shutdown_background(); + std::thread::sleep(std::time::Duration::from_secs(2)); } } diff --git a/src/agent/src/rpc.rs b/src/agent/src/rpc.rs index 457251fa18..2b7afbdee8 100644 --- a/src/agent/src/rpc.rs +++ b/src/agent/src/rpc.rs @@ -209,24 +209,27 @@ impl AgentService { // cannot predict everything from the caller. add_devices(&req.devices, &mut oci, &self.sandbox).await?; - if cfg!(feature = "sealed-secret") { + #[cfg(feature = "sealed-secret")] + let mut sealed_source_path = { let process = oci .process .as_mut() .ok_or_else(|| anyhow!("Spec didn't contain process field"))?; + let client = self + .cdh_client + .as_ref() + .ok_or(anyhow!("get cdh_client failed"))?; for env in process.env.iter_mut() { - let client = self - .cdh_client - .as_ref() - .ok_or(anyhow!("get cdh_client failed"))?; let unsealed_env = client .unseal_env(env) .await .map_err(|e| anyhow!("unseal env failed: {:?}", e))?; *env = unsealed_env.to_string(); } - } + + client.create_sealed_secret_mounts(&mut oci)? + }; let linux = oci .linux @@ -311,6 +314,17 @@ impl AgentService { return Err(anyhow!(nix::Error::EINVAL)); }; + #[cfg(feature = "sealed-secret")] + { + let client = self + .cdh_client + .as_ref() + .ok_or(anyhow!("get cdh_client failed"))?; + for source_path in sealed_source_path.iter_mut() { + client.unseal_file(source_path).await?; + } + } + // if starting container failed, we will do some rollback work // to ensure no resources are leaked. if let Err(err) = ctr.start(p).await { diff --git a/src/runtime/virtcontainers/kata_agent.go b/src/runtime/virtcontainers/kata_agent.go index 65fff6dde4..0276c7ae91 100644 --- a/src/runtime/virtcontainers/kata_agent.go +++ b/src/runtime/virtcontainers/kata_agent.go @@ -911,6 +911,10 @@ func (k *kataAgent) replaceOCIMountSource(spec *specs.Spec, guestMounts map[stri for index, m := range ociMounts { if guestMount, ok := guestMounts[m.Destination]; ok { k.Logger().Debugf("Replacing OCI mount (%s) source %s with %s", m.Destination, m.Source, guestMount.Source) + if strings.Contains(m.Source, "kubernetes.io~secret") { + ociMounts[index].Destination = "/sealed" + m.Destination + k.Logger().Debugf("Replacing OCI mount (%s) with new destination %s", m.Destination, ociMounts[index].Destination) + } ociMounts[index].Source = guestMount.Source } }