agent: support sealed secret as file in kata

Fixes: #7555

Signed-off-by: Linda Yu <linda.yu@intel.com>
This commit is contained in:
Linda Yu 2023-08-09 17:51:38 +08:00
parent c60adedf99
commit d7873e5251
3 changed files with 130 additions and 20 deletions

View File

@ -8,24 +8,42 @@
// 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<SealedSecretServiceClient>,
}
impl CDHClient {
pub fn new() -> Result<Self> {
let c = ttrpc::asynchronous::Client::connect(CDH_ADDR)?;
let ssclient = sealed_secret_ttrpc_async::SealedSecretServiceClient::new(c);
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: ssclient,
sealed_secret_client: Some(ssclient),
})
}
Err(_) => Ok(CDHClient {
sealed_secret_client: None,
}),
}
}
pub async fn unseal_secret_async(
&self,
@ -33,27 +51,25 @@ impl CDHClient {
) -> Result<sealed_secret::UnsealSecretOutput> {
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<String> {
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<Vec<String>> {
let mut sealed_source_path: Vec<String> = 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));
}
}

View File

@ -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"))?;
for env in process.env.iter_mut() {
let client = self
.cdh_client
.as_ref()
.ok_or(anyhow!("get cdh_client failed"))?;
for env in process.env.iter_mut() {
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 {

View File

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