From f6d25c11419df388c99e8a0468e8e04597a6fdee Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Fri, 20 Mar 2026 18:59:19 +0000 Subject: [PATCH 01/37] something that compiles --- bin/propolis-server/src/lib/initializer.rs | 15 ++-- bin/propolis-server/src/main.rs | 12 ++++ lib/propolis/src/attestation/mod.rs | 79 ++++++++++++++++++++++ lib/propolis/src/lib.rs | 1 + 4 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 lib/propolis/src/attestation/mod.rs diff --git a/bin/propolis-server/src/lib/initializer.rs b/bin/propolis-server/src/lib/initializer.rs index d05df6e20..7586a1739 100644 --- a/bin/propolis-server/src/lib/initializer.rs +++ b/bin/propolis-server/src/lib/initializer.rs @@ -25,6 +25,7 @@ use crucible_client_types::VolumeConstructionRequest; pub use nexus_client::Client as NexusClient; use oximeter::types::ProducerRegistry; use oximeter_instruments::kstat::KstatSampler; +use propolis::attestation; use propolis::block; use propolis::chardev::{self, BlockingSource, Source}; use propolis::common::{Lifecycle, GB, MB, PAGE_SIZE}; @@ -485,18 +486,18 @@ impl MachineInitializer<'_> { use propolis::vsock::proxy::VsockPortMapping; // OANA Port 605 - VM Attestation RFD 605 - const ATTESTATION_PORT: u16 = 605; - const ATTESTATION_ADDR: SocketAddr = SocketAddr::new( - IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - ATTESTATION_PORT, - ); + //const ATTESTATION_PORT: u16 = 605; + //const ATTESTATION_ADDR: SocketAddr = SocketAddr::new( + //IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + //ATTESTATION_PORT, + //); if let Some(vsock) = &self.spec.vsock { let bdf: pci::Bdf = vsock.spec.pci_path.into(); let mappings = vec![VsockPortMapping::new( - ATTESTATION_PORT.into(), - ATTESTATION_ADDR, + attestation::ATTESTATION_PORT.into(), + attestation::ATTESTATION_ADDR, )]; let guest_cid = GuestCid::try_from(vsock.spec.guest_cid) diff --git a/bin/propolis-server/src/main.rs b/bin/propolis-server/src/main.rs index b03c8149a..0bba534f2 100644 --- a/bin/propolis-server/src/main.rs +++ b/bin/propolis-server/src/main.rs @@ -8,6 +8,7 @@ use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; +use propolis::attestation; use propolis::usdt::register_probes; use propolis_server::{ config, @@ -168,6 +169,14 @@ fn run_server( None => None, }; + // Start listener for attestation server + let tcp_attest = api_runtime.block_on(async { + attestation::AttestationSock::new( + log.new(slog::o!("component" => "attestation-server")), + ) + .await + })?; + info!(log, "Starting server..."); let server = dropshot::ServerBuilder::new( @@ -193,6 +202,9 @@ fn run_server( api_runtime.block_on(async { vnc.halt().await }); } + // Clean up attestation socket + api_runtime.block_on(async { tcp_attest.halt().await }); + result.map_err(|e| anyhow!("Server exited with an error: {e}")) } diff --git a/lib/propolis/src/attestation/mod.rs b/lib/propolis/src/attestation/mod.rs new file mode 100644 index 000000000..a23e664f8 --- /dev/null +++ b/lib/propolis/src/attestation/mod.rs @@ -0,0 +1,79 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use std::io; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::sync::Arc; + +use slog::{info, error, Logger}; +use tokio::net::{TcpListener, TcpStream}; +use tokio::sync::oneshot; +use tokio::task::JoinHandle; + +// See: https://github.com/oxidecomputer/oana +pub const ATTESTATION_PORT: u16 = 605; +pub const ATTESTATION_ADDR: SocketAddr = + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), ATTESTATION_PORT); + +pub struct AttestationSock { + join_hdl: JoinHandle<()>, + hup_send: oneshot::Sender<()>, +} + +// TODO: +// + +impl AttestationSock { + pub async fn new(log: Logger) -> io::Result { + info!(log, "attestation server created"); + let listener = TcpListener::bind(ATTESTATION_ADDR).await?; + let (hup_send, hup_recv) = oneshot::channel::<()>(); + let join_hdl = tokio::spawn(async move { + Self::run(log, listener, hup_recv).await; + }); + Ok(Self { join_hdl, hup_send }) + } + + pub async fn halt(self) { + let Self { join_hdl, hup_send } = self; + + // Signal the socket listener to hang up, then wait for it to bail + let _ = hup_send.send(()); + let _ = join_hdl.await; + } + + async fn handle_conn(log: Logger, conn: TcpStream) { + info!(log, "handling connection"); + // TODO + } + + pub async fn run( + log: Logger, + listener: TcpListener, + mut hup_recv: oneshot::Receiver<()>, + ) { + info!(log, "attestation server running"); + + loop { + tokio::select! { + biased; + + _ = &mut hup_recv => { + return; + }, + sock_res = listener.accept() => { + info!(log, "new client connected"); + match sock_res { + Ok((sock, addr)) => { + tokio::spawn(Self::handle_conn(log.clone(), sock)); + } + Err(e) => { + error!(log, "Attestation TCP listener error: {:?}", e); + } + } + }, + }; + } + } +} diff --git a/lib/propolis/src/lib.rs b/lib/propolis/src/lib.rs index 5f07fa1bd..d11a3aa4c 100644 --- a/lib/propolis/src/lib.rs +++ b/lib/propolis/src/lib.rs @@ -16,6 +16,7 @@ extern crate bitflags; pub mod accessors; pub mod api_version; +pub mod attestation; pub mod block; pub mod chardev; pub mod common; From 5dbf46c069444eb28b71af6b22357347aef518a8 Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Fri, 20 Mar 2026 21:02:37 +0000 Subject: [PATCH 02/37] starting to sketch out sled-agent attest code --- Cargo.lock | 1595 +++++++++++++++++++++++---- Cargo.toml | 6 + lib/propolis/Cargo.toml | 1 + lib/propolis/src/attestation/mod.rs | 29 +- 4 files changed, 1410 insertions(+), 221 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 35aa4f1ef..cb8435ec9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,6 +159,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "api_identity" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +dependencies = [ + "omicron-workspace-hack", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "arc-swap" version = "1.7.1" @@ -256,6 +267,24 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "attest-data" +version = "0.5.0" +dependencies = [ + "const-oid", + "der", + "getrandom 0.3.4", + "hex", + "hubpack", + "rats-corim", + "salty", + "serde", + "serde_with", + "sha3", + "static_assertions", + "thiserror 2.0.18", +] + [[package]] name = "atty" version = "0.2.14" @@ -311,6 +340,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "backon" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +dependencies = [ + "fastrand", +] + [[package]] name = "backtrace" version = "0.3.71" @@ -326,6 +364,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.21.7" @@ -363,6 +407,16 @@ dependencies = [ "strum 0.26.3", ] +[[package]] +name = "bhyve_api" +version = "0.0.0" +source = "git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2#368a2225b79328514ce0ea9181d8f874019edaa2" +dependencies = [ + "bhyve_api_sys 0.0.0 (git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2)", + "libc", + "strum 0.26.3", +] + [[package]] name = "bhyve_api" version = "0.0.0" @@ -381,6 +435,15 @@ dependencies = [ "strum 0.26.3", ] +[[package]] +name = "bhyve_api_sys" +version = "0.0.0" +source = "git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2#368a2225b79328514ce0ea9181d8f874019edaa2" +dependencies = [ + "libc", + "strum 0.26.3", +] + [[package]] name = "bhyve_api_sys" version = "0.0.0" @@ -532,6 +595,35 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bootstore" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +dependencies = [ + "bytes", + "camino", + "chacha20poly1305", + "ciborium", + "derive_more", + "hex", + "hkdf", + "omicron-ledger", + "omicron-workspace-hack", + "rand 0.8.5", + "secrecy", + "serde", + "serde_with", + "sha3", + "sled-hardware-types 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "slog", + "slog-error-chain", + "thiserror 2.0.18", + "tokio", + "uuid", + "vsss-rs", + "zeroize", +] + [[package]] name = "bstr" version = "1.9.1" @@ -698,6 +790,30 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.44" @@ -723,6 +839,33 @@ dependencies = [ "serde", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.4.4" @@ -731,6 +874,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", + "zeroize", ] [[package]] @@ -799,9 +943,9 @@ dependencies = [ "derive_more", "expectorate", "itertools 0.14.0", - "omicron-common", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "omicron-workspace-hack", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "slog", @@ -816,7 +960,7 @@ dependencies = [ "camino", "clap", "derive_more", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "thiserror 1.0.64", @@ -845,9 +989,9 @@ source = "git+https://github.com/oxidecomputer/omicron?branch=main#b8efb9a08b366 dependencies = [ "chrono", "csv", - "omicron-common", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "omicron-workspace-hack", - "schemars", + "schemars 0.8.22", "serde", "thiserror 2.0.18", ] @@ -890,6 +1034,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "const_format" version = "0.2.35" @@ -983,8 +1133,8 @@ version = "0.0.0" dependencies = [ "bhyve_api 0.0.0", "bitflags 2.9.4", - "propolis_api_types", - "propolis_types", + "propolis_api_types 0.0.0", + "propolis_types 0.0.0", "proptest", "thiserror 1.0.64", ] @@ -1065,8 +1215,8 @@ dependencies = [ "itertools 0.14.0", "libc", "nexus-client", - "omicron-common", - "omicron-uuid-kinds", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "oximeter", "oximeter-producer", "rand 0.9.2", @@ -1075,7 +1225,7 @@ dependencies = [ "reqwest 0.12.23", "reqwest 0.13.2", "ringbuffer", - "schemars", + "schemars 0.8.22", "semver 1.0.27", "serde", "serde_json", @@ -1101,7 +1251,7 @@ source = "git+https://github.com/oxidecomputer/crucible?rev=a945a32ba9e1f2098ce3 dependencies = [ "base64 0.22.1", "crucible-workspace-hack", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "uuid", @@ -1118,7 +1268,7 @@ dependencies = [ "dropshot", "nix 0.30.1", "rustls-pemfile 1.0.4", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "slog", @@ -1148,7 +1298,7 @@ dependencies = [ "crucible-common", "crucible-workspace-hack", "num_enum 0.7.4", - "schemars", + "schemars 0.8.22", "serde", "strum 0.27.2", "strum_macros 0.27.2", @@ -1169,12 +1319,42 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "crucible-smf" +version = "0.0.0" +source = "git+https://github.com/oxidecomputer/crucible?rev=7103cd3a3d7b0112d2949dd135db06fef0c156bb#7103cd3a3d7b0112d2949dd135db06fef0c156bb" +dependencies = [ + "crucible-workspace-hack", + "libc", + "num-derive 0.4.2", + "num-traits", + "thiserror 2.0.18", +] + [[package]] name = "crucible-workspace-hack" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbd293370c6cb9c334123675263de33fc9e53bbdfc8bdd5e329237cf0205fdc7" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -1246,6 +1426,34 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "rand_core 0.6.4", + "rustc_version 0.4.1", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "daft" version = "0.1.5" @@ -1290,6 +1498,16 @@ dependencies = [ "darling_macro 0.21.3", ] +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + [[package]] name = "darling_core" version = "0.20.11" @@ -1318,6 +1536,19 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim 0.11.1", + "syn 2.0.117", +] + [[package]] name = "darling_macro" version = "0.20.11" @@ -1340,6 +1571,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.117", +] + [[package]] name = "data-encoding" version = "2.6.0" @@ -1390,6 +1632,30 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "der_derive", + "flagset", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "deranged" version = "0.5.3" @@ -1397,6 +1663,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" dependencies = [ "powerfmt", + "serde", ] [[package]] @@ -1466,6 +1733,29 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "dice-verifier" +version = "0.3.0-pre0" +dependencies = [ + "attest-data", + "const-oid", + "ed25519-dalek", + "env_logger", + "hex", + "hubpack", + "log", + "p384", + "rats-corim", + "sha3", + "sled-agent-client", + "sled-agent-types-versions", + "slog", + "tempfile", + "thiserror 2.0.18", + "tokio", + "x509-cert", +] + [[package]] name = "digest" version = "0.9.0" @@ -1482,6 +1772,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", + "const-oid", "crypto-common", "subtle", ] @@ -1591,7 +1882,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9ba64b39d5fd68e09169e63c8e82b7a50c9b6082f2c44f52db2a11e3b9d7dd4" dependencies = [ "anyhow", - "indexmap", + "indexmap 2.13.0", "openapiv3", "regex", "serde", @@ -1619,14 +1910,14 @@ dependencies = [ "http-body-util", "hyper", "hyper-util", - "indexmap", + "indexmap 2.13.0", "multer", "openapiv3", "paste", "percent-encoding", "rustls 0.22.4", "rustls-pemfile 2.2.0", - "schemars", + "schemars 0.8.22", "scopeguard", "semver 1.0.27", "serde", @@ -1744,12 +2035,71 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2 0.10.9", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "embedded-io" version = "0.4.0" @@ -1789,6 +2139,25 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "env_filter" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" +dependencies = [ + "log", +] + +[[package]] +name = "env_logger" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" +dependencies = [ + "env_filter", + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1820,9 +2189,9 @@ version = "0.1.0" source = "git+https://github.com/oxidecomputer/omicron?branch=main#b8efb9a08b366541c71eb6334b54768f3cbee724" dependencies = [ "dropshot", - "omicron-uuid-kinds", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "omicron-workspace-hack", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "thiserror 2.0.18", @@ -1922,6 +2291,22 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "filedescriptor" version = "0.8.2" @@ -1963,6 +2348,12 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" +[[package]] +name = "flagset" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe" + [[package]] name = "flate2" version = "1.0.30" @@ -2175,12 +2566,12 @@ dependencies = [ "ereport-types", "gateway-messages", "gateway-types", - "omicron-uuid-kinds", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "omicron-workspace-hack", "progenitor 0.10.0", "rand 0.9.2", "reqwest 0.12.23", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "slog", @@ -2215,10 +2606,10 @@ dependencies = [ "dropshot", "gateway-messages", "hex", - "omicron-common", - "omicron-uuid-kinds", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "omicron-workspace-hack", - "schemars", + "schemars 0.8.22", "serde", "thiserror 2.0.18", "tufaceous-artifact", @@ -2247,6 +2638,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -2278,24 +2670,40 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasip2", "wasm-bindgen", ] +[[package]] +name = "gfss" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +dependencies = [ + "digest 0.10.7", + "omicron-workspace-hack", + "rand 0.9.2", + "schemars 0.8.22", + "secrecy", + "serde", + "subtle", + "thiserror 2.0.18", + "zeroize", +] + [[package]] name = "gimli" version = "0.28.1" @@ -2380,6 +2788,17 @@ dependencies = [ "scroll 0.13.0", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "h2" version = "0.4.6" @@ -2392,13 +2811,24 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy 0.8.27", +] + [[package]] name = "hash32" version = "0.3.1" @@ -2408,6 +2838,12 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.15.2" @@ -2470,9 +2906,6 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -dependencies = [ - "serde", -] [[package]] name = "hickory-proto" @@ -2571,6 +3004,24 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9040319a6910b901d5d49cbada4a99db52836a1b63228a05f7e2b7f8feef89b1" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "home" version = "0.5.11" @@ -2770,7 +3221,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core", ] [[package]] @@ -2908,15 +3359,15 @@ dependencies = [ "daft", "derive-where", "omicron-workspace-hack", - "schemars", + "schemars 0.8.22", "serde", ] [[package]] name = "iddqd" -version = "0.3.14" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac5efd33e0c5eb0ac45cbd210541a214dac576896ca97ba08e16e3b1079cdd8" +checksum = "6b215e67ed1d1a4b1702acd787c487d16e4c977c5dcbcc4587bdb5ea26b6ce06" dependencies = [ "allocator-api2", "daft", @@ -2925,7 +3376,7 @@ dependencies = [ "hashbrown 0.16.1", "ref-cast", "rustc-hash 2.1.1", - "schemars", + "schemars 0.8.22", "serde_core", "serde_json", ] @@ -2957,6 +3408,16 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "illumos-devinfo" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/illumos-devinfo?branch=main#4323b17bfdd0c94d2875ac64b47f0e60fac1d640" +dependencies = [ + "anyhow", + "libc", + "num_enum 0.5.11", +] + [[package]] name = "illumos-sys-hdrs" version = "0.1.0" @@ -2965,6 +3426,14 @@ dependencies = [ "bitflags 2.9.4", ] +[[package]] +name = "illumos-sys-hdrs" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/opte?rev=e547d07b08c3f3d6c821c9eb7a958adcffce6e56#e547d07b08c3f3d6c821c9eb7a958adcffce6e56" +dependencies = [ + "bitflags 2.9.4", +] + [[package]] name = "illumos-utils" version = "0.1.0" @@ -2977,28 +3446,76 @@ dependencies = [ "camino", "camino-tempfile", "cfg-if", - "crucible-smf", + "crucible-smf 0.0.0 (git+https://github.com/oxidecomputer/crucible?rev=65ca41e821ef53ec9c28909357f23e3348169e4f)", + "debug-ignore", + "dropshot", + "futures", + "http", + "ipnetwork", + "itertools 0.14.0", + "libc", + "macaddr", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", + "omicron-workspace-hack", + "opte-ioctl 0.1.0 (git+https://github.com/oxidecomputer/opte?rev=795a1e0aeefb7a2c6fe4139779fdf66930d09b80)", + "oxide-vpc 0.1.0 (git+https://github.com/oxidecomputer/opte?rev=795a1e0aeefb7a2c6fe4139779fdf66930d09b80)", + "oxlog 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", + "oxnet", + "schemars 0.8.22", + "serde", + "slog", + "slog-error-chain", + "smf", + "thiserror 2.0.18", + "tokio", + "uuid", + "whoami", + "zone", +] + +[[package]] +name = "illumos-utils" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +dependencies = [ + "anyhow", + "async-trait", + "bhyve_api 0.0.0 (git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2)", + "byteorder", + "camino", + "camino-tempfile", + "cfg-if", + "chrono", + "crucible-smf 0.0.0 (git+https://github.com/oxidecomputer/crucible?rev=7103cd3a3d7b0112d2949dd135db06fef0c156bb)", "debug-ignore", "dropshot", "futures", "http", + "iddqd", "ipnetwork", "itertools 0.14.0", + "key-manager-types", "libc", "macaddr", - "omicron-common", - "omicron-uuid-kinds", + "nix 0.31.1", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron)", "omicron-workspace-hack", - "opte-ioctl", - "oxide-vpc", - "oxlog", + "opte-ioctl 0.1.0 (git+https://github.com/oxidecomputer/opte?rev=e547d07b08c3f3d6c821c9eb7a958adcffce6e56)", + "oxide-vpc 0.1.0 (git+https://github.com/oxidecomputer/opte?rev=e547d07b08c3f3d6c821c9eb7a958adcffce6e56)", + "oxlog 0.1.0 (git+https://github.com/oxidecomputer/omicron)", "oxnet", - "schemars", + "rustix 1.1.2", + "schemars 0.8.22", "serde", "slog", + "slog-async", "slog-error-chain", + "slog-term", "smf", "thiserror 2.0.18", + "tofino", "tokio", "uuid", "whoami", @@ -3023,6 +3540,17 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cfe9645a18782869361d9c8732246be7b410ad4e919d3609ebabdac00ba12c3" +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.13.0" @@ -3101,8 +3629,8 @@ dependencies = [ "hickory-proto 0.25.2", "hickory-resolver 0.25.2", "internal-dns-types", - "omicron-common", - "omicron-uuid-kinds", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "omicron-workspace-hack", "qorb", "reqwest 0.12.23", @@ -3117,10 +3645,10 @@ source = "git+https://github.com/oxidecomputer/omicron?branch=main#b8efb9a08b366 dependencies = [ "anyhow", "chrono", - "omicron-common", - "omicron-uuid-kinds", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "omicron-workspace-hack", - "schemars", + "schemars 0.8.22", "serde", ] @@ -3148,7 +3676,7 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf370abdafd54d13e54a620e8c3e1145f28e46cc9d704bc6d94414559df41763" dependencies = [ - "schemars", + "schemars 0.8.22", "serde", ] @@ -3308,6 +3836,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "key-manager-types" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +dependencies = [ + "omicron-workspace-hack", + "secrecy", +] + [[package]] name = "kstat-macro" version = "0.1.0" @@ -3317,6 +3863,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "kstat-macro" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/opte?rev=e547d07b08c3f3d6c821c9eb7a958adcffce6e56#e547d07b08c3f3d6c821c9eb7a958adcffce6e56" +dependencies = [ + "quote", + "syn 2.0.117", +] + [[package]] name = "kstat-rs" version = "0.2.4" @@ -3619,9 +4174,18 @@ checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" [[package]] name = "memoffset" -version = "0.6.5" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] @@ -3636,7 +4200,7 @@ dependencies = [ "percent-encoding", "progenitor 0.11.1", "reqwest 0.12.23", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "slog", @@ -3672,7 +4236,7 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi 0.3.9", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.52.0", ] @@ -3768,11 +4332,11 @@ dependencies = [ [[package]] name = "newtype-uuid" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d1216f62e63be5fb25a9ecd1e2b37b1556a9b8c02f4831770f5d01df85c226" +checksum = "5c012d14ef788ab066a347d19e3dda699916c92293b05b85ba2c76b8c82d2830" dependencies = [ - "schemars", + "schemars 0.8.22", "serde", "serde_json", "uuid", @@ -3811,15 +4375,15 @@ dependencies = [ "iddqd", "nexus-sled-agent-shared", "nexus-types", - "omicron-common", - "omicron-passwords", - "omicron-uuid-kinds", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", + "omicron-passwords 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "omicron-workspace-hack", "oxnet", "progenitor 0.10.0", "regress", "reqwest 0.12.23", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "slog", @@ -3836,16 +4400,16 @@ dependencies = [ "daft", "id-map", "iddqd", - "illumos-utils", + "illumos-utils 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "indent_write", - "omicron-common", - "omicron-passwords", - "omicron-uuid-kinds", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", + "omicron-passwords 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "omicron-workspace-hack", - "schemars", + "schemars 0.8.22", "serde", "serde_json", - "sled-hardware-types", + "sled-hardware-types 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "strum 0.27.2", "thiserror 2.0.18", "tufaceous-artifact", @@ -3858,7 +4422,7 @@ version = "0.1.0" source = "git+https://github.com/oxidecomputer/omicron?branch=main#b8efb9a08b366541c71eb6334b54768f3cbee724" dependencies = [ "anyhow", - "api_identity", + "api_identity 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "async-trait", "base64 0.22.1", "chrono", @@ -3877,7 +4441,7 @@ dependencies = [ "humantime", "id-map", "iddqd", - "illumos-utils", + "illumos-utils 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "indent_write", "internal-dns-types", "ipnetwork", @@ -3885,9 +4449,9 @@ dependencies = [ "newtype-uuid", "newtype_derive", "nexus-sled-agent-shared", - "omicron-common", - "omicron-passwords", - "omicron-uuid-kinds", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", + "omicron-passwords 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "omicron-workspace-hack", "openssl", "oximeter-db", @@ -3895,7 +4459,7 @@ dependencies = [ "oxql-types", "parse-display", "regex", - "schemars", + "schemars 0.8.22", "semver 1.0.27", "serde", "serde_json", @@ -3927,7 +4491,7 @@ dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "memoffset", + "memoffset 0.6.5", ] [[package]] @@ -3964,6 +4528,7 @@ dependencies = [ "cfg-if", "cfg_aliases 0.2.1", "libc", + "memoffset 0.9.1", ] [[package]] @@ -4209,7 +4774,7 @@ version = "0.1.0" source = "git+https://github.com/oxidecomputer/omicron?branch=main#b8efb9a08b366541c71eb6334b54768f3cbee724" dependencies = [ "anyhow", - "api_identity", + "api_identity 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "async-trait", "backoff", "camino", @@ -4224,7 +4789,7 @@ dependencies = [ "ipnetwork", "macaddr", "mg-admin-client", - "omicron-uuid-kinds", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "omicron-workspace-hack", "oxnet", "parse-display", @@ -4233,7 +4798,55 @@ dependencies = [ "rand 0.9.2", "regress", "reqwest 0.12.23", - "schemars", + "schemars 0.8.22", + "semver 1.0.27", + "serde", + "serde_human_bytes", + "serde_json", + "serde_with", + "slog", + "slog-error-chain", + "strum 0.27.2", + "thiserror 2.0.18", + "tokio", + "tufaceous-artifact", + "uuid", +] + +[[package]] +name = "omicron-common" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +dependencies = [ + "anyhow", + "api_identity 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "async-trait", + "backoff", + "backon", + "camino", + "chrono", + "daft", + "dropshot", + "futures", + "hex", + "http", + "iddqd", + "ipnetwork", + "itertools 0.14.0", + "macaddr", + "omicron-ledger", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "omicron-workspace-hack", + "oxnet", + "parse-display", + "progenitor-client 0.10.0", + "progenitor-client 0.13.0", + "progenitor-extras", + "protocol", + "rand 0.9.2", + "regress", + "reqwest 0.13.2", + "schemars 0.8.22", "semver 1.0.27", "serde", "serde_human_bytes", @@ -4248,6 +4861,22 @@ dependencies = [ "uuid", ] +[[package]] +name = "omicron-ledger" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +dependencies = [ + "async-trait", + "camino", + "omicron-workspace-hack", + "serde", + "serde_json", + "slog", + "slog-error-chain", + "thiserror 2.0.18", + "tokio", +] + [[package]] name = "omicron-passwords" version = "0.1.0" @@ -4256,7 +4885,22 @@ dependencies = [ "argon2", "omicron-workspace-hack", "rand 0.9.2", - "schemars", + "schemars 0.8.22", + "secrecy", + "serde", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "omicron-passwords" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +dependencies = [ + "argon2", + "omicron-workspace-hack", + "rand 0.9.2", + "schemars 0.8.22", "secrecy", "serde", "serde_with", @@ -4272,7 +4916,19 @@ dependencies = [ "newtype-uuid", "newtype-uuid-macros", "paste", - "schemars", + "schemars 0.8.22", +] + +[[package]] +name = "omicron-uuid-kinds" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +dependencies = [ + "daft", + "newtype-uuid", + "newtype-uuid-macros", + "paste", + "schemars 0.8.22", ] [[package]] @@ -4335,7 +4991,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8d427828b22ae1fff2833a03d8486c2c881367f1c336349f307f321e7f4d05" dependencies = [ - "indexmap", + "indexmap 2.13.0", "serde", "serde_json", ] @@ -4397,10 +5053,29 @@ source = "git+https://github.com/oxidecomputer/opte?rev=795a1e0aeefb7a2c6fe41397 dependencies = [ "bitflags 2.9.4", "dyn-clone", - "illumos-sys-hdrs", + "illumos-sys-hdrs 0.1.0 (git+https://github.com/oxidecomputer/opte?rev=795a1e0aeefb7a2c6fe4139779fdf66930d09b80)", + "ingot", + "kstat-macro 0.1.0 (git+https://github.com/oxidecomputer/opte?rev=795a1e0aeefb7a2c6fe4139779fdf66930d09b80)", + "opte-api 0.1.0 (git+https://github.com/oxidecomputer/opte?rev=795a1e0aeefb7a2c6fe4139779fdf66930d09b80)", + "postcard", + "ref-cast", + "serde", + "tabwriter", + "version_check", + "zerocopy 0.8.27", +] + +[[package]] +name = "opte" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/opte?rev=e547d07b08c3f3d6c821c9eb7a958adcffce6e56#e547d07b08c3f3d6c821c9eb7a958adcffce6e56" +dependencies = [ + "bitflags 2.9.4", + "dyn-clone", + "illumos-sys-hdrs 0.1.0 (git+https://github.com/oxidecomputer/opte?rev=e547d07b08c3f3d6c821c9eb7a958adcffce6e56)", "ingot", - "kstat-macro", - "opte-api", + "kstat-macro 0.1.0 (git+https://github.com/oxidecomputer/opte?rev=e547d07b08c3f3d6c821c9eb7a958adcffce6e56)", + "opte-api 0.1.0 (git+https://github.com/oxidecomputer/opte?rev=e547d07b08c3f3d6c821c9eb7a958adcffce6e56)", "postcard", "ref-cast", "serde", @@ -4414,7 +5089,20 @@ name = "opte-api" version = "0.1.0" source = "git+https://github.com/oxidecomputer/opte?rev=795a1e0aeefb7a2c6fe4139779fdf66930d09b80#795a1e0aeefb7a2c6fe4139779fdf66930d09b80" dependencies = [ - "illumos-sys-hdrs", + "illumos-sys-hdrs 0.1.0 (git+https://github.com/oxidecomputer/opte?rev=795a1e0aeefb7a2c6fe4139779fdf66930d09b80)", + "ingot", + "ipnetwork", + "postcard", + "serde", + "smoltcp", +] + +[[package]] +name = "opte-api" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/opte?rev=e547d07b08c3f3d6c821c9eb7a958adcffce6e56#e547d07b08c3f3d6c821c9eb7a958adcffce6e56" +dependencies = [ + "illumos-sys-hdrs 0.1.0 (git+https://github.com/oxidecomputer/opte?rev=e547d07b08c3f3d6c821c9eb7a958adcffce6e56)", "ingot", "ipnetwork", "postcard", @@ -4429,8 +5117,22 @@ source = "git+https://github.com/oxidecomputer/opte?rev=795a1e0aeefb7a2c6fe41397 dependencies = [ "libc", "libnet", - "opte", - "oxide-vpc", + "opte 0.1.0 (git+https://github.com/oxidecomputer/opte?rev=795a1e0aeefb7a2c6fe4139779fdf66930d09b80)", + "oxide-vpc 0.1.0 (git+https://github.com/oxidecomputer/opte?rev=795a1e0aeefb7a2c6fe4139779fdf66930d09b80)", + "postcard", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "opte-ioctl" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/opte?rev=e547d07b08c3f3d6c821c9eb7a958adcffce6e56#e547d07b08c3f3d6c821c9eb7a958adcffce6e56" +dependencies = [ + "libc", + "libnet", + "opte 0.1.0 (git+https://github.com/oxidecomputer/opte?rev=e547d07b08c3f3d6c821c9eb7a958adcffce6e56)", + "oxide-vpc 0.1.0 (git+https://github.com/oxidecomputer/opte?rev=e547d07b08c3f3d6c821c9eb7a958adcffce6e56)", "postcard", "serde", "thiserror 2.0.18", @@ -4487,8 +5189,22 @@ version = "0.1.0" source = "git+https://github.com/oxidecomputer/opte?rev=795a1e0aeefb7a2c6fe4139779fdf66930d09b80#795a1e0aeefb7a2c6fe4139779fdf66930d09b80" dependencies = [ "cfg-if", - "illumos-sys-hdrs", - "opte", + "illumos-sys-hdrs 0.1.0 (git+https://github.com/oxidecomputer/opte?rev=795a1e0aeefb7a2c6fe4139779fdf66930d09b80)", + "opte 0.1.0 (git+https://github.com/oxidecomputer/opte?rev=795a1e0aeefb7a2c6fe4139779fdf66930d09b80)", + "serde", + "tabwriter", + "uuid", + "zerocopy 0.8.27", +] + +[[package]] +name = "oxide-vpc" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/opte?rev=e547d07b08c3f3d6c821c9eb7a958adcffce6e56#e547d07b08c3f3d6c821c9eb7a958adcffce6e56" +dependencies = [ + "cfg-if", + "illumos-sys-hdrs 0.1.0 (git+https://github.com/oxidecomputer/opte?rev=e547d07b08c3f3d6c821c9eb7a958adcffce6e56)", + "opte 0.1.0 (git+https://github.com/oxidecomputer/opte?rev=e547d07b08c3f3d6c821c9eb7a958adcffce6e56)", "serde", "tabwriter", "uuid", @@ -4536,11 +5252,11 @@ dependencies = [ "gethostname 0.5.0", "highway", "iana-time-zone", - "indexmap", + "indexmap 2.13.0", "libc", "nom 7.1.3", "num", - "omicron-common", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "omicron-workspace-hack", "oxide-tokio-rt", "oximeter", @@ -4550,7 +5266,7 @@ dependencies = [ "quote", "regex", "reqwest 0.12.23", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "slog", @@ -4606,10 +5322,10 @@ dependencies = [ "internal-dns-resolver", "internal-dns-types", "nexus-client", - "omicron-common", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "omicron-workspace-hack", "oximeter", - "schemars", + "schemars 0.8.22", "serde", "slog", "slog-dtrace", @@ -4632,7 +5348,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "schemars", + "schemars 0.8.22", "serde", "slog-error-chain", "syn 2.0.117", @@ -4661,11 +5377,11 @@ dependencies = [ "chrono", "float-ord", "num", - "omicron-common", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "omicron-workspace-hack", "parse-display", "regex", - "schemars", + "schemars 0.8.22", "serde", "strum 0.27.2", "thiserror 2.0.18", @@ -4689,14 +5405,31 @@ dependencies = [ "uuid", ] +[[package]] +name = "oxlog" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +dependencies = [ + "anyhow", + "camino", + "chrono", + "clap", + "glob", + "jiff", + "omicron-workspace-hack", + "rayon", + "sigpipe", + "uuid", +] + [[package]] name = "oxnet" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8200429754152e6379fbb1dd06eea90156c3b67591f6e31d08e787d010ef0168" +checksum = "5dc6fb07ecd6d2a17ff1431bc5b3ce11036c0b6dd93a3c4904db5b910817b162" dependencies = [ "ipnetwork", - "schemars", + "schemars 0.8.22", "serde", "serde_json", ] @@ -4712,12 +5445,24 @@ dependencies = [ "num", "omicron-workspace-hack", "oximeter-types", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "uuid", ] +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.9", +] + [[package]] name = "p4rs" version = "0.1.0" @@ -4840,6 +5585,15 @@ dependencies = [ "serde", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -4896,7 +5650,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset 0.4.2", - "indexmap", + "indexmap 2.13.0", "serde", "serde_derive", ] @@ -4909,7 +5663,7 @@ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset 0.5.7", "hashbrown 0.15.2", - "indexmap", + "indexmap 2.13.0", "serde", ] @@ -4933,9 +5687,9 @@ dependencies = [ "hex", "libc", "newtype_derive", - "omicron-common", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "oximeter", - "propolis-client", + "propolis-client 0.1.0", "rand 0.9.2", "reqwest 0.13.2", "ring", @@ -5012,11 +5766,11 @@ dependencies = [ "http", "itertools 0.13.0", "linkme", - "omicron-common", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "oximeter", "oximeter-producer", "phd-testcase", - "propolis-client", + "propolis-client 0.1.0", "reqwest 0.13.2", "slog", "slog-term", @@ -5146,6 +5900,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.30" @@ -5171,6 +5935,17 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "polyval" version = "0.6.2" @@ -5270,6 +6045,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -5391,6 +6175,18 @@ dependencies = [ "serde_urlencoded", ] +[[package]] +name = "progenitor-extras" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf56b338efdf90f218a4d6b3316a9c56faac4b7133bb8ecdc56d5db930d7ff" +dependencies = [ + "backon", + "http", + "progenitor-client 0.13.0", + "tokio", +] + [[package]] name = "progenitor-impl" version = "0.10.0" @@ -5399,12 +6195,12 @@ checksum = "b17e5363daa50bf1cccfade6b0fb970d2278758fd5cfa9ab69f25028e4b1afa3" dependencies = [ "heck 0.5.0", "http", - "indexmap", + "indexmap 2.13.0", "openapiv3", "proc-macro2", "quote", "regex", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "syn 2.0.117", @@ -5421,12 +6217,12 @@ checksum = "8276d558f1dfd4cc7fc4cceee0a51dab482b5a4be2e69e7eab8c57fbfb1472f4" dependencies = [ "heck 0.5.0", "http", - "indexmap", + "indexmap 2.13.0", "openapiv3", "proc-macro2", "quote", "regex", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "syn 2.0.117", @@ -5443,12 +6239,12 @@ checksum = "de362a0477182f45accdbad4d43cd89a95a1db0a518a7c1ddf3e525e6896f0f0" dependencies = [ "heck 0.5.0", "http", - "indexmap", + "indexmap 2.13.0", "openapiv3", "proc-macro2", "quote", "regex", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "syn 2.0.117", @@ -5467,7 +6263,7 @@ dependencies = [ "proc-macro2", "progenitor-impl 0.10.0", "quote", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "serde_tokenstream", @@ -5485,7 +6281,7 @@ dependencies = [ "proc-macro2", "progenitor-impl 0.11.1", "quote", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "serde_tokenstream", @@ -5503,7 +6299,7 @@ dependencies = [ "proc-macro2", "progenitor-impl 0.13.0", "quote", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "serde_tokenstream", @@ -5526,6 +6322,7 @@ dependencies = [ "crossbeam-channel", "crucible", "crucible-client-types", + "dice-verifier", "dladm", "dlpi 0.2.0 (git+https://github.com/oxidecomputer/dlpi-sys?branch=main)", "erased-serde 0.4.5", @@ -5541,7 +6338,7 @@ dependencies = [ "p9ds", "paste", "pin-project-lite", - "propolis_types", + "propolis_types 0.0.0", "rand 0.9.2", "rfb", "rgb_frame", @@ -5568,14 +6365,27 @@ name = "propolis-api-types-versions" version = "0.0.0" dependencies = [ "crucible-client-types", - "propolis_types", - "schemars", + "propolis_types 0.0.0", + "schemars 0.8.22", "serde", "serde_json", "thiserror 1.0.64", "uuid", ] +[[package]] +name = "propolis-api-types-versions" +version = "0.0.0" +source = "git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2#368a2225b79328514ce0ea9181d8f874019edaa2" +dependencies = [ + "crucible-client-types", + "propolis_types 0.0.0 (git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2)", + "schemars 0.8.22", + "serde", + "thiserror 1.0.64", + "uuid", +] + [[package]] name = "propolis-cli" version = "0.1.0" @@ -5587,7 +6397,7 @@ dependencies = [ "futures", "libc", "newtype-uuid", - "propolis-client", + "propolis-client 0.1.0", "propolis-config-toml", "reqwest 0.13.2", "serde", @@ -5610,10 +6420,34 @@ dependencies = [ "futures", "progenitor 0.13.0", "progenitor-client 0.13.0", - "propolis-api-types-versions", + "propolis-api-types-versions 0.0.0", + "rand 0.9.2", + "reqwest 0.13.2", + "schemars 0.8.22", + "serde", + "serde_json", + "slog", + "thiserror 1.0.64", + "tokio", + "tokio-tungstenite", + "uuid", +] + +[[package]] +name = "propolis-client" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2#368a2225b79328514ce0ea9181d8f874019edaa2" +dependencies = [ + "async-trait", + "base64 0.21.7", + "crucible-client-types", + "futures", + "progenitor 0.13.0", + "progenitor-client 0.13.0", + "propolis-api-types-versions 0.0.0 (git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2)", "rand 0.9.2", "reqwest 0.13.2", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "slog", @@ -5628,7 +6462,7 @@ name = "propolis-config-toml" version = "0.0.0" dependencies = [ "cpuid_profile_config", - "propolis-client", + "propolis-client 0.1.0", "serde", "serde_derive", "thiserror 1.0.64", @@ -5661,12 +6495,12 @@ dependencies = [ "futures", "hyper", "progenitor 0.13.0", - "propolis-api-types-versions", - "propolis_api_types", - "propolis_types", + "propolis-api-types-versions 0.0.0", + "propolis_api_types 0.0.0", + "propolis_types 0.0.0", "rand 0.9.2", "reqwest 0.13.2", - "schemars", + "schemars 0.8.22", "semver 1.0.27", "serde", "serde_json", @@ -5722,24 +6556,24 @@ dependencies = [ "lazy_static", "mockall", "nexus-client", - "omicron-common", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "oxide-tokio-rt", "oximeter", "oximeter-instruments", "oximeter-producer", "pbind", "propolis", - "propolis-api-types-versions", + "propolis-api-types-versions 0.0.0", "propolis-server-api", - "propolis_api_types", - "propolis_types", + "propolis_api_types 0.0.0", + "propolis_types 0.0.0", "proptest", "reqwest 0.13.2", "rfb", "rgb_frame", "ring", "ron", - "schemars", + "schemars 0.8.22", "semver 1.0.27", "serde", "serde_derive", @@ -5766,7 +6600,7 @@ dependencies = [ "crucible-client-types", "dropshot", "dropshot-api-manager-types", - "propolis-api-types-versions", + "propolis-api-types-versions 0.0.0", ] [[package]] @@ -5788,7 +6622,7 @@ dependencies = [ "oxide-tokio-rt", "pbind", "propolis", - "propolis_types", + "propolis_types 0.0.0", "serde", "serde_json", "slog", @@ -5813,7 +6647,7 @@ dependencies = [ "cpuid_utils", "libc", "propolis", - "propolis_api_types", + "propolis_api_types 0.0.0", "serde", "serde_json", ] @@ -5823,19 +6657,37 @@ name = "propolis_api_types" version = "0.0.0" dependencies = [ "crucible-client-types", - "propolis-api-types-versions", + "propolis-api-types-versions 0.0.0", +] + +[[package]] +name = "propolis_api_types" +version = "0.0.0" +source = "git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2#368a2225b79328514ce0ea9181d8f874019edaa2" +dependencies = [ + "crucible-client-types", + "propolis-api-types-versions 0.0.0 (git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2)", ] [[package]] name = "propolis_types" version = "0.0.0" dependencies = [ - "schemars", + "schemars 0.8.22", "serde", "serde_json", "serde_test", ] +[[package]] +name = "propolis_types" +version = "0.0.0" +source = "git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2#368a2225b79328514ce0ea9181d8f874019edaa2" +dependencies = [ + "schemars 0.8.22", + "serde", +] + [[package]] name = "proptest" version = "1.5.0" @@ -5862,7 +6714,7 @@ version = "0.1.0" source = "git+https://github.com/oxidecomputer/lldp#b12d9c04ecafbb30b2c3c2d3fc03d32a14a9f6be" dependencies = [ "anyhow", - "schemars", + "schemars 0.8.22", "serde", "thiserror 1.0.64", ] @@ -5922,7 +6774,7 @@ checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "aws-lc-rs", "bytes", - "getrandom 0.3.2", + "getrandom 0.3.4", "lru-slab", "rand 0.9.2", "ring", @@ -6026,7 +6878,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.4", ] [[package]] @@ -6038,6 +6890,21 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rats-corim" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/rats-corim#f0d5d5168d3d31487a56df32c676b0c6240bcc6b" +dependencies = [ + "ciborium", + "ciborium-io", + "clap", + "hex", + "serde", + "serde_with", + "strum 0.26.3", + "thiserror 2.0.18", +] + [[package]] name = "rayon" version = "1.11.0" @@ -6286,6 +7153,16 @@ dependencies = [ "zerocopy 0.8.27", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rgb_frame" version = "0.0.0" @@ -6571,6 +7448,16 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "salty" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b947325a585e90733e0e9ec097228f40b637cc346f9bd68f84d5c6297d0fcfef" +dependencies = [ + "subtle", + "zeroize", +] + [[package]] name = "same-file" version = "1.0.6" @@ -6606,6 +7493,30 @@ dependencies = [ "uuid", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "schemars_derive" version = "0.8.22" @@ -6680,6 +7591,20 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "secrecy" version = "0.10.3" @@ -6821,10 +7746,11 @@ dependencies = [ [[package]] name = "serde_human_bytes" version = "0.1.0" -source = "git+http://github.com/oxidecomputer/serde_human_bytes?branch=main#0a09794501b6208120528c3b457d5f3a8cb17424" +source = "git+http://github.com/oxidecomputer/serde_human_bytes?branch=main#8f60acdfe7c6d9e2a01f59be920c1c2b19129322" dependencies = [ + "base64 0.22.1", "hex", - "serde", + "serde_core", ] [[package]] @@ -6924,13 +7850,18 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.15.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093cd8c01b25262b84927e0f7151692158fab02d961e04c979d3903eba7ecc5" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" dependencies = [ "base64 0.22.1", "chrono", "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "schemars 0.8.22", + "schemars 0.9.0", + "schemars 1.2.1", "serde_core", "serde_json", "serde_with_macros", @@ -6939,11 +7870,11 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.15.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7e6c180db0816026a61afa1cff5344fb7ebded7e4d3062772179f2501481c27" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" dependencies = [ - "darling 0.21.3", + "darling 0.23.0", "proc-macro2", "quote", "syn 2.0.117", @@ -6955,7 +7886,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap", + "indexmap 2.13.0", "itoa", "ryu", "serde", @@ -6997,6 +7928,16 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -7031,6 +7972,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + [[package]] name = "sigpipe" version = "0.1.3" @@ -7076,16 +8027,126 @@ dependencies = [ "autocfg", ] +[[package]] +name = "sled-agent-client" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +dependencies = [ + "anyhow", + "async-trait", + "chrono", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "omicron-workspace-hack", + "oxnet", + "progenitor 0.13.0", + "propolis-client 0.1.0 (git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2)", + "regress", + "reqwest 0.13.2", + "schemars 0.8.22", + "serde", + "serde_json", + "sled-agent-types", + "sled-agent-types-versions", + "sled-hardware-types 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "slog", + "trust-quorum-types", + "uuid", +] + +[[package]] +name = "sled-agent-types" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +dependencies = [ + "anyhow", + "async-trait", + "bootstore", + "camino", + "chrono", + "daft", + "iddqd", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "omicron-workspace-hack", + "oxnet", + "schemars 0.8.22", + "serde", + "serde_human_bytes", + "serde_json", + "sled-agent-types-versions", + "sled-hardware-types 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "slog", + "slog-error-chain", + "strum 0.27.2", + "swrite", + "thiserror 2.0.18", + "toml 0.8.23", + "tufaceous-artifact", + "uuid", +] + +[[package]] +name = "sled-agent-types-versions" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +dependencies = [ + "anyhow", + "async-trait", + "bootstore", + "camino", + "chrono", + "daft", + "iddqd", + "illumos-utils 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "indent_write", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "omicron-ledger", + "omicron-passwords 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "omicron-workspace-hack", + "oxnet", + "propolis-api-types-versions 0.0.0 (git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2)", + "propolis_api_types 0.0.0 (git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2)", + "schemars 0.8.22", + "serde", + "serde_human_bytes", + "serde_json", + "serde_with", + "sha3", + "sled-hardware-types 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "slog", + "slog-error-chain", + "strum 0.27.2", + "thiserror 2.0.18", + "trust-quorum-types-versions", + "tufaceous-artifact", + "uuid", +] + [[package]] name = "sled-hardware-types" version = "0.1.0" source = "git+https://github.com/oxidecomputer/omicron?branch=main#b8efb9a08b366541c71eb6334b54768f3cbee724" dependencies = [ - "illumos-utils", - "omicron-common", + "illumos-utils 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", + "omicron-workspace-hack", + "schemars 0.8.22", + "serde", +] + +[[package]] +name = "sled-hardware-types" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +dependencies = [ + "daft", "omicron-workspace-hack", - "schemars", + "schemars 0.8.22", "serde", + "slog", + "thiserror 2.0.18", ] [[package]] @@ -7315,6 +8376,16 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -7340,7 +8411,7 @@ dependencies = [ "lazy_static", "newtype_derive", "petgraph 0.6.5", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "slog", @@ -7429,9 +8500,9 @@ dependencies = [ [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "supports-color" @@ -7590,7 +8661,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.2", + "getrandom 0.3.4", "once_cell", "rustix 1.1.2", "windows-sys 0.61.2", @@ -7765,6 +8836,26 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "thiserror-impl-no-std" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e6318948b519ba6dc2b442a6d0b904ebfb8d411a3ad3e07843615a72249758" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "thiserror-no-std" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ad459d94dd517257cc96add8a43190ee620011bb6e6cdc82dafd97dfafafea" +dependencies = [ + "thiserror-impl-no-std", +] + [[package]] name = "thread-id" version = "4.2.1" @@ -7853,6 +8944,37 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tls_codec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b" +dependencies = [ + "tls_codec_derive", + "zeroize", +] + +[[package]] +name = "tls_codec_derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tofino" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/tofino#7e56ab6e9a64ebae27cd97cd6e10ebf2cfdc3a33" +dependencies = [ + "anyhow", + "cc", + "illumos-devinfo", +] + [[package]] name = "tokio" version = "1.49.0" @@ -8002,7 +9124,7 @@ version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "indexmap", + "indexmap 2.13.0", "serde_core", "serde_spanned 1.0.4", "toml_datetime 0.7.5+spec-1.1.0", @@ -8017,7 +9139,7 @@ version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "399b1124a3c9e16766831c6bba21e50192572cdd98706ea114f9502509686ffc" dependencies = [ - "indexmap", + "indexmap 2.13.0", "serde_core", "serde_spanned 1.0.4", "toml_datetime 1.0.0+spec-1.1.0", @@ -8059,7 +9181,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap", + "indexmap 2.13.0", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -8072,7 +9194,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap", + "indexmap 2.13.0", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -8290,6 +9412,37 @@ dependencies = [ "tracing-log 0.2.0", ] +[[package]] +name = "trust-quorum-types" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +dependencies = [ + "omicron-workspace-hack", + "trust-quorum-types-versions", +] + +[[package]] +name = "trust-quorum-types-versions" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +dependencies = [ + "daft", + "derive_more", + "gfss", + "iddqd", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "omicron-workspace-hack", + "rand 0.9.2", + "schemars 0.8.22", + "serde", + "serde_human_bytes", + "serde_with", + "sled-hardware-types 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "slog", + "slog-error-chain", + "thiserror 2.0.18", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -8304,7 +9457,7 @@ dependencies = [ "daft", "hex", "proptest", - "schemars", + "schemars 0.8.22", "semver 1.0.27", "serde", "serde_human_bytes", @@ -8390,7 +9543,7 @@ dependencies = [ "proc-macro2", "quote", "regress", - "schemars", + "schemars 0.8.22", "semver 1.0.27", "serde", "serde_json", @@ -8410,7 +9563,7 @@ dependencies = [ "proc-macro2", "quote", "regress", - "schemars", + "schemars 0.8.22", "semver 1.0.27", "serde", "serde_json", @@ -8427,7 +9580,7 @@ checksum = "9708a3ceb6660ba3f8d2b8f0567e7d4b8b198e2b94d093b8a6077a751425de9e" dependencies = [ "proc-macro2", "quote", - "schemars", + "schemars 0.8.22", "semver 1.0.27", "serde", "serde_json", @@ -8444,7 +9597,7 @@ checksum = "fd04bb1207cd4e250941cc1641f4c4815f7eaa2145f45c09dd49cb0a3691710a" dependencies = [ "proc-macro2", "quote", - "schemars", + "schemars 0.8.22", "semver 1.0.27", "serde", "serde_json", @@ -8551,13 +9704,13 @@ dependencies = [ "either", "futures", "indent_write", - "indexmap", + "indexmap 2.13.0", "libsw", "linear-map", "omicron-workspace-hack", "owo-colors", "petgraph 0.8.3", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "serde_with", @@ -8736,7 +9889,7 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.4", "js-sys", "serde_core", "wasm-bindgen", @@ -8809,6 +9962,24 @@ dependencies = [ "nvpair 0.0.0", ] +[[package]] +name = "vsss-rs" +version = "3.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196bbee60607a195bc850e94f0e040bd090e45794ad8df0e9c5a422b9975a00f" +dependencies = [ + "curve25519-dalek", + "elliptic-curve", + "hex", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde", + "subtle", + "thiserror-no-std", + "zeroize", +] + [[package]] name = "vtparse" version = "0.6.2" @@ -8862,12 +10033,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] @@ -9134,7 +10305,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" dependencies = [ "windows-collections", - "windows-core 0.61.2", + "windows-core", "windows-future", "windows-link 0.1.1", "windows-numerics", @@ -9146,7 +10317,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core 0.61.2", + "windows-core", ] [[package]] @@ -9158,21 +10329,8 @@ dependencies = [ "windows-implement", "windows-interface", "windows-link 0.1.1", - "windows-result 0.3.4", - "windows-strings 0.4.2", -] - -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", + "windows-result", + "windows-strings", ] [[package]] @@ -9181,7 +10339,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core 0.61.2", + "windows-core", "windows-link 0.1.1", "windows-threading", ] @@ -9226,7 +10384,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core 0.61.2", + "windows-core", "windows-link 0.1.1", ] @@ -9237,8 +10395,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820" dependencies = [ "windows-link 0.1.1", - "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-result", + "windows-strings", ] [[package]] @@ -9250,15 +10408,6 @@ dependencies = [ "windows-link 0.1.1", ] -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link 0.2.1", -] - [[package]] name = "windows-strings" version = "0.4.2" @@ -9268,15 +10417,6 @@ dependencies = [ "windows-link 0.1.1", ] -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link 0.2.1", -] - [[package]] name = "windows-sys" version = "0.45.0" @@ -9611,13 +10751,10 @@ dependencies = [ ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.4", -] +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "write16" @@ -9640,6 +10777,18 @@ dependencies = [ "tap", ] +[[package]] +name = "x509-cert" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" +dependencies = [ + "const-oid", + "der", + "spki", + "tls_codec", +] + [[package]] name = "xattr" version = "1.3.1" @@ -9756,9 +10905,23 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] name = "zerovec" diff --git a/Cargo.toml b/Cargo.toml index 4265b5595..33aa608ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,6 +95,12 @@ sled-agent-client = { git = "https://github.com/oxidecomputer/omicron", branch = crucible = { git = "https://github.com/oxidecomputer/crucible", rev = "a945a32ba9e1f2098ce3a8963765f1894f37110b" } crucible-client-types = { git = "https://github.com/oxidecomputer/crucible", rev = "a945a32ba9e1f2098ce3a8963765f1894f37110b" } +# TODO: pin these to git SHAs +# Attestation +#dice-verifier = { git = "https://github.com/oxidecomputer/dice-util", features = ["sled-agent"] } +dice-verifier = { path = "/home/jordan/src/dice-util/verifier", features = ["sled-agent"] } +vm-attest = { git = "https://github.com/oxidecomputer/vm-attest", default-features = false } + # External dependencies anyhow = "1.0" async-trait = "0.1.88" diff --git a/lib/propolis/Cargo.toml b/lib/propolis/Cargo.toml index 5f409dd09..2197a4852 100644 --- a/lib/propolis/Cargo.toml +++ b/lib/propolis/Cargo.toml @@ -11,6 +11,7 @@ bit_field.workspace = true bitflags.workspace = true bitstruct.workspace = true byteorder.workspace = true +dice-verifier.workspace = true lazy_static.workspace = true thiserror.workspace = true bhyve_api.workspace = true diff --git a/lib/propolis/src/attestation/mod.rs b/lib/propolis/src/attestation/mod.rs index a23e664f8..d64f82375 100644 --- a/lib/propolis/src/attestation/mod.rs +++ b/lib/propolis/src/attestation/mod.rs @@ -3,14 +3,18 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use std::io; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::io::{BufRead, BufReader, Read, Write}; +use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV6}; use std::sync::Arc; -use slog::{info, error, Logger}; +use slog::{error, info, Logger}; use tokio::net::{TcpListener, TcpStream}; use tokio::sync::oneshot; use tokio::task::JoinHandle; +use dice_verifier::sled_agent::AttestSledAgent; +use dice_verifier::Attest; + // See: https://github.com/oxidecomputer/oana pub const ATTESTATION_PORT: u16 = 605; pub const ATTESTATION_ADDR: SocketAddr = @@ -43,9 +47,24 @@ impl AttestationSock { let _ = join_hdl.await; } - async fn handle_conn(log: Logger, conn: TcpStream) { + async fn handle_conn(log: Logger, conn: TcpStream, sa_addr: SocketAddrV6) { info!(log, "handling connection"); - // TODO + + // Read in the request. + const MAX_LINE_LENGTH: u32 = 1024; + //let reader = BufReader::with_capacity(MAX_LINE_LENGTH, conn?); + + // XXX: these are hard-coded qualifying data values that match a test OS image + // https://github.com/oxidecomputer/vm-attest-demo/blob/main/test-data/vm-instance-cfg.json + let uuid = "db5bf54c-48c5-4455-a1e1-6c7dfc26e351"; + let img_digest = + "be4df4e085175f3de0c8ac4837e1c2c9a34e8983209dac6b549e94154f7cdd9c"; + + //let ox_attest: Box = Box::new(AttestSledAgent(sa_addr, log)); + + //let response = match ox_attest.attest(&qualifying_data) { + //Ok(a) => + //} } pub async fn run( @@ -66,7 +85,7 @@ impl AttestationSock { info!(log, "new client connected"); match sock_res { Ok((sock, addr)) => { - tokio::spawn(Self::handle_conn(log.clone(), sock)); + //tokio::spawn(Self::handle_conn(log.clone(), sock)); } Err(e) => { error!(log, "Attestation TCP listener error: {:?}", e); From e12a38f146d51a4ffb6883bc33f5345c71c827af Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Fri, 20 Mar 2026 22:09:28 +0000 Subject: [PATCH 03/37] mvp attestation?? --- Cargo.lock | 301 +++++++++++++++------------- Cargo.toml | 7 +- bin/propolis-server/src/main.rs | 16 +- lib/propolis/Cargo.toml | 1 + lib/propolis/src/attestation/mod.rs | 138 +++++++++++-- 5 files changed, 302 insertions(+), 161 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cb8435ec9..f4a418bfc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -162,7 +162,7 @@ dependencies = [ [[package]] name = "api_identity" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +source = "git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831#becbbb616f5f18b59cc42e511c148734c2ba3831" dependencies = [ "omicron-workspace-hack", "proc-macro2", @@ -270,6 +270,7 @@ dependencies = [ [[package]] name = "attest-data" version = "0.5.0" +source = "git+https://github.com/oxidecomputer/dice-util#ff9f27aa0d6ef6fb64c349890b6e3c242ea3d8fc" dependencies = [ "const-oid", "der", @@ -340,15 +341,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "backon" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" -dependencies = [ - "fastrand", -] - [[package]] name = "backtrace" version = "0.3.71" @@ -410,9 +402,9 @@ dependencies = [ [[package]] name = "bhyve_api" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2#368a2225b79328514ce0ea9181d8f874019edaa2" +source = "git+https://github.com/oxidecomputer/propolis?rev=827e6615bfebfd94d41504dcd1517a0f22e3166a#827e6615bfebfd94d41504dcd1517a0f22e3166a" dependencies = [ - "bhyve_api_sys 0.0.0 (git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2)", + "bhyve_api_sys 0.0.0 (git+https://github.com/oxidecomputer/propolis?rev=827e6615bfebfd94d41504dcd1517a0f22e3166a)", "libc", "strum 0.26.3", ] @@ -420,9 +412,9 @@ dependencies = [ [[package]] name = "bhyve_api" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=827e6615bfebfd94d41504dcd1517a0f22e3166a#827e6615bfebfd94d41504dcd1517a0f22e3166a" +source = "git+https://github.com/oxidecomputer/propolis?rev=8ccddb47a4c93b7e3480919495dae851afc83782#8ccddb47a4c93b7e3480919495dae851afc83782" dependencies = [ - "bhyve_api_sys 0.0.0 (git+https://github.com/oxidecomputer/propolis?rev=827e6615bfebfd94d41504dcd1517a0f22e3166a)", + "bhyve_api_sys 0.0.0 (git+https://github.com/oxidecomputer/propolis?rev=8ccddb47a4c93b7e3480919495dae851afc83782)", "libc", "strum 0.26.3", ] @@ -438,7 +430,7 @@ dependencies = [ [[package]] name = "bhyve_api_sys" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2#368a2225b79328514ce0ea9181d8f874019edaa2" +source = "git+https://github.com/oxidecomputer/propolis?rev=827e6615bfebfd94d41504dcd1517a0f22e3166a#827e6615bfebfd94d41504dcd1517a0f22e3166a" dependencies = [ "libc", "strum 0.26.3", @@ -447,7 +439,7 @@ dependencies = [ [[package]] name = "bhyve_api_sys" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=827e6615bfebfd94d41504dcd1517a0f22e3166a#827e6615bfebfd94d41504dcd1517a0f22e3166a" +source = "git+https://github.com/oxidecomputer/propolis?rev=8ccddb47a4c93b7e3480919495dae851afc83782#8ccddb47a4c93b7e3480919495dae851afc83782" dependencies = [ "libc", "strum 0.26.3", @@ -598,7 +590,7 @@ dependencies = [ [[package]] name = "bootstore" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +source = "git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831#becbbb616f5f18b59cc42e511c148734c2ba3831" dependencies = [ "bytes", "camino", @@ -607,14 +599,14 @@ dependencies = [ "derive_more", "hex", "hkdf", - "omicron-ledger", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831)", "omicron-workspace-hack", "rand 0.8.5", "secrecy", "serde", "serde_with", "sha3", - "sled-hardware-types 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "sled-hardware-types 0.1.0 (git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831)", "slog", "slog-error-chain", "thiserror 2.0.18", @@ -1202,7 +1194,7 @@ dependencies = [ "bytes", "cfg-if", "chrono", - "crucible-client-types", + "crucible-client-types 0.1.0 (git+https://github.com/oxidecomputer/crucible?rev=a945a32ba9e1f2098ce3a8963765f1894f37110b)", "crucible-common", "crucible-protocol", "crucible-workspace-hack", @@ -1244,6 +1236,19 @@ dependencies = [ "version_check", ] +[[package]] +name = "crucible-client-types" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/crucible?rev=7103cd3a3d7b0112d2949dd135db06fef0c156bb#7103cd3a3d7b0112d2949dd135db06fef0c156bb" +dependencies = [ + "base64 0.22.1", + "crucible-workspace-hack", + "schemars 0.8.22", + "serde", + "serde_json", + "uuid", +] + [[package]] name = "crucible-client-types" version = "0.1.0" @@ -1736,6 +1741,7 @@ dependencies = [ [[package]] name = "dice-verifier" version = "0.3.0-pre0" +source = "git+https://github.com/oxidecomputer/dice-util#ff9f27aa0d6ef6fb64c349890b6e3c242ea3d8fc" dependencies = [ "attest-data", "const-oid", @@ -1743,6 +1749,7 @@ dependencies = [ "env_logger", "hex", "hubpack", + "libipcc", "log", "p384", "rats-corim", @@ -2691,7 +2698,7 @@ dependencies = [ [[package]] name = "gfss" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +source = "git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831#becbbb616f5f18b59cc42e511c148734c2ba3831" dependencies = [ "digest 0.10.7", "omicron-workspace-hack", @@ -3477,11 +3484,11 @@ dependencies = [ [[package]] name = "illumos-utils" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +source = "git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831#becbbb616f5f18b59cc42e511c148734c2ba3831" dependencies = [ "anyhow", "async-trait", - "bhyve_api 0.0.0 (git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2)", + "bhyve_api 0.0.0 (git+https://github.com/oxidecomputer/propolis?rev=8ccddb47a4c93b7e3480919495dae851afc83782)", "byteorder", "camino", "camino-tempfile", @@ -3495,16 +3502,15 @@ dependencies = [ "iddqd", "ipnetwork", "itertools 0.14.0", - "key-manager-types", "libc", "macaddr", - "nix 0.31.1", - "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron)", - "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "nix 0.30.1", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831)", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831)", "omicron-workspace-hack", "opte-ioctl 0.1.0 (git+https://github.com/oxidecomputer/opte?rev=e547d07b08c3f3d6c821c9eb7a958adcffce6e56)", "oxide-vpc 0.1.0 (git+https://github.com/oxidecomputer/opte?rev=e547d07b08c3f3d6c821c9eb7a958adcffce6e56)", - "oxlog 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "oxlog 0.1.0 (git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831)", "oxnet", "rustix 1.1.2", "schemars 0.8.22", @@ -3845,15 +3851,6 @@ dependencies = [ "cpufeatures", ] -[[package]] -name = "key-manager-types" -version = "0.1.0" -source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" -dependencies = [ - "omicron-workspace-hack", - "secrecy", -] - [[package]] name = "kstat-macro" version = "0.1.0" @@ -3928,6 +3925,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libipcc" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/ipcc-rs?rev=7cdf2ab9c8d9e9267a8b366aa780c6c26f9a5ecf#7cdf2ab9c8d9e9267a8b366aa780c6c26f9a5ecf" +dependencies = [ + "cfg-if", + "libc", + "thiserror 1.0.64", +] + [[package]] name = "libloading" version = "0.7.4" @@ -4206,6 +4213,24 @@ dependencies = [ "slog", ] +[[package]] +name = "mg-admin-client" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/maghemite?rev=3abfb8eb7f6d4ca4658981b4a7a76759a0a3f8ec#3abfb8eb7f6d4ca4658981b4a7a76759a0a3f8ec" +dependencies = [ + "chrono", + "colored", + "progenitor 0.11.1", + "rdb-types", + "reqwest 0.12.23", + "schemars 0.8.22", + "serde", + "serde_json", + "slog", + "tabwriter", + "uuid", +] + [[package]] name = "mime" version = "0.3.17" @@ -4516,6 +4541,7 @@ dependencies = [ "cfg-if", "cfg_aliases 0.2.1", "libc", + "memoffset 0.9.1", ] [[package]] @@ -4528,7 +4554,6 @@ dependencies = [ "cfg-if", "cfg_aliases 0.2.1", "libc", - "memoffset 0.9.1", ] [[package]] @@ -4788,7 +4813,7 @@ dependencies = [ "iddqd", "ipnetwork", "macaddr", - "mg-admin-client", + "mg-admin-client 0.1.0 (git+https://github.com/oxidecomputer/maghemite?rev=08f2a34d487658e87545ffbba3add632a82baf0d)", "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron?branch=main)", "omicron-workspace-hack", "oxnet", @@ -4816,13 +4841,12 @@ dependencies = [ [[package]] name = "omicron-common" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +source = "git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831#becbbb616f5f18b59cc42e511c148734c2ba3831" dependencies = [ "anyhow", - "api_identity 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "api_identity 0.1.0 (git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831)", "async-trait", "backoff", - "backon", "camino", "chrono", "daft", @@ -4834,18 +4858,16 @@ dependencies = [ "ipnetwork", "itertools 0.14.0", "macaddr", - "omicron-ledger", - "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "mg-admin-client 0.1.0 (git+https://github.com/oxidecomputer/maghemite?rev=3abfb8eb7f6d4ca4658981b4a7a76759a0a3f8ec)", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831)", "omicron-workspace-hack", "oxnet", "parse-display", "progenitor-client 0.10.0", - "progenitor-client 0.13.0", - "progenitor-extras", "protocol", "rand 0.9.2", "regress", - "reqwest 0.13.2", + "reqwest 0.12.23", "schemars 0.8.22", "semver 1.0.27", "serde", @@ -4861,22 +4883,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "omicron-ledger" -version = "0.1.0" -source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" -dependencies = [ - "async-trait", - "camino", - "omicron-workspace-hack", - "serde", - "serde_json", - "slog", - "slog-error-chain", - "thiserror 2.0.18", - "tokio", -] - [[package]] name = "omicron-passwords" version = "0.1.0" @@ -4895,7 +4901,7 @@ dependencies = [ [[package]] name = "omicron-passwords" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +source = "git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831#becbbb616f5f18b59cc42e511c148734c2ba3831" dependencies = [ "argon2", "omicron-workspace-hack", @@ -4922,7 +4928,7 @@ dependencies = [ [[package]] name = "omicron-uuid-kinds" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +source = "git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831#becbbb616f5f18b59cc42e511c148734c2ba3831" dependencies = [ "daft", "newtype-uuid", @@ -5408,7 +5414,7 @@ dependencies = [ [[package]] name = "oxlog" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +source = "git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831#becbbb616f5f18b59cc42e511c148734c2ba3831" dependencies = [ "anyhow", "camino", @@ -6175,18 +6181,6 @@ dependencies = [ "serde_urlencoded", ] -[[package]] -name = "progenitor-extras" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf56b338efdf90f218a4d6b3316a9c56faac4b7133bb8ecdc56d5db930d7ff" -dependencies = [ - "backon", - "http", - "progenitor-client 0.13.0", - "tokio", -] - [[package]] name = "progenitor-impl" version = "0.10.0" @@ -6321,7 +6315,7 @@ dependencies = [ "cpuid_utils", "crossbeam-channel", "crucible", - "crucible-client-types", + "crucible-client-types 0.1.0 (git+https://github.com/oxidecomputer/crucible?rev=a945a32ba9e1f2098ce3a8963765f1894f37110b)", "dice-verifier", "dladm", "dlpi 0.2.0 (git+https://github.com/oxidecomputer/dlpi-sys?branch=main)", @@ -6357,6 +6351,7 @@ dependencies = [ "usdt 0.6.0", "uuid", "viona_api", + "vm-attest", "zerocopy 0.8.27", ] @@ -6364,7 +6359,7 @@ dependencies = [ name = "propolis-api-types-versions" version = "0.0.0" dependencies = [ - "crucible-client-types", + "crucible-client-types 0.1.0 (git+https://github.com/oxidecomputer/crucible?rev=a945a32ba9e1f2098ce3a8963765f1894f37110b)", "propolis_types 0.0.0", "schemars 0.8.22", "serde", @@ -6373,19 +6368,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "propolis-api-types-versions" -version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2#368a2225b79328514ce0ea9181d8f874019edaa2" -dependencies = [ - "crucible-client-types", - "propolis_types 0.0.0 (git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2)", - "schemars 0.8.22", - "serde", - "thiserror 1.0.64", - "uuid", -] - [[package]] name = "propolis-cli" version = "0.1.0" @@ -6393,7 +6375,7 @@ dependencies = [ "anyhow", "base64 0.21.7", "clap", - "crucible-client-types", + "crucible-client-types 0.1.0 (git+https://github.com/oxidecomputer/crucible?rev=a945a32ba9e1f2098ce3a8963765f1894f37110b)", "futures", "libc", "newtype-uuid", @@ -6416,11 +6398,11 @@ version = "0.1.0" dependencies = [ "async-trait", "base64 0.21.7", - "crucible-client-types", + "crucible-client-types 0.1.0 (git+https://github.com/oxidecomputer/crucible?rev=a945a32ba9e1f2098ce3a8963765f1894f37110b)", "futures", "progenitor 0.13.0", "progenitor-client 0.13.0", - "propolis-api-types-versions 0.0.0", + "propolis-api-types-versions", "rand 0.9.2", "reqwest 0.13.2", "schemars 0.8.22", @@ -6436,17 +6418,17 @@ dependencies = [ [[package]] name = "propolis-client" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2#368a2225b79328514ce0ea9181d8f874019edaa2" +source = "git+https://github.com/oxidecomputer/propolis?rev=8ccddb47a4c93b7e3480919495dae851afc83782#8ccddb47a4c93b7e3480919495dae851afc83782" dependencies = [ "async-trait", "base64 0.21.7", - "crucible-client-types", + "crucible-client-types 0.1.0 (git+https://github.com/oxidecomputer/crucible?rev=7103cd3a3d7b0112d2949dd135db06fef0c156bb)", "futures", - "progenitor 0.13.0", - "progenitor-client 0.13.0", - "propolis-api-types-versions 0.0.0 (git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2)", + "progenitor 0.10.0", + "progenitor-client 0.10.0", + "propolis_api_types 0.0.0 (git+https://github.com/oxidecomputer/propolis?rev=8ccddb47a4c93b7e3480919495dae851afc83782)", "rand 0.9.2", - "reqwest 0.13.2", + "reqwest 0.12.23", "schemars 0.8.22", "serde", "serde_json", @@ -6495,7 +6477,7 @@ dependencies = [ "futures", "hyper", "progenitor 0.13.0", - "propolis-api-types-versions 0.0.0", + "propolis-api-types-versions", "propolis_api_types 0.0.0", "propolis_types 0.0.0", "rand 0.9.2", @@ -6542,7 +6524,7 @@ dependencies = [ "clap", "const_format", "cpuid_utils", - "crucible-client-types", + "crucible-client-types 0.1.0 (git+https://github.com/oxidecomputer/crucible?rev=a945a32ba9e1f2098ce3a8963765f1894f37110b)", "dropshot", "erased-serde 0.4.5", "expectorate", @@ -6563,7 +6545,7 @@ dependencies = [ "oximeter-producer", "pbind", "propolis", - "propolis-api-types-versions 0.0.0", + "propolis-api-types-versions", "propolis-server-api", "propolis_api_types 0.0.0", "propolis_types 0.0.0", @@ -6597,10 +6579,10 @@ dependencies = [ name = "propolis-server-api" version = "0.1.0" dependencies = [ - "crucible-client-types", + "crucible-client-types 0.1.0 (git+https://github.com/oxidecomputer/crucible?rev=a945a32ba9e1f2098ce3a8963765f1894f37110b)", "dropshot", "dropshot-api-manager-types", - "propolis-api-types-versions 0.0.0", + "propolis-api-types-versions", ] [[package]] @@ -6613,7 +6595,7 @@ dependencies = [ "clap", "cpuid_profile_config", "cpuid_utils", - "crucible-client-types", + "crucible-client-types 0.1.0 (git+https://github.com/oxidecomputer/crucible?rev=a945a32ba9e1f2098ce3a8963765f1894f37110b)", "ctrlc", "erased-serde 0.4.5", "fatfs", @@ -6656,17 +6638,21 @@ dependencies = [ name = "propolis_api_types" version = "0.0.0" dependencies = [ - "crucible-client-types", - "propolis-api-types-versions 0.0.0", + "crucible-client-types 0.1.0 (git+https://github.com/oxidecomputer/crucible?rev=a945a32ba9e1f2098ce3a8963765f1894f37110b)", + "propolis-api-types-versions", ] [[package]] name = "propolis_api_types" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2#368a2225b79328514ce0ea9181d8f874019edaa2" +source = "git+https://github.com/oxidecomputer/propolis?rev=8ccddb47a4c93b7e3480919495dae851afc83782#8ccddb47a4c93b7e3480919495dae851afc83782" dependencies = [ - "crucible-client-types", - "propolis-api-types-versions 0.0.0 (git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2)", + "crucible-client-types 0.1.0 (git+https://github.com/oxidecomputer/crucible?rev=7103cd3a3d7b0112d2949dd135db06fef0c156bb)", + "propolis_types 0.0.0 (git+https://github.com/oxidecomputer/propolis?rev=8ccddb47a4c93b7e3480919495dae851afc83782)", + "schemars 0.8.22", + "serde", + "thiserror 1.0.64", + "uuid", ] [[package]] @@ -6682,7 +6668,7 @@ dependencies = [ [[package]] name = "propolis_types" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2#368a2225b79328514ce0ea9181d8f874019edaa2" +source = "git+https://github.com/oxidecomputer/propolis?rev=8ccddb47a4c93b7e3480919495dae851afc83782#8ccddb47a4c93b7e3480919495dae851afc83782" dependencies = [ "schemars 0.8.22", "serde", @@ -6925,6 +6911,16 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rdb-types" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/maghemite?rev=3abfb8eb7f6d4ca4658981b4a7a76759a0a3f8ec#3abfb8eb7f6d4ca4658981b4a7a76759a0a3f8ec" +dependencies = [ + "oxnet", + "schemars 0.8.22", + "serde", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -8030,25 +8026,25 @@ dependencies = [ [[package]] name = "sled-agent-client" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +source = "git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831#becbbb616f5f18b59cc42e511c148734c2ba3831" dependencies = [ "anyhow", "async-trait", "chrono", - "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron)", - "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831)", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831)", "omicron-workspace-hack", "oxnet", - "progenitor 0.13.0", - "propolis-client 0.1.0 (git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2)", + "progenitor 0.10.0", + "propolis-client 0.1.0 (git+https://github.com/oxidecomputer/propolis?rev=8ccddb47a4c93b7e3480919495dae851afc83782)", "regress", - "reqwest 0.13.2", + "reqwest 0.12.23", "schemars 0.8.22", "serde", "serde_json", "sled-agent-types", "sled-agent-types-versions", - "sled-hardware-types 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "sled-hardware-types 0.1.0 (git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831)", "slog", "trust-quorum-types", "uuid", @@ -8057,7 +8053,7 @@ dependencies = [ [[package]] name = "sled-agent-types" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +source = "git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831#becbbb616f5f18b59cc42e511c148734c2ba3831" dependencies = [ "anyhow", "async-trait", @@ -8066,8 +8062,8 @@ dependencies = [ "chrono", "daft", "iddqd", - "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron)", - "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831)", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831)", "omicron-workspace-hack", "oxnet", "schemars 0.8.22", @@ -8075,7 +8071,7 @@ dependencies = [ "serde_human_bytes", "serde_json", "sled-agent-types-versions", - "sled-hardware-types 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "sled-hardware-types 0.1.0 (git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831)", "slog", "slog-error-chain", "strum 0.27.2", @@ -8089,34 +8085,30 @@ dependencies = [ [[package]] name = "sled-agent-types-versions" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +source = "git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831#becbbb616f5f18b59cc42e511c148734c2ba3831" dependencies = [ - "anyhow", "async-trait", "bootstore", "camino", "chrono", "daft", "iddqd", - "illumos-utils 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "illumos-utils 0.1.0 (git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831)", "indent_write", - "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron)", - "omicron-ledger", - "omicron-passwords 0.1.0 (git+https://github.com/oxidecomputer/omicron)", - "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831)", + "omicron-passwords 0.1.0 (git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831)", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831)", "omicron-workspace-hack", "oxnet", - "propolis-api-types-versions 0.0.0 (git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2)", - "propolis_api_types 0.0.0 (git+https://github.com/oxidecomputer/propolis?rev=368a2225b79328514ce0ea9181d8f874019edaa2)", + "propolis_api_types 0.0.0 (git+https://github.com/oxidecomputer/propolis?rev=8ccddb47a4c93b7e3480919495dae851afc83782)", "schemars 0.8.22", "serde", "serde_human_bytes", "serde_json", "serde_with", "sha3", - "sled-hardware-types 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "sled-hardware-types 0.1.0 (git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831)", "slog", - "slog-error-chain", "strum 0.27.2", "thiserror 2.0.18", "trust-quorum-types-versions", @@ -8139,9 +8131,11 @@ dependencies = [ [[package]] name = "sled-hardware-types" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +source = "git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831#becbbb616f5f18b59cc42e511c148734c2ba3831" dependencies = [ "daft", + "illumos-utils 0.1.0 (git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831)", + "omicron-common 0.1.0 (git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831)", "omicron-workspace-hack", "schemars 0.8.22", "serde", @@ -9415,7 +9409,7 @@ dependencies = [ [[package]] name = "trust-quorum-types" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +source = "git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831#becbbb616f5f18b59cc42e511c148734c2ba3831" dependencies = [ "omicron-workspace-hack", "trust-quorum-types-versions", @@ -9424,20 +9418,20 @@ dependencies = [ [[package]] name = "trust-quorum-types-versions" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/omicron#e7d260aaabe6e5303b4b41a93cf29f34c60424db" +source = "git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831#becbbb616f5f18b59cc42e511c148734c2ba3831" dependencies = [ "daft", "derive_more", "gfss", "iddqd", - "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "omicron-uuid-kinds 0.1.0 (git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831)", "omicron-workspace-hack", "rand 0.9.2", "schemars 0.8.22", "serde", "serde_human_bytes", "serde_with", - "sled-hardware-types 0.1.0 (git+https://github.com/oxidecomputer/omicron)", + "sled-hardware-types 0.1.0 (git+https://github.com/oxidecomputer/omicron?rev=becbbb616f5f18b59cc42e511c148734c2ba3831)", "slog", "slog-error-chain", "thiserror 2.0.18", @@ -9962,6 +9956,31 @@ dependencies = [ "nvpair 0.0.0", ] +[[package]] +name = "vm-attest" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/vm-attest#1fba555e4a5a3acbf58b072f6263b5ceff7fd205" +dependencies = [ + "anyhow", + "attest-data", + "const-oid", + "dice-verifier", + "ed25519-dalek", + "getrandom 0.3.4", + "hex", + "hubpack", + "libc", + "log", + "rats-corim", + "serde", + "serde_json", + "serde_with", + "sha2 0.10.9", + "thiserror 2.0.18", + "uuid", + "x509-cert", +] + [[package]] name = "vsss-rs" version = "3.3.4" diff --git a/Cargo.toml b/Cargo.toml index 33aa608ab..a45c10683 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,8 +97,8 @@ crucible-client-types = { git = "https://github.com/oxidecomputer/crucible", rev # TODO: pin these to git SHAs # Attestation -#dice-verifier = { git = "https://github.com/oxidecomputer/dice-util", features = ["sled-agent"] } -dice-verifier = { path = "/home/jordan/src/dice-util/verifier", features = ["sled-agent"] } +#dice-verifier = { git = "https://github.com/oxidecomputer/dice-util", branch = "jhendricks/update-sled-agent-types-versions", features = ["sled-agent"] } +dice-verifier = { git = "https://github.com/oxidecomputer/dice-util", features = ["sled-agent"] } vm-attest = { git = "https://github.com/oxidecomputer/vm-attest", default-features = false } # External dependencies @@ -207,3 +207,6 @@ zerocopy = "0.8.25" # [patch."https://github.com/oxidecomputer/crucible"] # crucible = { path = "../crucible/upstairs" } # crucible-client-types = { path = "../crucible/crucible-client-types" } + +#[patch."https://github.com/oxidecomputer/dice-util"] +#dice-verifier = { path = "/home/jordan/src/dice-util/verifier", features = ["sled-agent"] } diff --git a/bin/propolis-server/src/main.rs b/bin/propolis-server/src/main.rs index 0bba534f2..e78cb93c8 100644 --- a/bin/propolis-server/src/main.rs +++ b/bin/propolis-server/src/main.rs @@ -3,11 +3,12 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use std::fmt; -use std::net::{IpAddr, Ipv6Addr, SocketAddr}; +use std::net::{IpAddr, Ipv6Addr, SocketAddr, SocketAddrV6}; use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; +use omicron_common::address::Ipv6Subnet; use propolis::attestation; use propolis::usdt::register_probes; use propolis_server::{ @@ -115,6 +116,7 @@ fn run_server( config_dropshot: dropshot::ConfigDropshot, config_metrics: Option, vnc_addr: Option, + sa_addr: SocketAddrV6, log: slog::Logger, ) -> anyhow::Result<()> { use propolis::api_version; @@ -173,6 +175,7 @@ fn run_server( let tcp_attest = api_runtime.block_on(async { attestation::AttestationSock::new( log.new(slog::o!("component" => "attestation-server")), + sa_addr, ) .await })?; @@ -314,6 +317,16 @@ fn main() -> anyhow::Result<()> { vnc_addr, log_level, } => { + let sa_addr = match propolis_addr.ip() { + IpAddr::V4(_) => todo!("no good!"), + IpAddr::V6(ipv6_addr) => { + let sled_subnet = Ipv6Subnet::< + { omicron_common::address::SLED_PREFIX }, + >::new(ipv6_addr); + omicron_common::address::get_sled_address(sled_subnet) + } + }; + // Dropshot configuration. let config_dropshot = ConfigDropshot { bind_address: propolis_addr, @@ -336,6 +349,7 @@ fn main() -> anyhow::Result<()> { config_dropshot, metric_config, vnc_addr, + sa_addr, log, ) } diff --git a/lib/propolis/Cargo.toml b/lib/propolis/Cargo.toml index 2197a4852..492c00ab1 100644 --- a/lib/propolis/Cargo.toml +++ b/lib/propolis/Cargo.toml @@ -42,6 +42,7 @@ nexus-client = { workspace = true, optional = true } async-trait.workspace = true iddqd.workspace = true nix.workspace = true +vm-attest.workspace = true # falcon libloading = { workspace = true, optional = true } diff --git a/lib/propolis/src/attestation/mod.rs b/lib/propolis/src/attestation/mod.rs index d64f82375..1175b25b7 100644 --- a/lib/propolis/src/attestation/mod.rs +++ b/lib/propolis/src/attestation/mod.rs @@ -2,18 +2,23 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +use anyhow::Context; use std::io; -use std::io::{BufRead, BufReader, Read, Write}; use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV6}; -use std::sync::Arc; use slog::{error, info, Logger}; +use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::net::{TcpListener, TcpStream}; use tokio::sync::oneshot; use tokio::task::JoinHandle; +use uuid::Uuid; use dice_verifier::sled_agent::AttestSledAgent; use dice_verifier::Attest; +use vm_attest::VmInstanceAttester; +use vm_attest::{ + Measurement, Request, Response, VmInstanceConf, VmInstanceRot, +}; // See: https://github.com/oxidecomputer/oana pub const ATTESTATION_PORT: u16 = 605; @@ -29,12 +34,12 @@ pub struct AttestationSock { // impl AttestationSock { - pub async fn new(log: Logger) -> io::Result { + pub async fn new(log: Logger, sa_addr: SocketAddrV6) -> io::Result { info!(log, "attestation server created"); let listener = TcpListener::bind(ATTESTATION_ADDR).await?; let (hup_send, hup_recv) = oneshot::channel::<()>(); let join_hdl = tokio::spawn(async move { - Self::run(log, listener, hup_recv).await; + Self::run(log, listener, hup_recv, sa_addr).await; }); Ok(Self { join_hdl, hup_send }) } @@ -47,30 +52,124 @@ impl AttestationSock { let _ = join_hdl.await; } - async fn handle_conn(log: Logger, conn: TcpStream, sa_addr: SocketAddrV6) { + async fn handle_conn( + log: Logger, + conn: TcpStream, + sa_addr: SocketAddrV6, + ) -> anyhow::Result<()> { info!(log, "handling connection"); - // Read in the request. - const MAX_LINE_LENGTH: u32 = 1024; - //let reader = BufReader::with_capacity(MAX_LINE_LENGTH, conn?); + let mut msg = String::new(); + + const MAX_LINE_LENGTH: usize = 1024; + let (reader, mut writer) = tokio::io::split(conn); + let mut reader = BufReader::with_capacity(MAX_LINE_LENGTH, reader); // XXX: these are hard-coded qualifying data values that match a test OS image // https://github.com/oxidecomputer/vm-attest-demo/blob/main/test-data/vm-instance-cfg.json - let uuid = "db5bf54c-48c5-4455-a1e1-6c7dfc26e351"; - let img_digest = - "be4df4e085175f3de0c8ac4837e1c2c9a34e8983209dac6b549e94154f7cdd9c"; + let uuid: Uuid = "db5bf54c-48c5-4455-a1e1-6c7dfc26e351" + .parse() + .context("couldn't parse uuid")?; + let img_digest: Measurement = + "be4df4e085175f3de0c8ac4837e1c2c9a34e8983209dac6b549e94154f7cdd9c" + .parse() + .context("couldn't parse boot digest")?; + + let vm_conf = + VmInstanceConf { uuid, boot_digest: Some(img_digest) }; + let ox_attest: Box = + Box::new(AttestSledAgent::new(sa_addr, &log)); + let rot = VmInstanceRot::new(ox_attest, vm_conf); + + loop { + let bytes_read = reader.read_line(&mut msg).await?; + if bytes_read == 0 { + break; + } + + // Check if the limit was hit and a newline wasn't found + if bytes_read == MAX_LINE_LENGTH + && !msg.ends_with('\n') + { + slog::warn!( + log, + "Line length exceeded the limit of {} bytes.", + MAX_LINE_LENGTH + ); + let response = + Response::Error("Request too long".to_string()); + let mut response = + serde_json::to_string(&response)?; + response.push('\n'); + slog::info!( + log, + "sending error response: {response}" + ); + writer + .write_all(response.as_bytes()) + .await?; + break; + } + + slog::debug!(log, "JSON received: {msg}"); + + let result: Result = + serde_json::from_str(&msg); + let request = match result { + Ok(q) => q, + Err(e) => { + let response = + Response::Error(e.to_string()); + let mut response = + serde_json::to_string(&response)?; + response.push('\n'); + slog::info!( + log, + "sending error response: {response}" + ); + writer + .write_all(response.as_bytes()) + .await?; + break; + } + }; + + let response = match request { + Request::Attest(q) => { + slog::debug!( + log, + "qualifying data received: {q:?}" + ); + match rot.attest(&q) { + Ok(a) => Response::Attest(a), + Err(e) => { + Response::Error(e.to_string()) + } + } + } + }; + + let mut response = + serde_json::to_string(&response)?; + response.push('\n'); - //let ox_attest: Box = Box::new(AttestSledAgent(sa_addr, log)); + slog::debug!( + log, + "sending response: {response}" + ); + writer.write_all(response.as_bytes()).await?; + msg.clear(); + } - //let response = match ox_attest.attest(&qualifying_data) { - //Ok(a) => - //} + info!(log, "ALL DONE"); + Ok(()) } pub async fn run( log: Logger, listener: TcpListener, mut hup_recv: oneshot::Receiver<()>, + sa_addr: SocketAddrV6, ) { info!(log, "attestation server running"); @@ -84,8 +183,13 @@ impl AttestationSock { sock_res = listener.accept() => { info!(log, "new client connected"); match sock_res { - Ok((sock, addr)) => { - //tokio::spawn(Self::handle_conn(log.clone(), sock)); + Ok((sock, _addr)) => { + let log = log.clone(); + tokio::spawn(async move { + if let Err(e) = Self::handle_conn(log.clone(), sock, sa_addr).await { + slog::error!(log, "handle_conn error: {e}"); + } + }); } Err(e) => { error!(log, "Attestation TCP listener error: {:?}", e); From 63353238ccadb0eaed59ce812f3513375a168b36 Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Fri, 20 Mar 2026 22:36:06 +0000 Subject: [PATCH 04/37] remove dep on libipcc --- Cargo.lock | 13 +------------ Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f4a418bfc..cccbb34be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1749,7 +1749,6 @@ dependencies = [ "env_logger", "hex", "hubpack", - "libipcc", "log", "p384", "rats-corim", @@ -3925,16 +3924,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "libipcc" -version = "0.1.0" -source = "git+https://github.com/oxidecomputer/ipcc-rs?rev=7cdf2ab9c8d9e9267a8b366aa780c6c26f9a5ecf#7cdf2ab9c8d9e9267a8b366aa780c6c26f9a5ecf" -dependencies = [ - "cfg-if", - "libc", - "thiserror 1.0.64", -] - [[package]] name = "libloading" version = "0.7.4" @@ -9959,7 +9948,7 @@ dependencies = [ [[package]] name = "vm-attest" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/vm-attest#1fba555e4a5a3acbf58b072f6263b5ceff7fd205" +source = "git+https://github.com/oxidecomputer/vm-attest?branch=jhendricks/ipcc-no-more#c9feaf92b459d3da9539c13b72a65cdff857b820" dependencies = [ "anyhow", "attest-data", diff --git a/Cargo.toml b/Cargo.toml index a45c10683..db5a310f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,7 +99,7 @@ crucible-client-types = { git = "https://github.com/oxidecomputer/crucible", rev # Attestation #dice-verifier = { git = "https://github.com/oxidecomputer/dice-util", branch = "jhendricks/update-sled-agent-types-versions", features = ["sled-agent"] } dice-verifier = { git = "https://github.com/oxidecomputer/dice-util", features = ["sled-agent"] } -vm-attest = { git = "https://github.com/oxidecomputer/vm-attest", default-features = false } +vm-attest = { git = "https://github.com/oxidecomputer/vm-attest", branch = "jhendricks/ipcc-no-more", default-features = false } # External dependencies anyhow = "1.0" From ef01e4b751fb97fdf53dcf0594398706993c42f7 Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Fri, 20 Mar 2026 22:53:09 +0000 Subject: [PATCH 05/37] make boot digest parseable --- lib/propolis/src/attestation/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/propolis/src/attestation/mod.rs b/lib/propolis/src/attestation/mod.rs index 1175b25b7..8e9195f3c 100644 --- a/lib/propolis/src/attestation/mod.rs +++ b/lib/propolis/src/attestation/mod.rs @@ -71,7 +71,7 @@ impl AttestationSock { .parse() .context("couldn't parse uuid")?; let img_digest: Measurement = - "be4df4e085175f3de0c8ac4837e1c2c9a34e8983209dac6b549e94154f7cdd9c" + "sha-256;be4df4e085175f3de0c8ac4837e1c2c9a34e8983209dac6b549e94154f7cdd9c" .parse() .context("couldn't parse boot digest")?; From 591b9f5e0c0866e18047bd81bd1ee950ba036096 Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Fri, 20 Mar 2026 23:29:57 +0000 Subject: [PATCH 06/37] ready for a racklette spin --- Cargo.lock | 2 +- Cargo.toml | 2 +- crates/propolis-config-toml/src/spec.rs | 1 + lib/propolis/src/attestation/mod.rs | 92 ++++++++++--------------- 4 files changed, 38 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cccbb34be..a07fcd550 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9948,7 +9948,7 @@ dependencies = [ [[package]] name = "vm-attest" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/vm-attest?branch=jhendricks/ipcc-no-more#c9feaf92b459d3da9539c13b72a65cdff857b820" +source = "git+https://github.com/oxidecomputer/vm-attest?rev=4c98ee6b5e7c2e58820bfb57604d4f153975cee2#4c98ee6b5e7c2e58820bfb57604d4f153975cee2" dependencies = [ "anyhow", "attest-data", diff --git a/Cargo.toml b/Cargo.toml index db5a310f8..210f2ce14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,7 +99,7 @@ crucible-client-types = { git = "https://github.com/oxidecomputer/crucible", rev # Attestation #dice-verifier = { git = "https://github.com/oxidecomputer/dice-util", branch = "jhendricks/update-sled-agent-types-versions", features = ["sled-agent"] } dice-verifier = { git = "https://github.com/oxidecomputer/dice-util", features = ["sled-agent"] } -vm-attest = { git = "https://github.com/oxidecomputer/vm-attest", branch = "jhendricks/ipcc-no-more", default-features = false } +vm-attest = { git = "https://github.com/oxidecomputer/vm-attest", rev = "4c98ee6b5e7c2e58820bfb57604d4f153975cee2", default-features = false } # External dependencies anyhow = "1.0" diff --git a/crates/propolis-config-toml/src/spec.rs b/crates/propolis-config-toml/src/spec.rs index 2b6371892..5b8043bb8 100644 --- a/crates/propolis-config-toml/src/spec.rs +++ b/crates/propolis-config-toml/src/spec.rs @@ -446,6 +446,7 @@ fn parse_vsock_from_config( name: &str, device: &super::Device, ) -> Result { + eprintln!("{:?}", device); let guest_cid = device .get("guest_cid") .ok_or_else(|| TomlToSpecError::NoVsockGuestCid(name.to_owned()))?; diff --git a/lib/propolis/src/attestation/mod.rs b/lib/propolis/src/attestation/mod.rs index 8e9195f3c..af4127e37 100644 --- a/lib/propolis/src/attestation/mod.rs +++ b/lib/propolis/src/attestation/mod.rs @@ -5,6 +5,8 @@ use anyhow::Context; use std::io; use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV6}; +use std::sync::Arc; +use std::sync::Mutex; use slog::{error, info, Logger}; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; @@ -54,8 +56,8 @@ impl AttestationSock { async fn handle_conn( log: Logger, + rot: Arc>, conn: TcpStream, - sa_addr: SocketAddrV6, ) -> anyhow::Result<()> { info!(log, "handling connection"); @@ -65,22 +67,6 @@ impl AttestationSock { let (reader, mut writer) = tokio::io::split(conn); let mut reader = BufReader::with_capacity(MAX_LINE_LENGTH, reader); - // XXX: these are hard-coded qualifying data values that match a test OS image - // https://github.com/oxidecomputer/vm-attest-demo/blob/main/test-data/vm-instance-cfg.json - let uuid: Uuid = "db5bf54c-48c5-4455-a1e1-6c7dfc26e351" - .parse() - .context("couldn't parse uuid")?; - let img_digest: Measurement = - "sha-256;be4df4e085175f3de0c8ac4837e1c2c9a34e8983209dac6b549e94154f7cdd9c" - .parse() - .context("couldn't parse boot digest")?; - - let vm_conf = - VmInstanceConf { uuid, boot_digest: Some(img_digest) }; - let ox_attest: Box = - Box::new(AttestSledAgent::new(sa_addr, &log)); - let rot = VmInstanceRot::new(ox_attest, vm_conf); - loop { let bytes_read = reader.read_line(&mut msg).await?; if bytes_read == 0 { @@ -88,26 +74,17 @@ impl AttestationSock { } // Check if the limit was hit and a newline wasn't found - if bytes_read == MAX_LINE_LENGTH - && !msg.ends_with('\n') - { + if bytes_read == MAX_LINE_LENGTH && !msg.ends_with('\n') { slog::warn!( log, "Line length exceeded the limit of {} bytes.", MAX_LINE_LENGTH ); - let response = - Response::Error("Request too long".to_string()); - let mut response = - serde_json::to_string(&response)?; + let response = Response::Error("Request too long".to_string()); + let mut response = serde_json::to_string(&response)?; response.push('\n'); - slog::info!( - log, - "sending error response: {response}" - ); - writer - .write_all(response.as_bytes()) - .await?; + slog::info!(log, "sending error response: {response}"); + writer.write_all(response.as_bytes()).await?; break; } @@ -118,45 +95,29 @@ impl AttestationSock { let request = match result { Ok(q) => q, Err(e) => { - let response = - Response::Error(e.to_string()); - let mut response = - serde_json::to_string(&response)?; + let response = Response::Error(e.to_string()); + let mut response = serde_json::to_string(&response)?; response.push('\n'); - slog::info!( - log, - "sending error response: {response}" - ); - writer - .write_all(response.as_bytes()) - .await?; + slog::info!(log, "sending error response: {response}"); + writer.write_all(response.as_bytes()).await?; break; } }; let response = match request { Request::Attest(q) => { - slog::debug!( - log, - "qualifying data received: {q:?}" - ); - match rot.attest(&q) { + slog::debug!(log, "qualifying data received: {q:?}"); + match rot.lock().unwrap().attest(&q) { Ok(a) => Response::Attest(a), - Err(e) => { - Response::Error(e.to_string()) - } + Err(e) => Response::Error(e.to_string()), } } }; - let mut response = - serde_json::to_string(&response)?; + let mut response = serde_json::to_string(&response)?; response.push('\n'); - slog::debug!( - log, - "sending response: {response}" - ); + slog::debug!(log, "sending response: {response}"); writer.write_all(response.as_bytes()).await?; msg.clear(); } @@ -173,6 +134,22 @@ impl AttestationSock { ) { info!(log, "attestation server running"); + // XXX: these are hard-coded qualifying data values that match a test OS image + // https://github.com/oxidecomputer/vm-attest-demo/blob/main/test-data/vm-instance-cfg.json + let uuid: Uuid = "db5bf54c-48c5-4455-a1e1-6c7dfc26e351" + .parse() + .context("couldn't parse uuid") + .unwrap(); + let img_digest: Measurement = + "sha-256;be4df4e085175f3de0c8ac4837e1c2c9a34e8983209dac6b549e94154f7cdd9c" + .parse() + .context("couldn't parse boot digest").unwrap(); + + let vm_conf = VmInstanceConf { uuid, boot_digest: Some(img_digest) }; + let ox_attest: Box = + Box::new(AttestSledAgent::new(sa_addr, &log)); + let rot = Arc::new(Mutex::new(VmInstanceRot::new(ox_attest, vm_conf))); + loop { tokio::select! { biased; @@ -184,9 +161,10 @@ impl AttestationSock { info!(log, "new client connected"); match sock_res { Ok((sock, _addr)) => { + let rot = rot.clone(); let log = log.clone(); tokio::spawn(async move { - if let Err(e) = Self::handle_conn(log.clone(), sock, sa_addr).await { + if let Err(e) = Self::handle_conn(log.clone(), rot, sock).await { slog::error!(log, "handle_conn error: {e}"); } }); From 4ca28cbee6490c99513df252fd544b1cdaeb4b47 Mon Sep 17 00:00:00 2001 From: iximeow Date: Sat, 21 Mar 2026 00:54:32 +0000 Subject: [PATCH 07/37] paper over async/sync/async bits --- bin/propolis-server/src/lib/initializer.rs | 1 - lib/propolis/src/attestation/mod.rs | 27 ++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/bin/propolis-server/src/lib/initializer.rs b/bin/propolis-server/src/lib/initializer.rs index 7586a1739..bb6e14c56 100644 --- a/bin/propolis-server/src/lib/initializer.rs +++ b/bin/propolis-server/src/lib/initializer.rs @@ -4,7 +4,6 @@ use std::convert::TryInto; use std::fs::File; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::num::{NonZeroU8, NonZeroUsize}; use std::os::unix::fs::FileTypeExt; use std::sync::Arc; diff --git a/lib/propolis/src/attestation/mod.rs b/lib/propolis/src/attestation/mod.rs index af4127e37..1c15477e6 100644 --- a/lib/propolis/src/attestation/mod.rs +++ b/lib/propolis/src/attestation/mod.rs @@ -107,9 +107,32 @@ impl AttestationSock { let response = match request { Request::Attest(q) => { slog::debug!(log, "qualifying data received: {q:?}"); - match rot.lock().unwrap().attest(&q) { + let rot_guard = rot.lock().unwrap(); + + // very unfortunate: `attest` is a trait of synchronous + // functions, in our case wrapping async calls into + // sled-agent. to make this work, the sled-agent attestor + // holds a runtime handle and will block on async calls + // internally. This all happens in `attest()`. We're calling + // that from an async task, so just directly calling + // `attest()` is a sure-fire way to panic as the contained + // runtime tries to start on this already-in-a-runtime + // thread. + // + // okay. so, it'd be ideal to just give AttestSledAgent our + // runtime and let it `block_on()`. for now, instead, just + // call it in a context it's allowed to do its own + // `block_on()`: + let attest_result = + tokio::task::block_in_place(move || { + rot_guard.attest(&q) + }); + match attest_result { Ok(a) => Response::Attest(a), - Err(e) => Response::Error(e.to_string()), + Err(e) => { + slog::warn!(log, "attestation error: {e:?}"); + Response::Error(e.to_string()) + } } } }; From 5f12a782140e389629999df76a45984817a96ebf Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Tue, 24 Mar 2026 22:15:02 +0000 Subject: [PATCH 08/37] added recv channel for vm conf in attestation server --- 605-todo.md | 77 ++++++++++++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- bin/propolis-server/src/lib/initializer.rs | 9 +-- lib/propolis/src/attestation/mod.rs | 90 +++++++++++++++------- 5 files changed, 145 insertions(+), 35 deletions(-) create mode 100644 605-todo.md diff --git a/605-todo.md b/605-todo.md new file mode 100644 index 000000000..be334b3cb --- /dev/null +++ b/605-todo.md @@ -0,0 +1,77 @@ +# Remaining tasks for 605 propolis-server prototype + +## Status + +as of 2026-03-24: + +* yesterday we discussed putting the attestation server in the instance ensure + handler. this makes sense because there's no instance to attest until that + call happens. presumably a boot disk hashing thread gets kicked off there too. +* my next question: what does the interface between attestation and the boot + digest calculation look like? we need some sort of concurrency primitive. + + +in no particular order: + + +## Miscellaneous + +* what to do if the boot digest is not yet calculated, but we get an attestation + request? + +answer: return an error; there is an Option field on the `attest` config structure + +* what tokio runtime should the attestation server and boot digest etc tasks run + on? there exist: an API runtime for external API requests, a vmm runtime for + handling vmm tasks + + +* should we make the creation of the attestation server happen via a sled-agent + api call, instead of an implicit one by creating a vm? + +initial thoughts: no, probably not. the advantage to this is that it makes it +marginally easier for oxide technicians to turn the behavior off if something +catastrophic happens. but we have made the product decision to offer attestation +in the product regardless. if we find that we need to make it configurable, or +have a horrible bug that makes us want to turn it off, we'll deal with that +later. (keeping in mind the urgency of shipping this now) + + +## Boot digest calculation + +from ixi in chat: + +* how do we properly deal with boot order +* is the following thing out of scope: booting from the boot disk fails, and we + ended up in a secondary guest OS because edk2 kept trying + +thoughts from the call: if there isn't an explicit boot disk provided by the +control plane, we don't have a "boot disk", because ovmf will just try disks in +whatever order. it also doesn't make sense to try a second disk because if the +boot disk failed to boot, we are hosed anyway + +the boot disk is part of the propolis spec + + + + + + + +### Where does the boot digest hash thread get kicked off? + +Current state: the attestation server thread is kicked off by `run_server`, +prior to any inbound API calls + +The boot disk digest thread cannot be kicked off until we have a boot disk, +which means it can only begin after `instance_ensure` + + +## Fetch instance ID from instance properties + + +## + + + + diff --git a/Cargo.lock b/Cargo.lock index a07fcd550..ebd59b1fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9948,7 +9948,7 @@ dependencies = [ [[package]] name = "vm-attest" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/vm-attest?rev=4c98ee6b5e7c2e58820bfb57604d4f153975cee2#4c98ee6b5e7c2e58820bfb57604d4f153975cee2" +source = "git+https://github.com/oxidecomputer/vm-attest?rev=a7c2a341866e359a3126aaaa67823ec5097000cd#a7c2a341866e359a3126aaaa67823ec5097000cd" dependencies = [ "anyhow", "attest-data", diff --git a/Cargo.toml b/Cargo.toml index 210f2ce14..b4f9e3ade 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,7 +99,7 @@ crucible-client-types = { git = "https://github.com/oxidecomputer/crucible", rev # Attestation #dice-verifier = { git = "https://github.com/oxidecomputer/dice-util", branch = "jhendricks/update-sled-agent-types-versions", features = ["sled-agent"] } dice-verifier = { git = "https://github.com/oxidecomputer/dice-util", features = ["sled-agent"] } -vm-attest = { git = "https://github.com/oxidecomputer/vm-attest", rev = "4c98ee6b5e7c2e58820bfb57604d4f153975cee2", default-features = false } +vm-attest = { git = "https://github.com/oxidecomputer/vm-attest", rev = "a7c2a341866e359a3126aaaa67823ec5097000cd", default-features = false } # External dependencies anyhow = "1.0" diff --git a/bin/propolis-server/src/lib/initializer.rs b/bin/propolis-server/src/lib/initializer.rs index bb6e14c56..3e8915fcd 100644 --- a/bin/propolis-server/src/lib/initializer.rs +++ b/bin/propolis-server/src/lib/initializer.rs @@ -484,13 +484,6 @@ impl MachineInitializer<'_> { ) -> Result<(), MachineInitError> { use propolis::vsock::proxy::VsockPortMapping; - // OANA Port 605 - VM Attestation RFD 605 - //const ATTESTATION_PORT: u16 = 605; - //const ATTESTATION_ADDR: SocketAddr = SocketAddr::new( - //IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - //ATTESTATION_PORT, - //); - if let Some(vsock) = &self.spec.vsock { let bdf: pci::Bdf = vsock.spec.pci_path.into(); @@ -516,6 +509,8 @@ impl MachineInitializer<'_> { self.devices.insert(vsock.id.clone(), device.clone()); chipset.pci_attach(bdf, device); + + // Spawn attestation server that will go over the vsock } Ok(()) diff --git a/lib/propolis/src/attestation/mod.rs b/lib/propolis/src/attestation/mod.rs index 1c15477e6..08628fdca 100644 --- a/lib/propolis/src/attestation/mod.rs +++ b/lib/propolis/src/attestation/mod.rs @@ -22,6 +22,12 @@ use vm_attest::{ Measurement, Request, Response, VmInstanceConf, VmInstanceRot, }; +/// TODO: block comment +/// +/// - explain vm conf structure: this defines additional data tying the guest challenge (qualifying +/// data) to the instance. Currently it's definition is in the vm_attest crate. +/// + // See: https://github.com/oxidecomputer/oana pub const ATTESTATION_PORT: u16 = 605; pub const ATTESTATION_ADDR: SocketAddr = @@ -32,16 +38,14 @@ pub struct AttestationSock { hup_send: oneshot::Sender<()>, } -// TODO: -// - impl AttestationSock { pub async fn new(log: Logger, sa_addr: SocketAddrV6) -> io::Result { info!(log, "attestation server created"); let listener = TcpListener::bind(ATTESTATION_ADDR).await?; + let (vm_conf_send, vm_conf_recv) = oneshot::channel::(); let (hup_send, hup_recv) = oneshot::channel::<()>(); let join_hdl = tokio::spawn(async move { - Self::run(log, listener, hup_recv, sa_addr).await; + Self::run(log, listener, vm_conf_recv, hup_recv, sa_addr).await; }); Ok(Self { join_hdl, hup_send }) } @@ -57,6 +61,7 @@ impl AttestationSock { async fn handle_conn( log: Logger, rot: Arc>, + vm_conf: Arc>>, conn: TcpStream, ) -> anyhow::Result<()> { info!(log, "handling connection"); @@ -107,6 +112,15 @@ impl AttestationSock { let response = match request { Request::Attest(q) => { slog::debug!(log, "qualifying data received: {q:?}"); + + let guard = vm_conf.lock().unwrap(); + let conf = guard.clone(); + drop(guard); + + match conf { + Some(conf) => { + info!(log, "vm conf is ready = {:?}", conf); + let rot_guard = rot.lock().unwrap(); // very unfortunate: `attest` is a trait of synchronous @@ -125,14 +139,30 @@ impl AttestationSock { // `block_on()`: let attest_result = tokio::task::block_in_place(move || { - rot_guard.attest(&q) + rot_guard.attest(&conf, &q) }); - match attest_result { - Ok(a) => Response::Attest(a), - Err(e) => { - slog::warn!(log, "attestation error: {e:?}"); - Response::Error(e.to_string()) - } + + + match attest_result { + Ok(a) => Response::Attest(a), + Err(e) => { + slog::warn!(log, "attestation error: {e:?}"); + Response::Error(e.to_string()) + }, + } + }, + + // The VM conf isn't ready yet. + None => { + info!(log, "vm conf is NOT ready"); + let response = Response::Error( + "VmInstanceConf not ready".to_string(), + ); + //let mut response = + //serde_json::to_string(&response)?; + //response.push('\n'); + response + }, } } }; @@ -145,49 +175,57 @@ impl AttestationSock { msg.clear(); } - info!(log, "ALL DONE"); + info!(log, "handle_conn: ALL DONE"); Ok(()) } pub async fn run( log: Logger, listener: TcpListener, + vm_conf_recv: oneshot::Receiver, mut hup_recv: oneshot::Receiver<()>, sa_addr: SocketAddrV6, ) { info!(log, "attestation server running"); - // XXX: these are hard-coded qualifying data values that match a test OS image - // https://github.com/oxidecomputer/vm-attest-demo/blob/main/test-data/vm-instance-cfg.json - let uuid: Uuid = "db5bf54c-48c5-4455-a1e1-6c7dfc26e351" - .parse() - .context("couldn't parse uuid") - .unwrap(); - let img_digest: Measurement = - "sha-256;be4df4e085175f3de0c8ac4837e1c2c9a34e8983209dac6b549e94154f7cdd9c" - .parse() - .context("couldn't parse boot digest").unwrap(); - - let vm_conf = VmInstanceConf { uuid, boot_digest: Some(img_digest) }; + // Attestation requests get to the RoT via sled-agent API endpoints. let ox_attest: Box = Box::new(AttestSledAgent::new(sa_addr, &log)); - let rot = Arc::new(Mutex::new(VmInstanceRot::new(ox_attest, vm_conf))); + let rot = Arc::new(Mutex::new(VmInstanceRot::new(ox_attest))); + + let vm_conf = Arc::new(Mutex::new(None)); + + let vm_conf_cloned = vm_conf.clone(); + tokio::spawn(async move { + match vm_conf_recv.await { + Ok(conf) => { + *vm_conf_cloned.lock().unwrap() = Some(conf); + }, + Err(e) => { + panic!("unexpected loss of boot digest thread: {:?}", e); + } + } + }); loop { tokio::select! { biased; + // TODO: do we need this _ = &mut hup_recv => { return; }, + sock_res = listener.accept() => { info!(log, "new client connected"); match sock_res { Ok((sock, _addr)) => { let rot = rot.clone(); let log = log.clone(); + let vm_conf = vm_conf.clone(); + tokio::spawn(async move { - if let Err(e) = Self::handle_conn(log.clone(), rot, sock).await { + if let Err(e) = Self::handle_conn(log.clone(), rot, vm_conf, sock).await { slog::error!(log, "handle_conn error: {e}"); } }); From b1c710cf1332d4e5df5cbc9a2b99b57471c9a315 Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Thu, 26 Mar 2026 01:27:38 +0000 Subject: [PATCH 09/37] moved tcp attest server inside of vm objects --- bin/propolis-server/src/lib/initializer.rs | 24 ++- bin/propolis-server/src/lib/server.rs | 7 + bin/propolis-server/src/lib/vm/ensure.rs | 4 +- bin/propolis-server/src/lib/vm/mod.rs | 3 + bin/propolis-server/src/lib/vm/objects.rs | 6 + bin/propolis-server/src/main.rs | 42 ++-- lib/propolis/src/attestation/boot_digest.rs | 3 + lib/propolis/src/attestation/mod.rs | 228 +------------------- lib/propolis/src/attestation/server.rs | 221 +++++++++++++++++++ 9 files changed, 285 insertions(+), 253 deletions(-) create mode 100644 lib/propolis/src/attestation/boot_digest.rs create mode 100644 lib/propolis/src/attestation/server.rs diff --git a/bin/propolis-server/src/lib/initializer.rs b/bin/propolis-server/src/lib/initializer.rs index 3e8915fcd..f9a47a065 100644 --- a/bin/propolis-server/src/lib/initializer.rs +++ b/bin/propolis-server/src/lib/initializer.rs @@ -4,6 +4,7 @@ use std::convert::TryInto; use std::fs::File; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::num::{NonZeroU8, NonZeroUsize}; use std::os::unix::fs::FileTypeExt; use std::sync::Arc; @@ -25,6 +26,8 @@ pub use nexus_client::Client as NexusClient; use oximeter::types::ProducerRegistry; use oximeter_instruments::kstat::KstatSampler; use propolis::attestation; +use propolis::attestation::server::AttestationServerConfig; +use propolis::attestation::server::AttestationSock; use propolis::block; use propolis::chardev::{self, BlockingSource, Source}; use propolis::common::{Lifecycle, GB, MB, PAGE_SIZE}; @@ -105,6 +108,9 @@ pub enum MachineInitError { #[error("failed to specialize CPUID for vcpu {0}")] CpuidSpecializationFailed(i32, #[source] propolis::cpuid::SpecializeError), + #[error("failed to start attestation server")] + AttestationServer(#[source] std::io::Error), + #[cfg(feature = "falcon")] #[error("softnpu p9 device missing")] SoftNpuP9Missing, @@ -478,10 +484,11 @@ impl MachineInitializer<'_> { Ok(()) } - pub fn initialize_vsock( + pub async fn initialize_vsock( &mut self, chipset: &RegisteredChipset, - ) -> Result<(), MachineInitError> { + attest_cfg: Option, + ) -> Result, MachineInitError> { use propolis::vsock::proxy::VsockPortMapping; if let Some(vsock) = &self.spec.vsock { @@ -511,9 +518,20 @@ impl MachineInitializer<'_> { chipset.pci_attach(bdf, device); // Spawn attestation server that will go over the vsock + if let cfg = attest_cfg.unwrap() { + let attest = AttestationSock::new( + self.log.new(slog::o!("component" => "attestation-server")), + cfg.sled_agent_addr, + ) + .await + .map_err(MachineInitError::AttestationServer)?; + return Ok(Some(attest)); + } else { + return Ok(None); + } } - Ok(()) + Ok(None) } async fn create_storage_backend_from_spec( diff --git a/bin/propolis-server/src/lib/server.rs b/bin/propolis-server/src/lib/server.rs index bd70c1c1b..15404be72 100644 --- a/bin/propolis-server/src/lib/server.rs +++ b/bin/propolis-server/src/lib/server.rs @@ -36,6 +36,7 @@ use internal_dns_resolver::{ResolveError, Resolver}; use internal_dns_types::names::ServiceName; pub use nexus_client::Client as NexusClient; use oximeter::types::ProducerRegistry; +use propolis::attestation::server::AttestationServerConfig; use propolis_api_types::disk::{ InstanceVCRReplace, SnapshotRequestPathParams, VCRRequestPathParams, VolumeStatus, VolumeStatusPathParams, @@ -95,6 +96,9 @@ pub struct StaticConfig { /// The configuration to use when setting up this server's Oximeter /// endpoint. metrics: Option, + + /// TODO: comment + attest_config: Option, } /// Context accessible from HTTP callbacks. @@ -113,6 +117,7 @@ impl DropshotEndpointContext { use_reservoir: bool, log: slog::Logger, metric_config: Option, + attest_config: Option, ) -> Self { let vnc_server = VncServer::new(log.clone()); Self { @@ -121,6 +126,7 @@ impl DropshotEndpointContext { bootrom_version, use_reservoir, metrics: metric_config, + attest_config, }, vnc_server, vm: crate::vm::Vm::new(&log), @@ -245,6 +251,7 @@ impl PropolisServerApi for PropolisServerImpl { nexus_client, vnc_server: server_context.vnc_server.clone(), local_server_addr: rqctx.server.local_addr, + attest_config: server_context.static_config.attest_config, }; let vm_init = match init { diff --git a/bin/propolis-server/src/lib/vm/ensure.rs b/bin/propolis-server/src/lib/vm/ensure.rs index 040c91c85..cf5a19ad1 100644 --- a/bin/propolis-server/src/lib/vm/ensure.rs +++ b/bin/propolis-server/src/lib/vm/ensure.rs @@ -563,7 +563,8 @@ async fn initialize_vm_objects( &properties, ))?; init.initialize_network_devices(&chipset).await?; - init.initialize_vsock(&chipset)?; + let tcp_attest = + init.initialize_vsock(&chipset, options.attest_config).await?; #[cfg(feature = "failure-injection")] init.initialize_test_devices(); @@ -642,6 +643,7 @@ async fn initialize_vm_objects( com1, framebuffer: Some(ramfb), ps2ctrl, + tcp_attest, }; // Another really terrible hack. As we've found in Propolis#1008, brk() diff --git a/bin/propolis-server/src/lib/vm/mod.rs b/bin/propolis-server/src/lib/vm/mod.rs index e40d9e4f7..142221468 100644 --- a/bin/propolis-server/src/lib/vm/mod.rs +++ b/bin/propolis-server/src/lib/vm/mod.rs @@ -100,6 +100,7 @@ use state_publisher::StatePublisher; use tokio::sync::{oneshot, watch, RwLock, RwLockReadGuard}; use crate::{server::MetricsEndpointConfig, spec::Spec, vnc::VncServer}; +use propolis::attestation::server::AttestationServerConfig; mod active; pub(crate) mod ensure; @@ -309,6 +310,8 @@ pub(super) struct EnsureOptions { /// The address of this Propolis process, used by the live migration /// protocol to transfer serial console connections. pub(super) local_server_addr: SocketAddr, + + pub(super) attest_config: Option, } impl Vm { diff --git a/bin/propolis-server/src/lib/vm/objects.rs b/bin/propolis-server/src/lib/vm/objects.rs index 9908d3c9c..ced4244ec 100644 --- a/bin/propolis-server/src/lib/vm/objects.rs +++ b/bin/propolis-server/src/lib/vm/objects.rs @@ -13,6 +13,7 @@ use std::{ use futures::{future::BoxFuture, stream::FuturesUnordered, StreamExt}; use propolis::{ + attestation, hw::{ps2::ctrl::PS2Ctrl, qemu::ramfb::RamFb, uart::LpcUart}, vmm::VmmHdl, Machine, @@ -51,6 +52,7 @@ pub(super) struct InputVmObjects { pub com1: Arc>, pub framebuffer: Option>, pub ps2ctrl: Arc, + pub tcp_attest: Option, } /// The collection of objects and state that make up a Propolis instance. @@ -86,6 +88,9 @@ pub(crate) struct VmObjectsLocked { /// A handle to the VM's PS/2 controller. ps2ctrl: Arc, + + /// Attestation server + tcp_attest: Option, } impl VmObjects { @@ -126,6 +131,7 @@ impl VmObjectsLocked { com1: input.com1, framebuffer: input.framebuffer, ps2ctrl: input.ps2ctrl, + tcp_attest: input.tcp_attest, } } diff --git a/bin/propolis-server/src/main.rs b/bin/propolis-server/src/main.rs index e78cb93c8..ed7223076 100644 --- a/bin/propolis-server/src/main.rs +++ b/bin/propolis-server/src/main.rs @@ -9,7 +9,7 @@ use std::str::FromStr; use std::sync::Arc; use omicron_common::address::Ipv6Subnet; -use propolis::attestation; +use propolis::attestation::server::AttestationServerConfig; use propolis::usdt::register_probes; use propolis_server::{ config, @@ -116,7 +116,7 @@ fn run_server( config_dropshot: dropshot::ConfigDropshot, config_metrics: Option, vnc_addr: Option, - sa_addr: SocketAddrV6, + attest_config: Option, log: slog::Logger, ) -> anyhow::Result<()> { use propolis::api_version; @@ -150,6 +150,7 @@ fn run_server( use_reservoir, log.new(slog::o!()), config_metrics, + attest_config, ); // Spawn the runtime for handling API processing @@ -171,15 +172,6 @@ fn run_server( None => None, }; - // Start listener for attestation server - let tcp_attest = api_runtime.block_on(async { - attestation::AttestationSock::new( - log.new(slog::o!("component" => "attestation-server")), - sa_addr, - ) - .await - })?; - info!(log, "Starting server..."); let server = dropshot::ServerBuilder::new( @@ -205,8 +197,7 @@ fn run_server( api_runtime.block_on(async { vnc.halt().await }); } - // Clean up attestation socket - api_runtime.block_on(async { tcp_attest.halt().await }); + // TODO: clean up attestation server. result.map_err(|e| anyhow!("Server exited with an error: {e}")) } @@ -317,16 +308,6 @@ fn main() -> anyhow::Result<()> { vnc_addr, log_level, } => { - let sa_addr = match propolis_addr.ip() { - IpAddr::V4(_) => todo!("no good!"), - IpAddr::V6(ipv6_addr) => { - let sled_subnet = Ipv6Subnet::< - { omicron_common::address::SLED_PREFIX }, - >::new(ipv6_addr); - omicron_common::address::get_sled_address(sled_subnet) - } - }; - // Dropshot configuration. let config_dropshot = ConfigDropshot { bind_address: propolis_addr, @@ -343,13 +324,26 @@ fn main() -> anyhow::Result<()> { propolis_addr.ip(), )?; + // TODO: how will this behave running server in isolation? is it still usable for devs? + let sa_addr = match propolis_addr.ip() { + IpAddr::V4(_) => todo!("no good!"), + IpAddr::V6(ipv6_addr) => { + let sled_subnet = Ipv6Subnet::< + { omicron_common::address::SLED_PREFIX }, + >::new(ipv6_addr); + omicron_common::address::get_sled_address(sled_subnet) + } + }; + let attest_config = AttestationServerConfig::new(sa_addr); + run_server( bootrom_path, bootrom_version, config_dropshot, metric_config, vnc_addr, - sa_addr, + // TODO + Some(attest_config), log, ) } diff --git a/lib/propolis/src/attestation/boot_digest.rs b/lib/propolis/src/attestation/boot_digest.rs new file mode 100644 index 000000000..7be716e27 --- /dev/null +++ b/lib/propolis/src/attestation/boot_digest.rs @@ -0,0 +1,3 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. diff --git a/lib/propolis/src/attestation/mod.rs b/lib/propolis/src/attestation/mod.rs index 08628fdca..062e84736 100644 --- a/lib/propolis/src/attestation/mod.rs +++ b/lib/propolis/src/attestation/mod.rs @@ -2,25 +2,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use anyhow::Context; -use std::io; -use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV6}; -use std::sync::Arc; -use std::sync::Mutex; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; -use slog::{error, info, Logger}; -use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; -use tokio::net::{TcpListener, TcpStream}; -use tokio::sync::oneshot; -use tokio::task::JoinHandle; -use uuid::Uuid; - -use dice_verifier::sled_agent::AttestSledAgent; -use dice_verifier::Attest; -use vm_attest::VmInstanceAttester; -use vm_attest::{ - Measurement, Request, Response, VmInstanceConf, VmInstanceRot, -}; +pub mod boot_digest; +pub mod server; /// TODO: block comment /// @@ -32,210 +17,3 @@ use vm_attest::{ pub const ATTESTATION_PORT: u16 = 605; pub const ATTESTATION_ADDR: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), ATTESTATION_PORT); - -pub struct AttestationSock { - join_hdl: JoinHandle<()>, - hup_send: oneshot::Sender<()>, -} - -impl AttestationSock { - pub async fn new(log: Logger, sa_addr: SocketAddrV6) -> io::Result { - info!(log, "attestation server created"); - let listener = TcpListener::bind(ATTESTATION_ADDR).await?; - let (vm_conf_send, vm_conf_recv) = oneshot::channel::(); - let (hup_send, hup_recv) = oneshot::channel::<()>(); - let join_hdl = tokio::spawn(async move { - Self::run(log, listener, vm_conf_recv, hup_recv, sa_addr).await; - }); - Ok(Self { join_hdl, hup_send }) - } - - pub async fn halt(self) { - let Self { join_hdl, hup_send } = self; - - // Signal the socket listener to hang up, then wait for it to bail - let _ = hup_send.send(()); - let _ = join_hdl.await; - } - - async fn handle_conn( - log: Logger, - rot: Arc>, - vm_conf: Arc>>, - conn: TcpStream, - ) -> anyhow::Result<()> { - info!(log, "handling connection"); - - let mut msg = String::new(); - - const MAX_LINE_LENGTH: usize = 1024; - let (reader, mut writer) = tokio::io::split(conn); - let mut reader = BufReader::with_capacity(MAX_LINE_LENGTH, reader); - - loop { - let bytes_read = reader.read_line(&mut msg).await?; - if bytes_read == 0 { - break; - } - - // Check if the limit was hit and a newline wasn't found - if bytes_read == MAX_LINE_LENGTH && !msg.ends_with('\n') { - slog::warn!( - log, - "Line length exceeded the limit of {} bytes.", - MAX_LINE_LENGTH - ); - let response = Response::Error("Request too long".to_string()); - let mut response = serde_json::to_string(&response)?; - response.push('\n'); - slog::info!(log, "sending error response: {response}"); - writer.write_all(response.as_bytes()).await?; - break; - } - - slog::debug!(log, "JSON received: {msg}"); - - let result: Result = - serde_json::from_str(&msg); - let request = match result { - Ok(q) => q, - Err(e) => { - let response = Response::Error(e.to_string()); - let mut response = serde_json::to_string(&response)?; - response.push('\n'); - slog::info!(log, "sending error response: {response}"); - writer.write_all(response.as_bytes()).await?; - break; - } - }; - - let response = match request { - Request::Attest(q) => { - slog::debug!(log, "qualifying data received: {q:?}"); - - let guard = vm_conf.lock().unwrap(); - let conf = guard.clone(); - drop(guard); - - match conf { - Some(conf) => { - info!(log, "vm conf is ready = {:?}", conf); - - let rot_guard = rot.lock().unwrap(); - - // very unfortunate: `attest` is a trait of synchronous - // functions, in our case wrapping async calls into - // sled-agent. to make this work, the sled-agent attestor - // holds a runtime handle and will block on async calls - // internally. This all happens in `attest()`. We're calling - // that from an async task, so just directly calling - // `attest()` is a sure-fire way to panic as the contained - // runtime tries to start on this already-in-a-runtime - // thread. - // - // okay. so, it'd be ideal to just give AttestSledAgent our - // runtime and let it `block_on()`. for now, instead, just - // call it in a context it's allowed to do its own - // `block_on()`: - let attest_result = - tokio::task::block_in_place(move || { - rot_guard.attest(&conf, &q) - }); - - - match attest_result { - Ok(a) => Response::Attest(a), - Err(e) => { - slog::warn!(log, "attestation error: {e:?}"); - Response::Error(e.to_string()) - }, - } - }, - - // The VM conf isn't ready yet. - None => { - info!(log, "vm conf is NOT ready"); - let response = Response::Error( - "VmInstanceConf not ready".to_string(), - ); - //let mut response = - //serde_json::to_string(&response)?; - //response.push('\n'); - response - }, - } - } - }; - - let mut response = serde_json::to_string(&response)?; - response.push('\n'); - - slog::debug!(log, "sending response: {response}"); - writer.write_all(response.as_bytes()).await?; - msg.clear(); - } - - info!(log, "handle_conn: ALL DONE"); - Ok(()) - } - - pub async fn run( - log: Logger, - listener: TcpListener, - vm_conf_recv: oneshot::Receiver, - mut hup_recv: oneshot::Receiver<()>, - sa_addr: SocketAddrV6, - ) { - info!(log, "attestation server running"); - - // Attestation requests get to the RoT via sled-agent API endpoints. - let ox_attest: Box = - Box::new(AttestSledAgent::new(sa_addr, &log)); - let rot = Arc::new(Mutex::new(VmInstanceRot::new(ox_attest))); - - let vm_conf = Arc::new(Mutex::new(None)); - - let vm_conf_cloned = vm_conf.clone(); - tokio::spawn(async move { - match vm_conf_recv.await { - Ok(conf) => { - *vm_conf_cloned.lock().unwrap() = Some(conf); - }, - Err(e) => { - panic!("unexpected loss of boot digest thread: {:?}", e); - } - } - }); - - loop { - tokio::select! { - biased; - - // TODO: do we need this - _ = &mut hup_recv => { - return; - }, - - sock_res = listener.accept() => { - info!(log, "new client connected"); - match sock_res { - Ok((sock, _addr)) => { - let rot = rot.clone(); - let log = log.clone(); - let vm_conf = vm_conf.clone(); - - tokio::spawn(async move { - if let Err(e) = Self::handle_conn(log.clone(), rot, vm_conf, sock).await { - slog::error!(log, "handle_conn error: {e}"); - } - }); - } - Err(e) => { - error!(log, "Attestation TCP listener error: {:?}", e); - } - } - }, - }; - } - } -} diff --git a/lib/propolis/src/attestation/server.rs b/lib/propolis/src/attestation/server.rs new file mode 100644 index 000000000..e04535c53 --- /dev/null +++ b/lib/propolis/src/attestation/server.rs @@ -0,0 +1,221 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use std::io; +use std::net::SocketAddrV6; +use std::sync::Arc; +use std::sync::Mutex; + +use slog::{error, info, Logger}; +use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; +use tokio::net::{TcpListener, TcpStream}; +use tokio::sync::oneshot; +use tokio::task::JoinHandle; + +use dice_verifier::sled_agent::AttestSledAgent; +use dice_verifier::Attest; + +use vm_attest::VmInstanceAttester; + +use crate::attestation::ATTESTATION_ADDR; + +#[derive(Copy, Clone)] +pub struct AttestationServerConfig { + pub sled_agent_addr: SocketAddrV6, +} + +impl AttestationServerConfig { + pub fn new(sled_agent_addr: SocketAddrV6) -> Self { + Self { sled_agent_addr } + } +} + +/// TODO: comment +pub struct AttestationSock { + join_hdl: JoinHandle<()>, + hup_send: oneshot::Sender<()>, +} + +impl AttestationSock { + pub async fn new(log: Logger, sa_addr: SocketAddrV6) -> io::Result { + info!(log, "attestation server created"); + let listener = TcpListener::bind(ATTESTATION_ADDR).await?; + let (vm_conf_send, vm_conf_recv) = + oneshot::channel::(); + let (hup_send, hup_recv) = oneshot::channel::<()>(); + let join_hdl = tokio::spawn(async move { + Self::run(log, listener, vm_conf_recv, hup_recv, sa_addr).await; + }); + Ok(Self { join_hdl, hup_send }) + } + + pub async fn halt(self) { + let Self { join_hdl, hup_send } = self; + + // Signal the socket listener to hang up, then wait for it to bail + let _ = hup_send.send(()); + let _ = join_hdl.await; + } + + // Handle an incoming connection to the attestation port. + async fn handle_conn( + log: Logger, + rot: Arc>, + vm_conf: Arc>>, + conn: TcpStream, + ) -> anyhow::Result<()> { + info!(log, "handling connection"); + + let mut msg = String::new(); + + const MAX_LINE_LENGTH: usize = 1024; + let (reader, mut writer) = tokio::io::split(conn); + let mut reader = BufReader::with_capacity(MAX_LINE_LENGTH, reader); + + loop { + let bytes_read = reader.read_line(&mut msg).await?; + if bytes_read == 0 { + break; + } + + // Check if the limit was hit and a newline wasn't found + if bytes_read == MAX_LINE_LENGTH && !msg.ends_with('\n') { + slog::warn!( + log, + "Line length exceeded the limit of {} bytes.", + MAX_LINE_LENGTH + ); + let response = + vm_attest::Response::Error("Request too long".to_string()); + let mut response = serde_json::to_string(&response)?; + response.push('\n'); + slog::info!(log, "sending error response: {response}"); + writer.write_all(response.as_bytes()).await?; + break; + } + + slog::debug!(log, "JSON received: {msg}"); + + let result: Result = + serde_json::from_str(&msg); + let request = match result { + Ok(q) => q, + Err(e) => { + let response = vm_attest::Response::Error(e.to_string()); + let mut response = serde_json::to_string(&response)?; + response.push('\n'); + slog::info!(log, "sending error response: {response}"); + writer.write_all(response.as_bytes()).await?; + break; + } + }; + + let response = match request { + vm_attest::Request::Attest(q) => { + slog::debug!(log, "qualifying data received: {q:?}"); + + let guard = vm_conf.lock().unwrap(); + let conf = guard.clone(); + drop(guard); + + match conf { + Some(conf) => { + info!(log, "vm conf is ready = {:?}", conf); + + match rot.lock().unwrap().attest(&conf, &q) { + Ok(a) => vm_attest::Response::Attest(a), + Err(e) => { + vm_attest::Response::Error(e.to_string()) + } + } + } + + // The VM conf isn't ready yet. + None => { + info!(log, "vm conf is NOT ready"); + let response = vm_attest::Response::Error( + "VmInstanceConf not ready".to_string(), + ); + //let mut response = + //serde_json::to_string(&response)?; + //response.push('\n'); + response + } + } + } + }; + + let mut response = serde_json::to_string(&response)?; + response.push('\n'); + + slog::debug!(log, "sending response: {response}"); + writer.write_all(response.as_bytes()).await?; + msg.clear(); + } + + info!(log, "handle_conn: ALL DONE"); + Ok(()) + } + + pub async fn run( + log: Logger, + listener: TcpListener, + vm_conf_recv: oneshot::Receiver, + mut hup_recv: oneshot::Receiver<()>, + sa_addr: SocketAddrV6, + ) { + info!(log, "attestation server running"); + + // Attestation requests get to the RoT via sled-agent API endpoints. + let ox_attest: Box = + Box::new(AttestSledAgent::new(sa_addr, &log)); + let rot = + Arc::new(Mutex::new(vm_attest::VmInstanceRot::new(ox_attest))); + + let vm_conf = Arc::new(Mutex::new(None)); + + let vm_conf_cloned = vm_conf.clone(); + tokio::spawn(async move { + match vm_conf_recv.await { + Ok(conf) => { + *vm_conf_cloned.lock().unwrap() = Some(conf); + } + Err(e) => { + panic!("unexpected loss of boot digest thread: {:?}", e); + } + } + }); + + loop { + tokio::select! { + biased; + + // TODO: do we need this + _ = &mut hup_recv => { + return; + }, + + sock_res = listener.accept() => { + info!(log, "new client connected"); + match sock_res { + Ok((sock, _addr)) => { + let rot = rot.clone(); + let log = log.clone(); + let vm_conf = vm_conf.clone(); + + tokio::spawn(async move { + if let Err(e) = Self::handle_conn(log.clone(), rot, vm_conf, sock).await { + slog::error!(log, "handle_conn error: {e}"); + } + }); + } + Err(e) => { + error!(log, "Attestation TCP listener error: {:?}", e); + } + } + }, + }; + } + } +} From e4b4a52d34e9bb514482c086bfb660cea3a920cc Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Thu, 26 Mar 2026 01:44:27 +0000 Subject: [PATCH 10/37] remove warning --- bin/propolis-server/src/lib/initializer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/propolis-server/src/lib/initializer.rs b/bin/propolis-server/src/lib/initializer.rs index f9a47a065..6efee6f7e 100644 --- a/bin/propolis-server/src/lib/initializer.rs +++ b/bin/propolis-server/src/lib/initializer.rs @@ -518,7 +518,7 @@ impl MachineInitializer<'_> { chipset.pci_attach(bdf, device); // Spawn attestation server that will go over the vsock - if let cfg = attest_cfg.unwrap() { + if let Some(cfg) = attest_cfg { let attest = AttestationSock::new( self.log.new(slog::o!("component" => "attestation-server")), cfg.sled_agent_addr, From 1c55d2b63dcab7c0449e8bff344762d315eb45d0 Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Thu, 26 Mar 2026 02:46:42 +0000 Subject: [PATCH 11/37] start adding boot digest stuff --- Cargo.lock | 1 + Cargo.toml | 1 + bin/propolis-server/src/lib/initializer.rs | 9 +++ bin/propolis-server/src/lib/vm/ensure.rs | 2 + lib/propolis/Cargo.toml | 1 + lib/propolis/src/attestation/boot_digest.rs | 79 +++++++++++++++++++++ lib/propolis/src/block/crucible.rs | 7 ++ lib/propolis/src/block/file.rs | 4 ++ lib/propolis/src/block/in_memory.rs | 4 ++ lib/propolis/src/block/mem_async.rs | 3 + lib/propolis/src/block/mod.rs | 3 + 11 files changed, 114 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index ebd59b1fd..b2ff3e2f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6328,6 +6328,7 @@ dependencies = [ "serde", "serde_arrays", "serde_json", + "sha2 0.10.9", "slog", "slog-async", "slog-term", diff --git a/Cargo.toml b/Cargo.toml index b4f9e3ade..fa2c72bf0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -169,6 +169,7 @@ serde_arrays = "0.1" serde_derive = "1.0" serde_json = "1.0" serde_test = "1.0.138" +sha2 = "0.10.9" slog = "2.7" slog-async = "2.8" slog-bunyan = "2.4.0" diff --git a/bin/propolis-server/src/lib/initializer.rs b/bin/propolis-server/src/lib/initializer.rs index 6efee6f7e..550fbfcdd 100644 --- a/bin/propolis-server/src/lib/initializer.rs +++ b/bin/propolis-server/src/lib/initializer.rs @@ -685,6 +685,15 @@ impl MachineInitializer<'_> { } } + // TODO: if this is running and we want to tear down a vm, will this get handled properly? or + // will it block instance shutdown? + // + // I had a small moment about debugging this at scale T_T + pub async fn initialize_rot_data(&self) -> Result<(), MachineInitError> { + // TODO + Ok(()) + } + /// Initializes the storage devices and backends listed in this /// initializer's instance spec. /// diff --git a/bin/propolis-server/src/lib/vm/ensure.rs b/bin/propolis-server/src/lib/vm/ensure.rs index cf5a19ad1..edadee2dc 100644 --- a/bin/propolis-server/src/lib/vm/ensure.rs +++ b/bin/propolis-server/src/lib/vm/ensure.rs @@ -579,6 +579,8 @@ async fn initialize_vm_objects( .initialize_storage_devices(&chipset, options.nexus_client.clone()) .await?; + //tokio::spawn(self.initialize_rot_data()); + let ramfb = init.initialize_fwcfg(spec.board.cpus, &options.bootrom_version)?; diff --git a/lib/propolis/Cargo.toml b/lib/propolis/Cargo.toml index 492c00ab1..5b23e70d0 100644 --- a/lib/propolis/Cargo.toml +++ b/lib/propolis/Cargo.toml @@ -32,6 +32,7 @@ serde.workspace = true serde_arrays.workspace = true erased-serde.workspace = true serde_json.workspace = true +sha2.workspace = true strum = { workspace = true, features = ["derive"] } uuid.workspace = true zerocopy = { workspace = true, features = ["derive"] } diff --git a/lib/propolis/src/attestation/boot_digest.rs b/lib/propolis/src/attestation/boot_digest.rs index 7be716e27..becf49460 100644 --- a/lib/propolis/src/attestation/boot_digest.rs +++ b/lib/propolis/src/attestation/boot_digest.rs @@ -1,3 +1,82 @@ // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use crucible::BlockIO; +use crucible::BlockIndex; +use crucible::Buffer; + +use anyhow::Result; +use sha2::{Digest, Sha256}; +use slog::Logger; +use std::time::Instant; + +pub async fn boot_disk_digest( + vol: crucible::Volume, + log: &Logger, +) -> Result { + let vol_uuid = vol.get_uuid().await.expect("could not get volume UUID"); + let vol_size = vol.total_size().await.expect("could not get volume size"); + let block_size = + vol.get_block_size().await.expect("could not get volume block size"); + + slog::info!( + log, + "starting hash of volume {:?} (total_size={}, block_size={})", + vol_uuid, + vol_size, + block_size + ); + let hash_start = Instant::now(); + + let end = vol_size; + + // TODO: it's jank, apparently + // copying this I/O sizing from the crucible scrub code + let block_count = 131072 / block_size; + + let mut hasher = Sha256::new(); + + let mut offset = 0; + while offset < end { + let remaining = end - offset; + let this_block_count = block_count.min(remaining); + if this_block_count != block_count { + slog::info!( + log, + "adjusting block_count to {} at offset {}", + this_block_count, + offset + ); + } + assert!( + offset + this_block_count <= end, + "offset={}, block_count={}, end={}", + offset, + this_block_count, + end + ); + + let block = BlockIndex(offset); + let mut buffer = + Buffer::new(this_block_count as usize, block_size as usize); + vol.read(block, &mut buffer).await.expect("could not read volume"); + + hasher.update(&*buffer); + + offset += this_block_count; + + // TODO: delay? + } + + let elapsed = hash_start.elapsed(); + slog::info!( + log, + "hash of vol {:?} took {:?} secs", + vol_uuid, + elapsed.as_secs() + ); + + let hash = hasher.finalize(); + Ok(format!("{:x}", hash)) +} diff --git a/lib/propolis/src/block/crucible.rs b/lib/propolis/src/block/crucible.rs index f0cfdb26e..e783ba71b 100644 --- a/lib/propolis/src/block/crucible.rs +++ b/lib/propolis/src/block/crucible.rs @@ -363,6 +363,10 @@ impl CrucibleBackend { pub async fn volume_is_active(&self) -> Result { self.state.volume.query_is_active().await } + + pub fn clone_volume(&self) -> Volume { + self.state.volume.clone() + } } #[async_trait::async_trait] @@ -380,6 +384,9 @@ impl block::Backend for CrucibleBackend { self.block_attach.stop(); self.workers.join_all().await; } + fn as_any(&self) -> &dyn std::any::Any { + self + } } #[derive(Debug, Error)] diff --git a/lib/propolis/src/block/file.rs b/lib/propolis/src/block/file.rs index 97d801534..0d95aa43f 100644 --- a/lib/propolis/src/block/file.rs +++ b/lib/propolis/src/block/file.rs @@ -251,6 +251,10 @@ impl block::Backend for FileBackend { self.block_attach.stop(); self.workers.block_until_joined(); } + + fn as_any(&self) -> &dyn std::any::Any { + self + } } mod dkioc { diff --git a/lib/propolis/src/block/in_memory.rs b/lib/propolis/src/block/in_memory.rs index 964bf963e..949412a45 100644 --- a/lib/propolis/src/block/in_memory.rs +++ b/lib/propolis/src/block/in_memory.rs @@ -172,6 +172,10 @@ impl block::Backend for InMemoryBackend { self.block_attach.stop(); self.workers.block_until_joined(); } + + fn as_any(&self) -> &dyn std::any::Any { + self + } } /// Read from bytes into guest memory diff --git a/lib/propolis/src/block/mem_async.rs b/lib/propolis/src/block/mem_async.rs index 616a59944..d834bd388 100644 --- a/lib/propolis/src/block/mem_async.rs +++ b/lib/propolis/src/block/mem_async.rs @@ -163,6 +163,9 @@ impl block::Backend for MemAsyncBackend { self.block_attach.stop(); self.workers.join_all().await; } + fn as_any(&self) -> &dyn std::any::Any { + self + } } struct MmapSeg(NonNull, usize); diff --git a/lib/propolis/src/block/mod.rs b/lib/propolis/src/block/mod.rs index ccc183f8b..674f2a985 100644 --- a/lib/propolis/src/block/mod.rs +++ b/lib/propolis/src/block/mod.rs @@ -327,6 +327,9 @@ pub trait Backend: Send + Sync + 'static { /// requests when they are told to pause (and will only report they are /// fully paused when all their in-flight requests have completed). async fn stop(&self); + + /// TODO: good comment here explaining the downcasting + fn as_any(&self) -> &dyn std::any::Any; } /// Consumer of per-[Request] metrics From 1c6ed476ecf7ce327a5818e0768920f101b74316 Mon Sep 17 00:00:00 2001 From: iximeow Date: Thu, 26 Mar 2026 04:03:21 +0000 Subject: [PATCH 12/37] might have strung all the needful through propolis-server? --- Cargo.lock | 1 + bin/propolis-server/Cargo.toml | 1 + bin/propolis-server/src/lib/initializer.rs | 99 ++++++++++++++++++--- bin/propolis-server/src/lib/vm/ensure.rs | 12 ++- lib/propolis/src/attestation/boot_digest.rs | 11 ++- lib/propolis/src/attestation/server.rs | 84 ++++++++++++++++- 6 files changed, 186 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b2ff3e2f6..74e95881c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6563,6 +6563,7 @@ dependencies = [ "toml 0.7.8", "usdt 0.6.0", "uuid", + "vm-attest", ] [[package]] diff --git a/bin/propolis-server/Cargo.toml b/bin/propolis-server/Cargo.toml index 54d46216e..de5a10195 100644 --- a/bin/propolis-server/Cargo.toml +++ b/bin/propolis-server/Cargo.toml @@ -69,6 +69,7 @@ rgb_frame.workspace = true rfb = { workspace = true, features = ["tungstenite"] } uuid.workspace = true usdt.workspace = true +vm-attest.workspace = true base64.workspace = true schemars = { workspace = true, features = ["chrono", "uuid1"] } diff --git a/bin/propolis-server/src/lib/initializer.rs b/bin/propolis-server/src/lib/initializer.rs index 550fbfcdd..ff412c2d3 100644 --- a/bin/propolis-server/src/lib/initializer.rs +++ b/bin/propolis-server/src/lib/initializer.rs @@ -28,6 +28,7 @@ use oximeter_instruments::kstat::KstatSampler; use propolis::attestation; use propolis::attestation::server::AttestationServerConfig; use propolis::attestation::server::AttestationSock; +use propolis::attestation::server::AttestationSockInit; use propolis::block; use propolis::chardev::{self, BlockingSource, Source}; use propolis::common::{Lifecycle, GB, MB, PAGE_SIZE}; @@ -488,9 +489,13 @@ impl MachineInitializer<'_> { &mut self, chipset: &RegisteredChipset, attest_cfg: Option, - ) -> Result, MachineInitError> { + ) -> Result< + (Option, Option), + MachineInitError, + > { use propolis::vsock::proxy::VsockPortMapping; + // TODO: early return if none? if let Some(vsock) = &self.spec.vsock { let bdf: pci::Bdf = vsock.spec.pci_path.into(); @@ -519,19 +524,17 @@ impl MachineInitializer<'_> { // Spawn attestation server that will go over the vsock if let Some(cfg) = attest_cfg { - let attest = AttestationSock::new( + let (attest, attest_init) = AttestationSock::new( self.log.new(slog::o!("component" => "attestation-server")), cfg.sled_agent_addr, ) .await .map_err(MachineInitError::AttestationServer)?; - return Ok(Some(attest)); - } else { - return Ok(None); + return Ok((Some(attest), Some(attest_init))); } } - Ok(None) + Ok((None, None)) } async fn create_storage_backend_from_spec( @@ -685,13 +688,83 @@ impl MachineInitializer<'_> { } } - // TODO: if this is running and we want to tear down a vm, will this get handled properly? or - // will it block instance shutdown? - // - // I had a small moment about debugging this at scale T_T - pub async fn initialize_rot_data(&self) -> Result<(), MachineInitError> { - // TODO - Ok(()) + /// Collect the necessary information out of the VM under construction into the provided + /// `AttestationSocketInit`. This is expected to populate `attest_init` with information so the + /// caller can spawn off `AttestationSockInit::run`. + pub fn prepare_rot_initializer( + &self, + attest_init: &mut AttestationSockInit, + ) { + let uuid = self.properties.id; + + attest_init.instance_uuid = Some(uuid); + + // The first boot entry is a key into `self.spec.disks`, which is how we'll get to a + // Crucible volume backing this boot option. + // + // TODO: remove this, but for reference: + // > if let Some(spec) = self.spec.disks.et(&boot_entry.device_id) + let boot_disk_entry = self.spec.boot_settings.as_ref() + .and_then(|settings| { + if settings.order.len() >= 2 { + // In a rack we only configure propolis-server with zero or one boot disks. + // It's possible to provide a fuller list, and in the future the product may + // actually expose such a capability. At that time, we'll need to have a + // reckoning for what "boot disk measurement" from the RoT actually means; it + // probably "should" be "the measurement of the disk that EDK2 decided to boot + // into", but that communication to and from the guest is a little more + // complicated than we want or need to build out today. + // + // Since as the system exists we either have no specific boot disk (and don't + // know where the guest is expected to end up), or one boot disk (and can + // determine which disk to collect a measurement of before even running guest + // firmware), we encode this expectation up front. If the product has changed + // such that this assert is reached, "that's exciting!" and "sorry for crashing + // your Propolis". + panic!("Unsupported VM RoT configuration: more than one boot disk"); + } + + settings.order.get(0) + }); + + if let Some(boot_entry) = boot_disk_entry { + let disk_entry = self.spec.disks.get(&boot_entry.device_id) + .expect("TODO: crosscheck against boot config stuff: boot entry is valid"); + + let backend_id = match &disk_entry.device_spec { + spec::StorageDevice::Virtio(disk) => &disk.backend_id, + spec::StorageDevice::Nvme(disk) => &disk.backend_id, + }; + + let volume = match self.block_backends.get(&backend_id) { + Some(block_backend) => { + let crucible_backend = match block_backend + .as_any() + .downcast_ref::( + ) { + Some(backend) => backend, + None => { + // Probably fine, just not handled right now. + slog::error!( + self.log, + "boot disk is not a Crucible volume" + ); + return; + } + }; + crucible_backend.clone_volume() + } + None => { + slog::error!( + self.log, + "boot disk does not name a block backend?!" + ); + return; + } + }; + + attest_init.volume_ref = Some(volume); + } } /// Initializes the storage devices and backends listed in this diff --git a/bin/propolis-server/src/lib/vm/ensure.rs b/bin/propolis-server/src/lib/vm/ensure.rs index edadee2dc..d7f791791 100644 --- a/bin/propolis-server/src/lib/vm/ensure.rs +++ b/bin/propolis-server/src/lib/vm/ensure.rs @@ -563,7 +563,7 @@ async fn initialize_vm_objects( &properties, ))?; init.initialize_network_devices(&chipset).await?; - let tcp_attest = + let (tcp_attest, attest_init) = init.initialize_vsock(&chipset, options.attest_config).await?; #[cfg(feature = "failure-injection")] @@ -579,7 +579,15 @@ async fn initialize_vm_objects( .initialize_storage_devices(&chipset, options.nexus_client.clone()) .await?; - //tokio::spawn(self.initialize_rot_data()); + // If we have a VM RoT, that RoT needs to be able to collect some + // information about the guest before it can be actually usable. That + // information collection can - at the moment - happen entirely + // asynchronously. So, prepare the RoT initialization if necessary, then + // spawn it off to run independently. + if let Some(mut attest_init) = attest_init { + init.prepare_rot_initializer(&mut attest_init); + tokio::spawn(attest_init.run()); + } let ramfb = init.initialize_fwcfg(spec.board.cpus, &options.bootrom_version)?; diff --git a/lib/propolis/src/attestation/boot_digest.rs b/lib/propolis/src/attestation/boot_digest.rs index becf49460..ad8474877 100644 --- a/lib/propolis/src/attestation/boot_digest.rs +++ b/lib/propolis/src/attestation/boot_digest.rs @@ -6,6 +6,8 @@ use crucible::BlockIO; use crucible::BlockIndex; use crucible::Buffer; +use vm_attest::Measurement; + use anyhow::Result; use sha2::{Digest, Sha256}; use slog::Logger; @@ -14,7 +16,7 @@ use std::time::Instant; pub async fn boot_disk_digest( vol: crucible::Volume, log: &Logger, -) -> Result { +) -> Result { let vol_uuid = vol.get_uuid().await.expect("could not get volume UUID"); let vol_size = vol.total_size().await.expect("could not get volume size"); let block_size = @@ -60,7 +62,9 @@ pub async fn boot_disk_digest( let block = BlockIndex(offset); let mut buffer = Buffer::new(this_block_count as usize, block_size as usize); - vol.read(block, &mut buffer).await.expect("could not read volume"); + + // TODO: should retry on read failures? + vol.read(block, &mut buffer).await?; hasher.update(&*buffer); @@ -77,6 +81,5 @@ pub async fn boot_disk_digest( elapsed.as_secs() ); - let hash = hasher.finalize(); - Ok(format!("{:x}", hash)) + Ok(Measurement::Sha256(hasher.finalize().into())) } diff --git a/lib/propolis/src/attestation/server.rs b/lib/propolis/src/attestation/server.rs index e04535c53..ea561478e 100644 --- a/lib/propolis/src/attestation/server.rs +++ b/lib/propolis/src/attestation/server.rs @@ -16,7 +16,7 @@ use tokio::task::JoinHandle; use dice_verifier::sled_agent::AttestSledAgent; use dice_verifier::Attest; -use vm_attest::VmInstanceAttester; +use vm_attest::{VmInstanceAttester, VmInstanceConf}; use crate::attestation::ATTESTATION_ADDR; @@ -37,17 +37,95 @@ pub struct AttestationSock { hup_send: oneshot::Sender<()>, } +/// This struct manages providing the requisite data for a corresponding `AttestationSock` to +/// become fully functional. +pub struct AttestationSockInit { + log: slog::Logger, + vm_conf_send: oneshot::Sender, + // kind of terrible: parts of the VM RoT initializer that must be filled in before `run()`. + // this *could* and probably should be more builder-y and typestateful. + pub instance_uuid: Option, + pub volume_ref: Option, +} + +impl AttestationSockInit { + /// Construct a future that does any remaining work of collecting VM RoT measurements in + /// support of this VM's attestation server. + /// + /// TODO: it is expected this future is simply spawned onto a Tokio runtime. If the VM is torn + /// down while this future is running, nothing will stop this future from operating? We + /// probably need to tie in a shutdown signal from the corresponding `AttestationSockInit` to + /// discover if we should (for example) stop calculating a boot digest midway. + pub async fn run(mut self) { + let uuid = self + .instance_uuid + .take() + .expect("AttestationSockInit was provided instance uuid"); + let mut vm_conf = vm_attest::VmInstanceConf { uuid, boot_digest: None }; + + if let Some(volume) = self.volume_ref.take() { + let boot_digest = + match crate::attestation::boot_digest::boot_disk_digest( + volume, &self.log, + ) + .await + { + Ok(digest) => digest, + Err(e) => { + slog::error!( + self.log, + "failed to compute boot disk digest: {e:?}" + ); + return; + } + }; + + vm_conf.boot_digest = Some(boot_digest); + } else { + slog::warn!(self.log, "not computing boot disk digest"); + } + + // keep a log reference around to report potential errors after this is taken and dropped + // in `provide()`. + let log = self.log.clone(); + let send_res = self.provide(vm_conf); + if let Err(_) = send_res { + slog::error!( + log, + "attestation server is not listening for its config?" + ); + } + } + + pub fn provide(self, conf: VmInstanceConf) -> Result<(), VmInstanceConf> { + self.vm_conf_send.send(conf) + } +} + impl AttestationSock { - pub async fn new(log: Logger, sa_addr: SocketAddrV6) -> io::Result { + pub async fn new( + log: Logger, + sa_addr: SocketAddrV6, + ) -> io::Result<(Self, AttestationSockInit)> { info!(log, "attestation server created"); let listener = TcpListener::bind(ATTESTATION_ADDR).await?; let (vm_conf_send, vm_conf_recv) = oneshot::channel::(); let (hup_send, hup_recv) = oneshot::channel::<()>(); + // TODO: would love to describe this sub-log as specifically VM RoT init, but dunno how to + // drive slog like that. + let attest_init_log = log.clone(); let join_hdl = tokio::spawn(async move { Self::run(log, listener, vm_conf_recv, hup_recv, sa_addr).await; }); - Ok(Self { join_hdl, hup_send }) + let attestation_sock = Self { join_hdl, hup_send }; + let attestation_init = AttestationSockInit { + log: attest_init_log, + vm_conf_send, + instance_uuid: None, + volume_ref: None, + }; + Ok((attestation_sock, attestation_init)) } pub async fn halt(self) { From 14122a26e747dd79bce5f1d9a02a57691395569e Mon Sep 17 00:00:00 2001 From: iximeow Date: Thu, 26 Mar 2026 04:27:06 +0000 Subject: [PATCH 13/37] clippy lints and cargo fmt --- bin/propolis-server/src/lib/initializer.rs | 5 ++--- bin/propolis-server/src/lib/vm/objects.rs | 6 +++++- bin/propolis-server/src/main.rs | 2 +- lib/propolis/src/attestation/mod.rs | 12 ++++++------ 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/bin/propolis-server/src/lib/initializer.rs b/bin/propolis-server/src/lib/initializer.rs index ff412c2d3..e97623e47 100644 --- a/bin/propolis-server/src/lib/initializer.rs +++ b/bin/propolis-server/src/lib/initializer.rs @@ -4,7 +4,6 @@ use std::convert::TryInto; use std::fs::File; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::num::{NonZeroU8, NonZeroUsize}; use std::os::unix::fs::FileTypeExt; use std::sync::Arc; @@ -724,7 +723,7 @@ impl MachineInitializer<'_> { panic!("Unsupported VM RoT configuration: more than one boot disk"); } - settings.order.get(0) + settings.order.first() }); if let Some(boot_entry) = boot_disk_entry { @@ -736,7 +735,7 @@ impl MachineInitializer<'_> { spec::StorageDevice::Nvme(disk) => &disk.backend_id, }; - let volume = match self.block_backends.get(&backend_id) { + let volume = match self.block_backends.get(backend_id) { Some(block_backend) => { let crucible_backend = match block_backend .as_any() diff --git a/bin/propolis-server/src/lib/vm/objects.rs b/bin/propolis-server/src/lib/vm/objects.rs index ced4244ec..b76569810 100644 --- a/bin/propolis-server/src/lib/vm/objects.rs +++ b/bin/propolis-server/src/lib/vm/objects.rs @@ -89,7 +89,11 @@ pub(crate) struct VmObjectsLocked { /// A handle to the VM's PS/2 controller. ps2ctrl: Arc, - /// Attestation server + /// Attestation server. + // + // This is held here only to keep the attestation server *somewhere*, but + // it's never used after being spawned. + #[allow(dead_code)] tcp_attest: Option, } diff --git a/bin/propolis-server/src/main.rs b/bin/propolis-server/src/main.rs index ed7223076..8e1f2d8e5 100644 --- a/bin/propolis-server/src/main.rs +++ b/bin/propolis-server/src/main.rs @@ -3,7 +3,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use std::fmt; -use std::net::{IpAddr, Ipv6Addr, SocketAddr, SocketAddrV6}; +use std::net::{IpAddr, Ipv6Addr, SocketAddr}; use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; diff --git a/lib/propolis/src/attestation/mod.rs b/lib/propolis/src/attestation/mod.rs index 062e84736..1ff188b41 100644 --- a/lib/propolis/src/attestation/mod.rs +++ b/lib/propolis/src/attestation/mod.rs @@ -2,17 +2,17 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +//! TODO: block comment +//! +//! - explain vm conf structure: this defines additional data tying the guest challenge (qualifying +//! data) to the instance. Currently it's definition is in the vm_attest crate. +//! + use std::net::{IpAddr, Ipv4Addr, SocketAddr}; pub mod boot_digest; pub mod server; -/// TODO: block comment -/// -/// - explain vm conf structure: this defines additional data tying the guest challenge (qualifying -/// data) to the instance. Currently it's definition is in the vm_attest crate. -/// - // See: https://github.com/oxidecomputer/oana pub const ATTESTATION_PORT: u16 = 605; pub const ATTESTATION_ADDR: SocketAddr = From 449a3b2c4a3308b15ad0247cd98d7e968618186e Mon Sep 17 00:00:00 2001 From: iximeow Date: Thu, 26 Mar 2026 18:46:58 +0000 Subject: [PATCH 14/37] racklette debug :( --- lib/propolis/src/attestation/server.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/propolis/src/attestation/server.rs b/lib/propolis/src/attestation/server.rs index ea561478e..00e71353d 100644 --- a/lib/propolis/src/attestation/server.rs +++ b/lib/propolis/src/attestation/server.rs @@ -64,6 +64,15 @@ impl AttestationSockInit { let mut vm_conf = vm_attest::VmInstanceConf { uuid, boot_digest: None }; if let Some(volume) = self.volume_ref.take() { + // TODO: load-bearing sleep: we have a Crucible volume, but we can be here and chomping + // at the bit to get a digest calculation started well before the volume has been + // activated; in `propolis-server` we need to wait for at least a subsequent instance + // start. Similar to the scrub task for Crucible disks, delay some number of seconds in + // the hopes that activation is done promptly. + // + // This should be replaced by awaiting for some kind of actual "activated" signal. + tokio::time::sleep(std::time::Duration::from_secs(10)).await; + let boot_digest = match crate::attestation::boot_digest::boot_disk_digest( volume, &self.log, @@ -72,11 +81,12 @@ impl AttestationSockInit { { Ok(digest) => digest, Err(e) => { - slog::error!( - self.log, - "failed to compute boot disk digest: {e:?}" - ); - return; + // a panic here is unfortunate, but helps us debug for now; if the digest + // calculation fails it may be some retryable issue that a guest OS would + // survive. but panicking here means we've stopped Propolis at the actual + // error, rather than noticing the `vm_conf_sender` having dropped + // elsewhere. + panic!("failed to compute boot disk digest: {e:?}"); } }; From 19cfbf7e9b689fde758775f89f5deedcfbcb6f0e Mon Sep 17 00:00:00 2001 From: iximeow Date: Thu, 26 Mar 2026 19:56:02 +0000 Subject: [PATCH 15/37] more debugging --- lib/propolis/src/attestation/boot_digest.rs | 39 +++++++++++++-------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/lib/propolis/src/attestation/boot_digest.rs b/lib/propolis/src/attestation/boot_digest.rs index ad8474877..1249ca801 100644 --- a/lib/propolis/src/attestation/boot_digest.rs +++ b/lib/propolis/src/attestation/boot_digest.rs @@ -22,27 +22,30 @@ pub async fn boot_disk_digest( let block_size = vol.get_block_size().await.expect("could not get volume block size"); - slog::info!( - log, - "starting hash of volume {:?} (total_size={}, block_size={})", - vol_uuid, - vol_size, - block_size - ); let hash_start = Instant::now(); - let end = vol_size; + let end_block = vol_size / block_size; // TODO: it's jank, apparently // copying this I/O sizing from the crucible scrub code let block_count = 131072 / block_size; + slog::info!( + log, + "starting hash of volume {:?} (total_size={}, block_size={} end_block={}, block_count={})", + vol_uuid, + vol_size, + block_size, + end_block, + block_count, + ); + let mut hasher = Sha256::new(); let mut offset = 0; - while offset < end { - let remaining = end - offset; - let this_block_count = block_count.min(remaining); + while offset < end_block { + let remaining_blocks = end_block - offset; + let this_block_count = block_count.min(remaining_blocks); if this_block_count != block_count { slog::info!( log, @@ -52,11 +55,11 @@ pub async fn boot_disk_digest( ); } assert!( - offset + this_block_count <= end, + offset + this_block_count <= end_block, "offset={}, block_count={}, end={}", offset, this_block_count, - end + end_block ); let block = BlockIndex(offset); @@ -64,7 +67,15 @@ pub async fn boot_disk_digest( Buffer::new(this_block_count as usize, block_size as usize); // TODO: should retry on read failures? - vol.read(block, &mut buffer).await?; + let res = vol.read(block, &mut buffer).await; + + if let Err(e) = res { + panic!("read failed: {e:?}. + offset={offset}, + this_block_cout={this_block_count}, + block_size={block_size}, + end_block={end_block}"); + } hasher.update(&*buffer); From d89273b1769d6628072e8e888457f293e446a672 Mon Sep 17 00:00:00 2001 From: iximeow Date: Thu, 26 Mar 2026 21:43:45 +0000 Subject: [PATCH 16/37] restore 4ca28cbe --- lib/propolis/src/attestation/server.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/propolis/src/attestation/server.rs b/lib/propolis/src/attestation/server.rs index 00e71353d..2e572c79e 100644 --- a/lib/propolis/src/attestation/server.rs +++ b/lib/propolis/src/attestation/server.rs @@ -211,7 +211,27 @@ impl AttestationSock { Some(conf) => { info!(log, "vm conf is ready = {:?}", conf); - match rot.lock().unwrap().attest(&conf, &q) { + let rot_guard = rot.lock().unwrap(); + + // very unfortunate: `attest` is a trait of synchronous + // functions, in our case wrapping async calls into + // sled-agent. to make this work, the sled-agent attestor + // holds a runtime handle and will block on async calls + // internally. This all happens in `attest()`. We're calling + // that from an async task, so just directly calling + // `attest()` is a sure-fire way to panic as the contained + // runtime tries to start on this already-in-a-runtime + // thread. + // + // okay. so, it'd be ideal to just give AttestSledAgent our + // runtime and let it `block_on()`. for now, instead, just + // call it in a context it's allowed to do its own + // `block_on()`: + let attest_result = + tokio::task::block_in_place(move || { + rot_guard.attest(&conf, &q) + }); + match attest_result { Ok(a) => vm_attest::Response::Attest(a), Err(e) => { vm_attest::Response::Error(e.to_string()) From 2d0a0e405b325adab8b420cd2c725748294d161b Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Fri, 27 Mar 2026 19:10:08 +0000 Subject: [PATCH 17/37] remove todo file from tree --- 605-todo.md | 77 ----------------------------------------------------- 1 file changed, 77 deletions(-) delete mode 100644 605-todo.md diff --git a/605-todo.md b/605-todo.md deleted file mode 100644 index be334b3cb..000000000 --- a/605-todo.md +++ /dev/null @@ -1,77 +0,0 @@ -# Remaining tasks for 605 propolis-server prototype - -## Status - -as of 2026-03-24: - -* yesterday we discussed putting the attestation server in the instance ensure - handler. this makes sense because there's no instance to attest until that - call happens. presumably a boot disk hashing thread gets kicked off there too. -* my next question: what does the interface between attestation and the boot - digest calculation look like? we need some sort of concurrency primitive. - - -in no particular order: - - -## Miscellaneous - -* what to do if the boot digest is not yet calculated, but we get an attestation - request? - -answer: return an error; there is an Option field on the `attest` config structure - -* what tokio runtime should the attestation server and boot digest etc tasks run - on? there exist: an API runtime for external API requests, a vmm runtime for - handling vmm tasks - - -* should we make the creation of the attestation server happen via a sled-agent - api call, instead of an implicit one by creating a vm? - -initial thoughts: no, probably not. the advantage to this is that it makes it -marginally easier for oxide technicians to turn the behavior off if something -catastrophic happens. but we have made the product decision to offer attestation -in the product regardless. if we find that we need to make it configurable, or -have a horrible bug that makes us want to turn it off, we'll deal with that -later. (keeping in mind the urgency of shipping this now) - - -## Boot digest calculation - -from ixi in chat: - -* how do we properly deal with boot order -* is the following thing out of scope: booting from the boot disk fails, and we - ended up in a secondary guest OS because edk2 kept trying - -thoughts from the call: if there isn't an explicit boot disk provided by the -control plane, we don't have a "boot disk", because ovmf will just try disks in -whatever order. it also doesn't make sense to try a second disk because if the -boot disk failed to boot, we are hosed anyway - -the boot disk is part of the propolis spec - - - - - - - -### Where does the boot digest hash thread get kicked off? - -Current state: the attestation server thread is kicked off by `run_server`, -prior to any inbound API calls - -The boot disk digest thread cannot be kicked off until we have a boot disk, -which means it can only begin after `instance_ensure` - - -## Fetch instance ID from instance properties - - -## - - - - From fea9dbb2a5ad7187b43162697e50d823c7a603b2 Mon Sep 17 00:00:00 2001 From: iximeow Date: Mon, 30 Mar 2026 20:36:49 +0000 Subject: [PATCH 18/37] bump dice-util/vm-attest for AttestAsync this fixes issues (read: panics) related to AttestSledAgent's internal `rt`, block_on, and dropping. --- Cargo.lock | 15 ++++----- Cargo.toml | 4 +-- lib/propolis/src/attestation/server.rs | 43 ++++++++------------------ 3 files changed, 23 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74e95881c..037ddcf5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -270,7 +270,7 @@ dependencies = [ [[package]] name = "attest-data" version = "0.5.0" -source = "git+https://github.com/oxidecomputer/dice-util#ff9f27aa0d6ef6fb64c349890b6e3c242ea3d8fc" +source = "git+https://github.com/oxidecomputer/dice-util?rev=dd6111a7923910c0f8336e3f8454a77f0764d7a3#dd6111a7923910c0f8336e3f8454a77f0764d7a3" dependencies = [ "const-oid", "der", @@ -1741,8 +1741,9 @@ dependencies = [ [[package]] name = "dice-verifier" version = "0.3.0-pre0" -source = "git+https://github.com/oxidecomputer/dice-util#ff9f27aa0d6ef6fb64c349890b6e3c242ea3d8fc" +source = "git+https://github.com/oxidecomputer/dice-util?rev=dd6111a7923910c0f8336e3f8454a77f0764d7a3#dd6111a7923910c0f8336e3f8454a77f0764d7a3" dependencies = [ + "async-trait", "attest-data", "const-oid", "ed25519-dalek", @@ -2147,18 +2148,18 @@ dependencies = [ [[package]] name = "env_filter" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" dependencies = [ "log", ] [[package]] name = "env_logger" -version = "0.11.9" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" dependencies = [ "env_filter", "log", @@ -9950,7 +9951,7 @@ dependencies = [ [[package]] name = "vm-attest" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/vm-attest?rev=a7c2a341866e359a3126aaaa67823ec5097000cd#a7c2a341866e359a3126aaaa67823ec5097000cd" +source = "git+https://github.com/oxidecomputer/vm-attest?rev=750fc59214716f49a4d83db214c43e8005142fa9#750fc59214716f49a4d83db214c43e8005142fa9" dependencies = [ "anyhow", "attest-data", diff --git a/Cargo.toml b/Cargo.toml index fa2c72bf0..054d78f27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,8 +98,8 @@ crucible-client-types = { git = "https://github.com/oxidecomputer/crucible", rev # TODO: pin these to git SHAs # Attestation #dice-verifier = { git = "https://github.com/oxidecomputer/dice-util", branch = "jhendricks/update-sled-agent-types-versions", features = ["sled-agent"] } -dice-verifier = { git = "https://github.com/oxidecomputer/dice-util", features = ["sled-agent"] } -vm-attest = { git = "https://github.com/oxidecomputer/vm-attest", rev = "a7c2a341866e359a3126aaaa67823ec5097000cd", default-features = false } +dice-verifier = { git = "https://github.com/oxidecomputer/dice-util", rev = "dd6111a7923910c0f8336e3f8454a77f0764d7a3", features = ["sled-agent"] } +vm-attest = { git = "https://github.com/oxidecomputer/vm-attest", rev = "750fc59214716f49a4d83db214c43e8005142fa9", default-features = false } # External dependencies anyhow = "1.0" diff --git a/lib/propolis/src/attestation/server.rs b/lib/propolis/src/attestation/server.rs index 2e572c79e..127bd2da9 100644 --- a/lib/propolis/src/attestation/server.rs +++ b/lib/propolis/src/attestation/server.rs @@ -10,13 +10,13 @@ use std::sync::Mutex; use slog::{error, info, Logger}; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::net::{TcpListener, TcpStream}; -use tokio::sync::oneshot; +use tokio::sync::{oneshot, Mutex as TokioMutex}; use tokio::task::JoinHandle; use dice_verifier::sled_agent::AttestSledAgent; -use dice_verifier::Attest; +use dice_verifier::AttestAsync; -use vm_attest::{VmInstanceAttester, VmInstanceConf}; +use vm_attest::VmInstanceConf; use crate::attestation::ATTESTATION_ADDR; @@ -149,7 +149,7 @@ impl AttestationSock { // Handle an incoming connection to the attestation port. async fn handle_conn( log: Logger, - rot: Arc>, + rot: Arc>, vm_conf: Arc>>, conn: TcpStream, ) -> anyhow::Result<()> { @@ -203,35 +203,18 @@ impl AttestationSock { vm_attest::Request::Attest(q) => { slog::debug!(log, "qualifying data received: {q:?}"); - let guard = vm_conf.lock().unwrap(); - let conf = guard.clone(); - drop(guard); + let conf = { + let guard = vm_conf.lock().unwrap(); + guard.to_owned() + }; match conf { Some(conf) => { info!(log, "vm conf is ready = {:?}", conf); - let rot_guard = rot.lock().unwrap(); - - // very unfortunate: `attest` is a trait of synchronous - // functions, in our case wrapping async calls into - // sled-agent. to make this work, the sled-agent attestor - // holds a runtime handle and will block on async calls - // internally. This all happens in `attest()`. We're calling - // that from an async task, so just directly calling - // `attest()` is a sure-fire way to panic as the contained - // runtime tries to start on this already-in-a-runtime - // thread. - // - // okay. so, it'd be ideal to just give AttestSledAgent our - // runtime and let it `block_on()`. for now, instead, just - // call it in a context it's allowed to do its own - // `block_on()`: - let attest_result = - tokio::task::block_in_place(move || { - rot_guard.attest(&conf, &q) - }); - match attest_result { + let rot_guard = rot.lock().await; + + match rot_guard.attest(&conf, &q).await { Ok(a) => vm_attest::Response::Attest(a), Err(e) => { vm_attest::Response::Error(e.to_string()) @@ -276,10 +259,10 @@ impl AttestationSock { info!(log, "attestation server running"); // Attestation requests get to the RoT via sled-agent API endpoints. - let ox_attest: Box = + let ox_attest: Box = Box::new(AttestSledAgent::new(sa_addr, &log)); let rot = - Arc::new(Mutex::new(vm_attest::VmInstanceRot::new(ox_attest))); + Arc::new(TokioMutex::new(vm_attest::VmInstanceRot::new(ox_attest))); let vm_conf = Arc::new(Mutex::new(None)); From 60c8c0486a5c6597aabd3084775ed086d75fed69 Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Mon, 30 Mar 2026 21:54:19 +0000 Subject: [PATCH 19/37] enforce read-only boot disk --- bin/propolis-server/src/lib/initializer.rs | 7 +++++++ lib/propolis/src/attestation/boot_digest.rs | 6 ++++-- lib/propolis/src/block/crucible.rs | 4 ++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/bin/propolis-server/src/lib/initializer.rs b/bin/propolis-server/src/lib/initializer.rs index e97623e47..e3b54c025 100644 --- a/bin/propolis-server/src/lib/initializer.rs +++ b/bin/propolis-server/src/lib/initializer.rs @@ -751,6 +751,13 @@ impl MachineInitializer<'_> { return; } }; + + if !crucible_backend.is_read_only() { + // Disk must be read-only to be used for attestation. + slog::info!(self.log, "boot disk is not read-only"); + return; + } + crucible_backend.clone_volume() } None => { diff --git a/lib/propolis/src/attestation/boot_digest.rs b/lib/propolis/src/attestation/boot_digest.rs index 1249ca801..15806a52f 100644 --- a/lib/propolis/src/attestation/boot_digest.rs +++ b/lib/propolis/src/attestation/boot_digest.rs @@ -70,11 +70,13 @@ pub async fn boot_disk_digest( let res = vol.read(block, &mut buffer).await; if let Err(e) = res { - panic!("read failed: {e:?}. + panic!( + "read failed: {e:?}. offset={offset}, this_block_cout={this_block_count}, block_size={block_size}, - end_block={end_block}"); + end_block={end_block}" + ); } hasher.update(&*buffer); diff --git a/lib/propolis/src/block/crucible.rs b/lib/propolis/src/block/crucible.rs index e783ba71b..5ba04a14b 100644 --- a/lib/propolis/src/block/crucible.rs +++ b/lib/propolis/src/block/crucible.rs @@ -367,6 +367,10 @@ impl CrucibleBackend { pub fn clone_volume(&self) -> Volume { self.state.volume.clone() } + + pub fn is_read_only(&self) -> bool { + self.state.info.read_only + } } #[async_trait::async_trait] From 9efdfb601ba3ff2b79d75dbcabd76024b3a61853 Mon Sep 17 00:00:00 2001 From: iximeow Date: Mon, 30 Mar 2026 22:55:22 +0000 Subject: [PATCH 20/37] rev dice-util and vm-attest further --- Cargo.lock | 6 +++--- Cargo.toml | 4 ++-- lib/propolis/src/attestation/server.rs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 037ddcf5a..61096f203 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -270,7 +270,7 @@ dependencies = [ [[package]] name = "attest-data" version = "0.5.0" -source = "git+https://github.com/oxidecomputer/dice-util?rev=dd6111a7923910c0f8336e3f8454a77f0764d7a3#dd6111a7923910c0f8336e3f8454a77f0764d7a3" +source = "git+https://github.com/oxidecomputer/dice-util?rev=c008287fb6c62654094d9d145ba9d08d90dcf811#c008287fb6c62654094d9d145ba9d08d90dcf811" dependencies = [ "const-oid", "der", @@ -1741,7 +1741,7 @@ dependencies = [ [[package]] name = "dice-verifier" version = "0.3.0-pre0" -source = "git+https://github.com/oxidecomputer/dice-util?rev=dd6111a7923910c0f8336e3f8454a77f0764d7a3#dd6111a7923910c0f8336e3f8454a77f0764d7a3" +source = "git+https://github.com/oxidecomputer/dice-util?rev=c008287fb6c62654094d9d145ba9d08d90dcf811#c008287fb6c62654094d9d145ba9d08d90dcf811" dependencies = [ "async-trait", "attest-data", @@ -9951,7 +9951,7 @@ dependencies = [ [[package]] name = "vm-attest" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/vm-attest?rev=750fc59214716f49a4d83db214c43e8005142fa9#750fc59214716f49a4d83db214c43e8005142fa9" +source = "git+https://github.com/oxidecomputer/vm-attest?rev=732c34fa9bc2749312fcc36b1e9ccff880995a83#732c34fa9bc2749312fcc36b1e9ccff880995a83" dependencies = [ "anyhow", "attest-data", diff --git a/Cargo.toml b/Cargo.toml index 054d78f27..47e0fc87f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,8 +98,8 @@ crucible-client-types = { git = "https://github.com/oxidecomputer/crucible", rev # TODO: pin these to git SHAs # Attestation #dice-verifier = { git = "https://github.com/oxidecomputer/dice-util", branch = "jhendricks/update-sled-agent-types-versions", features = ["sled-agent"] } -dice-verifier = { git = "https://github.com/oxidecomputer/dice-util", rev = "dd6111a7923910c0f8336e3f8454a77f0764d7a3", features = ["sled-agent"] } -vm-attest = { git = "https://github.com/oxidecomputer/vm-attest", rev = "750fc59214716f49a4d83db214c43e8005142fa9", default-features = false } +dice-verifier = { git = "https://github.com/oxidecomputer/dice-util", rev = "c008287fb6c62654094d9d145ba9d08d90dcf811", features = ["sled-agent"] } +vm-attest = { git = "https://github.com/oxidecomputer/vm-attest", rev = "732c34fa9bc2749312fcc36b1e9ccff880995a83", default-features = false } # External dependencies anyhow = "1.0" diff --git a/lib/propolis/src/attestation/server.rs b/lib/propolis/src/attestation/server.rs index 127bd2da9..1c816fe60 100644 --- a/lib/propolis/src/attestation/server.rs +++ b/lib/propolis/src/attestation/server.rs @@ -14,7 +14,7 @@ use tokio::sync::{oneshot, Mutex as TokioMutex}; use tokio::task::JoinHandle; use dice_verifier::sled_agent::AttestSledAgent; -use dice_verifier::AttestAsync; +use dice_verifier::Attest; use vm_attest::VmInstanceConf; @@ -259,7 +259,7 @@ impl AttestationSock { info!(log, "attestation server running"); // Attestation requests get to the RoT via sled-agent API endpoints. - let ox_attest: Box = + let ox_attest: Box = Box::new(AttestSledAgent::new(sa_addr, &log)); let rot = Arc::new(TokioMutex::new(vm_attest::VmInstanceRot::new(ox_attest))); From b137a90f1e3f606ea9bebcbf3d73841e3922d843 Mon Sep 17 00:00:00 2001 From: iximeow Date: Wed, 1 Apr 2026 00:59:49 +0000 Subject: [PATCH 21/37] rev dice-util, vm-attest --- Cargo.lock | 6 +++--- Cargo.toml | 9 ++------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 61096f203..418935f3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -270,7 +270,7 @@ dependencies = [ [[package]] name = "attest-data" version = "0.5.0" -source = "git+https://github.com/oxidecomputer/dice-util?rev=c008287fb6c62654094d9d145ba9d08d90dcf811#c008287fb6c62654094d9d145ba9d08d90dcf811" +source = "git+https://github.com/oxidecomputer/dice-util?rev=1d3084b514389847e8e0f5d966d2be4f18d02d32#1d3084b514389847e8e0f5d966d2be4f18d02d32" dependencies = [ "const-oid", "der", @@ -1741,7 +1741,7 @@ dependencies = [ [[package]] name = "dice-verifier" version = "0.3.0-pre0" -source = "git+https://github.com/oxidecomputer/dice-util?rev=c008287fb6c62654094d9d145ba9d08d90dcf811#c008287fb6c62654094d9d145ba9d08d90dcf811" +source = "git+https://github.com/oxidecomputer/dice-util?rev=1d3084b514389847e8e0f5d966d2be4f18d02d32#1d3084b514389847e8e0f5d966d2be4f18d02d32" dependencies = [ "async-trait", "attest-data", @@ -9951,7 +9951,7 @@ dependencies = [ [[package]] name = "vm-attest" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/vm-attest?rev=732c34fa9bc2749312fcc36b1e9ccff880995a83#732c34fa9bc2749312fcc36b1e9ccff880995a83" +source = "git+https://github.com/oxidecomputer/vm-attest?rev=2cdd17580a4fc6c871d24797016af8dbaac9421d#2cdd17580a4fc6c871d24797016af8dbaac9421d" dependencies = [ "anyhow", "attest-data", diff --git a/Cargo.toml b/Cargo.toml index 47e0fc87f..78124fc4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,11 +95,9 @@ sled-agent-client = { git = "https://github.com/oxidecomputer/omicron", branch = crucible = { git = "https://github.com/oxidecomputer/crucible", rev = "a945a32ba9e1f2098ce3a8963765f1894f37110b" } crucible-client-types = { git = "https://github.com/oxidecomputer/crucible", rev = "a945a32ba9e1f2098ce3a8963765f1894f37110b" } -# TODO: pin these to git SHAs # Attestation -#dice-verifier = { git = "https://github.com/oxidecomputer/dice-util", branch = "jhendricks/update-sled-agent-types-versions", features = ["sled-agent"] } -dice-verifier = { git = "https://github.com/oxidecomputer/dice-util", rev = "c008287fb6c62654094d9d145ba9d08d90dcf811", features = ["sled-agent"] } -vm-attest = { git = "https://github.com/oxidecomputer/vm-attest", rev = "732c34fa9bc2749312fcc36b1e9ccff880995a83", default-features = false } +dice-verifier = { git = "https://github.com/oxidecomputer/dice-util", rev = "1d3084b514389847e8e0f5d966d2be4f18d02d32", features = ["sled-agent"] } +vm-attest = { git = "https://github.com/oxidecomputer/vm-attest", rev = "2cdd17580a4fc6c871d24797016af8dbaac9421d", default-features = false } # External dependencies anyhow = "1.0" @@ -208,6 +206,3 @@ zerocopy = "0.8.25" # [patch."https://github.com/oxidecomputer/crucible"] # crucible = { path = "../crucible/upstairs" } # crucible-client-types = { path = "../crucible/crucible-client-types" } - -#[patch."https://github.com/oxidecomputer/dice-util"] -#dice-verifier = { path = "/home/jordan/src/dice-util/verifier", features = ["sled-agent"] } From cf55c6e2d2fefedbdd26e20626a3167e6e427518 Mon Sep 17 00:00:00 2001 From: iximeow Date: Wed, 1 Apr 2026 04:24:08 +0000 Subject: [PATCH 22/37] shuffle things around to be able to reign in a cancelled init task --- bin/propolis-server/src/lib/initializer.rs | 143 +++++++++++---------- bin/propolis-server/src/lib/vm/ensure.rs | 20 ++- lib/propolis/src/attestation/server.rs | 107 ++++++++++----- 3 files changed, 157 insertions(+), 113 deletions(-) diff --git a/bin/propolis-server/src/lib/initializer.rs b/bin/propolis-server/src/lib/initializer.rs index e3b54c025..dddafa958 100644 --- a/bin/propolis-server/src/lib/initializer.rs +++ b/bin/propolis-server/src/lib/initializer.rs @@ -27,7 +27,6 @@ use oximeter_instruments::kstat::KstatSampler; use propolis::attestation; use propolis::attestation::server::AttestationServerConfig; use propolis::attestation::server::AttestationSock; -use propolis::attestation::server::AttestationSockInit; use propolis::block; use propolis::chardev::{self, BlockingSource, Source}; use propolis::common::{Lifecycle, GB, MB, PAGE_SIZE}; @@ -99,6 +98,12 @@ pub enum MachineInitError { #[error("boot order entry {0:?} does not refer to an attached disk")] BootOrderEntryWithoutDevice(SpecKey), + #[error( + "disk device {device_id:?} refers to a \ + non-existent block backend {backend_id:?}" + )] + DeviceWithoutBlockBackend { device_id: SpecKey, backend_id: SpecKey }, + #[error("boot entry {0:?} refers to a device on non-zero PCI bus {1}")] BootDeviceOnDownstreamPciBus(SpecKey, u8), @@ -488,10 +493,7 @@ impl MachineInitializer<'_> { &mut self, chipset: &RegisteredChipset, attest_cfg: Option, - ) -> Result< - (Option, Option), - MachineInitError, - > { + ) -> Result, MachineInitError> { use propolis::vsock::proxy::VsockPortMapping; // TODO: early return if none? @@ -523,17 +525,17 @@ impl MachineInitializer<'_> { // Spawn attestation server that will go over the vsock if let Some(cfg) = attest_cfg { - let (attest, attest_init) = AttestationSock::new( + let attest = AttestationSock::new( self.log.new(slog::o!("component" => "attestation-server")), cfg.sled_agent_addr, ) .await .map_err(MachineInitError::AttestationServer)?; - return Ok((Some(attest), Some(attest_init))); + return Ok(Some(attest)); } } - Ok((None, None)) + Ok(None) } async fn create_storage_backend_from_spec( @@ -692,85 +694,88 @@ impl MachineInitializer<'_> { /// caller can spawn off `AttestationSockInit::run`. pub fn prepare_rot_initializer( &self, - attest_init: &mut AttestationSockInit, - ) { + vm_rot: &mut AttestationSock, + ) -> Result<(), MachineInitError> { let uuid = self.properties.id; - attest_init.instance_uuid = Some(uuid); - - // The first boot entry is a key into `self.spec.disks`, which is how we'll get to a - // Crucible volume backing this boot option. - // - // TODO: remove this, but for reference: - // > if let Some(spec) = self.spec.disks.et(&boot_entry.device_id) - let boot_disk_entry = self.spec.boot_settings.as_ref() - .and_then(|settings| { + // The first boot entry is a key into `self.spec.disks`, which is how + // we'll get to a Crucible volume backing this boot option. + let boot_disk_entry = + self.spec.boot_settings.as_ref().and_then(|settings| { if settings.order.len() >= 2 { - // In a rack we only configure propolis-server with zero or one boot disks. - // It's possible to provide a fuller list, and in the future the product may - // actually expose such a capability. At that time, we'll need to have a - // reckoning for what "boot disk measurement" from the RoT actually means; it - // probably "should" be "the measurement of the disk that EDK2 decided to boot - // into", but that communication to and from the guest is a little more + // In a rack we only configure propolis-server with zero or + // one boot disks. It's possible to provide a fuller list, + // and in the future the product may actually expose such a + // capability. At that time, we'll need to have a reckoning + // for what "boot disk measurement" from the RoT actually + // means; it probably "should" be "the measurement of the + // disk that EDK2 decided to boot into", but that + // communication to and from the guest is a little more // complicated than we want or need to build out today. // - // Since as the system exists we either have no specific boot disk (and don't - // know where the guest is expected to end up), or one boot disk (and can - // determine which disk to collect a measurement of before even running guest - // firmware), we encode this expectation up front. If the product has changed - // such that this assert is reached, "that's exciting!" and "sorry for crashing - // your Propolis". - panic!("Unsupported VM RoT configuration: more than one boot disk"); + // Since as the system exists we either have no specific + // boot disk (and don't know where the guest is expected to + // end up), or one boot disk (and can determine which disk + // to collect a measurement of before even running guest + // firmware), we encode this expectation up front. If the + // product has changed such that this assert is reached, + // "that's exciting!" and "sorry for crashing your + // Propolis". + panic!( + "Unsupported VM RoT configuration: \ + more than one boot disk" + ); } settings.order.first() }); - if let Some(boot_entry) = boot_disk_entry { - let disk_entry = self.spec.disks.get(&boot_entry.device_id) - .expect("TODO: crosscheck against boot config stuff: boot entry is valid"); + let crucible_volume = if let Some(entry) = boot_disk_entry { + let disk_dev = + self.spec.disks.get(&entry.device_id).ok_or_else(|| { + MachineInitError::BootOrderEntryWithoutDevice( + entry.device_id.clone(), + ) + })?; - let backend_id = match &disk_entry.device_spec { + let backend_id = match &disk_dev.device_spec { spec::StorageDevice::Virtio(disk) => &disk.backend_id, spec::StorageDevice::Nvme(disk) => &disk.backend_id, }; - let volume = match self.block_backends.get(backend_id) { - Some(block_backend) => { - let crucible_backend = match block_backend - .as_any() - .downcast_ref::( - ) { - Some(backend) => backend, - None => { - // Probably fine, just not handled right now. - slog::error!( - self.log, - "boot disk is not a Crucible volume" - ); - return; - } - }; - - if !crucible_backend.is_read_only() { - // Disk must be read-only to be used for attestation. - slog::info!(self.log, "boot disk is not read-only"); - return; - } + let Some(block_backend) = self.block_backends.get(backend_id) + else { + return Err(MachineInitError::DeviceWithoutBlockBackend { + device_id: entry.device_id.to_owned(), + backend_id: backend_id.to_owned(), + }); + }; - crucible_backend.clone_volume() - } - None => { - slog::error!( - self.log, - "boot disk does not name a block backend?!" - ); - return; + if let Some(backend) = + block_backend.as_any().downcast_ref::() + { + if backend.is_read_only() { + Some(backend.clone_volume()) + } else { + // Disk must be read-only to be used for attestation. + slog::info!(self.log, "boot disk is not read-only"); + None } - }; + } else { + // Probably fine, just not handled right now. + slog::warn!( + self.log, + "VM RoT ignoring boot disk: not a Crucible volume" + ); + None + } + } else { + None + }; - attest_init.volume_ref = Some(volume); - } + vm_rot.prepare_instance_conf(uuid, crucible_volume); + + Ok(()) } /// Initializes the storage devices and backends listed in this diff --git a/bin/propolis-server/src/lib/vm/ensure.rs b/bin/propolis-server/src/lib/vm/ensure.rs index d7f791791..8ea5490b5 100644 --- a/bin/propolis-server/src/lib/vm/ensure.rs +++ b/bin/propolis-server/src/lib/vm/ensure.rs @@ -563,7 +563,7 @@ async fn initialize_vm_objects( &properties, ))?; init.initialize_network_devices(&chipset).await?; - let (tcp_attest, attest_init) = + let mut tcp_attest = init.initialize_vsock(&chipset, options.attest_config).await?; #[cfg(feature = "failure-injection")] @@ -579,19 +579,17 @@ async fn initialize_vm_objects( .initialize_storage_devices(&chipset, options.nexus_client.clone()) .await?; - // If we have a VM RoT, that RoT needs to be able to collect some - // information about the guest before it can be actually usable. That - // information collection can - at the moment - happen entirely - // asynchronously. So, prepare the RoT initialization if necessary, then - // spawn it off to run independently. - if let Some(mut attest_init) = attest_init { - init.prepare_rot_initializer(&mut attest_init); - tokio::spawn(attest_init.run()); - } - let ramfb = init.initialize_fwcfg(spec.board.cpus, &options.bootrom_version)?; + // If we have a VM RoT, that RoT needs to be able to collect some + // information about the guest before it can be actually usable. It will do + // that asynchronously, but have to provide references for initial necessary + // VM state. + if let Some(tcp_attest) = tcp_attest.as_mut() { + init.prepare_rot_initializer(tcp_attest)?; + } + init.register_guest_hv_interface(guest_hv_lifecycle); init.initialize_cpus().await?; diff --git a/lib/propolis/src/attestation/server.rs b/lib/propolis/src/attestation/server.rs index 1c816fe60..a089dbc48 100644 --- a/lib/propolis/src/attestation/server.rs +++ b/lib/propolis/src/attestation/server.rs @@ -33,19 +33,33 @@ impl AttestationServerConfig { /// TODO: comment pub struct AttestationSock { + log: slog::Logger, join_hdl: JoinHandle<()>, hup_send: oneshot::Sender<()>, + init_state: AttestationInitState, +} + +#[derive(Debug)] +enum AttestationInitState { + Preparing { + vm_conf_send: oneshot::Sender, + }, + /// A transient state while we're getting the initializer ready, having + /// taken `Preparing` and its `vm_conf_send`, but before we've got a + /// `JoinHandle` to track as running. + Initializing, + Running { + init_task: JoinHandle<()>, + }, } -/// This struct manages providing the requisite data for a corresponding `AttestationSock` to -/// become fully functional. +/// This struct manages providing the requisite data for a corresponding +/// `AttestationSock` to become fully functional. pub struct AttestationSockInit { log: slog::Logger, vm_conf_send: oneshot::Sender, - // kind of terrible: parts of the VM RoT initializer that must be filled in before `run()`. - // this *could* and probably should be more builder-y and typestateful. - pub instance_uuid: Option, - pub volume_ref: Option, + uuid: uuid::Uuid, + volume_ref: Option, } impl AttestationSockInit { @@ -56,14 +70,12 @@ impl AttestationSockInit { /// down while this future is running, nothing will stop this future from operating? We /// probably need to tie in a shutdown signal from the corresponding `AttestationSockInit` to /// discover if we should (for example) stop calculating a boot digest midway. - pub async fn run(mut self) { - let uuid = self - .instance_uuid - .take() - .expect("AttestationSockInit was provided instance uuid"); + pub async fn run(self) { + let AttestationSockInit { log, vm_conf_send, uuid, volume_ref } = self; + let mut vm_conf = vm_attest::VmInstanceConf { uuid, boot_digest: None }; - if let Some(volume) = self.volume_ref.take() { + if let Some(volume) = volume_ref { // TODO: load-bearing sleep: we have a Crucible volume, but we can be here and chomping // at the bit to get a digest calculation started well before the volume has been // activated; in `propolis-server` we need to wait for at least a subsequent instance @@ -75,7 +87,7 @@ impl AttestationSockInit { let boot_digest = match crate::attestation::boot_digest::boot_disk_digest( - volume, &self.log, + volume, &log, ) .await { @@ -92,13 +104,13 @@ impl AttestationSockInit { vm_conf.boot_digest = Some(boot_digest); } else { - slog::warn!(self.log, "not computing boot disk digest"); + slog::warn!(log, "not computing boot disk digest"); } // keep a log reference around to report potential errors after this is taken and dropped // in `provide()`. - let log = self.log.clone(); - let send_res = self.provide(vm_conf); + let log = log.clone(); + let send_res = vm_conf_send.send(vm_conf); if let Err(_) = send_res { slog::error!( log, @@ -106,17 +118,10 @@ impl AttestationSockInit { ); } } - - pub fn provide(self, conf: VmInstanceConf) -> Result<(), VmInstanceConf> { - self.vm_conf_send.send(conf) - } } impl AttestationSock { - pub async fn new( - log: Logger, - sa_addr: SocketAddrV6, - ) -> io::Result<(Self, AttestationSockInit)> { + pub async fn new(log: Logger, sa_addr: SocketAddrV6) -> io::Result { info!(log, "attestation server created"); let listener = TcpListener::bind(ATTESTATION_ADDR).await?; let (vm_conf_send, vm_conf_recv) = @@ -128,22 +133,25 @@ impl AttestationSock { let join_hdl = tokio::spawn(async move { Self::run(log, listener, vm_conf_recv, hup_recv, sa_addr).await; }); - let attestation_sock = Self { join_hdl, hup_send }; - let attestation_init = AttestationSockInit { + let attestation_sock = Self { log: attest_init_log, - vm_conf_send, - instance_uuid: None, - volume_ref: None, + join_hdl, + hup_send, + init_state: AttestationInitState::Preparing { vm_conf_send }, }; - Ok((attestation_sock, attestation_init)) + Ok(attestation_sock) } pub async fn halt(self) { - let Self { join_hdl, hup_send } = self; + let Self { join_hdl, hup_send, init_state, log: _ } = self; // Signal the socket listener to hang up, then wait for it to bail let _ = hup_send.send(()); let _ = join_hdl.await; + + if let AttestationInitState::Running { init_task } = init_state { + init_task.abort(); + } } // Handle an incoming connection to the attestation port. @@ -249,6 +257,34 @@ impl AttestationSock { Ok(()) } + pub fn prepare_instance_conf( + &mut self, + uuid: uuid::Uuid, + volume_ref: Option, + ) { + let init_state = std::mem::replace( + &mut self.init_state, + AttestationInitState::Initializing, + ); + let vm_conf_send = match init_state { + AttestationInitState::Preparing { vm_conf_send } => vm_conf_send, + other => { + panic!( + "VM RoT used incorrectly: prepare_instance_conf called \ + more than once. current state {other:?}" + ); + } + }; + let init = AttestationSockInit { + log: self.log.clone(), + uuid, + volume_ref, + vm_conf_send, + }; + let init_task = tokio::spawn(init.run()); + self.init_state = AttestationInitState::Running { init_task }; + } + pub async fn run( log: Logger, listener: TcpListener, @@ -266,14 +302,19 @@ impl AttestationSock { let vm_conf = Arc::new(Mutex::new(None)); + let log_ref = log.clone(); let vm_conf_cloned = vm_conf.clone(); tokio::spawn(async move { match vm_conf_recv.await { Ok(conf) => { *vm_conf_cloned.lock().unwrap() = Some(conf); } - Err(e) => { - panic!("unexpected loss of boot digest thread: {:?}", e); + Err(_e) => { + slog::info!( + log_ref, + "lost of boot digest sender, \ + hopefully Propolis is stopping" + ); } } }); From 7f84255d12fc983a90098e294e0e4ad6379277d8 Mon Sep 17 00:00:00 2001 From: iximeow Date: Wed, 1 Apr 2026 21:31:00 +0000 Subject: [PATCH 23/37] halt cleanup actually stop the `AttestationSock` when we stop other Propolis devices/backends, and along the way `tcp_attest` -> `attest_handle`. --- bin/propolis-server/src/lib/initializer.rs | 7 +- bin/propolis-server/src/lib/vm/objects.rs | 16 ++--- lib/propolis/src/attestation/server.rs | 74 +++++++++++++--------- 3 files changed, 57 insertions(+), 40 deletions(-) diff --git a/bin/propolis-server/src/lib/initializer.rs b/bin/propolis-server/src/lib/initializer.rs index dddafa958..63a27c7fa 100644 --- a/bin/propolis-server/src/lib/initializer.rs +++ b/bin/propolis-server/src/lib/initializer.rs @@ -689,9 +689,10 @@ impl MachineInitializer<'_> { } } - /// Collect the necessary information out of the VM under construction into the provided - /// `AttestationSocketInit`. This is expected to populate `attest_init` with information so the - /// caller can spawn off `AttestationSockInit::run`. + /// Collect the necessary information out of the VM under construction into + /// the provided `AttestationSocketInit`. This is expected to populate + /// `attest_init` with information so the caller can spawn off + /// `AttestationSockInit::run`. pub fn prepare_rot_initializer( &self, vm_rot: &mut AttestationSock, diff --git a/bin/propolis-server/src/lib/vm/objects.rs b/bin/propolis-server/src/lib/vm/objects.rs index b76569810..994ddadd1 100644 --- a/bin/propolis-server/src/lib/vm/objects.rs +++ b/bin/propolis-server/src/lib/vm/objects.rs @@ -52,7 +52,7 @@ pub(super) struct InputVmObjects { pub com1: Arc>, pub framebuffer: Option>, pub ps2ctrl: Arc, - pub tcp_attest: Option, + pub attest_handle: Option, } /// The collection of objects and state that make up a Propolis instance. @@ -89,12 +89,8 @@ pub(crate) struct VmObjectsLocked { /// A handle to the VM's PS/2 controller. ps2ctrl: Arc, - /// Attestation server. - // - // This is held here only to keep the attestation server *somewhere*, but - // it's never used after being spawned. - #[allow(dead_code)] - tcp_attest: Option, + /// A handle to the VM's attestation server. + attest_handle: Option, } impl VmObjects { @@ -135,7 +131,7 @@ impl VmObjectsLocked { com1: input.com1, framebuffer: input.framebuffer, ps2ctrl: input.ps2ctrl, - tcp_attest: input.tcp_attest, + attest_handle: input.attest_handle, } } @@ -396,6 +392,10 @@ impl VmObjectsLocked { backend.stop().await; backend.attachment().detach(); } + + if let Some(attest_handle) = self.attest_handle.take() { + attest_handle.halt().await; + } } /// Resets a VM's kernel vCPU objects to their initial states. diff --git a/lib/propolis/src/attestation/server.rs b/lib/propolis/src/attestation/server.rs index a089dbc48..fe2986987 100644 --- a/lib/propolis/src/attestation/server.rs +++ b/lib/propolis/src/attestation/server.rs @@ -63,26 +63,24 @@ pub struct AttestationSockInit { } impl AttestationSockInit { - /// Construct a future that does any remaining work of collecting VM RoT measurements in - /// support of this VM's attestation server. - /// - /// TODO: it is expected this future is simply spawned onto a Tokio runtime. If the VM is torn - /// down while this future is running, nothing will stop this future from operating? We - /// probably need to tie in a shutdown signal from the corresponding `AttestationSockInit` to - /// discover if we should (for example) stop calculating a boot digest midway. + /// Do any any remaining work of collecting VM RoT measurements in support + /// of this VM's attestation server. pub async fn run(self) { let AttestationSockInit { log, vm_conf_send, uuid, volume_ref } = self; let mut vm_conf = vm_attest::VmInstanceConf { uuid, boot_digest: None }; if let Some(volume) = volume_ref { - // TODO: load-bearing sleep: we have a Crucible volume, but we can be here and chomping - // at the bit to get a digest calculation started well before the volume has been - // activated; in `propolis-server` we need to wait for at least a subsequent instance - // start. Similar to the scrub task for Crucible disks, delay some number of seconds in - // the hopes that activation is done promptly. + // TODO: load-bearing sleep: we have a Crucible volume, but we can + // be here and chomping at the bit to get a digest calculation + // started well before the volume has been activated; in + // `propolis-server` we need to wait for at least a subsequent + // instance start. Similar to the scrub task for Crucible disks, + // delay some number of seconds in the hopes that activation is done + // promptly. // - // This should be replaced by awaiting for some kind of actual "activated" signal. + // This should be replaced by awaiting for some kind of actual + // "activated" signal. tokio::time::sleep(std::time::Duration::from_secs(10)).await; let boot_digest = @@ -93,11 +91,12 @@ impl AttestationSockInit { { Ok(digest) => digest, Err(e) => { - // a panic here is unfortunate, but helps us debug for now; if the digest - // calculation fails it may be some retryable issue that a guest OS would - // survive. but panicking here means we've stopped Propolis at the actual - // error, rather than noticing the `vm_conf_sender` having dropped - // elsewhere. + // a panic here is unfortunate, but helps us debug for + // now; if the digest calculation fails it may be some + // retryable issue that a guest OS would survive. but + // panicking here means we've stopped Propolis at the + // actual error, rather than noticing the + // `vm_conf_sender` having dropped elsewhere. panic!("failed to compute boot disk digest: {e:?}"); } }; @@ -107,9 +106,6 @@ impl AttestationSockInit { slog::warn!(log, "not computing boot disk digest"); } - // keep a log reference around to report potential errors after this is taken and dropped - // in `provide()`. - let log = log.clone(); let send_res = vm_conf_send.send(vm_conf); if let Err(_) = send_res { slog::error!( @@ -127,8 +123,8 @@ impl AttestationSock { let (vm_conf_send, vm_conf_recv) = oneshot::channel::(); let (hup_send, hup_recv) = oneshot::channel::<()>(); - // TODO: would love to describe this sub-log as specifically VM RoT init, but dunno how to - // drive slog like that. + // TODO: would love to describe this sub-log as specifically VM RoT + // init, but dunno how to drive slog like that. let attest_init_log = log.clone(); let join_hdl = tokio::spawn(async move { Self::run(log, listener, vm_conf_recv, hup_recv, sa_addr).await; @@ -142,6 +138,13 @@ impl AttestationSock { Ok(attestation_sock) } + /// Stop the attestation server and abort in-flight initialization, if any + /// is in progress. + /// + /// We don't worry about stopping any related `handle_conn` because they + /// will discover that one or both ends of the connection are gone soon; we + /// are closing our end, and the guest's side will close when the + /// corresponding virtio-socket device is stopped. pub async fn halt(self) { let Self { join_hdl, hup_send, init_state, log: _ } = self; @@ -154,12 +157,27 @@ impl AttestationSock { } } - // Handle an incoming connection to the attestation port. + /// Handle an incoming connection to the attestation port. async fn handle_conn( log: Logger, rot: Arc>, vm_conf: Arc>>, conn: TcpStream, + ) { + let res = Self::handle_conn_inner(&log, rot, vm_conf, conn).await; + if let Err(e) = res { + slog::error!(log, "handle_conn error: {e}"); + } + } + + /// The actual work of handling an incoming connection. This should only be + /// called from `handle_conn`, and is distinct only for `?`/`Result` + /// ergonomics. + async fn handle_conn_inner( + log: &Logger, + rot: Arc>, + vm_conf: Arc>>, + conn: TcpStream, ) -> anyhow::Result<()> { info!(log, "handling connection"); @@ -336,11 +354,9 @@ impl AttestationSock { let log = log.clone(); let vm_conf = vm_conf.clone(); - tokio::spawn(async move { - if let Err(e) = Self::handle_conn(log.clone(), rot, vm_conf, sock).await { - slog::error!(log, "handle_conn error: {e}"); - } - }); + let handler = Self::handle_conn(log, rot, vm_conf, sock); + tokio::spawn(handler); + } Err(e) => { error!(log, "Attestation TCP listener error: {:?}", e); From 776795ad271c4f19eee6e36c824cfc5f3e938be2 Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Wed, 1 Apr 2026 15:53:13 -0700 Subject: [PATCH 24/37] cleaning up some todos --- lib/propolis/src/attestation/boot_digest.rs | 21 ++++++++++++++++----- lib/propolis/src/attestation/server.rs | 6 +++--- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/propolis/src/attestation/boot_digest.rs b/lib/propolis/src/attestation/boot_digest.rs index 15806a52f..0235d2a15 100644 --- a/lib/propolis/src/attestation/boot_digest.rs +++ b/lib/propolis/src/attestation/boot_digest.rs @@ -26,8 +26,9 @@ pub async fn boot_disk_digest( let end_block = vol_size / block_size; - // TODO: it's jank, apparently - // copying this I/O sizing from the crucible scrub code + // XXX: copying this from the crucible scrub code + // This is so that we can read 128KiB of data on each read, regardless of + // block size. let block_count = 131072 / block_size; slog::info!( @@ -66,7 +67,19 @@ pub async fn boot_disk_digest( let mut buffer = Buffer::new(this_block_count as usize, block_size as usize); - // TODO: should retry on read failures? + // TODO(jph): We don't want to panic in the case of a failed read. How + // should we handle an unresponsive disk? + // + // Options: + // + // * retry indefinitely or some N times + // * you never get an attestation (no boot disk digest) if the hashing + // fails + // * you can still get attestations but without a boot disk digest measurement + // + // Crucible scrub code also inserts a delay between reads. We probably + // don't want to do that but release testing will reveal that, + // hopefully.. let res = vol.read(block, &mut buffer).await; if let Err(e) = res { @@ -82,8 +95,6 @@ pub async fn boot_disk_digest( hasher.update(&*buffer); offset += this_block_count; - - // TODO: delay? } let elapsed = hash_start.elapsed(); diff --git a/lib/propolis/src/attestation/server.rs b/lib/propolis/src/attestation/server.rs index fe2986987..c11d46bca 100644 --- a/lib/propolis/src/attestation/server.rs +++ b/lib/propolis/src/attestation/server.rs @@ -71,6 +71,7 @@ impl AttestationSockInit { let mut vm_conf = vm_attest::VmInstanceConf { uuid, boot_digest: None }; if let Some(volume) = volume_ref { + // TODO(jph): make propolis issue, link to #1078 and add a log line // TODO: load-bearing sleep: we have a Crucible volume, but we can // be here and chomping at the bit to get a digest calculation // started well before the volume has been activated; in @@ -328,9 +329,9 @@ impl AttestationSock { *vm_conf_cloned.lock().unwrap() = Some(conf); } Err(_e) => { - slog::info!( + slog::warn!( log_ref, - "lost of boot digest sender, \ + "lost boot digest sender, \ hopefully Propolis is stopping" ); } @@ -341,7 +342,6 @@ impl AttestationSock { tokio::select! { biased; - // TODO: do we need this _ = &mut hup_recv => { return; }, From 9af75aaa19a9d111c4150ced233669fdecab6de1 Mon Sep 17 00:00:00 2001 From: iximeow Date: Wed, 1 Apr 2026 23:10:02 +0000 Subject: [PATCH 25/37] how had i not rebuilt the server...?? --- bin/propolis-server/src/lib/vm/ensure.rs | 8 ++++---- bin/propolis-server/src/lib/vm/objects.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bin/propolis-server/src/lib/vm/ensure.rs b/bin/propolis-server/src/lib/vm/ensure.rs index 8ea5490b5..c9a669d14 100644 --- a/bin/propolis-server/src/lib/vm/ensure.rs +++ b/bin/propolis-server/src/lib/vm/ensure.rs @@ -563,7 +563,7 @@ async fn initialize_vm_objects( &properties, ))?; init.initialize_network_devices(&chipset).await?; - let mut tcp_attest = + let mut attest_handle = init.initialize_vsock(&chipset, options.attest_config).await?; #[cfg(feature = "failure-injection")] @@ -586,8 +586,8 @@ async fn initialize_vm_objects( // information about the guest before it can be actually usable. It will do // that asynchronously, but have to provide references for initial necessary // VM state. - if let Some(tcp_attest) = tcp_attest.as_mut() { - init.prepare_rot_initializer(tcp_attest)?; + if let Some(attest_handle) = attest_handle.as_mut() { + init.prepare_rot_initializer(attest_handle)?; } init.register_guest_hv_interface(guest_hv_lifecycle); @@ -651,7 +651,7 @@ async fn initialize_vm_objects( com1, framebuffer: Some(ramfb), ps2ctrl, - tcp_attest, + attest_handle, }; // Another really terrible hack. As we've found in Propolis#1008, brk() diff --git a/bin/propolis-server/src/lib/vm/objects.rs b/bin/propolis-server/src/lib/vm/objects.rs index 994ddadd1..49ba61c84 100644 --- a/bin/propolis-server/src/lib/vm/objects.rs +++ b/bin/propolis-server/src/lib/vm/objects.rs @@ -377,7 +377,7 @@ impl VmObjectsLocked { /// Stops all of a VM's devices and detaches its block backends from their /// devices. - async fn halt_devices(&self) { + async fn halt_devices(&mut self) { // Take care not to wedge the runtime with any device halt // implementations which might block. tokio::task::block_in_place(|| { From 60935ca5d8464a45791a63d072e28d27c58ab046 Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Wed, 1 Apr 2026 16:33:25 -0700 Subject: [PATCH 26/37] testing a phd fix --- bin/propolis-server/src/main.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/bin/propolis-server/src/main.rs b/bin/propolis-server/src/main.rs index 8e1f2d8e5..c6d04cc26 100644 --- a/bin/propolis-server/src/main.rs +++ b/bin/propolis-server/src/main.rs @@ -324,17 +324,17 @@ fn main() -> anyhow::Result<()> { propolis_addr.ip(), )?; - // TODO: how will this behave running server in isolation? is it still usable for devs? - let sa_addr = match propolis_addr.ip() { - IpAddr::V4(_) => todo!("no good!"), + let attest_cfg = match propolis_addr.ip() { + IpAddr::V4(_) => None, IpAddr::V6(ipv6_addr) => { let sled_subnet = Ipv6Subnet::< { omicron_common::address::SLED_PREFIX }, >::new(ipv6_addr); - omicron_common::address::get_sled_address(sled_subnet) + omicron_common::address::get_sled_address(sled_subnet); + + Some(AttestationServerConfig::new(sa_addr)) } }; - let attest_config = AttestationServerConfig::new(sa_addr); run_server( bootrom_path, @@ -342,8 +342,7 @@ fn main() -> anyhow::Result<()> { config_dropshot, metric_config, vnc_addr, - // TODO - Some(attest_config), + attest_config, log, ) } From 50c24ff8af2c5dff68bc7f3a6b635f55a4ff0d0f Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Wed, 1 Apr 2026 23:37:32 +0000 Subject: [PATCH 27/37] my turn to not compile propolis-server --- bin/propolis-server/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/propolis-server/src/main.rs b/bin/propolis-server/src/main.rs index c6d04cc26..77b9ba456 100644 --- a/bin/propolis-server/src/main.rs +++ b/bin/propolis-server/src/main.rs @@ -324,13 +324,13 @@ fn main() -> anyhow::Result<()> { propolis_addr.ip(), )?; - let attest_cfg = match propolis_addr.ip() { + let attest_config = match propolis_addr.ip() { IpAddr::V4(_) => None, IpAddr::V6(ipv6_addr) => { let sled_subnet = Ipv6Subnet::< { omicron_common::address::SLED_PREFIX }, >::new(ipv6_addr); - omicron_common::address::get_sled_address(sled_subnet); + let sa_addr = omicron_common::address::get_sled_address(sled_subnet); Some(AttestationServerConfig::new(sa_addr)) } From 014950e47a897be5992bb597e500f980ac87c741 Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Fri, 3 Apr 2026 09:18:18 -0700 Subject: [PATCH 28/37] first round of review feedback: minor things --- bin/propolis-server/src/lib/initializer.rs | 14 +++++++------- bin/propolis-server/src/main.rs | 2 -- lib/propolis/src/attestation/boot_digest.rs | 2 +- lib/propolis/src/attestation/server.rs | 3 --- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/bin/propolis-server/src/lib/initializer.rs b/bin/propolis-server/src/lib/initializer.rs index 63a27c7fa..9c8ca027f 100644 --- a/bin/propolis-server/src/lib/initializer.rs +++ b/bin/propolis-server/src/lib/initializer.rs @@ -496,7 +496,6 @@ impl MachineInitializer<'_> { ) -> Result, MachineInitError> { use propolis::vsock::proxy::VsockPortMapping; - // TODO: early return if none? if let Some(vsock) = &self.spec.vsock { let bdf: pci::Bdf = vsock.spec.pci_path.into(); @@ -506,9 +505,9 @@ impl MachineInitializer<'_> { )]; let guest_cid = GuestCid::try_from(vsock.spec.guest_cid) - .context("guest cid")?; + .context("could not parse guest cid")?; // While the spec does not recommend how large the virtio descriptor - // table should be we sized this appropriately in testing so + // table should be, we sized this appropriately in testing, so // that the guest is able to move vsock packets at a reasonable // throughput without the need to be much larger. let num_queues = 256; @@ -523,7 +522,7 @@ impl MachineInitializer<'_> { self.devices.insert(vsock.id.clone(), device.clone()); chipset.pci_attach(bdf, device); - // Spawn attestation server that will go over the vsock + // Spawn attestation server that will go over the vsock device if let Some(cfg) = attest_cfg { let attest = AttestationSock::new( self.log.new(slog::o!("component" => "attestation-server")), @@ -533,9 +532,10 @@ impl MachineInitializer<'_> { .map_err(MachineInitError::AttestationServer)?; return Ok(Some(attest)); } + } else { + info!(self.log, "no vsock device in instance spec"); + Ok(None) } - - Ok(None) } async fn create_storage_backend_from_spec( @@ -759,7 +759,7 @@ impl MachineInitializer<'_> { Some(backend.clone_volume()) } else { // Disk must be read-only to be used for attestation. - slog::info!(self.log, "boot disk is not read-only"); + slog::info!(self.log, "boot disk is not read-only (and will not be used for attestations)"); None } } else { diff --git a/bin/propolis-server/src/main.rs b/bin/propolis-server/src/main.rs index 77b9ba456..67509d634 100644 --- a/bin/propolis-server/src/main.rs +++ b/bin/propolis-server/src/main.rs @@ -197,8 +197,6 @@ fn run_server( api_runtime.block_on(async { vnc.halt().await }); } - // TODO: clean up attestation server. - result.map_err(|e| anyhow!("Server exited with an error: {e}")) } diff --git a/lib/propolis/src/attestation/boot_digest.rs b/lib/propolis/src/attestation/boot_digest.rs index 0235d2a15..1c79774e8 100644 --- a/lib/propolis/src/attestation/boot_digest.rs +++ b/lib/propolis/src/attestation/boot_digest.rs @@ -26,7 +26,7 @@ pub async fn boot_disk_digest( let end_block = vol_size / block_size; - // XXX: copying this from the crucible scrub code + // XXX(jph): Copied from the crucible scrub code. // This is so that we can read 128KiB of data on each read, regardless of // block size. let block_count = 131072 / block_size; diff --git a/lib/propolis/src/attestation/server.rs b/lib/propolis/src/attestation/server.rs index c11d46bca..c19446e18 100644 --- a/lib/propolis/src/attestation/server.rs +++ b/lib/propolis/src/attestation/server.rs @@ -255,9 +255,6 @@ impl AttestationSock { let response = vm_attest::Response::Error( "VmInstanceConf not ready".to_string(), ); - //let mut response = - //serde_json::to_string(&response)?; - //response.push('\n'); response } } From 71b14da626e4442f9b44becde085ba582d9ddee0 Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Fri, 3 Apr 2026 12:27:20 -0700 Subject: [PATCH 29/37] compiling, my bad --- bin/propolis-server/src/lib/initializer.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bin/propolis-server/src/lib/initializer.rs b/bin/propolis-server/src/lib/initializer.rs index 9c8ca027f..e87449d8f 100644 --- a/bin/propolis-server/src/lib/initializer.rs +++ b/bin/propolis-server/src/lib/initializer.rs @@ -534,8 +534,10 @@ impl MachineInitializer<'_> { } } else { info!(self.log, "no vsock device in instance spec"); - Ok(None) + return Ok(None); } + + Ok(None) } async fn create_storage_backend_from_spec( From 2d8818d1591083080d2d36a0be2ab322bfed9728 Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Fri, 3 Apr 2026 15:57:40 -0700 Subject: [PATCH 30/37] add retries for crucible reads --- bin/propolis-server/src/main.rs | 3 +- lib/propolis/src/attestation/boot_digest.rs | 51 ++++++++++++--------- lib/propolis/src/attestation/server.rs | 10 ++-- 3 files changed, 37 insertions(+), 27 deletions(-) diff --git a/bin/propolis-server/src/main.rs b/bin/propolis-server/src/main.rs index 67509d634..d4c742a14 100644 --- a/bin/propolis-server/src/main.rs +++ b/bin/propolis-server/src/main.rs @@ -328,7 +328,8 @@ fn main() -> anyhow::Result<()> { let sled_subnet = Ipv6Subnet::< { omicron_common::address::SLED_PREFIX }, >::new(ipv6_addr); - let sa_addr = omicron_common::address::get_sled_address(sled_subnet); + let sa_addr = + omicron_common::address::get_sled_address(sled_subnet); Some(AttestationServerConfig::new(sa_addr)) } diff --git a/lib/propolis/src/attestation/boot_digest.rs b/lib/propolis/src/attestation/boot_digest.rs index 1c79774e8..4df64bc67 100644 --- a/lib/propolis/src/attestation/boot_digest.rs +++ b/lib/propolis/src/attestation/boot_digest.rs @@ -8,7 +8,7 @@ use crucible::Buffer; use vm_attest::Measurement; -use anyhow::Result; +use anyhow::{anyhow, Result}; use sha2::{Digest, Sha256}; use slog::Logger; use std::time::Instant; @@ -67,29 +67,38 @@ pub async fn boot_disk_digest( let mut buffer = Buffer::new(this_block_count as usize, block_size as usize); - // TODO(jph): We don't want to panic in the case of a failed read. How - // should we handle an unresponsive disk? + // Read the whole disk. If a read fails, we'll retry a given number of times, but if those + // fail, we return an error to the attestation machinery. It's unlikely that instance is + // doing well in this case, anyway, if it's boot disk is erroring on reads. // - // Options: - // - // * retry indefinitely or some N times - // * you never get an attestation (no boot disk digest) if the hashing - // fails - // * you can still get attestations but without a boot disk digest measurement - // - // Crucible scrub code also inserts a delay between reads. We probably - // don't want to do that but release testing will reveal that, - // hopefully.. - let res = vol.read(block, &mut buffer).await; - - if let Err(e) = res { - panic!( - "read failed: {e:?}. + // XXX(JPH): Crucible scrub code also inserts a delay between reads. We probably + // don't want to do that but we'll see how this goes in production. + let retry_count = 5; + let mut n_retries = 0; + loop { + if n_retries >= retry_count { + slog::error!(log, "failed to read from boot disk {n_retries} tries; aborting boot + digest hash"); + + return Err(anyhow!("could not hash boot disk digest")); + } + + let res = vol.read(block, &mut buffer).await; + + if let Err(e) = res { + slog::error!( + log, + "read failed: {e:?}. offset={offset}, this_block_cout={this_block_count}, block_size={block_size}, end_block={end_block}" - ); + ); + + n_retries += 1; + } else { + break; + } } hasher.update(&*buffer); @@ -100,9 +109,9 @@ pub async fn boot_disk_digest( let elapsed = hash_start.elapsed(); slog::info!( log, - "hash of vol {:?} took {:?} secs", + "hash of volume {:?} took {:?} ms", vol_uuid, - elapsed.as_secs() + elapsed.as_millis() ); Ok(Measurement::Sha256(hasher.finalize().into())) diff --git a/lib/propolis/src/attestation/server.rs b/lib/propolis/src/attestation/server.rs index c19446e18..dd57e3d8d 100644 --- a/lib/propolis/src/attestation/server.rs +++ b/lib/propolis/src/attestation/server.rs @@ -31,7 +31,6 @@ impl AttestationServerConfig { } } -/// TODO: comment pub struct AttestationSock { log: slog::Logger, join_hdl: JoinHandle<()>, @@ -71,7 +70,6 @@ impl AttestationSockInit { let mut vm_conf = vm_attest::VmInstanceConf { uuid, boot_digest: None }; if let Some(volume) = volume_ref { - // TODO(jph): make propolis issue, link to #1078 and add a log line // TODO: load-bearing sleep: we have a Crucible volume, but we can // be here and chomping at the bit to get a digest calculation // started well before the volume has been activated; in @@ -82,6 +80,8 @@ impl AttestationSockInit { // // This should be replaced by awaiting for some kind of actual // "activated" signal. + // + // see #1078 tokio::time::sleep(std::time::Duration::from_secs(10)).await; let boot_digest = @@ -167,7 +167,7 @@ impl AttestationSock { ) { let res = Self::handle_conn_inner(&log, rot, vm_conf, conn).await; if let Err(e) = res { - slog::error!(log, "handle_conn error: {e}"); + slog::error!(log, "error handling attestation server connection: {e}"); } } @@ -180,7 +180,7 @@ impl AttestationSock { vm_conf: Arc>>, conn: TcpStream, ) -> anyhow::Result<()> { - info!(log, "handling connection"); + info!(log, "handling attestation connection"); let mut msg = String::new(); @@ -356,7 +356,7 @@ impl AttestationSock { } Err(e) => { - error!(log, "Attestation TCP listener error: {:?}", e); + error!(log, "attestation TCP listener error: {:?}", e); } } }, From 38cb2347eabc0e8e83236254488bf63f5eba1c9f Mon Sep 17 00:00:00 2001 From: Jordan Hendricks Date: Sun, 5 Apr 2026 15:06:45 -0700 Subject: [PATCH 31/37] nits from eliza (ty!) Co-authored-by: Eliza Weisman --- bin/propolis-server/src/lib/initializer.rs | 5 ++++- crates/propolis-config-toml/src/spec.rs | 1 - lib/propolis/src/attestation/boot_digest.rs | 12 ++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/bin/propolis-server/src/lib/initializer.rs b/bin/propolis-server/src/lib/initializer.rs index e87449d8f..bbbb9813e 100644 --- a/bin/propolis-server/src/lib/initializer.rs +++ b/bin/propolis-server/src/lib/initializer.rs @@ -761,7 +761,10 @@ impl MachineInitializer<'_> { Some(backend.clone_volume()) } else { // Disk must be read-only to be used for attestation. - slog::info!(self.log, "boot disk is not read-only (and will not be used for attestations)"); + slog::info!( + self.log, + "boot disk is not read-only (and will not be used for attestations)", + ); None } } else { diff --git a/crates/propolis-config-toml/src/spec.rs b/crates/propolis-config-toml/src/spec.rs index 5b8043bb8..2b6371892 100644 --- a/crates/propolis-config-toml/src/spec.rs +++ b/crates/propolis-config-toml/src/spec.rs @@ -446,7 +446,6 @@ fn parse_vsock_from_config( name: &str, device: &super::Device, ) -> Result { - eprintln!("{:?}", device); let guest_cid = device .get("guest_cid") .ok_or_else(|| TomlToSpecError::NoVsockGuestCid(name.to_owned()))?; diff --git a/lib/propolis/src/attestation/boot_digest.rs b/lib/propolis/src/attestation/boot_digest.rs index 4df64bc67..3a4c4fcb1 100644 --- a/lib/propolis/src/attestation/boot_digest.rs +++ b/lib/propolis/src/attestation/boot_digest.rs @@ -33,12 +33,12 @@ pub async fn boot_disk_digest( slog::info!( log, - "starting hash of volume {:?} (total_size={}, block_size={} end_block={}, block_count={})", - vol_uuid, - vol_size, - block_size, - end_block, - block_count, + "starting hash of volume"; + "volume_id" => %vol_uuid,, + "volume_size" => vol_size, + "block_size" => block_size, + "end_block" => end_block, + "block_count" => block_count, ); let mut hasher = Sha256::new(); From c096720f1907f9b764c509ae18ffbb5303a654cf Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Mon, 6 Apr 2026 10:54:10 -0700 Subject: [PATCH 32/37] final bits of review feedback, comments, add sleep between crucible failed reads --- lib/propolis/src/attestation/boot_digest.rs | 59 +++++++++++---------- lib/propolis/src/attestation/mod.rs | 39 ++++++++++++-- lib/propolis/src/attestation/server.rs | 20 ++++--- 3 files changed, 80 insertions(+), 38 deletions(-) diff --git a/lib/propolis/src/attestation/boot_digest.rs b/lib/propolis/src/attestation/boot_digest.rs index 3a4c4fcb1..32e857f51 100644 --- a/lib/propolis/src/attestation/boot_digest.rs +++ b/lib/propolis/src/attestation/boot_digest.rs @@ -10,9 +10,11 @@ use vm_attest::Measurement; use anyhow::{anyhow, Result}; use sha2::{Digest, Sha256}; -use slog::Logger; -use std::time::Instant; +use slog::{error, info, Logger}; +use std::time::{Duration, Instant}; +/// Find the SHA256 sum of a crucible volume. This should be from a read-only +/// disk; otherwise, this isn't a reliable hash. pub async fn boot_disk_digest( vol: crucible::Volume, log: &Logger, @@ -21,20 +23,17 @@ pub async fn boot_disk_digest( let vol_size = vol.total_size().await.expect("could not get volume size"); let block_size = vol.get_block_size().await.expect("could not get volume block size"); - - let hash_start = Instant::now(); - let end_block = vol_size / block_size; + let hash_start = Instant::now(); - // XXX(jph): Copied from the crucible scrub code. - // This is so that we can read 128KiB of data on each read, regardless of - // block size. + // XXX(jph): This was copied from the crucible scrub code, so that we can + // read 128KiB of data on each read, regardless of block size. let block_count = 131072 / block_size; - slog::info!( + info!( log, "starting hash of volume"; - "volume_id" => %vol_uuid,, + "volume_id" => %vol_uuid, "volume_size" => vol_size, "block_size" => block_size, "end_block" => end_block, @@ -42,13 +41,12 @@ pub async fn boot_disk_digest( ); let mut hasher = Sha256::new(); - let mut offset = 0; while offset < end_block { let remaining_blocks = end_block - offset; let this_block_count = block_count.min(remaining_blocks); if this_block_count != block_count { - slog::info!( + info!( log, "adjusting block_count to {} at offset {}", this_block_count, @@ -67,18 +65,21 @@ pub async fn boot_disk_digest( let mut buffer = Buffer::new(this_block_count as usize, block_size as usize); - // Read the whole disk. If a read fails, we'll retry a given number of times, but if those - // fail, we return an error to the attestation machinery. It's unlikely that instance is - // doing well in this case, anyway, if it's boot disk is erroring on reads. + // Read the whole disk and hash it. // - // XXX(JPH): Crucible scrub code also inserts a delay between reads. We probably - // don't want to do that but we'll see how this goes in production. + // If an individual read call fails, we'll retry some number of times, + // but if that fails, just return an error to the attestation server. + // If reads are failing on the boot disk, it's unlikely the instance is + // doing well anyway, so there's not much to do here. let retry_count = 5; let mut n_retries = 0; loop { if n_retries >= retry_count { - slog::error!(log, "failed to read from boot disk {n_retries} tries; aborting boot - digest hash"); + error!( + log, + "failed to read boot disk in {n_retries} tries \ + aborting hash of boot digest" + ); return Err(anyhow!("could not hash boot disk digest")); } @@ -86,28 +87,30 @@ pub async fn boot_disk_digest( let res = vol.read(block, &mut buffer).await; if let Err(e) = res { - slog::error!( - log, - "read failed: {e:?}. - offset={offset}, - this_block_cout={this_block_count}, - block_size={block_size}, - end_block={end_block}" + error!(log, + "read failed: {e:?}"; + "retry_count" => retry_count, + "io_offset" => offset, + "this_block_count" => this_block_count, + "block_size" => block_size, + "end_block" => end_block, ); + let delay = 1; + error!(log, "will retry in {delay} secs"); n_retries += 1; + tokio::time::sleep(Duration::from_secs(delay)).await; } else { break; } } hasher.update(&*buffer); - offset += this_block_count; } let elapsed = hash_start.elapsed(); - slog::info!( + info!( log, "hash of volume {:?} took {:?} ms", vol_uuid, diff --git a/lib/propolis/src/attestation/mod.rs b/lib/propolis/src/attestation/mod.rs index 1ff188b41..c198be0e3 100644 --- a/lib/propolis/src/attestation/mod.rs +++ b/lib/propolis/src/attestation/mod.rs @@ -2,10 +2,43 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -//! TODO: block comment +//! RFD 605: VM Attestation //! -//! - explain vm conf structure: this defines additional data tying the guest challenge (qualifying -//! data) to the instance. Currently it's definition is in the vm_attest crate. +//! +//! INSTANCE IDENTITY DATA: +//! +//! Our MVP includes the following identity data for an instance: +//! +//! * boot digest, aka SHA256 hash of the boot disk specified for the instance +//! (iff the instance has a boot disk, and that boot disk is read-only) +//! * instance UUID +//! +//! If there is no boot disk, or the boot disk is not read-only, only the +//! instance ID is used as identifying data. +//! +//! If there is a read-only disk, the attestation server will fail challenge +//! requests from guest until the boot disk has been hashed. +//! +//! +//! HIGH-LEVEL DESIGN: +//! +//! The following assumes that the instance has a vsock device configured. +//! (If there is no vsock device, there will be no attestation server listening +//! there.) +//! +//! * Guest software submits a 32-byte nonce to a known attestation port. +//! * This port is backed by a vsock device in propolis. +//! * When the instance is created (via `instance_ensure` for propolis-server), +//! a tokio task begins to hash the boot disk of the instance (assuming that +//! a boot disk is specified and that it is read-only.) +//! * The attestation server waits on a tokio oneshot channel for the "VM conf", +//! a structure containing data relevant to instance identity. This conf is +//! sent to the attestation server once all of the VM identity data is done +//! (so, in practice, when the boot disk is hashed). +//! * Until the VM conf is ready, the attestation server fails challenges. +//! Once the VM conf is ready, these challenges are passed through to the +//! sled-agent RoT APIs via the vm_attest crate, and those results are +//! propagated back to the user. //! use std::net::{IpAddr, Ipv4Addr, SocketAddr}; diff --git a/lib/propolis/src/attestation/server.rs b/lib/propolis/src/attestation/server.rs index dd57e3d8d..e01e392ec 100644 --- a/lib/propolis/src/attestation/server.rs +++ b/lib/propolis/src/attestation/server.rs @@ -7,7 +7,7 @@ use std::net::SocketAddrV6; use std::sync::Arc; use std::sync::Mutex; -use slog::{error, info, Logger}; +use slog::{error, info, o, Logger}; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::net::{TcpListener, TcpStream}; use tokio::sync::{oneshot, Mutex as TokioMutex}; @@ -119,14 +119,16 @@ impl AttestationSockInit { impl AttestationSock { pub async fn new(log: Logger, sa_addr: SocketAddrV6) -> io::Result { - info!(log, "attestation server created"); + info!(log, "attestation server created (sled-agent addr {:?}", sa_addr); + let listener = TcpListener::bind(ATTESTATION_ADDR).await?; let (vm_conf_send, vm_conf_recv) = oneshot::channel::(); let (hup_send, hup_recv) = oneshot::channel::<()>(); + // TODO: would love to describe this sub-log as specifically VM RoT // init, but dunno how to drive slog like that. - let attest_init_log = log.clone(); + let attest_init_log = log.new(o!("component" => "attestation-server")); let join_hdl = tokio::spawn(async move { Self::run(log, listener, vm_conf_recv, hup_recv, sa_addr).await; }); @@ -167,7 +169,10 @@ impl AttestationSock { ) { let res = Self::handle_conn_inner(&log, rot, vm_conf, conn).await; if let Err(e) = res { - slog::error!(log, "error handling attestation server connection: {e}"); + slog::error!( + log, + "error handling attestation server connection: {e}" + ); } } @@ -180,7 +185,7 @@ impl AttestationSock { vm_conf: Arc>>, conn: TcpStream, ) -> anyhow::Result<()> { - info!(log, "handling attestation connection"); + info!(log, "handling attestation request"); let mut msg = String::new(); @@ -344,14 +349,15 @@ impl AttestationSock { }, sock_res = listener.accept() => { - info!(log, "new client connected"); + info!(log, "new attestation client connected"); match sock_res { Ok((sock, _addr)) => { let rot = rot.clone(); let log = log.clone(); let vm_conf = vm_conf.clone(); - let handler = Self::handle_conn(log, rot, vm_conf, sock); + let handler = Self::handle_conn(log, rot, vm_conf, + sock); tokio::spawn(handler); } From e6bbd3adef4a3fb421a0ee3a578d0f6a6772740b Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Mon, 6 Apr 2026 12:04:32 -0700 Subject: [PATCH 33/37] clean up log todo --- lib/propolis/src/attestation/server.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/propolis/src/attestation/server.rs b/lib/propolis/src/attestation/server.rs index e01e392ec..f9fdffa04 100644 --- a/lib/propolis/src/attestation/server.rs +++ b/lib/propolis/src/attestation/server.rs @@ -126,11 +126,10 @@ impl AttestationSock { oneshot::channel::(); let (hup_send, hup_recv) = oneshot::channel::<()>(); - // TODO: would love to describe this sub-log as specifically VM RoT - // init, but dunno how to drive slog like that. let attest_init_log = log.new(o!("component" => "attestation-server")); + let attest_log_clone = attest_init_log.clone(); let join_hdl = tokio::spawn(async move { - Self::run(log, listener, vm_conf_recv, hup_recv, sa_addr).await; + Self::run(attest_log_clone, listener, vm_conf_recv, hup_recv, sa_addr).await; }); let attestation_sock = Self { log: attest_init_log, @@ -274,7 +273,7 @@ impl AttestationSock { msg.clear(); } - info!(log, "handle_conn: ALL DONE"); + info!(log, "attestation request completed"); Ok(()) } From 1ff4e3e7b80f956c57763786c82a27afadf3c68d Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Mon, 6 Apr 2026 12:12:42 -0700 Subject: [PATCH 34/37] hopefully resolve merge conflict with master --- Cargo.lock | 259 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 222 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 418935f3e..f1f36cdec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2475,9 +2475,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -2490,9 +2490,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -2506,9 +2506,9 @@ checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -2517,15 +2517,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -2534,21 +2534,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -2558,7 +2558,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -2690,11 +2689,24 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.2.0", "wasip2", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + [[package]] name = "gfss" version = "0.1.0" @@ -3191,14 +3203,13 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", - "futures-core", "futures-util", "http", "http-body", @@ -3228,7 +3239,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core", + "windows-core 0.62.2", ] [[package]] @@ -3358,6 +3369,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "id-map" version = "0.1.0" @@ -3897,6 +3914,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" version = "0.2.180" @@ -6794,6 +6817,12 @@ version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "radium" version = "0.7.0" @@ -8559,9 +8588,9 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ "bitflags 2.9.4", "core-foundation 0.9.4", @@ -8963,9 +8992,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", @@ -9871,11 +9900,11 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.19.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.2", "js-sys", "serde_core", "wasm-bindgen", @@ -10052,6 +10081,15 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasite" version = "0.1.0" @@ -10117,6 +10155,28 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.13.0", + "wasm-encoder", + "wasmparser", +] + [[package]] name = "wasm-streams" version = "0.4.0" @@ -10143,6 +10203,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.9.4", + "hashbrown 0.15.2", + "indexmap 2.13.0", + "semver 1.0.27", +] + [[package]] name = "web-sys" version = "0.3.85" @@ -10316,7 +10388,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" dependencies = [ "windows-collections", - "windows-core", + "windows-core 0.61.2", "windows-future", "windows-link 0.1.1", "windows-numerics", @@ -10328,7 +10400,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core", + "windows-core 0.61.2", ] [[package]] @@ -10340,8 +10412,21 @@ dependencies = [ "windows-implement", "windows-interface", "windows-link 0.1.1", - "windows-result", - "windows-strings", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -10350,7 +10435,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link 0.1.1", "windows-threading", ] @@ -10395,7 +10480,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link 0.1.1", ] @@ -10406,8 +10491,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820" dependencies = [ "windows-link 0.1.1", - "windows-result", - "windows-strings", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] @@ -10419,6 +10504,15 @@ dependencies = [ "windows-link 0.1.1", ] +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-strings" version = "0.4.2" @@ -10428,6 +10522,15 @@ dependencies = [ "windows-link 0.1.1", ] +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -10766,6 +10869,88 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap 2.13.0", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.9.4", + "indexmap 2.13.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.0", + "log", + "semver 1.0.27", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "write16" From 786ef2713dd9091673735af1343a5413e0b18991 Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Mon, 6 Apr 2026 12:23:34 -0700 Subject: [PATCH 35/37] more eliza review feedback --- lib/propolis/src/attestation/boot_digest.rs | 2 +- lib/propolis/src/attestation/mod.rs | 36 ++++++++++----------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/propolis/src/attestation/boot_digest.rs b/lib/propolis/src/attestation/boot_digest.rs index 32e857f51..f6d9b7c13 100644 --- a/lib/propolis/src/attestation/boot_digest.rs +++ b/lib/propolis/src/attestation/boot_digest.rs @@ -78,7 +78,7 @@ pub async fn boot_disk_digest( error!( log, "failed to read boot disk in {n_retries} tries \ - aborting hash of boot digest" + aborting hash of boot digest" ); return Err(anyhow!("could not hash boot disk digest")); diff --git a/lib/propolis/src/attestation/mod.rs b/lib/propolis/src/attestation/mod.rs index c198be0e3..c606389f7 100644 --- a/lib/propolis/src/attestation/mod.rs +++ b/lib/propolis/src/attestation/mod.rs @@ -2,10 +2,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -//! RFD 605: VM Attestation +//! # RFD 605: VM Attestation //! //! -//! INSTANCE IDENTITY DATA: +//! ## Instance Identity Data //! //! Our MVP includes the following identity data for an instance: //! @@ -16,29 +16,29 @@ //! If there is no boot disk, or the boot disk is not read-only, only the //! instance ID is used as identifying data. //! -//! If there is a read-only disk, the attestation server will fail challenge -//! requests from guest until the boot disk has been hashed. +//! If there is a read-only boot disk, the attestation server will fail +//! challenge requests from guest until the boot disk has been hashed. //! //! -//! HIGH-LEVEL DESIGN: +//! ## High-Level Design //! //! The following assumes that the instance has a vsock device configured. //! (If there is no vsock device, there will be no attestation server listening //! there.) //! -//! * Guest software submits a 32-byte nonce to a known attestation port. -//! * This port is backed by a vsock device in propolis. -//! * When the instance is created (via `instance_ensure` for propolis-server), -//! a tokio task begins to hash the boot disk of the instance (assuming that -//! a boot disk is specified and that it is read-only.) -//! * The attestation server waits on a tokio oneshot channel for the "VM conf", -//! a structure containing data relevant to instance identity. This conf is -//! sent to the attestation server once all of the VM identity data is done -//! (so, in practice, when the boot disk is hashed). -//! * Until the VM conf is ready, the attestation server fails challenges. -//! Once the VM conf is ready, these challenges are passed through to the -//! sled-agent RoT APIs via the vm_attest crate, and those results are -//! propagated back to the user. +//! - Guest software submits a 32-byte nonce to a known attestation port. +//! - This port is backed by a vsock device in propolis. +//! - When the instance is created (via `instance_ensure`), a tokio task +//! begins to hash the boot disk of the instance (assuming that a boot disk +//! is specified and that it is read-only.) +//! - The attestation server waits on a tokio oneshot channel for the +//! "VM conf", a structure containing data relevant to instance identity. +//! This conf is sent to the attestation server once all of the VM identity +//! data is done (so, in practice, when the boot disk is hashed). +//! - Until the VM conf is ready, the attestation server fails challenges. +//! - Once the VM conf is ready, these challenges are passed through to the +//! sled-agent RoT APIs via the vm_attest crate, and those results are +//! propagated back to the user. //! use std::net::{IpAddr, Ipv4Addr, SocketAddr}; From 0f843dddd458afbd5ee39db9efa862f50d0ad56f Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Mon, 6 Apr 2026 12:34:42 -0700 Subject: [PATCH 36/37] final bits of review feedback? --- lib/propolis/src/attestation/boot_digest.rs | 12 ++++-------- lib/propolis/src/attestation/server.rs | 9 ++++++++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/propolis/src/attestation/boot_digest.rs b/lib/propolis/src/attestation/boot_digest.rs index f6d9b7c13..fbbd9dfea 100644 --- a/lib/propolis/src/attestation/boot_digest.rs +++ b/lib/propolis/src/attestation/boot_digest.rs @@ -10,7 +10,7 @@ use vm_attest::Measurement; use anyhow::{anyhow, Result}; use sha2::{Digest, Sha256}; -use slog::{error, info, Logger}; +use slog::{error, info, o, Logger}; use std::time::{Duration, Instant}; /// Find the SHA256 sum of a crucible volume. This should be from a read-only @@ -26,6 +26,8 @@ pub async fn boot_disk_digest( let end_block = vol_size / block_size; let hash_start = Instant::now(); + let log = log.new(o!("volume_id" => vol_uuid.to_string())); + // XXX(jph): This was copied from the crucible scrub code, so that we can // read 128KiB of data on each read, regardless of block size. let block_count = 131072 / block_size; @@ -33,7 +35,6 @@ pub async fn boot_disk_digest( info!( log, "starting hash of volume"; - "volume_id" => %vol_uuid, "volume_size" => vol_size, "block_size" => block_size, "end_block" => end_block, @@ -110,12 +111,7 @@ pub async fn boot_disk_digest( } let elapsed = hash_start.elapsed(); - info!( - log, - "hash of volume {:?} took {:?} ms", - vol_uuid, - elapsed.as_millis() - ); + info!(log, "hash of volume took {:?} ms", elapsed.as_millis()); Ok(Measurement::Sha256(hasher.finalize().into())) } diff --git a/lib/propolis/src/attestation/server.rs b/lib/propolis/src/attestation/server.rs index f9fdffa04..ebcd6df9e 100644 --- a/lib/propolis/src/attestation/server.rs +++ b/lib/propolis/src/attestation/server.rs @@ -129,7 +129,14 @@ impl AttestationSock { let attest_init_log = log.new(o!("component" => "attestation-server")); let attest_log_clone = attest_init_log.clone(); let join_hdl = tokio::spawn(async move { - Self::run(attest_log_clone, listener, vm_conf_recv, hup_recv, sa_addr).await; + Self::run( + attest_log_clone, + listener, + vm_conf_recv, + hup_recv, + sa_addr, + ) + .await; }); let attestation_sock = Self { log: attest_init_log, From a42814d07e90928408d7e6b162144fafe2749c19 Mon Sep 17 00:00:00 2001 From: Jordan Paige Hendricks Date: Mon, 6 Apr 2026 13:21:11 -0700 Subject: [PATCH 37/37] fix clippy CI job --- xtask/src/task_clippy.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/xtask/src/task_clippy.rs b/xtask/src/task_clippy.rs index d0fe2e1a7..377c19132 100644 --- a/xtask/src/task_clippy.rs +++ b/xtask/src/task_clippy.rs @@ -44,14 +44,17 @@ pub(crate) fn cmd_clippy(strict: bool, quiet: bool) -> Result<()> { run_clippy(&["-p", "propolis-server", "--features", "omicron-build"])?; // Check the Falcon bits - failed |= run_clippy(&[ - "--features", - "falcon", - "-p", - "propolis-server", - "-p", - "propolis-client", - ])?; + // + // TODO(jph): Currently specifying both the propolis-client and + // propolis-server packages in a single clippy command will cause clippy + // to fail because cargo finds 2 copies of propolis-client. This is because + // dice-util depends on sled-agent-client, which depends on a rev of + // propolis. + // + // We should clean this up by making sled-agent-client not re-export + // propolis-client.. + failed |= run_clippy(&["--features", "falcon", "-p", "propolis-server"])?; + failed |= run_clippy(&["-p", "propolis-client"])?; // Check the mock server failed |= run_clippy(&["-p", "propolis-mock-server"])?;