diff --git a/Cargo.lock b/Cargo.lock index c4ea2cb79..4b6e07aec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -899,7 +899,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ "memchr", - "regex-automata", + "regex-automata 0.4.9", "serde", ] @@ -1233,6 +1233,8 @@ dependencies = [ "tokio", "tonic", "tonic-build", + "tracing", + "tracing-subscriber", "ttrpc", "ttrpc-codegen", "zeroize", @@ -3538,7 +3540,7 @@ dependencies = [ "lalrpop-util", "petgraph 0.6.5", "regex", - "regex-syntax", + "regex-syntax 0.8.5", "string_cache", "term", "tiny-keccak", @@ -3552,7 +3554,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" dependencies = [ - "regex-automata", + "regex-automata 0.4.9", ] [[package]] @@ -3685,6 +3687,15 @@ dependencies = [ "libc", ] +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "matchit" version = "0.7.3" @@ -3898,6 +3909,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -4352,6 +4373,12 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "p12" version = "0.6.3" @@ -4438,7 +4465,7 @@ checksum = "914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a" dependencies = [ "parse-display-derive", "regex", - "regex-syntax", + "regex-syntax 0.8.5", ] [[package]] @@ -4450,7 +4477,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "regex-syntax", + "regex-syntax 0.8.5", "structmeta", "syn 2.0.99", ] @@ -5252,8 +5279,17 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -5264,9 +5300,15 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.5", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.5" @@ -5906,7 +5948,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "regex", - "regex-syntax", + "regex-syntax 0.8.5", "ripemd", "rsa", "sha1collisiondetection", @@ -6219,6 +6261,15 @@ dependencies = [ "keccak", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -6681,6 +6732,15 @@ dependencies = [ "syn 2.0.99", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "time" version = "0.3.39" @@ -7040,6 +7100,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -7294,6 +7384,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/confidential-data-hub/Makefile b/confidential-data-hub/Makefile index 07bde5561..d4ee532c7 100644 --- a/confidential-data-hub/Makefile +++ b/confidential-data-hub/Makefile @@ -15,6 +15,7 @@ TARGET_DIR := ../target BIN_NAME := confidential-data-hub ONE_SHOT ?= false +GUEST_SERVICES ?= false SOURCE_ARCH := $(shell uname -m) RPC ?= ttrpc @@ -37,6 +38,10 @@ ifeq ($(ONE_SHOT), true) binary = --bin cdh-oneshot features += bin binary_name = cdh-oneshot +else ifeq ($(GUEST_SERVICES), true) + binary = --bin guest-services + features += bin,ttrpc + binary_name = guest-services else ifeq ($(RPC), ttrpc) binary = --bin ttrpc-cdh features += bin,ttrpc diff --git a/confidential-data-hub/hub/Cargo.toml b/confidential-data-hub/hub/Cargo.toml index ffc88d5fc..40fd9248e 100644 --- a/confidential-data-hub/hub/Cargo.toml +++ b/confidential-data-hub/hub/Cargo.toml @@ -27,6 +27,10 @@ required-features = ["bin", "grpc"] name = "cdh-oneshot" required-features = ["bin"] +[[bin]] +name = "guest-services" +required-features = ["bin", "ttrpc"] + [[bin]] name = "secret" path = "src/bin/secret_cli.rs" @@ -44,6 +48,8 @@ crypto.path = "../../attestation-agent/deps/crypto" env_logger = { workspace = true, optional = true } image-rs = { path = "../../image-rs", default-features = false, features = ["kata-cc-rustls-tls"] } kms = { path = "../kms", default-features = false } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } log.workspace = true prost = { workspace = true, optional = true } protobuf = { workspace = true, optional = true } diff --git a/confidential-data-hub/hub/src/bin/guest-services.rs b/confidential-data-hub/hub/src/bin/guest-services.rs new file mode 100644 index 000000000..cee93c101 --- /dev/null +++ b/confidential-data-hub/hub/src/bin/guest-services.rs @@ -0,0 +1,266 @@ +mod message; +mod protos; +mod ttrpc_server; + +use std::sync::Arc; +use std::{collections::HashMap, os::unix::fs::PermissionsExt, path::PathBuf}; + +use anyhow::{anyhow, Context, Result}; +use clap::{Parser, Subcommand}; +use tokio::signal::unix::{signal, SignalKind}; +use tracing::{error, info, instrument, Level}; +use tracing_subscriber::fmt::format::FmtSpan; +use ttrpc::asynchronous::Service; +use ttrpc::r#async::Server as TtrpcServer; + +use confidential_data_hub::CdhConfig; +use protos::api_ttrpc; +use ttrpc_server::Server; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Clone, Debug, Subcommand)] +enum Commands { + /// Run image pull service + ImagePull { + /// Socket path for the service + #[arg(short, long, default_value = "/run/guest-services/imagepull.socket")] + socket: PathBuf, + /// Configuration directory + #[arg(short, long, default_value = "/run/measured-cfg")] + config_dir: PathBuf, + }, + /// Run sealed secrets service + SealedSecrets { + #[arg( + short, + long, + default_value = "/run/guest-services/sealedsecrets.socket" + )] + socket: PathBuf, + #[arg(short, long, default_value = "/run/measured-cfg")] + config_dir: PathBuf, + }, + /// Run secure mount service + SecureMount { + #[arg(short, long, default_value = "/run/guest-services/securemount.socket")] + socket: PathBuf, + #[arg(short, long, default_value = "/run/measured-cfg")] + config_dir: PathBuf, + }, + /// Run get resource service + GetResource { + #[arg(short, long, default_value = "/run/guest-services/getresource.socket")] + socket: PathBuf, + #[arg(short, long, default_value = "/run/measured-cfg")] + config_dir: PathBuf, + }, +} + +#[instrument(skip(service), fields(socket = %socket_path.display()))] +async fn run_service(service: HashMap, socket_path: PathBuf) -> Result<()> { + // No services to register + if service.is_empty() { + return Err(anyhow::anyhow!("No services provided to run")); + } + + // Ensure the parent directory for the socket exists. + if let Some(parent) = socket_path.parent() { + tokio::fs::create_dir_all(parent).await.context(format!( + "Failed to create socket parent directory: {parent:?}" + ))?; + } + + // Remove any stale socket before binding. + if socket_path.exists() { + tokio::fs::remove_file(&socket_path).await.context(format!( + "Failed to remove existing socket file: {socket_path:?}" + ))?; + } + + let sock_addr = format!("unix://{}", &socket_path.display()); + let mut server = TtrpcServer::new() + .bind(&sock_addr) + .context(format!("Failed to bind to socket: {sock_addr}"))? + .register_service(service); + info!("Successfully bound to socket and registered services: {sock_addr}"); + + // Ensure socket exists and has correct permissions + { + // Verify socket was created + if !socket_path.exists() { + return Err(anyhow::anyhow!( + "Socket file was not created during bind operation" + )); + } + + // Set socket permissions + let perms = std::fs::Permissions::from_mode(0o666); + tokio::fs::set_permissions(&socket_path, perms) + .await + .context(format!( + "Failed to set permissions on socket: {socket_path:?}" + ))?; + + info!("Set socket permissions to 0666"); + + // Verify the socket has correct permissions + let metadata = tokio::fs::metadata(&socket_path).await?; + let file_perms = metadata.permissions(); + info!( + "Socket file confirmed with permissions: {:o}", + file_perms.mode() & 0o777 + ); + } + + info!("TTRPC server loop starting..."); + if let Err(e) = server.start().await { + // Clean up socket when server shutdown + if socket_path.exists() { + if let Err(e) = tokio::fs::remove_file(&socket_path).await { + error!(error = ?e, "Failed to remove socket file during cleanup"); + } else { + info!(info = ?socket_path, "Cleaned up socket file"); + } + } + return Err(anyhow!("TTRPC server start failed with error: {e:?}")); + } + + // Graceful shutdown signal handling + // SIGINT (`Ctrl+C`) + let mut sigint = + signal(SignalKind::interrupt()).context("Failed to register SIGINT handler")?; + // SIGTERM (`systemctl stop xxx`) + let mut sigterm = + signal(SignalKind::terminate()).context("Failed to register SIGTERM handler")?; + tokio::select! { + _ = sigint.recv() => { + info!("Received SIGINT (Ctrl+C), initiating graceful shutdown..."); + server.shutdown().await?; + }, + _ = sigterm.recv() => { + info!("Received SIGTERM, initiating graceful shutdown..."); + server.shutdown().await?; + } + } + tokio::fs::remove_file(&socket_path).await?; + + info!("Service shutdown complete."); + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<()> { + // Setup panic hook to ensure panics are logged + std::panic::set_hook(Box::new(|panic_info| { + error!(panic_info = %panic_info, "A task panicked"); + })); + + // Initialize logging + tracing_subscriber::fmt() + .with_max_level(Level::INFO) + .with_span_events(FmtSpan::CLOSE) + .with_target(true) + .init(); + + let cli = Cli::parse(); + info!("Starting guest-services with command: {:?}", cli.command); + + match run(cli).await { + Ok(_) => { + info!("Service exited successfully"); + Ok(()) + } + Err(e) => { + error!(error = ?e, "Service failed"); + Err(e) + } + } +} + +#[instrument(skip_all, fields(command = ?cli.command))] +async fn run(cli: Cli) -> Result<()> { + // Extract common parameters + let (socket, config_dir, service_name) = match &cli.command { + Commands::ImagePull { socket, config_dir } => { + (socket.clone(), config_dir.clone(), "ImagePull") + } + Commands::SealedSecrets { socket, config_dir } => { + (socket.clone(), config_dir.clone(), "SealedSecrets") + } + Commands::SecureMount { socket, config_dir } => { + (socket.clone(), config_dir.clone(), "SecureMount") + } + Commands::GetResource { socket, config_dir } => { + (socket.clone(), config_dir.clone(), "GetResource") + } + }; + + info!( + "Initializing {service_name} service with socket: {socket:?} and config_dir: {config_dir:?}" + ); + + // Initialize CDH configuration + let cdh_config = CdhConfig::new(Some(config_dir.display().to_string())) + .context("Failed to initialize CDH configuration")?; + + info!("CDH configuration initialized successfully"); + + // Create server instance + let server_instance = Arc::new( + Server::new(&cdh_config) + .await + .context("Failed to create main Server instance")?, + ); + + info!("Server instance created successfully"); + + // Create the appropriate service based on command + let service = match cli.command { + Commands::ImagePull { .. } => { + info!("Creating ImagePull ttrpc service"); + let svc = api_ttrpc::create_image_pull_service(server_instance); + info!("ImagePull service created with {} service(s)", svc.len()); + svc + } + Commands::SealedSecrets { .. } => { + info!("Creating SealedSecrets ttrpc service"); + let svc = api_ttrpc::create_sealed_secret_service(server_instance); + info!( + "SealedSecrets service created with {} service(s)", + svc.len() + ); + svc + } + Commands::SecureMount { .. } => { + info!("Creating SecureMount ttrpc service"); + let svc = api_ttrpc::create_secure_mount_service(server_instance); + info!("SecureMount service created with {} service(s)", svc.len()); + svc + } + Commands::GetResource { .. } => { + info!("Creating GetResource ttrpc service"); + let svc = api_ttrpc::create_get_resource_service(server_instance); + info!("GetResource service created with {} service(s)", svc.len()); + svc + } + }; + + // Validate that one more services were created + if service.is_empty() { + return Err(anyhow::anyhow!( + "Failed to create any services - service map is empty" + )); + } + + info!("Service creation completed, starting server..."); + + // Start the service and run until shutdown + run_service(service, socket).await +}