From 6003608fe6412bca26167364d74f600283f04b23 Mon Sep 17 00:00:00 2001 From: Linda Yu Date: Mon, 7 Aug 2023 19:07:03 +0800 Subject: [PATCH] agent: support sealed secret as env in kata When sealed-secret is enabled, the Kata Agent intercepts environment variables containing sealed secrets and uses the CDH to unseal the value. Signed-off-by: Tobin Feldman-Fitzthum Signed-off-by: Linda Yu --- src/agent/Cargo.lock | 1 + src/agent/Cargo.toml | 1 + src/agent/src/cdh.rs | 62 +++++++++++++++++++++++++++++++++++++++++++ src/agent/src/main.rs | 28 ++++++++++++++----- src/agent/src/rpc.rs | 28 +++++++++++++++++-- 5 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 src/agent/src/cdh.rs diff --git a/src/agent/Cargo.lock b/src/agent/Cargo.lock index 911bca114..c86cfb082 100644 --- a/src/agent/Cargo.lock +++ b/src/agent/Cargo.lock @@ -2142,6 +2142,7 @@ dependencies = [ "cgroups-rs", "clap", "const_format", + "derivative", "futures", "image-rs", "ipnetwork", diff --git a/src/agent/Cargo.toml b/src/agent/Cargo.toml index 0ab98b3e3..38bf34e47 100644 --- a/src/agent/Cargo.toml +++ b/src/agent/Cargo.toml @@ -23,6 +23,7 @@ regex = "1.10.4" serial_test = "0.5.1" oci-distribution = "0.10.0" url = "2.5.0" +derivative = "2.2.0" kata-sys-util = { path = "../libs/kata-sys-util" } kata-types = { path = "../libs/kata-types" } safe-path = { path = "../libs/safe-path" } diff --git a/src/agent/src/cdh.rs b/src/agent/src/cdh.rs new file mode 100644 index 000000000..a30282764 --- /dev/null +++ b/src/agent/src/cdh.rs @@ -0,0 +1,62 @@ +// Copyright (c) 2023 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +// Confidential Data Hub client wrapper. +// Confidential Data Hub is a service running inside guest to provide resource related APIs. +// https://github.com/confidential-containers/guest-components/tree/main/confidential-data-hub + +use anyhow::Result; +use derivative::Derivative; +use protocols::{ + sealed_secret, sealed_secret_ttrpc_async, sealed_secret_ttrpc_async::SealedSecretServiceClient, +}; + +use crate::CDH_SOCKET_URI; + +// Nanoseconds +const CDH_UNSEAL_TIMEOUT: i64 = 50 * 1000 * 1000 * 1000; +const SEALED_SECRET_PREFIX: &str = "sealed."; + +#[derive(Derivative)] +#[derivative(Clone, Debug)] +pub struct CDHClient { + #[derivative(Debug = "ignore")] + sealed_secret_client: SealedSecretServiceClient, +} + +impl CDHClient { + pub fn new() -> Result { + let client = ttrpc::asynchronous::Client::connect(CDH_SOCKET_URI)?; + let sealed_secret_client = + sealed_secret_ttrpc_async::SealedSecretServiceClient::new(client); + + Ok(CDHClient { + sealed_secret_client, + }) + } + + pub async fn unseal_secret_async(&self, sealed_secret: &str) -> Result> { + let mut input = sealed_secret::UnsealSecretInput::new(); + input.set_secret(sealed_secret.into()); + + let unsealed_secret = self + .sealed_secret_client + .unseal_secret(ttrpc::context::with_timeout(CDH_UNSEAL_TIMEOUT), &input) + .await?; + Ok(unsealed_secret.plaintext) + } + + pub async fn unseal_env(&self, env: &str) -> Result { + if let Some((key, value)) = env.split_once('=') { + if value.starts_with(SEALED_SECRET_PREFIX) { + let unsealed_value = self.unseal_secret_async(value).await?; + let unsealed_env = format!("{}={}", key, std::str::from_utf8(&unsealed_value)?); + + return Ok(unsealed_env); + } + } + Ok((*env.to_owned()).to_string()) + } +} diff --git a/src/agent/src/main.rs b/src/agent/src/main.rs index 0450b1bcc..299203b27 100644 --- a/src/agent/src/main.rs +++ b/src/agent/src/main.rs @@ -38,6 +38,7 @@ use std::process::Command; use std::sync::Arc; use tracing::{instrument, span}; +mod cdh; mod config; mod console; mod device; @@ -59,6 +60,7 @@ mod util; mod version; mod watcher; +use cdh::CDHClient; use config::GuestComponentsProcs; use mount::{cgroups_mount, general_mount}; use sandbox::Sandbox; @@ -104,6 +106,7 @@ const AA_ATTESTATION_URI: &str = concatcp!(UNIX_SOCKET_PREFIX, AA_ATTESTATION_SO const CDH_PATH: &str = "/usr/local/bin/confidential-data-hub"; const CDH_SOCKET: &str = "/run/confidential-containers/cdh.sock"; +const CDH_SOCKET_URI: &str = concatcp!(UNIX_SOCKET_PREFIX, CDH_SOCKET); const API_SERVER_PATH: &str = "/usr/local/bin/api-server-rest"; @@ -403,6 +406,7 @@ async fn start_sandbox( let (tx, rx) = tokio::sync::oneshot::channel(); sandbox.lock().await.sender = Some(tx); + let mut cdh_client = None; let gc_procs = config.guest_components_procs; if gc_procs != GuestComponentsProcs::None { if !attestation_binaries_available(logger, &gc_procs) { @@ -411,12 +415,19 @@ async fn start_sandbox( "attestation binaries requested for launch not available" ); } else { - init_attestation_components(logger, config)?; + cdh_client = init_attestation_components(logger, config)?; } } // vsock:///dev/vsock, port - let mut server = rpc::start(sandbox.clone(), config.server_addr.as_str(), init_mode).await?; + let mut server = rpc::start( + sandbox.clone(), + config.server_addr.as_str(), + init_mode, + cdh_client, + ) + .await?; + server.start().await?; rx.await?; @@ -445,10 +456,11 @@ fn attestation_binaries_available(logger: &Logger, procs: &GuestComponentsProcs) // Start-up attestation-agent, CDH and api-server-rest if they are packaged in the rootfs // and the corresponding procs are enabled in the agent configuration. the process will be // launched in the background and the function will return immediately. -fn init_attestation_components(logger: &Logger, config: &AgentConfig) -> Result<()> { +// If the CDH is started, a CDH client will be instantiated and returned. +fn init_attestation_components(logger: &Logger, config: &AgentConfig) -> Result> { // skip launch of any guest-component if config.guest_components_procs == GuestComponentsProcs::None { - return Ok(()); + return Ok(None); } debug!(logger, "spawning attestation-agent process {}", AA_PATH); @@ -463,7 +475,7 @@ fn init_attestation_components(logger: &Logger, config: &AgentConfig) -> Result< // skip launch of confidential-data-hub and api-server-rest if config.guest_components_procs == GuestComponentsProcs::AttestationAgent { - return Ok(()); + return Ok(None); } debug!( @@ -479,9 +491,11 @@ fn init_attestation_components(logger: &Logger, config: &AgentConfig) -> Result< ) .map_err(|e| anyhow!("launch_process {} failed: {:?}", CDH_PATH, e))?; + let cdh_client = CDHClient::new().context("Failed to create CDH Client")?; + // skip launch of api-server-rest if config.guest_components_procs == GuestComponentsProcs::ConfidentialDataHub { - return Ok(()); + return Ok(Some(cdh_client)); } let features = config.guest_components_rest_api; @@ -498,7 +512,7 @@ fn init_attestation_components(logger: &Logger, config: &AgentConfig) -> Result< ) .map_err(|e| anyhow!("launch_process {} failed: {:?}", API_SERVER_PATH, e))?; - Ok(()) + Ok(Some(cdh_client)) } fn wait_for_path_to_exist(logger: &Logger, path: &str, timeout_secs: i32) -> Result<()> { diff --git a/src/agent/src/rpc.rs b/src/agent/src/rpc.rs index aa598bc7c..fb65cb082 100644 --- a/src/agent/src/rpc.rs +++ b/src/agent/src/rpc.rs @@ -76,6 +76,8 @@ use crate::policy::{do_set_policy, is_allowed}; #[cfg(feature = "guest-pull")] use crate::image; +use crate::cdh::CDHClient; + use opentelemetry::global; use tracing::span; use tracing_opentelemetry::OpenTelemetrySpanExt; @@ -171,6 +173,7 @@ impl OptionToTtrpcResult for Option { pub struct AgentService { sandbox: Arc>, init_mode: bool, + cdh_client: Option, } impl AgentService { @@ -222,6 +225,22 @@ impl AgentService { // cannot predict everything from the caller. add_devices(&req.devices, &mut oci, &self.sandbox).await?; + if let Some(cdh) = self.cdh_client.as_ref() { + let process = oci + .process + .as_mut() + .ok_or_else(|| anyhow!("Spec didn't contain process field"))?; + + for env in process.env.iter_mut() { + match cdh.unseal_env(env).await { + Ok(unsealed_env) => *env = unsealed_env.to_string(), + Err(e) => { + warn!(sl(), "Failed to unseal secret: {}", e) + } + } + } + } + // Both rootfs and volumes (invoked with --volume for instance) will // be processed the same way. The idea is to always mount any provided // storage to the specified MountPoint, so that it will match what's @@ -1601,10 +1620,12 @@ pub async fn start( s: Arc>, server_address: &str, init_mode: bool, + cdh_client: Option, ) -> Result { let agent_service = Box::new(AgentService { sandbox: s, init_mode, + cdh_client, }) as Box; let aservice = agent_ttrpc::create_agent_service(Arc::new(agent_service)); @@ -2148,6 +2169,7 @@ mod tests { let agent_service = Box::new(AgentService { sandbox: Arc::new(Mutex::new(sandbox)), init_mode: true, + cdh_client: None, }); let req = protocols::agent::UpdateInterfaceRequest::default(); @@ -2162,10 +2184,10 @@ mod tests { async fn test_update_routes() { let logger = slog::Logger::root(slog::Discard, o!()); let sandbox = Sandbox::new(&logger).unwrap(); - let agent_service = Box::new(AgentService { sandbox: Arc::new(Mutex::new(sandbox)), init_mode: true, + cdh_client: None, }); let req = protocols::agent::UpdateRoutesRequest::default(); @@ -2180,10 +2202,10 @@ mod tests { async fn test_add_arp_neighbors() { let logger = slog::Logger::root(slog::Discard, o!()); let sandbox = Sandbox::new(&logger).unwrap(); - let agent_service = Box::new(AgentService { sandbox: Arc::new(Mutex::new(sandbox)), init_mode: true, + cdh_client: None, }); let req = protocols::agent::AddARPNeighborsRequest::default(); @@ -2322,6 +2344,7 @@ mod tests { let agent_service = Box::new(AgentService { sandbox: Arc::new(Mutex::new(sandbox)), init_mode: true, + cdh_client: None, }); let result = agent_service @@ -2811,6 +2834,7 @@ OtherField:other let agent_service = Box::new(AgentService { sandbox: Arc::new(Mutex::new(sandbox)), init_mode: true, + cdh_client: None, }); let ctx = mk_ttrpc_context();