Agent: Attestation Agent Integration

Pull an encrypted image using the Attestation Agent as
a keyprovider.

Fixes: #3022

Signed-off-by: Tobin Feldman-Fitzthum <tobin@linux.ibm.com>
Co-authored-by: Jakob Naucke <jakob.naucke@ibm.com>
This commit is contained in:
Tobin Feldman-Fitzthum 2021-10-27 03:29:54 +00:00 committed by Samuel Ortiz
parent 27c0dc260c
commit 7c41af4082
2 changed files with 67 additions and 2 deletions

View File

@ -83,6 +83,7 @@ pub struct AgentConfig {
pub endpoints: AgentEndpoints,
pub supports_seccomp: bool,
pub container_policy_path: String,
pub aa_kbc_params: String,
}
#[derive(Debug, Deserialize)]
@ -99,6 +100,7 @@ pub struct AgentConfigBuilder {
pub tracing: Option<bool>,
pub endpoints: Option<EndpointsConfig>,
pub container_policy_path: Option<String>,
pub aa_kbc_params: Option<String>,
}
macro_rules! config_override {
@ -161,6 +163,7 @@ impl Default for AgentConfig {
endpoints: Default::default(),
supports_seccomp: rpc::have_seccomp(),
container_policy_path: String::from(""),
aa_kbc_params: String::from(""),
}
}
}
@ -190,6 +193,7 @@ impl FromStr for AgentConfig {
config_override!(agent_config_builder, agent_config, unified_cgroup_hierarchy);
config_override!(agent_config_builder, agent_config, tracing);
config_override!(agent_config_builder, agent_config, container_policy_path);
config_override!(agent_config_builder, agent_config, aa_kbc_params);
// Populate the allowed endpoints hash set, if we got any from the config file.
if let Some(endpoints) = agent_config_builder.endpoints {

View File

@ -4,8 +4,10 @@
//
use std::fs;
use std::io::Write;
use std::path::Path;
use std::process::{Command, ExitStatus};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use anyhow::{anyhow, ensure, Result};
@ -21,6 +23,9 @@ use crate::AGENT_CONFIG;
const SKOPEO_PATH: &str = "/usr/bin/skopeo";
const UMOCI_PATH: &str = "/usr/local/bin/umoci";
const IMAGE_OCI: &str = "image_oci:latest";
const AA_PATH: &str = "/usr/local/bin/attestation-agent";
const AA_PORT: &str = "127.0.0.1:50000";
const OCICRYPT_CONFIG_PATH: &str = "/tmp/ocicrypt_config.json";
// Convenience macro to obtain the scope logger
macro_rules! sl {
@ -31,11 +36,15 @@ macro_rules! sl {
pub struct ImageService {
sandbox: Arc<Mutex<Sandbox>>,
attestation_agent_started: AtomicBool,
}
impl ImageService {
pub fn new(sandbox: Arc<Mutex<Sandbox>>) -> Self {
Self { sandbox }
Self {
sandbox,
attestation_agent_started: AtomicBool::new(false),
}
}
fn pull_image_from_registry(
@ -43,6 +52,7 @@ impl ImageService {
cid: &str,
source_creds: &Option<&str>,
policy_path: &Option<&String>,
aa_kbc_params: &str,
) -> Result<()> {
let source_image = format!("{}{}", "docker://", image);
@ -78,6 +88,15 @@ impl ImageService {
}
debug!(sl!(), "skopeo command: {:?}", &pull_command);
if !aa_kbc_params.is_empty() {
// Skopeo will copy an unencrypted image even if the decryption key argument is provided.
// Thus, this does not guarantee that the image was encrypted.
pull_command
.arg("--decryption-key")
.arg(format!("provider:attestation-agent:{}", aa_kbc_params))
.env("OCICRYPT_KEYPROVIDER_CONFIG", OCICRYPT_CONFIG_PATH);
}
let status: ExitStatus = pull_command.status()?;
if !status.success() {
@ -118,10 +137,40 @@ impl ImageService {
Ok(())
}
// If we fail to start the AA, Skopeo/ocicrypt won't be able to unwrap keys
// and container decryption will fail.
fn init_attestation_agent() {
let config_path = OCICRYPT_CONFIG_PATH;
// The image will need to be encrypted using a keyprovider
// that has the same name (at least according to the config).
let ocicrypt_config = serde_json::json!({
"key-providers": {
"attestation-agent":{
"grpc":AA_PORT
}
}
});
let mut config_file = fs::File::create(config_path).unwrap();
config_file
.write_all(ocicrypt_config.to_string().as_bytes())
.unwrap();
// The Attestation Agent will run for the duration of the guest.
Command::new(AA_PATH)
.arg("--grpc_sock")
.arg(AA_PORT)
.spawn()
.unwrap();
}
async fn pull_image(&self, req: &image::PullImageRequest) -> Result<String> {
let image = req.get_image();
let mut cid = req.get_container_id();
let aa_kbc_params = &AGENT_CONFIG.read().await.aa_kbc_params;
if cid.is_empty() {
let v: Vec<&str> = image.rsplit('/').collect();
if !v[0].is_empty() {
@ -133,6 +182,18 @@ impl ImageService {
verify_cid(cid)?;
}
if !aa_kbc_params.is_empty() {
match self.attestation_agent_started.compare_exchange_weak(
false,
true,
Ordering::SeqCst,
Ordering::SeqCst,
) {
Ok(_) => Self::init_attestation_agent(),
Err(_) => info!(sl!(), "Attestation Agent already running"),
}
}
let source_creds = (!req.get_source_creds().is_empty()).then(|| req.get_source_creds());
// Read the policy path from the agent config
@ -140,7 +201,7 @@ impl ImageService {
let policy_path = (!config_policy_path.is_empty()).then(|| config_policy_path);
info!(sl!(), "Using container policy_path: {:?}...", &policy_path);
Self::pull_image_from_registry(image, cid, &source_creds, &policy_path)?;
Self::pull_image_from_registry(image, cid, &source_creds, &policy_path, aa_kbc_params)?;
Self::unpack_image(cid)?;
let mut sandbox = self.sandbox.lock().await;