Merge pull request #9749 from mkulke/mkulke/configure-guest-components-spawning

CoCo: introduce config for guest-components procs
This commit is contained in:
Fabiano Fidêncio 2024-06-03 15:50:36 +02:00 committed by GitHub
commit 34d45f0868
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 194 additions and 34 deletions

View File

@ -126,7 +126,8 @@ The kata agent has the ability to configure agent options in guest kernel comman
| `agent.debug_console_vport` | Debug console port | Allow to specify the `vsock` port to connect the debugging console | integer | `0` |
| `agent.devmode` | Developer mode | Allow the agent process to coredump | boolean | `false` |
| `agent.hotplug_timeout` | Hotplug timeout | Allow to configure hotplug timeout(seconds) of block devices | integer | `3` |
| `agent.guest_components_rest_api` | `api-server-rest` configuration | Select the features that the API Server Rest attestation component will run with. Valid values are `all`, `attestation`, `resource`, or `none` to not launch the `api-server-rest` component | string | `resource` |
| `agent.guest_components_rest_api` | `api-server-rest` configuration | Select the features that the API Server Rest attestation component will run with. Valid values are `all`, `attestation`, `resource` | string | `resource` |
| `agent.guest_components_procs` | guest-components processes | Attestation-related processes that should be spawned as children of the guest. Valid values are `none`, `attestation-agent`, `confidential-data-hub` (implies `attestation-agent`), `api-server-rest` (implies `attestation-agent` and `confidential-data-hub`) | string | `api-server-rest` |
| `agent.https_proxy` | HTTPS proxy | Allow to configure `https_proxy` in the guest | string | `""` |
| `agent.log` | Log level | Allow the agent log level to be changed (produces more or less output) | string | `"info"` |
| `agent.log_vport` | Log port | Allow to specify the `vsock` port to read logs | integer | `0` |

View File

