mirror of
https://github.com/kata-containers/kata-containers.git
synced 2026-07-10 07:05:51 +00:00
agent: add systemd template unit for addon image mount/unmount
Add a systemd template unit kata-addon-mount@.service and companion helper scripts (kata-addon-mount.sh, kata-addon-umount.sh) that handle the guest-side setup of addon block device images. The mount script: - Discovers the block device via /dev/disk/by-id/virtio-addon-<name> - Reads verity parameters from kata.addon.<name>.verity_params on the kernel command line - Creates a dm-verity device and mounts the EROFS filesystem - Bind-mounts addon directories (usr/local/bin, etc, pause_bundle) into the rootfs The unit uses ConditionPathExists to only activate when the corresponding virtio-blk device is present, and is ordered before kata-agent.service so addon binaries are available when the agent starts. The agent Makefile is updated to install the unit and scripts. Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com> Assisted-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -91,6 +91,10 @@ ifeq ($(INIT),no)
|
||||
GENERATED_FILES += $(UNIT_FILES)
|
||||
# Target to be reached in systemd services
|
||||
UNIT_FILES += kata-containers.target
|
||||
# Template unit for mounting addon block devices (CoCo, GPU, etc.)
|
||||
UNIT_FILES += kata-addon-mount@.service
|
||||
# Helper scripts for addon mount/umount
|
||||
ADDON_SCRIPTS = kata-addon-mount.sh kata-addon-umount.sh
|
||||
endif
|
||||
|
||||
# Display name of command and it's version (or a message if not available).
|
||||
@@ -154,6 +158,8 @@ install-services: $(GENERATED_FILES)
|
||||
ifeq ($(INIT),no)
|
||||
@echo "Installing systemd unit files..."
|
||||
$(foreach f,$(UNIT_FILES),$(call INSTALL_FILE,$f,$(UNIT_DIR)))
|
||||
@echo "Installing addon helper scripts..."
|
||||
$(foreach f,$(ADDON_SCRIPTS),install -D -m 755 $f $(DESTDIR)/usr/libexec/$f || exit 1;)
|
||||
endif
|
||||
|
||||
show-header:
|
||||
|
||||
104
src/agent/kata-addon-mount.sh
Executable file
104
src/agent/kata-addon-mount.sh
Executable file
@@ -0,0 +1,104 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (c) 2026 NVIDIA Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Mount a kata addon block device with optional dm-verity verification.
|
||||
# Usage: kata-addon-mount.sh <addon-name>
|
||||
#
|
||||
# The block device is discovered by scanning /sys/block/*/serial for
|
||||
# a device whose serial matches "addon-<name>".
|
||||
# Verity params are read from kernel cmdline: kata.addon.<name>.verity_params=...
|
||||
# The addon is mounted read-only (erofs) at /run/kata-addons/<name>/.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ADDON_NAME="${1:?addon name required}"
|
||||
SERIAL="addon-${ADDON_NAME}"
|
||||
MOUNT_DIR="/run/kata-addons/${ADDON_NAME}"
|
||||
|
||||
find_block_dev_by_serial() {
|
||||
local wanted="$1"
|
||||
for s in /sys/block/*/serial; do
|
||||
[[ -f "${s}" ]] || continue
|
||||
local cur
|
||||
cur="$(cat "${s}" 2>/dev/null)" || continue
|
||||
if [[ "${cur}" == "${wanted}" ]]; then
|
||||
echo "/dev/$(basename "$(dirname "${s}")")"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
REAL_DEV="$(find_block_dev_by_serial "${SERIAL}")" || {
|
||||
echo "ERROR: no block device with serial ${SERIAL} found" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
get_verity_param() {
|
||||
local key="kata.addon.${ADDON_NAME}.verity_params"
|
||||
local cmdline
|
||||
cmdline="$(cat /proc/cmdline)"
|
||||
|
||||
local value=""
|
||||
for param in ${cmdline}; do
|
||||
case "${param}" in
|
||||
"${key}="*)
|
||||
value="${param#"${key}="}"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
echo "${value}"
|
||||
}
|
||||
|
||||
parse_verity_field() {
|
||||
local params="$1"
|
||||
local field="$2"
|
||||
echo "${params}" | tr ',' '\n' | while IFS='=' read -r k v; do
|
||||
if [[ "${k}" == "${field}" ]]; then
|
||||
echo "${v}"
|
||||
return
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
VERITY_PARAMS="$(get_verity_param)"
|
||||
|
||||
if [[ -n "${VERITY_PARAMS}" ]]; then
|
||||
ROOT_HASH="$(parse_verity_field "${VERITY_PARAMS}" "root_hash")"
|
||||
SALT="$(parse_verity_field "${VERITY_PARAMS}" "salt")"
|
||||
DATA_BLOCKS="$(parse_verity_field "${VERITY_PARAMS}" "data_blocks")"
|
||||
HASH_BLOCK_SIZE="$(parse_verity_field "${VERITY_PARAMS}" "hash_block_size")"
|
||||
DATA_BLOCK_SIZE="$(parse_verity_field "${VERITY_PARAMS}" "data_block_size")"
|
||||
|
||||
PART_SEP=""
|
||||
[[ "${REAL_DEV}" =~ [0-9]$ ]] && PART_SEP="p"
|
||||
DATA_DEV="${REAL_DEV}${PART_SEP}1"
|
||||
HASH_DEV="${REAL_DEV}${PART_SEP}2"
|
||||
|
||||
DM_NAME="addon-${ADDON_NAME}"
|
||||
|
||||
veritysetup open "${DATA_DEV}" "${DM_NAME}" "${HASH_DEV}" "${ROOT_HASH}" \
|
||||
--no-superblock \
|
||||
--hash-block-size="${HASH_BLOCK_SIZE:-4096}" \
|
||||
--data-block-size="${DATA_BLOCK_SIZE:-4096}" \
|
||||
--data-blocks="${DATA_BLOCKS}" \
|
||||
--salt="${SALT}"
|
||||
|
||||
MOUNT_SRC="/dev/mapper/${DM_NAME}"
|
||||
else
|
||||
PART_SEP=""
|
||||
[[ "${REAL_DEV}" =~ [0-9]$ ]] && PART_SEP="p"
|
||||
if [[ -e "${REAL_DEV}${PART_SEP}1" ]]; then
|
||||
MOUNT_SRC="${REAL_DEV}${PART_SEP}1"
|
||||
else
|
||||
MOUNT_SRC="${REAL_DEV}"
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p "${MOUNT_DIR}"
|
||||
mount -t erofs -o ro "${MOUNT_SRC}" "${MOUNT_DIR}"
|
||||
|
||||
echo "Addon ${ADDON_NAME} mounted at ${MOUNT_DIR}"
|
||||
22
src/agent/kata-addon-mount@.service
Normal file
22
src/agent/kata-addon-mount@.service
Normal file
@@ -0,0 +1,22 @@
|
||||
#
|
||||
# Copyright (c) 2026 NVIDIA Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
[Unit]
|
||||
Description=Mount Kata addon image %i
|
||||
DefaultDependencies=no
|
||||
Before=kata-agent.service
|
||||
After=local-fs-pre.target
|
||||
ConditionKernelCommandLine=kata.addon.%i.verity_params
|
||||
OnFailure=poweroff.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=/usr/libexec/kata-addon-mount.sh %i
|
||||
ExecStop=/usr/libexec/kata-addon-umount.sh %i
|
||||
|
||||
[Install]
|
||||
WantedBy=kata-containers.target
|
||||
49
src/agent/kata-addon-umount.sh
Executable file
49
src/agent/kata-addon-umount.sh
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (c) 2026 NVIDIA Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Unmount a kata addon block device.
|
||||
# Usage: kata-addon-umount.sh <addon-name>
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ADDON_NAME="${1:?addon name required}"
|
||||
MOUNT_DIR="/run/kata-addons/${ADDON_NAME}"
|
||||
DM_NAME="addon-${ADDON_NAME}"
|
||||
|
||||
# Undo bind mounts from /usr/local/bin/
|
||||
if [[ -d "${MOUNT_DIR}/usr/local/bin" ]]; then
|
||||
for bin in "${MOUNT_DIR}"/usr/local/bin/*; do
|
||||
[[ -f "${bin}" ]] || continue
|
||||
target="/usr/local/bin/$(basename "${bin}")"
|
||||
umount "${target}" 2>/dev/null || true
|
||||
done
|
||||
fi
|
||||
|
||||
# Undo bind mounts from /etc/
|
||||
if [[ -d "${MOUNT_DIR}/etc" ]]; then
|
||||
for cfg in "${MOUNT_DIR}"/etc/*; do
|
||||
[[ -e "${cfg}" ]] || continue
|
||||
target="/etc/$(basename "${cfg}")"
|
||||
umount "${target}" 2>/dev/null || true
|
||||
done
|
||||
fi
|
||||
|
||||
# Undo pause_bundle bind mount
|
||||
if mountpoint -q /pause_bundle 2>/dev/null; then
|
||||
umount /pause_bundle 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Unmount the addon filesystem
|
||||
if mountpoint -q "${MOUNT_DIR}" 2>/dev/null; then
|
||||
umount "${MOUNT_DIR}"
|
||||
fi
|
||||
|
||||
# Close dm-verity device if present
|
||||
if [[ -e "/dev/mapper/${DM_NAME}" ]]; then
|
||||
veritysetup close "${DM_NAME}" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo "Addon ${ADDON_NAME} unmounted"
|
||||
@@ -22,6 +22,15 @@ use protocols::agent::Storage;
|
||||
pub const KATA_IMAGE_WORK_DIR: &str = "/run/kata-containers/image/";
|
||||
const CONFIG_JSON: &str = "config.json";
|
||||
const KATA_PAUSE_BUNDLE: &str = "/pause_bundle";
|
||||
const KATA_PAUSE_BUNDLE_ADDON: &str = "/run/kata-addons/coco/pause_bundle";
|
||||
|
||||
fn resolve_pause_bundle() -> &'static str {
|
||||
if Path::new(KATA_PAUSE_BUNDLE_ADDON).exists() {
|
||||
KATA_PAUSE_BUNDLE_ADDON
|
||||
} else {
|
||||
KATA_PAUSE_BUNDLE
|
||||
}
|
||||
}
|
||||
|
||||
const K8S_CONTAINER_TYPE_KEYS: [&str; 2] = [
|
||||
"io.kubernetes.cri.container-type",
|
||||
@@ -46,7 +55,7 @@ fn copy_if_not_exists(src: &Path, dst: &Path) -> Result<()> {
|
||||
|
||||
/// get guest pause image process specification
|
||||
fn get_pause_image_process() -> Result<oci::Process> {
|
||||
let guest_pause_bundle = Path::new(KATA_PAUSE_BUNDLE);
|
||||
let guest_pause_bundle = Path::new(resolve_pause_bundle());
|
||||
if !guest_pause_bundle.exists() {
|
||||
bail!("Pause image not present in rootfs");
|
||||
}
|
||||
@@ -70,7 +79,7 @@ fn get_pause_image_process() -> Result<oci::Process> {
|
||||
pub fn unpack_pause_image(cid: &str) -> Result<String> {
|
||||
verify_id(cid).context("The guest pause image cid contains invalid characters.")?;
|
||||
|
||||
let guest_pause_bundle = Path::new(KATA_PAUSE_BUNDLE);
|
||||
let guest_pause_bundle = Path::new(resolve_pause_bundle());
|
||||
if !guest_pause_bundle.exists() {
|
||||
bail!("Pause image not present in rootfs");
|
||||
}
|
||||
|
||||
@@ -96,20 +96,34 @@ const NAME: &str = "kata-agent";
|
||||
|
||||
const UNIX_SOCKET_PREFIX: &str = "unix://";
|
||||
|
||||
const COCO_ADDON_DIR: &str = "/run/kata-addons/coco";
|
||||
|
||||
const AA_PATH: &str = "/usr/local/bin/attestation-agent";
|
||||
const AA_ADDON_PATH: &str = concatcp!(COCO_ADDON_DIR, "/usr/local/bin/attestation-agent");
|
||||
const AA_ATTESTATION_SOCKET: &str =
|
||||
"/run/confidential-containers/attestation-agent/attestation-agent.sock";
|
||||
const AA_ATTESTATION_URI: &str = concatcp!(UNIX_SOCKET_PREFIX, AA_ATTESTATION_SOCKET);
|
||||
|
||||
const CDH_PATH: &str = "/usr/local/bin/confidential-data-hub";
|
||||
const CDH_ADDON_PATH: &str = concatcp!(COCO_ADDON_DIR, "/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";
|
||||
const API_SERVER_ADDON_PATH: &str = concatcp!(COCO_ADDON_DIR, "/usr/local/bin/api-server-rest");
|
||||
|
||||
/// Path of ocicrypt config file. This is used by CDH when decrypting image.
|
||||
/// TODO: remove this when we move the launch of CDH out of the kata-agent.
|
||||
const OCICRYPT_CONFIG_PATH: &str = "/etc/ocicrypt_config.json";
|
||||
const OCICRYPT_CONFIG_ADDON_PATH: &str = concatcp!(COCO_ADDON_DIR, "/etc/ocicrypt_config.json");
|
||||
|
||||
/// Resolve a binary path: prefer the CoCo addon location, fall back to the
|
||||
/// legacy rootfs path.
|
||||
fn resolve_path<'a>(addon_path: &'a str, legacy_path: &'a str) -> &'a str {
|
||||
if Path::new(addon_path).exists() {
|
||||
addon_path
|
||||
} else {
|
||||
legacy_path
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref AGENT_CONFIG: AgentConfig =
|
||||
@@ -457,23 +471,31 @@ async fn start_sandbox(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Check if required attestation binaries are available on the rootfs.
|
||||
// Check if required attestation binaries are available, looking in the CoCo
|
||||
// addon mount first, then in the legacy rootfs paths.
|
||||
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],
|
||||
let binaries: Vec<(&str, &str)> = match procs {
|
||||
GuestComponentsProcs::AttestationAgent => vec![(AA_ADDON_PATH, AA_PATH)],
|
||||
GuestComponentsProcs::ConfidentialDataHub => {
|
||||
vec![(AA_ADDON_PATH, AA_PATH), (CDH_ADDON_PATH, CDH_PATH)]
|
||||
}
|
||||
GuestComponentsProcs::ApiServerRest => vec![
|
||||
(AA_ADDON_PATH, AA_PATH),
|
||||
(CDH_ADDON_PATH, CDH_PATH),
|
||||
(API_SERVER_ADDON_PATH, API_SERVER_PATH),
|
||||
],
|
||||
_ => vec![],
|
||||
};
|
||||
for binary in binaries.iter() {
|
||||
let exists = Path::new(binary)
|
||||
for (addon, legacy) in binaries.iter() {
|
||||
let resolved = resolve_path(addon, legacy);
|
||||
let exists = Path::new(resolved)
|
||||
.try_exists()
|
||||
.unwrap_or_else(|error| match error.kind() {
|
||||
ErrorKind::NotFound => {
|
||||
warn!(logger, "{} not found", binary);
|
||||
warn!(logger, "{} not found", resolved);
|
||||
false
|
||||
}
|
||||
_ => panic!("Path existence check failed for '{}': {}", binary, error),
|
||||
_ => panic!("Path existence check failed for '{}': {}", resolved, error),
|
||||
});
|
||||
|
||||
if !exists {
|
||||
@@ -492,7 +514,8 @@ async fn launch_guest_component_procs(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
debug!(logger, "spawning attestation-agent process {}", AA_PATH);
|
||||
let aa_path = resolve_path(AA_ADDON_PATH, AA_PATH);
|
||||
debug!(logger, "spawning attestation-agent process {}", aa_path);
|
||||
let mut aa_args = vec!["--attestation_sock", AA_ATTESTATION_URI];
|
||||
if initdata_return_value.is_some() {
|
||||
aa_args.push("--initdata-toml");
|
||||
@@ -501,7 +524,7 @@ async fn launch_guest_component_procs(
|
||||
|
||||
launch_process(
|
||||
logger,
|
||||
AA_PATH,
|
||||
aa_path,
|
||||
aa_args,
|
||||
Some(AA_CONFIG_PATH),
|
||||
AA_ATTESTATION_SOCKET,
|
||||
@@ -509,43 +532,44 @@ async fn launch_guest_component_procs(
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.map_err(|e| anyhow!("launch_process {} failed: {:?}", AA_PATH, e))?;
|
||||
.map_err(|e| anyhow!("launch_process {} failed: {:?}", aa_path, e))?;
|
||||
|
||||
// skip launch of confidential-data-hub and api-server-rest
|
||||
if config.guest_components_procs == GuestComponentsProcs::AttestationAgent {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let cdh_path = resolve_path(CDH_ADDON_PATH, CDH_PATH);
|
||||
let ocicrypt_path = resolve_path(OCICRYPT_CONFIG_ADDON_PATH, OCICRYPT_CONFIG_PATH);
|
||||
debug!(
|
||||
logger,
|
||||
"spawning confidential-data-hub process {}", CDH_PATH
|
||||
"spawning confidential-data-hub process {}", cdh_path
|
||||
);
|
||||
|
||||
launch_process(
|
||||
logger,
|
||||
CDH_PATH,
|
||||
cdh_path,
|
||||
vec![],
|
||||
Some(CDH_CONFIG_PATH),
|
||||
CDH_SOCKET,
|
||||
config.launch_process_timeout.as_secs(),
|
||||
&[("OCICRYPT_KEYPROVIDER_CONFIG", OCICRYPT_CONFIG_PATH)],
|
||||
&[("OCICRYPT_KEYPROVIDER_CONFIG", ocicrypt_path)],
|
||||
)
|
||||
.await
|
||||
.map_err(|e| anyhow!("launch_process {} failed: {:?}", CDH_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 api_path = resolve_path(API_SERVER_ADDON_PATH, API_SERVER_PATH);
|
||||
let features = config.guest_components_rest_api;
|
||||
debug!(
|
||||
logger,
|
||||
"spawning api-server-rest process {} --features {}", API_SERVER_PATH, features
|
||||
"spawning api-server-rest process {} --features {}", api_path, features
|
||||
);
|
||||
launch_process(
|
||||
logger,
|
||||
API_SERVER_PATH,
|
||||
api_path,
|
||||
vec!["--features", &features.to_string()],
|
||||
None,
|
||||
"",
|
||||
@@ -553,7 +577,7 @@ async fn launch_guest_component_procs(
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.map_err(|e| anyhow!("launch_process {} failed: {:?}", API_SERVER_PATH, e))?;
|
||||
.map_err(|e| anyhow!("launch_process {} failed: {:?}", api_path, e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user