mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-04-27 11:31:05 +00:00
agent: add initdata parse logic
Kata-agent now will check if a device /dev/vd* with 'initdata' magic number exists. If it exists, kata-agent will try to read it. Bytes 9~16 are the length of the compressed initdata toml in little endine. Bytes starting from 17 is the compressed initdata. The initdata image device layout looks like 0 8 16 16+length ... EOF 'initdata' length gzip(initdata toml) paddings The initdata will be parsed and put as aa.toml, cdh.toml and policy.rego to /run/confidential-containers/initdata. When AgentPolicy is initialized, the default policy will be overwritten by that. When AA is to be launched, if initdata is once processed, the launch arg will include --initdata parameter. Also, if /run/confidential-containers/initdata/aa.toml exists, the launch args will include -c /run/confidential-containers/initdata/aa.toml. When CDH is to be launched, if initdata is once processed, the launch args will include -c /run/confidential-containers/initdata/cdh.toml Signed-off-by: Xynnn007 <xynnn@linux.alibaba.com>
This commit is contained in:
parent
0dbf4ec39f
commit
17d0db9865
7
src/agent/Cargo.lock
generated
7
src/agent/Cargo.lock
generated
@ -263,9 +263,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.4.20"
|
||||
version = "0.4.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "310c9bcae737a48ef5cdee3174184e6d548b292739ede61a1f955ef76a738861"
|
||||
checksum = "59a194f9d963d8099596278594b3107448656ba73831c9d8c783e613ce86da64"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"futures-core",
|
||||
@ -3041,9 +3041,11 @@ name = "kata-agent"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-compression",
|
||||
"async-recursion 0.3.2",
|
||||
"async-std",
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"capctl",
|
||||
"cdi",
|
||||
"cfg-if 1.0.0",
|
||||
@ -3083,6 +3085,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serial_test",
|
||||
"sha2",
|
||||
"slog",
|
||||
"slog-scope",
|
||||
"slog-stdlog",
|
||||
|
@ -85,6 +85,11 @@ image-rs = { git = "https://github.com/confidential-containers/guest-components"
|
||||
cdi = { git = "https://github.com/cncf-tags/container-device-interface-rs", rev = "fba5677a8e7cc962fc6e495fcec98d7d765e332a" }
|
||||
kata-agent-policy = { path = "policy", optional = true }
|
||||
|
||||
# Initdata
|
||||
base64 = "0.22"
|
||||
sha2 = "0.10.8"
|
||||
async-compression = { version = "0.4.22", features = ["tokio", "gzip"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1.0"
|
||||
test-utils = { path = "../libs/test-utils" }
|
||||
@ -103,7 +108,7 @@ lto = true
|
||||
default-pull = []
|
||||
seccomp = ["rustjail/seccomp"]
|
||||
standard-oci-runtime = ["rustjail/standard-oci-runtime"]
|
||||
agent-policy = [ "kata-agent-policy" ]
|
||||
agent-policy = ["kata-agent-policy"]
|
||||
guest-pull = ["image-rs/kata-cc-rustls-tls"]
|
||||
|
||||
[[bin]]
|
||||
|
191
src/agent/src/initdata.rs
Normal file
191
src/agent/src/initdata.rs
Normal file
@ -0,0 +1,191 @@
|
||||
//! # Initdata Module
|
||||
//!
|
||||
//! This module will do the following things if a proper initdata device with initdata exists.
|
||||
//! 1. Parse the initdata block device and extract the config files to [`INITDATA_PATH`].
|
||||
//! 2. Return the initdata and the policy (if any).
|
||||
|
||||
// Copyright (c) 2025 Alibaba Cloud
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::{os::unix::fs::FileTypeExt, path::Path};
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use async_compression::tokio::bufread::GzipDecoder;
|
||||
use base64::{engine::general_purpose::STANDARD, Engine};
|
||||
use const_format::concatcp;
|
||||
use serde::Deserialize;
|
||||
use sha2::{Digest, Sha256, Sha384, Sha512};
|
||||
use slog::Logger;
|
||||
use tokio::io::{AsyncReadExt, AsyncSeekExt};
|
||||
|
||||
/// This is the target directory to store the extracted initdata.
|
||||
pub const INITDATA_PATH: &str = "/run/confidential-containers/initdata";
|
||||
|
||||
/// The path of AA's config file
|
||||
pub const AA_CONFIG_PATH: &str = concatcp!(INITDATA_PATH, "/aa.toml");
|
||||
|
||||
/// The path of CDH's config file
|
||||
pub const CDH_CONFIG_PATH: &str = concatcp!(INITDATA_PATH, "/cdh.toml");
|
||||
|
||||
/// Magic number of initdata device
|
||||
pub const INITDATA_MAGIC_NUMBER: &[u8] = b"initdata";
|
||||
|
||||
/// Now only initdata `0.1.0` is defined.
|
||||
const INITDATA_VERSION: &str = "0.1.0";
|
||||
|
||||
/// Initdata defined in
|
||||
/// <https://github.com/confidential-containers/trustee/blob/47d7a2338e0be76308ac19be5c0c172c592780aa/kbs/docs/initdata.md>
|
||||
#[derive(Deserialize)]
|
||||
pub struct Initdata {
|
||||
version: String,
|
||||
algorithm: String,
|
||||
data: DefinedFields,
|
||||
}
|
||||
|
||||
/// Well-defined keys for initdata of kata/CoCo
|
||||
#[derive(Deserialize, Default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct DefinedFields {
|
||||
#[serde(rename = "aa.toml")]
|
||||
aa_config: Option<String>,
|
||||
#[serde(rename = "cdh.toml")]
|
||||
cdh_config: Option<String>,
|
||||
#[serde(rename = "policy.rego")]
|
||||
policy: Option<String>,
|
||||
}
|
||||
|
||||
async fn detect_initdata_device(logger: &Logger) -> Result<Option<String>> {
|
||||
let dev_dir = Path::new("/dev");
|
||||
let mut read_dir = tokio::fs::read_dir(dev_dir).await?;
|
||||
while let Some(entry) = read_dir.next_entry().await? {
|
||||
let filename = entry.file_name();
|
||||
let filename = filename.to_string_lossy();
|
||||
debug!(logger, "Initdata check device `{filename}`");
|
||||
if !filename.starts_with("vd") {
|
||||
continue;
|
||||
}
|
||||
let path = entry.path();
|
||||
|
||||
debug!(logger, "Initdata find potential device: `{path:?}`");
|
||||
let metadata = std::fs::metadata(path.clone())?;
|
||||
if !metadata.file_type().is_block_device() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut file = tokio::fs::File::open(&path).await?;
|
||||
let mut magic = [0; 8];
|
||||
match file.read_exact(&mut magic).await {
|
||||
Ok(_) => {
|
||||
debug!(
|
||||
logger,
|
||||
"Initdata read device `{filename}` first 8 bytes: {magic:?}"
|
||||
);
|
||||
if magic == INITDATA_MAGIC_NUMBER {
|
||||
let path = path.as_path().to_string_lossy().to_string();
|
||||
debug!(logger, "Found initdata device {path}");
|
||||
return Ok(Some(path));
|
||||
}
|
||||
}
|
||||
Err(e) => debug!(logger, "Initdata read device `{filename}` failed: {e:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub async fn read_initdata(device_path: &str) -> Result<Vec<u8>> {
|
||||
let initdata_devfile = tokio::fs::File::open(device_path).await?;
|
||||
let mut buf_reader = tokio::io::BufReader::new(initdata_devfile);
|
||||
// skip the magic number "initdata"
|
||||
buf_reader.seek(std::io::SeekFrom::Start(8)).await?;
|
||||
|
||||
let mut len_buf = [0u8; 8];
|
||||
buf_reader.read_exact(&mut len_buf).await?;
|
||||
let length = u64::from_le_bytes(len_buf) as usize;
|
||||
|
||||
let mut buf = vec![0; length];
|
||||
buf_reader.read_exact(&mut buf).await?;
|
||||
let mut gzip_decoder = GzipDecoder::new(&buf[..]);
|
||||
|
||||
let mut initdata = Vec::new();
|
||||
let _ = gzip_decoder.read_to_end(&mut initdata).await?;
|
||||
Ok(initdata)
|
||||
}
|
||||
|
||||
pub struct InitdataReturnValue {
|
||||
pub digest: Vec<u8>,
|
||||
pub _policy: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn initialize_initdata(logger: &Logger) -> Result<Option<InitdataReturnValue>> {
|
||||
let logger = logger.new(o!("subsystem" => "initdata"));
|
||||
let Some(initdata_device) = detect_initdata_device(&logger).await? else {
|
||||
info!(
|
||||
logger,
|
||||
"Initdata device not found, skip initdata initialization"
|
||||
);
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
tokio::fs::create_dir_all(INITDATA_PATH)
|
||||
.await
|
||||
.inspect_err(|e| error!(logger, "Failed to create initdata dir: {e:?}"))?;
|
||||
|
||||
let initdata_content = read_initdata(&initdata_device)
|
||||
.await
|
||||
.inspect_err(|e| error!(logger, "Failed to read initdata: {e:?}"))?;
|
||||
|
||||
let initdata: Initdata =
|
||||
toml::from_slice(&initdata_content).context("parse initdata failed")?;
|
||||
info!(logger, "Initdata version: {}", initdata.version);
|
||||
|
||||
if initdata.version != INITDATA_VERSION {
|
||||
bail!("Unsupported initdata version");
|
||||
}
|
||||
|
||||
let digest = match &initdata.algorithm[..] {
|
||||
"sha256" => Sha256::digest(&initdata_content).to_vec(),
|
||||
"sha384" => Sha384::digest(&initdata_content).to_vec(),
|
||||
"sha512" => Sha512::digest(&initdata_content).to_vec(),
|
||||
others => bail!("Unsupported hash algorithm {others}"),
|
||||
};
|
||||
|
||||
if let Some(config) = initdata.data.aa_config {
|
||||
tokio::fs::write(AA_CONFIG_PATH, config)
|
||||
.await
|
||||
.context("write aa config failed")?;
|
||||
info!(logger, "write AA config from initdata");
|
||||
}
|
||||
|
||||
if let Some(config) = initdata.data.cdh_config {
|
||||
tokio::fs::write(CDH_CONFIG_PATH, config)
|
||||
.await
|
||||
.context("write cdh config failed")?;
|
||||
info!(logger, "write CDH config from initdata");
|
||||
}
|
||||
|
||||
debug!(logger, "Initdata digest: {}", STANDARD.encode(&digest));
|
||||
|
||||
let res = InitdataReturnValue {
|
||||
digest,
|
||||
_policy: initdata.data.policy,
|
||||
};
|
||||
|
||||
Ok(Some(res))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::initdata::read_initdata;
|
||||
|
||||
const INITDATA_IMG_PATH: &str = "testdata/initdata.img";
|
||||
const INITDATA_PLAINTEXT: &[u8] = b"some content";
|
||||
|
||||
#[tokio::test]
|
||||
async fn parse_initdata() {
|
||||
let initdata = read_initdata(INITDATA_IMG_PATH).await.unwrap();
|
||||
assert_eq!(initdata, INITDATA_PLAINTEXT);
|
||||
}
|
||||
}
|
@ -18,10 +18,12 @@ extern crate scopeguard;
|
||||
#[macro_use]
|
||||
extern crate slog;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use base64::Engine;
|
||||
use cfg_if::cfg_if;
|
||||
use clap::{AppSettings, Parser};
|
||||
use const_format::{concatcp, formatcp};
|
||||
use initdata::{InitdataReturnValue, AA_CONFIG_PATH, CDH_CONFIG_PATH};
|
||||
use nix::fcntl::OFlag;
|
||||
use nix::sys::reboot::{reboot, RebootMode};
|
||||
use nix::sys::socket::{self, AddressFamily, SockFlag, SockType, VsockAddr};
|
||||
@ -33,7 +35,6 @@ use std::os::unix::fs::{self as unixfs, FileTypeExt};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
use std::process::Command;
|
||||
use std::sync::Arc;
|
||||
use tracing::{instrument, span};
|
||||
|
||||
@ -42,6 +43,7 @@ mod config;
|
||||
mod console;
|
||||
mod device;
|
||||
mod features;
|
||||
mod initdata;
|
||||
mod linux_abi;
|
||||
mod metrics;
|
||||
mod mount;
|
||||
@ -419,6 +421,8 @@ async fn start_sandbox(
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
sandbox.lock().await.sender = Some(tx);
|
||||
|
||||
let initdata_return_value = initdata::initialize_initdata(logger).await?;
|
||||
|
||||
let gc_procs = config.guest_components_procs;
|
||||
if !attestation_binaries_available(logger, &gc_procs) {
|
||||
warn!(
|
||||
@ -426,7 +430,21 @@ async fn start_sandbox(
|
||||
"attestation binaries requested for launch not available"
|
||||
);
|
||||
} else {
|
||||
init_attestation_components(logger, config).await?;
|
||||
init_attestation_components(logger, config, &initdata_return_value).await?;
|
||||
}
|
||||
|
||||
// if policy is given via initdata, use it
|
||||
#[cfg(feature = "agent-policy")]
|
||||
if let Some(initdata_return_value) = initdata_return_value {
|
||||
if let Some(policy) = &initdata_return_value._policy {
|
||||
info!(logger, "using policy from initdata");
|
||||
AGENT_POLICY
|
||||
.lock()
|
||||
.await
|
||||
.set_policy(policy)
|
||||
.await
|
||||
.context("Failed to set policy from initdata")?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut oma = None;
|
||||
@ -472,19 +490,34 @@ fn attestation_binaries_available(logger: &Logger, procs: &GuestComponentsProcs)
|
||||
true
|
||||
}
|
||||
|
||||
async fn launch_guest_component_procs(logger: &Logger, config: &AgentConfig) -> Result<()> {
|
||||
async fn launch_guest_component_procs(
|
||||
logger: &Logger,
|
||||
config: &AgentConfig,
|
||||
initdata_return_value: &Option<InitdataReturnValue>,
|
||||
) -> Result<()> {
|
||||
if config.guest_components_procs == GuestComponentsProcs::None {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
debug!(logger, "spawning attestation-agent process {}", AA_PATH);
|
||||
let mut aa_args = vec!["--attestation_sock", AA_ATTESTATION_URI];
|
||||
let initdata_parameter;
|
||||
if let Some(initdata_return_value) = initdata_return_value {
|
||||
initdata_parameter =
|
||||
base64::engine::general_purpose::STANDARD.encode(&initdata_return_value.digest);
|
||||
aa_args.push("--initdata");
|
||||
aa_args.push(&initdata_parameter);
|
||||
}
|
||||
|
||||
launch_process(
|
||||
logger,
|
||||
AA_PATH,
|
||||
&vec!["--attestation_sock", AA_ATTESTATION_URI],
|
||||
aa_args,
|
||||
Some(AA_CONFIG_PATH),
|
||||
AA_ATTESTATION_SOCKET,
|
||||
DEFAULT_LAUNCH_PROCESS_TIMEOUT,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| anyhow!("launch_process {} failed: {:?}", AA_PATH, e))?;
|
||||
|
||||
// skip launch of confidential-data-hub and api-server-rest
|
||||
@ -500,10 +533,12 @@ async fn launch_guest_component_procs(logger: &Logger, config: &AgentConfig) ->
|
||||
launch_process(
|
||||
logger,
|
||||
CDH_PATH,
|
||||
&vec![],
|
||||
vec![],
|
||||
Some(CDH_CONFIG_PATH),
|
||||
CDH_SOCKET,
|
||||
DEFAULT_LAUNCH_PROCESS_TIMEOUT,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| anyhow!("launch_process {} failed: {:?}", CDH_PATH, e))?;
|
||||
|
||||
// skip launch of api-server-rest
|
||||
@ -519,10 +554,12 @@ async fn launch_guest_component_procs(logger: &Logger, config: &AgentConfig) ->
|
||||
launch_process(
|
||||
logger,
|
||||
API_SERVER_PATH,
|
||||
&vec!["--features", &features.to_string()],
|
||||
vec!["--features", &features.to_string()],
|
||||
None,
|
||||
"",
|
||||
0,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| anyhow!("launch_process {} failed: {:?}", API_SERVER_PATH, e))?;
|
||||
|
||||
Ok(())
|
||||
@ -532,8 +569,12 @@ async fn launch_guest_component_procs(logger: &Logger, config: &AgentConfig) ->
|
||||
// and the corresponding procs are enabled in the agent configuration. the process will be
|
||||
// launched in the background and the function will return immediately.
|
||||
// If the CDH is started, a CDH client will be instantiated and returned.
|
||||
async fn init_attestation_components(logger: &Logger, config: &AgentConfig) -> Result<()> {
|
||||
launch_guest_component_procs(logger, config).await?;
|
||||
async fn init_attestation_components(
|
||||
logger: &Logger,
|
||||
config: &AgentConfig,
|
||||
initdata_return_value: &Option<InitdataReturnValue>,
|
||||
) -> Result<()> {
|
||||
launch_guest_component_procs(logger, config, initdata_return_value).await?;
|
||||
|
||||
// If a CDH socket exists, initialize the CDH client and enable ocicrypt
|
||||
match tokio::fs::metadata(CDH_SOCKET).await {
|
||||
@ -555,11 +596,11 @@ async fn init_attestation_components(logger: &Logger, config: &AgentConfig) -> R
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wait_for_path_to_exist(logger: &Logger, path: &str, timeout_secs: i32) -> Result<()> {
|
||||
async fn wait_for_path_to_exist(logger: &Logger, path: &str, timeout_secs: i32) -> Result<()> {
|
||||
let p = Path::new(path);
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
if p.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
@ -576,22 +617,32 @@ fn wait_for_path_to_exist(logger: &Logger, path: &str, timeout_secs: i32) -> Res
|
||||
Err(anyhow!("wait for {} to exist timeout.", path))
|
||||
}
|
||||
|
||||
fn launch_process(
|
||||
async fn launch_process(
|
||||
logger: &Logger,
|
||||
path: &str,
|
||||
args: &Vec<&str>,
|
||||
mut args: Vec<&str>,
|
||||
config: Option<&str>,
|
||||
unix_socket_path: &str,
|
||||
timeout_secs: i32,
|
||||
) -> Result<()> {
|
||||
if !Path::new(path).exists() {
|
||||
return Err(anyhow!("path {} does not exist.", path));
|
||||
bail!("path {} does not exist.", path);
|
||||
}
|
||||
|
||||
if let Some(config_path) = config {
|
||||
if Path::new(config_path).exists() {
|
||||
args.push("-c");
|
||||
args.push(config_path);
|
||||
}
|
||||
}
|
||||
|
||||
if !unix_socket_path.is_empty() && Path::new(unix_socket_path).exists() {
|
||||
fs::remove_file(unix_socket_path)?;
|
||||
tokio::fs::remove_file(unix_socket_path).await?;
|
||||
}
|
||||
Command::new(path).args(args).spawn()?;
|
||||
|
||||
tokio::process::Command::new(path).args(args).spawn()?;
|
||||
if !unix_socket_path.is_empty() && timeout_secs > 0 {
|
||||
wait_for_path_to_exist(logger, unix_socket_path, timeout_secs)?;
|
||||
wait_for_path_to_exist(logger, unix_socket_path, timeout_secs).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
BIN
src/agent/testdata/initdata.img
vendored
Normal file
BIN
src/agent/testdata/initdata.img
vendored
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user