@ -28,6 +28,7 @@ const CONTAINER_PIPE_SIZE_OPTION: &str = "agent.container_pipe_size";
const UNIFIED_CGROUP_HIERARCHY_OPTION: &str = "systemd.unified_cgroup_hierarchy";
const CONFIG_FILE: &str = "agent.config_file";
const GUEST_COMPONENTS_REST_API_OPTION: &str = "agent.guest_components_rest_api";
const GUEST_COMPONENTS_PROCS_OPTION: &str = "agent.guest_components_procs";
// Configure the proxy settings for HTTPS requests in the guest,
// to solve the problem of not being able to access the specified image in some cases.
@ -59,7 +60,8 @@ const ERR_INVALID_CONTAINER_PIPE_SIZE_PARAM: &str = "unable to parse container p
const ERR_INVALID_CONTAINER_PIPE_SIZE_KEY: &str = "invalid container pipe size key name";
const ERR_INVALID_CONTAINER_PIPE_NEGATIVE: &str = "container pipe size should not be negative";
const ERR_INVALID_GUEST_COMPONENTS_REST_API_VALUE: &str = "invalid guest components rest api feature given. Valid values are `all`, `attestation`, `resource`, or `none`";
const ERR_INVALID_GUEST_COMPONENTS_REST_API_VALUE: &str = "invalid guest components rest api feature given. Valid values are `all`, `attestation`, `resource`";
const ERR_INVALID_GUEST_COMPONENTS_PROCS_VALUE: &str = "invalid guest components process param given. Valid values are `attestation-agent`, `confidential-data-hub`, `api-server-rest`, or `none`";
#[derive(Clone, Copy, Debug, Default, Display, Deserialize, EnumString, PartialEq)]
// Features seem to typically be in kebab-case format, but we only have single words at the moment
@ -67,11 +69,23 @@ const ERR_INVALID_GUEST_COMPONENTS_REST_API_VALUE: &str = "invalid guest compone
pub enum GuestComponentsFeatures {
All,
Attestation,
None,
#[default]
Resource,
}
#[derive(Clone, Copy, Debug, Default, Display, Deserialize, EnumString, PartialEq)]
/// Attestation-related processes that we want to spawn as children of the agent
#[strum(serialize_all = "kebab-case")]
pub enum GuestComponentsProcs {
None,
/// ApiServerRest implies ConfidentialDataHub and AttestationAgent
#[default]
ApiServerRest,
AttestationAgent,
/// ConfidentialDataHub implies AttestationAgent
ConfidentialDataHub,
}
#[derive(Debug)]
pub struct AgentConfig {
pub debug_console: bool,
@ -89,6 +103,7 @@ pub struct AgentConfig {
pub https_proxy: String,
pub no_proxy: String,
pub guest_components_rest_api: GuestComponentsFeatures,
pub guest_components_procs: GuestComponentsProcs,
}
#[derive(Debug, Deserialize)]
@ -107,6 +122,7 @@ pub struct AgentConfigBuilder {
pub https_proxy: Option<String>,
pub no_proxy: Option<String>,
pub guest_components_rest_api: Option<GuestComponentsFeatures>,
pub guest_components_procs: Option<GuestComponentsProcs>,
}
macro_rules! config_override {
@ -171,6 +187,7 @@ impl Default for AgentConfig {
https_proxy: String::from(""),
no_proxy: String::from(""),
guest_components_rest_api: GuestComponentsFeatures::default(),
guest_components_procs: GuestComponentsProcs::default(),
}
}
}
@ -314,6 +331,12 @@ impl AgentConfig {
config.guest_components_rest_api,
get_guest_components_features_value
);
parse_cmdline_param!(
param,
GUEST_COMPONENTS_PROCS_OPTION,
config.guest_components_procs,
get_guest_components_procs_value
);
}
if let Ok(addr) = env::var(SERVER_ADDR_ENV_VAR) {
@ -480,6 +503,19 @@ fn get_guest_components_features_value(param: &str) -> Result<GuestComponentsFea
.map_err(|_| anyhow!(ERR_INVALID_GUEST_COMPONENTS_REST_API_VALUE))
}
#[instrument]
fn get_guest_components_procs_value(param: &str) -> Result<GuestComponentsProcs> {
let fields: Vec<&str> = param.split('=').collect();
ensure!(fields.len() >= 2, ERR_INVALID_GET_VALUE_PARAM);
// We need name (but the value can be blank)
ensure!(!fields[0].is_empty(), ERR_INVALID_GET_VALUE_NO_NAME);
let value = fields[1..].join("=");
GuestComponentsProcs::from_str(&value)
.map_err(|_| anyhow!(ERR_INVALID_GUEST_COMPONENTS_PROCS_VALUE))
}
#[cfg(test)]
mod tests {
use test_utils::assert_result;
@ -519,6 +555,7 @@ mod tests {
https_proxy: &'a str,
no_proxy: &'a str,
guest_components_rest_api: GuestComponentsFeatures,
guest_components_procs: GuestComponentsProcs,
}
impl Default for TestData<'_> {
@ -537,6 +574,7 @@ mod tests {
https_proxy: "",
no_proxy: "",
guest_components_rest_api: GuestComponentsFeatures::default(),
guest_components_procs: GuestComponentsProcs::default(),
}
}
}
@ -942,8 +980,23 @@ mod tests {
..Default::default()
},
TestData {
contents: "agent.guest_components_rest_api=none",
guest_components_rest_api: GuestComponentsFeatures::None,
contents: "agent.guest_components_procs=api-server-rest",
guest_components_procs: GuestComponentsProcs::ApiServerRest,
..Default::default()
},
TestData {
contents: "agent.guest_components_procs=confidential-data-hub",
guest_components_procs: GuestComponentsProcs::ConfidentialDataHub,
..Default::default()
},
TestData {
contents: "agent.guest_components_procs=attestation-agent",
guest_components_procs: GuestComponentsProcs::AttestationAgent,
..Default::default()
},
TestData {
contents: "agent.guest_components_procs=none",
guest_components_procs: GuestComponentsProcs::None,
..Default::default()
},
];
@ -1000,6 +1053,11 @@ mod tests {
"{}",
msg
);
assert_eq!(
d.guest_components_procs, config.guest_components_procs,
"{}",
msg
);
for v in vars_to_unset {
env::remove_var(v);
@ -1500,10 +1558,6 @@ Caused by:
param: "x=attestation",
result: Ok(GuestComponentsFeatures::Attestation),
},
TestData {
param: "x=none",
result: Ok(GuestComponentsFeatures::None),
},
TestData {
param: "x=resource",
result: Ok(GuestComponentsFeatures::Resource),
@ -1533,6 +1587,68 @@ Caused by:
}
}
#[test]
fn test_get_guest_components_procs_value() {
#[derive(Debug)]
struct TestData<'a> {
param: &'a str,
result: Result<GuestComponentsProcs>,
}
let tests = &[
TestData {
param: "",
result: Err(anyhow!(ERR_INVALID_GET_VALUE_PARAM)),
},
TestData {
param: "=",
result: Err(anyhow!(ERR_INVALID_GET_VALUE_NO_NAME)),
},
TestData {
param: "==",
result: Err(anyhow!(ERR_INVALID_GET_VALUE_NO_NAME)),
},
TestData {
param: "x=attestation-agent",
result: Ok(GuestComponentsProcs::AttestationAgent),
},
TestData {
param: "x=confidential-data-hub",
result: Ok(GuestComponentsProcs::ConfidentialDataHub),
},
TestData {
param: "x=none",
result: Ok(GuestComponentsProcs::None),
},
TestData {
param: "x=api-server-rest",
result: Ok(GuestComponentsProcs::ApiServerRest),
},
TestData {
param: "x===",
result: Err(anyhow!(ERR_INVALID_GUEST_COMPONENTS_PROCS_VALUE)),
},
TestData {
param: "x==x",
result: Err(anyhow!(ERR_INVALID_GUEST_COMPONENTS_PROCS_VALUE)),
},
TestData {
param: "x=x",
result: Err(anyhow!(ERR_INVALID_GUEST_COMPONENTS_PROCS_VALUE)),
},
];
for (i, d) in tests.iter().enumerate() {
let msg = format!("test[{}]: {:?}", i, d);
let result = get_guest_components_procs_value(d.param);
let msg = format!("{}: result: {:?}", msg, result);
assert_result!(d.result, result, msg);
}
}
#[test]
fn test_config_builder_from_string() {
let config = AgentConfig::from_str(

View File

@ -59,11 +59,11 @@ mod util;
mod version;
mod watcher;
use config::GuestComponentsFeatures;
use config::GuestComponentsProcs;
use mount::{cgroups_mount, general_mount};
use sandbox::Sandbox;
use signal::setup_signal_handler;
use slog::{error, info, o, warn, Logger};
use slog::{debug, error, info, o, warn, Logger};
use uevent::watch_uevents;
use futures::future::join_all;
@ -403,8 +403,16 @@ async fn start_sandbox(
let (tx, rx) = tokio::sync::oneshot::channel();
sandbox.lock().await.sender = Some(tx);
if Path::new(CDH_PATH).exists() && Path::new(AA_PATH).exists() {
init_attestation_components(logger, config)?;
let gc_procs = config.guest_components_procs;
if gc_procs != GuestComponentsProcs::None {
if !attestation_binaries_available(logger, &gc_procs) {
warn!(
logger,
"attestation binaries requested for launch not available"
);
} else {
init_attestation_components(logger, config)?;
}
}
// vsock:///dev/vsock, port
@ -417,9 +425,33 @@ async fn start_sandbox(
Ok(())
}
// Check if required attestation binaries are available on the rootfs.
fn attestation_binaries_available(logger: &Logger, procs: &GuestComponentsProcs) -> bool {
let binaries = match procs {
GuestComponentsProcs::AttestationAgent => vec![AA_PATH],
GuestComponentsProcs::ConfidentialDataHub => vec![AA_PATH, CDH_PATH],
GuestComponentsProcs::ApiServerRest => vec![AA_PATH, CDH_PATH, API_SERVER_PATH],
_ => vec![],
};
for binary in binaries.iter() {
if !Path::new(binary).exists() {
warn!(logger, "{} not found", binary);
return false;
}
}
true
}
// Start-up attestation-agent, CDH and api-server-rest if they are packaged in the rootfs
fn init_attestation_components(logger: &Logger, _config: &AgentConfig) -> Result<()> {
// The Attestation Agent will run for the duration of the guest.
// 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<()> {
// skip launch of any guest-component
if config.guest_components_procs == GuestComponentsProcs::None {
return Ok(());
}
debug!(logger, "spawning attestation-agent process {}", AA_PATH);
launch_process(
logger,
AA_PATH,
@ -429,32 +461,43 @@ fn init_attestation_components(logger: &Logger, _config: &AgentConfig) -> Result
)
.map_err(|e| anyhow!("launch_process {} failed: {:?}", AA_PATH, e))?;
if let Err(e) = launch_process(
// skip launch of confidential-data-hub and api-server-rest
if config.guest_components_procs == GuestComponentsProcs::AttestationAgent {
return Ok(());
}
debug!(
logger,
"spawning confidential-data-hub process {}", CDH_PATH
);
launch_process(
logger,
CDH_PATH,
&vec![],
CDH_SOCKET,
DEFAULT_LAUNCH_PROCESS_TIMEOUT,
) {
error!(logger, "launch_process {} failed: {:?}", CDH_PATH, e);
} else {
let features = _config.guest_components_rest_api;
match features {
GuestComponentsFeatures::None => {}
_ => {
if let Err(e) = launch_process(
logger,
API_SERVER_PATH,
&vec!["--features", &features.to_string()],
"",
0,
) {
error!(logger, "launch_process {} failed: {:?}", API_SERVER_PATH, e);
}
}
}
)
.map_err(|e| anyhow!("launch_process {} failed: {:?}", CDH_PATH, e))?;
// skip launch of api-server-rest
if config.guest_components_procs == GuestComponentsProcs::ConfidentialDataHub {
return Ok(());
}
let features = config.guest_components_rest_api;
debug!(
logger,
"spawning api-server-rest process {} --features {}", API_SERVER_PATH, features
);
launch_process(
logger,
API_SERVER_PATH,
&vec!["--features", &features.to_string()],
"",
0,
)
.map_err(|e| anyhow!("launch_process {} failed: {:?}", API_SERVER_PATH, e))?;
Ok(())
}