From 77fefade6657177396332eb2166e900a80de102d Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 17 Dec 2024 10:53:42 +0000 Subject: [PATCH] Support for boot progress report --- Cargo.lock | 1 + basefiles/app-compose.service | 2 +- basefiles/app-compose.sh | 15 ++++ host-api/proto/host_api.proto | 2 +- tdxctl/src/fde_setup.rs | 113 ++++++++++++++++++------------ tdxctl/src/main.rs | 26 +++++++ tdxctl/src/notify_client.rs | 52 ++++++++++++++ tdxctl/src/tboot.rs | 17 ++++- tdxctl/src/utils.rs | 1 + teepod/Cargo.toml | 1 + teepod/rpc/proto/teepod_rpc.proto | 6 +- teepod/src/app.rs | 67 +++++++++++------- teepod/src/app/image.rs | 9 ++- teepod/src/app/qemu.rs | 15 ++-- teepod/src/console.html | 14 ++++ teepod/src/host_api_routes.rs | 5 ++ teepod/src/host_api_service.rs | 2 +- teepod/src/main_service.rs | 2 +- 18 files changed, 262 insertions(+), 88 deletions(-) create mode 100644 basefiles/app-compose.sh create mode 100644 tdxctl/src/notify_client.rs diff --git a/Cargo.lock b/Cargo.lock index 692673ff..d71ce725 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4844,6 +4844,7 @@ dependencies = [ "rocket", "rocket-apitoken", "rocket-vsock-listener", + "safe-write", "serde", "serde_json", "sha2", diff --git a/basefiles/app-compose.service b/basefiles/app-compose.service index 08fa4cd1..a670b78a 100644 --- a/basefiles/app-compose.service +++ b/basefiles/app-compose.service @@ -8,7 +8,7 @@ Type=oneshot RemainAfterExit=true EnvironmentFile=-/tapp/env WorkingDirectory=/tapp -ExecStart=/usr/bin/env docker compose up -d +ExecStart=/usr/bin/env app-compose.sh ExecStop=/usr/bin/env docker compose down StandardOutput=journal+console StandardError=journal+console diff --git a/basefiles/app-compose.sh b/basefiles/app-compose.sh new file mode 100644 index 00000000..2f07890f --- /dev/null +++ b/basefiles/app-compose.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +tdxctl notify-host -e "boot.progress" -d "pulling images" || true +if ! docker compose pull; then + tdxctl notify-host -e "boot.error" -d "failed to pull images" + exit 1 +fi + +tdxctl notify-host -e "boot.progress" -d "starting containers" || true +if ! docker compose up -d; then + tdxctl notify-host -e "boot.error" -d "failed to start containers" + exit 1 +fi + +tdxctl notify-host -e "boot.progress" -d "containers started" || true diff --git a/host-api/proto/host_api.proto b/host-api/proto/host_api.proto index c3322b83..ca6fdb03 100644 --- a/host-api/proto/host_api.proto +++ b/host-api/proto/host_api.proto @@ -12,7 +12,7 @@ message HostInfo { message Notification { string event = 1; - string message = 2; + string payload = 2; } service HostApi { diff --git a/tdxctl/src/fde_setup.rs b/tdxctl/src/fde_setup.rs index 2f1e7bac..a682b53c 100644 --- a/tdxctl/src/fde_setup.rs +++ b/tdxctl/src/fde_setup.rs @@ -14,6 +14,7 @@ use tracing::{info, warn}; use crate::{ cmd_gen_app_keys, cmd_gen_ra_cert, cmd_show, crypto::dh_decrypt, + notify_client::NotifyClient, utils::{ copy_dir_all, deserialize_json_file, extend_rtmr3, run_command, run_command_with_stdin, sha256, sha256_file, AppCompose, AppKeys, HashingFile, LocalConfig, @@ -56,6 +57,10 @@ pub struct SetupFdeArgs { rootfs_encryption: std::primitive::bool, } +fn umount(mount_point: &str) -> Result<()> { + run_command("umount", &[mount_point]).map(|_| ()) +} + fn mount_9p(share_name: &str, mount_point: &str) -> Result<()> { run_command( "mount", @@ -63,7 +68,7 @@ fn mount_9p(share_name: &str, mount_point: &str) -> Result<()> { "-t", "9p", "-o", - "trans=virtio,version=9p2000.L", + "trans=virtio,version=9p2000.L,ro", share_name, mount_point, ], @@ -169,23 +174,23 @@ fn truncate(s: &[u8], len: usize) -> &[u8] { impl SetupFdeArgs { fn app_keys_file(&self) -> PathBuf { - self.work_dir.join("appkeys.json") - } - - fn tapp_dir(&self) -> PathBuf { - self.rootfs_dir.join("tapp") + self.host_shared_copy.join("appkeys.json") } fn copy_host_shared(&self) -> Result { info!("Mounting host-shared"); - fs::create_dir_all(&self.host_shared) - .context("Failed to create host-sharing mount point")?; - mount_9p("host-shared", &self.host_shared.display().to_string()) - .context("Failed to mount host-sharing")?; + let shared_dir = self.host_shared.display().to_string(); + + fs::create_dir_all(&shared_dir).context("Failed to create host-sharing mount point")?; + mount_9p("host-shared", &shared_dir).context("Failed to mount host-sharing")?; + fs::create_dir_all(&self.host_shared_copy) .context("Failed to create host-shared copy dir")?; copy_dir_all(&self.host_shared, &self.host_shared_copy) .context("Failed to copy host-shared dir")?; + + umount(&shared_dir).context("Failed to unmount host-shared")?; + let host_shared_dir = HostShareDir::new(&self.host_shared_copy); let host_shared = HostShared::load(&host_shared_dir)?; Ok(host_shared) @@ -249,7 +254,7 @@ impl SetupFdeArgs { Ok(vars) } - fn mount_rootfs(&self, disk_crypt_key: &str) -> Result<()> { + fn mount_rootfs(&self, host_shared: &HostShared, disk_crypt_key: &str) -> Result<()> { if !self.rootfs_encryption { warn!("Rootfs encryption is disabled, skipping disk encryption"); run_command( @@ -281,6 +286,24 @@ impl SetupFdeArgs { ], ) .context("Failed to mount rootfs")?; + + let hash_file = self.rootfs_dir.join(".rootfs_hash"); + let existing_rootfs_hash = match fs::read(&hash_file) { + Ok(rootfs_hash) => rootfs_hash, + Err(_) => { + // Old image touches .bootstraped instead of .rootfs_hash + if !self.rootfs_dir.join(".bootstraped").exists() { + bail!("Rootfs is not bootstrapped"); + } + Default::default() + } + }; + + if existing_rootfs_hash != host_shared.vm_config.rootfs_hash { + let todo = "do upgrade"; + fs::remove_file(&hash_file).context("Failed to remove old rootfs hash file")?; + bail!("Rootfs hash mismatch"); + } Ok(()) } @@ -325,7 +348,7 @@ impl SetupFdeArgs { host_shared: &HostShared, disk_crypt_key: &str, instance_info: &InstanceInfo, - ) -> Result<()> { + ) -> Result { info!("Setting up disk encryption"); fs::create_dir_all(&self.root_cdrom_mnt) .context("Failed to create rootfs cdrom mount point")?; @@ -390,46 +413,28 @@ impl SetupFdeArgs { info!("Rootfs hash is valid"); let mut instance_info = instance_info.clone(); instance_info.bootstrapped = true; - // write instance info - let instance_info = - serde_json::to_string(&instance_info).context("Failed to serialize instance info")?; - let origin_host_shared_dir = HostShareDir::new(&self.host_shared); - fs::write(origin_host_shared_dir.instance_info_file(), instance_info) - .context("Failed to write instance info")?; - fs::File::create(self.rootfs_dir.join(".bootstraped")) - .context("Failed to touch bootstraped")?; + fs::write(self.rootfs_dir.join(".rootfs_hash"), &rootfs_hash) + .context("Failed to write rootfs hash")?; info!("Rootfs is ready"); - Ok(()) + Ok(instance_info) } - fn copy_files_to_rootfs(&self, decrypted_env: &BTreeMap) -> Result<()> { - let tapp_dir = self.tapp_dir(); - info!("Copying host-shared to tapp dir"); - fs::remove_dir_all(&tapp_dir).ok(); - copy_dir_all(&self.host_shared_copy, &tapp_dir).context("Failed to copy rootfs")?; - info!("Copying appkeys.json"); - fs::copy(self.app_keys_file(), tapp_dir.join("appkeys.json")) - .context("Failed to copy appkeys.json")?; - info!("Copying config.json"); - fs::copy( - self.host_shared_copy.join("config.json"), - tapp_dir.join("config.json"), - ) - .context("Failed to copy config.json")?; + fn write_decrypted_env(&self, decrypted_env: &BTreeMap) -> Result<()> { + info!("Writing env"); fs::write( - tapp_dir.join("env"), + self.host_shared_copy.join("env"), env_process::convert_env_to_str(decrypted_env), ) .context("Failed to write decrypted env file")?; - let env_json = - fs::File::create(tapp_dir.join("env.json")).context("Failed to create env file")?; + let env_json = fs::File::create(self.host_shared_copy.join("env.json")) + .context("Failed to create env file")?; serde_json::to_writer(env_json, &decrypted_env) .context("Failed to write decrypted env file")?; Ok(()) } - async fn setup_rootfs(&self) -> Result<()> { - let host_shared = self.copy_host_shared()?; + async fn setup_rootfs(&self, nc: &NotifyClient, host_shared: &HostShared) -> Result<()> { + nc.notify_q("boot.progress", "loading host-shared").await; let rootfs_hash = &host_shared.vm_config.rootfs_hash; let compose_hash = sha256_file(host_shared.dir.app_compose_file())?; let truncated_compose_hash = truncate(&compose_hash, 20); @@ -458,6 +463,9 @@ impl SetupFdeArgs { if !kms_enabled && instance_info.app_id != truncated_compose_hash { bail!("App upgrade is not supported without KMS"); } + + nc.notify_q("boot.progress", "extending RTMRs").await; + extend_rtmr3("rootfs-hash", rootfs_hash)?; extend_rtmr3("app-id", &instance_info.app_id)?; extend_rtmr3("compose-hash", &compose_hash)?; @@ -467,24 +475,41 @@ impl SetupFdeArgs { // Show the RTMR cmd_show()?; + nc.notify_q("boot.progress", "requesting app keys").await; + let app_keys = self.request_app_keys(&host_shared).await?; if app_keys.disk_crypt_key.is_empty() { bail!("Failed to get valid key phrase from KMS"); } + nc.notify_q("boot.progress", "decrypting env").await; // Decrypt env file let decrypted_env = self.decrypt_env_vars(&app_keys.env_crypt_key, &host_shared.encrypted_env)?; let disk_crypt_key = format!("{}\n", app_keys.disk_crypt_key); if instance_info.bootstrapped { - self.mount_rootfs(&disk_crypt_key)?; + nc.notify_q("boot.progress", "mounting rootfs").await; + self.mount_rootfs(&host_shared, &disk_crypt_key)?; } else { - self.bootstrap_rootfs(&host_shared, &disk_crypt_key, &instance_info)?; + nc.notify_q("boot.progress", "initializing rootfs").await; + let instance_info = + self.bootstrap_rootfs(&host_shared, &disk_crypt_key, &instance_info)?; + nc.notify_q("instance.info", &serde_json::to_string(&instance_info)?) + .await; } - self.copy_files_to_rootfs(&decrypted_env)?; + self.write_decrypted_env(&decrypted_env)?; + nc.notify_q("boot.progress", "rootfs ready").await; Ok(()) } } pub async fn cmd_setup_fde(args: SetupFdeArgs) -> Result<()> { - args.setup_rootfs().await + let host_shared = args.copy_host_shared()?; + let nc = NotifyClient::new(host_shared.vm_config.host_api_url.clone()); + match args.setup_rootfs(&nc, &host_shared).await { + Ok(_) => Ok(()), + Err(err) => { + nc.notify_q("boot.error", &format!("{err:?}")).await; + Err(err) + } + } } diff --git a/tdxctl/src/main.rs b/tdxctl/src/main.rs index d3cf1858..d6f224d8 100644 --- a/tdxctl/src/main.rs +++ b/tdxctl/src/main.rs @@ -3,6 +3,7 @@ use clap::{Parser, Subcommand}; use fde_setup::{cmd_setup_fde, SetupFdeArgs}; use fs_err as fs; use getrandom::getrandom; +use notify_client::NotifyClient; use ra_tls::{attestation::QuoteContentType, cert::CaCert}; use scale::Decode; use std::{ @@ -16,6 +17,7 @@ use utils::{deserialize_json_file, extend_rtmr, run_command, AppCompose}; mod crypto; mod fde_setup; +mod notify_client; mod tboot; mod utils; @@ -53,6 +55,8 @@ enum Commands { SetupFde(SetupFdeArgs), /// Boot the Tapp Tboot(TbootArgs), + /// Notify the host about the Tapp + NotifyHost(HostNotifyArgs), } #[derive(Parser)] @@ -157,6 +161,19 @@ struct TestAppFeatureArgs { compose: String, } +#[derive(Parser)] +/// Notify the host about the Tapp +struct HostNotifyArgs { + #[arg(short, long)] + url: Option, + /// event name + #[arg(short, long)] + event: String, + /// event payload + #[arg(short = 'd', long)] + payload: String, +} + fn cmd_quote() -> Result<()> { let mut report_data = [0; 64]; io::stdin() @@ -360,6 +377,12 @@ fn cmd_test_app_feature(args: TestAppFeatureArgs) -> Result<()> { Ok(()) } +async fn cmd_notify_host(args: HostNotifyArgs) -> Result<()> { + let client = NotifyClient::load_or_default(args.url)?; + client.notify(&args.event, &args.payload).await?; + Ok(()) +} + fn sha256(data: &[u8]) -> String { use sha2::Digest; let mut sha256 = sha2::Sha256::new(); @@ -414,6 +437,9 @@ async fn main() -> Result<()> { bail!("Failed to boot the Tapp"); } } + Commands::NotifyHost(args) => { + cmd_notify_host(args).await?; + } } Ok(()) diff --git a/tdxctl/src/notify_client.rs b/tdxctl/src/notify_client.rs new file mode 100644 index 00000000..ae2d227d --- /dev/null +++ b/tdxctl/src/notify_client.rs @@ -0,0 +1,52 @@ +use crate::utils::{deserialize_json_file, LocalConfig}; +use anyhow::Result; +use host_api::{ + client::{new_client, DefaultClient}, + Notification, +}; +use tracing::warn; + +pub(crate) struct NotifyClient { + client: DefaultClient, +} + +impl Default for NotifyClient { + fn default() -> Self { + Self::new("".into()) + } +} + +impl NotifyClient { + pub fn new(base_url: String) -> Self { + Self { + client: new_client(base_url), + } + } + + pub fn load_or_default(url: Option) -> Result { + let url = match url { + Some(url) => url, + None => { + let local_config: LocalConfig = deserialize_json_file("/tapp/config.json")?; + local_config.host_api_url.clone() + } + }; + Ok(Self::new(url)) + } + + pub async fn notify(&self, event: &str, payload: &str) -> Result<()> { + self.client + .notify(Notification { + event: event.to_string(), + payload: payload.to_string(), + }) + .await?; + Ok(()) + } + + pub async fn notify_q(&self, event: &str, payload: &str) { + if let Err(err) = self.notify(event, payload).await { + warn!("Failed to notify event {event} to host: {:?}", err); + } + } +} diff --git a/tdxctl/src/tboot.rs b/tdxctl/src/tboot.rs index 77f3217b..0ede3b47 100644 --- a/tdxctl/src/tboot.rs +++ b/tdxctl/src/tboot.rs @@ -9,6 +9,7 @@ use tracing::info; use crate::{ cmd_gen_ra_cert, + notify_client::NotifyClient, utils::{ deserialize_json_file, run_command, run_command_with_stdin, AppCompose, AppKeys, LocalConfig, @@ -60,9 +61,11 @@ impl<'a> Setup<'a> { self.args.resolve(path) } - async fn setup(&self) -> Result<()> { + async fn setup(&self, nc: &NotifyClient) -> Result<()> { self.prepare_certs()?; + nc.notify_q("boot.progress", "setting up tproxy net").await; self.setup_tproxy_net().await?; + nc.notify_q("boot.progress", "setting up docker").await; self.setup_docker_registry()?; self.setup_docker_account()?; self.prepare_docker_compose()?; @@ -264,6 +267,16 @@ impl<'a> Setup<'a> { } pub async fn tboot(args: &TbootArgs) -> Result<()> { - Setup::load(args)?.setup().await?; + let nc = NotifyClient::load_or_default(None).unwrap_or_default(); + if let Err(err) = tboot_inner(args, &nc).await { + nc.notify_q("boot.error", &format!("{err:?}")).await; + return Err(err); + } + Ok(()) +} + +pub async fn tboot_inner(args: &TbootArgs, nc: &NotifyClient) -> Result<()> { + nc.notify_q("boot.progress", "enter system").await; + Setup::load(args)?.setup(nc).await?; Ok(()) } diff --git a/tdxctl/src/utils.rs b/tdxctl/src/utils.rs index 3f571dd1..01aee189 100644 --- a/tdxctl/src/utils.rs +++ b/tdxctl/src/utils.rs @@ -172,6 +172,7 @@ pub struct LocalConfig { pub kms_url: Option, pub tproxy_url: Option, pub docker_registry: Option, + pub host_api_url: String, } #[derive(Deserialize)] diff --git a/teepod/Cargo.toml b/teepod/Cargo.toml index c20605d2..9a5d1903 100644 --- a/teepod/Cargo.toml +++ b/teepod/Cargo.toml @@ -35,3 +35,4 @@ teepod-rpc.workspace = true kms-rpc.workspace = true path-absolutize.workspace = true host-api.workspace = true +safe-write.workspace = true diff --git a/teepod/rpc/proto/teepod_rpc.proto b/teepod/rpc/proto/teepod_rpc.proto index 0eb1e784..fc0da873 100644 --- a/teepod/rpc/proto/teepod_rpc.proto +++ b/teepod/rpc/proto/teepod_rpc.proto @@ -25,10 +25,8 @@ message VmInfo { optional string exited_at = 9; // Boot progress string boot_progress = 10; - // Network interface eth0 - string eth0 = 11; - // Network interface wg0 - string wg0 = 12; + // Boot error + string boot_error = 11; } message Id { diff --git a/teepod/src/app.rs b/teepod/src/app.rs index c1bc3827..f7bf6cc8 100644 --- a/teepod/src/app.rs +++ b/teepod/src/app.rs @@ -28,7 +28,7 @@ use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex, MutexGuard}; use supervisor_client::SupervisorClient; use teepod_rpc as pb; -use tracing::error; +use tracing::{error, info}; pub use image::{Image, ImageInfo}; pub use qemu::{VmConfig, VmWorkDir}; @@ -118,6 +118,7 @@ impl App { image, cid, networking: self.config.networking.clone(), + workdir: vm_work_dir.path().to_path_buf(), }; if vm_config.manifest.disk_size > self.config.cvm.max_disk_size { bail!( @@ -135,18 +136,25 @@ impl App { } pub async fn start_vm(&self, id: &str) -> Result<()> { - let vm_state = self.lock().get(id).context("VM not found")?; - let work_dir = self.work_dir(id); - work_dir - .set_started(true) - .with_context(|| format!("Failed to set started for VM {id}"))?; - if work_dir.serial_pty().exists() { - // remove the existing pty - fs::remove_file(work_dir.serial_pty()).context("Failed to remove existing pty link")?; - } - let process_config = vm_state - .config - .config_qemu(&self.config.qemu_path, &work_dir)?; + let process_config = { + let mut state = self.lock(); + let vm_state = state.get_mut(id).context("VM not found")?; + let work_dir = self.work_dir(id); + work_dir + .set_started(true) + .with_context(|| format!("Failed to set started for VM {id}"))?; + if work_dir.serial_pty().exists() { + // remove the existing pty + fs::remove_file(work_dir.serial_pty()) + .context("Failed to remove existing pty link")?; + } + let process_config = vm_state + .config + .config_qemu(&self.config.qemu_path, &work_dir)?; + vm_state.state.boot_error.clear(); + vm_state.state.boot_progress.clear(); + process_config + }; self.supervisor .deploy(process_config) .await @@ -255,7 +263,8 @@ impl App { pub async fn vm_info(&self, id: &str) -> Result> { let proc_state = self.supervisor.info(id).await?; - let Some(vm_state) = self.lock().get(id) else { + let state = self.lock(); + let Some(vm_state) = state.get(id) else { return Ok(None); }; let info = vm_state @@ -264,20 +273,27 @@ impl App { Ok(Some(info)) } - pub(crate) fn vm_event_report(&self, cid: u32, event: &str, message: &str) -> Result<()> { + pub(crate) fn vm_event_report(&self, cid: u32, event: &str, body: String) -> Result<()> { + info!(cid, event, "VM event"); let mut state = self.lock(); let Some(vm) = state.vms.values_mut().find(|vm| vm.config.cid == cid) else { bail!("VM not found"); }; match event { "boot.progress" => { - vm.state.boot_progress = message.to_string(); + vm.state.boot_progress = body; } - "net.eth0" => { - vm.state.eth0 = message.to_string(); + "boot.error" => { + vm.state.boot_error = body; } - "net.wg0" => { - vm.state.wg0 = message.to_string(); + "instance.info" => { + if body.len() > 1024 * 4 { + error!("Instance info too large, skipping"); + return Ok(()); + } + let workdir = VmWorkDir::new(vm.config.workdir.clone()); + let instancd_info_path = workdir.instance_info_path(); + safe_write::safe_write(&instancd_info_path, &body)?; } _ => { error!("Guest reported unknown event: {event}"); @@ -296,8 +312,7 @@ pub struct VmState { #[derive(Debug, Clone, Default)] struct VmStateMut { boot_progress: String, - eth0: String, - wg0: String, + boot_error: String, } impl VmState { @@ -319,8 +334,12 @@ impl AppState { self.vms.insert(vm.config.manifest.id.clone(), vm); } - pub fn get(&self, id: &str) -> Option { - self.vms.get(id).cloned() + pub fn get(&self, id: &str) -> Option<&VmState> { + self.vms.get(id) + } + + pub fn get_mut(&mut self, id: &str) -> Option<&mut VmState> { + self.vms.get_mut(id) } pub fn remove(&mut self, id: &str) -> Option { diff --git a/teepod/src/app/image.rs b/teepod/src/app/image.rs index 40061509..1efd2379 100644 --- a/teepod/src/app/image.rs +++ b/teepod/src/app/image.rs @@ -14,11 +14,13 @@ pub struct ImageInfo { pub rootfs: Option, pub bios: Option, pub rootfs_hash: Option, + #[serde(default)] + pub shared_ro: bool, } impl ImageInfo { - pub fn load(filename: PathBuf) -> Result { - let file = fs::File::open(filename).context("failed to open image info")?; + pub fn load(filename: impl AsRef) -> Result { + let file = fs::File::open(filename.as_ref()).context("failed to open image info")?; let info: ImageInfo = serde_json::from_reader(file).context("failed to parse image info")?; Ok(info) @@ -33,6 +35,7 @@ pub struct Image { pub hda: Option, pub rootfs: Option, pub bios: Option, + pub shared_ro: bool, } impl Image { @@ -44,6 +47,7 @@ impl Image { let hda = info.hda.as_ref().map(|hda| base_path.join(hda)); let rootfs = info.rootfs.as_ref().map(|rootfs| base_path.join(rootfs)); let bios = info.bios.as_ref().map(|bios| base_path.join(bios)); + let shared_ro = info.shared_ro; Self { info, hda, @@ -51,6 +55,7 @@ impl Image { kernel, rootfs, bios, + shared_ro, } .ensure_exists() } diff --git a/teepod/src/app/qemu.rs b/teepod/src/app/qemu.rs index 50edabfd..3910042e 100644 --- a/teepod/src/app/qemu.rs +++ b/teepod/src/app/qemu.rs @@ -31,8 +31,7 @@ pub struct VmInfo { pub exited_at: Option, pub instance_id: Option, pub boot_progress: String, - pub eth0: String, - pub wg0: String, + pub boot_error: String, } #[derive(Debug, Builder)] @@ -41,6 +40,7 @@ pub struct VmConfig { pub image: Image, pub cid: u32, pub networking: Networking, + pub workdir: PathBuf, } #[derive(Deserialize, Serialize)] @@ -76,8 +76,7 @@ impl VmInfo { status: self.status.into(), uptime: self.uptime.clone(), boot_progress: self.boot_progress.clone(), - eth0: self.eth0.clone(), - wg0: self.wg0.clone(), + boot_error: self.boot_error.clone(), configuration: Some(pb::VmConfiguration { name: self.manifest.name.clone(), image: self.manifest.image.clone(), @@ -151,8 +150,7 @@ impl VmState { uptime, exited_at: Some(exited_at), boot_progress: self.state.boot_progress.clone(), - eth0: self.state.eth0.clone(), - wg0: self.state.wg0.clone(), + boot_error: self.state.boot_error.clone(), } } } @@ -229,9 +227,10 @@ impl VmConfig { .arg("-device") .arg(format!("vhost-vsock-pci,guest-cid={}", self.cid)); + let ro = if self.image.shared_ro { "on" } else { "off" }; command.arg("-virtfs").arg(format!( - "local,path={},mount_tag=host-shared,readonly=off,security_model=mapped,id=virtfs0", - shared_dir.display() + "local,path={},mount_tag=host-shared,readonly={ro},security_model=mapped,id=virtfs0", + shared_dir.display(), )); if let Some(cmdline) = &self.image.info.cmdline { command.arg("-append").arg(cmdline); diff --git a/teepod/src/console.html b/teepod/src/console.html index f965e532..931b50e0 100644 --- a/teepod/src/console.html +++ b/teepod/src/console.html @@ -666,6 +666,20 @@

VM List

+

Boot Status

+
+ + + + + + + + + +
Boot Progress:{{ vm.boot_progress || 'N/A' }}
Boot Error{{ vm.boot_error }}
+
+

VM Configuration

diff --git a/teepod/src/host_api_routes.rs b/teepod/src/host_api_routes.rs index 40958001..9e9109d0 100644 --- a/teepod/src/host_api_routes.rs +++ b/teepod/src/host_api_routes.rs @@ -5,6 +5,7 @@ use rocket::{ data::{Data, Limits}, get, http::ContentType, + listener::Endpoint, mtls::Certificate, post, response::status::Custom, @@ -14,6 +15,7 @@ use rocket::{ #[post("/?", data = "")] #[allow(clippy::too_many_arguments)] async fn prpc_post( + endpoint: &Endpoint, state: &State, cert: Option>, method: &str, @@ -24,6 +26,7 @@ async fn prpc_post( ) -> Custom> { PrpcHandler::builder() .state(&**state) + .remote_addr(endpoint.clone()) .maybe_certificate(cert) .method(method) .data(data) @@ -37,6 +40,7 @@ async fn prpc_post( #[get("/")] async fn prpc_get( + endpoint: &Endpoint, state: &State, method: &str, limits: &Limits, @@ -44,6 +48,7 @@ async fn prpc_get( ) -> Custom> { PrpcHandler::builder() .state(&**state) + .remote_addr(endpoint.clone()) .method(method) .limits(limits) .maybe_content_type(content_type) diff --git a/teepod/src/host_api_service.rs b/teepod/src/host_api_service.rs index 00c80893..dcfff149 100644 --- a/teepod/src/host_api_service.rs +++ b/teepod/src/host_api_service.rs @@ -45,6 +45,6 @@ impl HostApiRpc for HostApiHandler { async fn notify(self, request: Notification) -> Result<()> { self.app - .vm_event_report(self.endpoint.cid, &request.event, &request.message) + .vm_event_report(self.endpoint.cid, &request.event, request.payload) } } diff --git a/teepod/src/main_service.rs b/teepod/src/main_service.rs index b8f41521..06f38657 100644 --- a/teepod/src/main_service.rs +++ b/teepod/src/main_service.rs @@ -74,7 +74,7 @@ impl RpcHandler { "kms_url": cfg.cvm.kms_url, "tproxy_url": cfg.cvm.tproxy_url, "docker_registry": cfg.cvm.docker_registry, - "host_vsock_port": cfg.host_api.port, + "host_api_url": format!("vsock://2:{}/api", cfg.host_api.port), }); let vm_config_str = serde_json::to_string(&vm_config).context("Failed to serialize vm config")?;