From 65fa4d1a48d6555ea7c3a50292d976cd04ecb61a Mon Sep 17 00:00:00 2001 From: Laura Abbott Date: Wed, 1 May 2024 10:36:15 -0400 Subject: [PATCH] Bump gateway-messages and incorporate new RoT info The new RoT information includes details about stage0 (RoT bootloader) --- Cargo.lock | 208 +++------------ Cargo.toml | 4 +- clients/gateway-client/src/lib.rs | 1 + dev-tools/omdb/src/bin/omdb/mgs.rs | 91 ++++++- dev-tools/omdb/tests/successes.out | 80 ++++-- .../configs/sp_sim_config.test.toml | 10 +- gateway/src/http_entrypoints.rs | 107 +++++++- gateway/src/http_entrypoints/conversions.rs | 249 ++++++++++++++--- nexus/db-model/src/inventory.rs | 151 +++++++++++ nexus/db-model/src/schema.rs | 7 + nexus/db-model/src/schema_versions.rs | 3 +- .../db-queries/src/db/datastore/inventory.rs | 34 +++ nexus/inventory/src/builder.rs | 48 +++- nexus/inventory/src/collector.rs | 2 + nexus/inventory/src/examples.rs | 10 +- .../tests/output/collector_basic.txt | 24 +- .../tests/output/collector_errors.txt | 24 +- .../output/collector_sled_agent_errors.txt | 24 +- nexus/reconfigurator/planning/src/system.rs | 123 ++++++--- nexus/types/src/inventory.rs | 10 + openapi/gateway.json | 183 ++++++++++++- openapi/wicketd.json | 125 ++++++++- .../tests/output/self-stat-schema.json | 4 +- schema/crdb/dbinit.sql | 29 +- schema/crdb/expose-stage0/up01.sql | 17 ++ schema/crdb/expose-stage0/up02.sql | 8 + schema/crdb/expose-stage0/up03.sql | 2 + schema/crdb/expose-stage0/up04.sql | 2 + smf/sp-sim/config.toml | 4 +- sp-sim/examples/config.toml | 5 +- sp-sim/src/config.rs | 7 + sp-sim/src/gimlet.rs | 215 +++++++++++++-- sp-sim/src/lib.rs | 1 + sp-sim/src/sidecar.rs | 173 ++++++++++-- wicket/src/ui/panes/overview.rs | 250 +++++++++++++++++- wicketd/src/inventory.rs | 4 + wicketd/src/mgs/inventory.rs | 67 ++++- workspace-hack/Cargo.toml | 5 +- 38 files changed, 1948 insertions(+), 363 deletions(-) create mode 100644 schema/crdb/expose-stage0/up01.sql create mode 100644 schema/crdb/expose-stage0/up02.sql create mode 100644 schema/crdb/expose-stage0/up03.sql create mode 100644 schema/crdb/expose-stage0/up04.sql diff --git a/Cargo.lock b/Cargo.lock index 4f4fa019c19..8b73743ef35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -358,7 +358,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "serde_tokenstream 0.2.0", + "serde_tokenstream", "syn 2.0.64", ] @@ -1584,7 +1584,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "serde_tokenstream 0.2.0", + "serde_tokenstream", "syn 2.0.64", ] @@ -1776,7 +1776,7 @@ source = "git+https://github.com/oxidecomputer/diesel-dtrace?branch=main#62ef5ca dependencies = [ "diesel", "serde", - "usdt 0.5.0", + "usdt", "uuid", "version_check", ] @@ -1956,16 +1956,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" -[[package]] -name = "dof" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e6b21a1211455e82b1245d6e1b024f30606afbb734c114515d40d0e0b34ce81" -dependencies = [ - "thiserror", - "zerocopy 0.3.2", -] - [[package]] name = "dof" version = "0.3.0" @@ -2052,7 +2042,7 @@ dependencies = [ "tokio", "tokio-rustls 0.25.0", "toml 0.8.13", - "usdt 0.5.0", + "usdt", "uuid", "version_check", "waitgroup", @@ -2066,21 +2056,10 @@ dependencies = [ "proc-macro2", "quote", "serde", - "serde_tokenstream 0.2.0", + "serde_tokenstream", "syn 2.0.64", ] -[[package]] -name = "dtrace-parser" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bed110893a7f9f4ceb072e166354a09f6cb4cc416eec5b5e5e8ee367442d434b" -dependencies = [ - "pest", - "pest_derive", - "thiserror", -] - [[package]] name = "dtrace-parser" version = "0.2.0" @@ -2691,9 +2670,9 @@ dependencies = [ [[package]] name = "gateway-messages" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/management-gateway-service?rev=2739c18e80697aa6bc235c935176d14b4d757ee9#2739c18e80697aa6bc235c935176d14b4d757ee9" +source = "git+https://github.com/oxidecomputer/management-gateway-service?rev=c85a4ca043aaa389df12aac5348d8a3feda28762#c85a4ca043aaa389df12aac5348d8a3feda28762" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "hubpack 0.1.2", "serde", "serde_repr", @@ -2707,7 +2686,7 @@ dependencies = [ [[package]] name = "gateway-sp-comms" version = "0.1.1" -source = "git+https://github.com/oxidecomputer/management-gateway-service?rev=2739c18e80697aa6bc235c935176d14b4d757ee9#2739c18e80697aa6bc235c935176d14b4d757ee9" +source = "git+https://github.com/oxidecomputer/management-gateway-service?rev=c85a4ca043aaa389df12aac5348d8a3feda28762#c85a4ca043aaa389df12aac5348d8a3feda28762" dependencies = [ "async-trait", "backoff", @@ -2718,18 +2697,19 @@ dependencies = [ "hubpack 0.1.2", "hubtools", "lru-cache", - "nix 0.26.2", + "nix 0.27.1", "once_cell", "paste", "serde", "serde-big-array 0.5.1", "slog", + "slog-error-chain", "socket2 0.5.7", "string_cache", "thiserror", "tlvc 0.3.1 (git+https://github.com/oxidecomputer/tlvc.git?branch=main)", "tokio", - "usdt 0.3.5", + "usdt", "uuid", "version_check", "zip", @@ -4286,9 +4266,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.7.1" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] @@ -4686,7 +4666,7 @@ dependencies = [ "term", "thiserror", "tokio", - "usdt 0.5.0", + "usdt", "uuid", ] @@ -4988,15 +4968,14 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.2" -source = "git+https://github.com/jgallagher/nix?branch=r0.26-illumos#c1a3636db0524f194b714cfd117cd9b637b8b10e" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "cfg-if", "libc", "memoffset", - "pin-utils", - "static_assertions", ] [[package]] @@ -5805,7 +5784,7 @@ dependencies = [ "tokio-stream", "tokio-util", "toml 0.8.13", - "usdt 0.5.0", + "usdt", "uuid", "zeroize", "zone 0.3.0", @@ -5844,7 +5823,7 @@ dependencies = [ "thiserror", "tokio", "tokio-postgres", - "usdt 0.5.0", + "usdt", "uuid", "walkdir", ] @@ -5886,7 +5865,7 @@ dependencies = [ "der", "diesel", "digest", - "dof 0.3.0", + "dof", "either", "elliptic-curve", "ff", @@ -5968,8 +5947,8 @@ dependencies = [ "unicode-bidi", "unicode-normalization", "unicode-xid", - "usdt 0.5.0", - "usdt-impl 0.5.0", + "usdt", + "usdt-impl", "uuid", "yasna", "zerocopy 0.7.34", @@ -6331,7 +6310,7 @@ dependencies = [ "tempfile", "thiserror", "tokio", - "usdt 0.5.0", + "usdt", "uuid", ] @@ -7166,7 +7145,7 @@ dependencies = [ "schemars", "serde", "serde_json", - "serde_tokenstream 0.2.0", + "serde_tokenstream", "serde_yaml", "syn 2.0.64", ] @@ -7196,7 +7175,7 @@ dependencies = [ "strum", "thiserror", "tokio", - "usdt 0.5.0", + "usdt", "uuid", "viona_api", ] @@ -8533,17 +8512,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_tokenstream" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "797ba1d80299b264f3aac68ab5d12e5825a561749db4df7cd7c8083900c5d4e9" -dependencies = [ - "proc-macro2", - "serde", - "syn 1.0.109", -] - [[package]] name = "serde_tokenstream" version = "0.2.0" @@ -8908,7 +8876,7 @@ dependencies = [ "serde", "serde_json", "slog", - "usdt 0.5.0", + "usdt", "version_check", ] @@ -9437,18 +9405,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" -[[package]] -name = "synstructure" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "unicode-xid", -] - [[package]] name = "system-configuration" version = "0.5.1" @@ -10419,7 +10375,7 @@ dependencies = [ "semver 1.0.23", "serde", "serde_json", - "serde_tokenstream 0.2.0", + "serde_tokenstream", "syn 2.0.64", "typify-impl", ] @@ -10601,47 +10557,20 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "usdt" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b4c48f9e522b977bbe938a0d7c4d36633d267ba0155aaa253fb57d0531be0fb" -dependencies = [ - "dtrace-parser 0.1.14", - "serde", - "usdt-attr-macro 0.3.5", - "usdt-impl 0.3.5", - "usdt-macro 0.3.5", -] - [[package]] name = "usdt" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf5c47fb471a0bff3d7b17a250817bba8c6cc99b0492abaefe5b3bb99045f02" dependencies = [ - "dof 0.3.0", - "dtrace-parser 0.2.0", + "dof", + "dtrace-parser", "goblin", "memmap", "serde", - "usdt-attr-macro 0.5.0", - "usdt-impl 0.5.0", - "usdt-macro 0.5.0", -] - -[[package]] -name = "usdt-attr-macro" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e6ae4f982ae74dcbaa8eb17baf36ca0d464a3abc8a7172b3bd74c73e9505d6" -dependencies = [ - "dtrace-parser 0.1.14", - "proc-macro2", - "quote", - "serde_tokenstream 0.1.7", - "syn 1.0.109", - "usdt-impl 0.3.5", + "usdt-attr-macro", + "usdt-impl", + "usdt-macro", ] [[package]] @@ -10650,32 +10579,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "025161fff40db24774e7757f75df74ecc47e93d7e11e0f6cdfc31b40eacfe136" dependencies = [ - "dtrace-parser 0.2.0", + "dtrace-parser", "proc-macro2", "quote", - "serde_tokenstream 0.2.0", + "serde_tokenstream", "syn 2.0.64", - "usdt-impl 0.5.0", -] - -[[package]] -name = "usdt-impl" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f53b4ca0b33aae466dc47b30b98adc4f88454928837af8010b6ed02d18474cb1" -dependencies = [ - "byteorder", - "dof 0.1.5", - "dtrace-parser 0.1.14", - "libc", - "proc-macro2", - "quote", - "serde", - "serde_json", - "syn 1.0.109", - "thiserror", - "thread-id", - "version_check", + "usdt-impl", ] [[package]] @@ -10685,8 +10594,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f925814e5942ebb87af2d9fcf4c3f8665e37903f741eb11f0fa2396c6ef5f7b1" dependencies = [ "byteorder", - "dof 0.3.0", - "dtrace-parser 0.2.0", + "dof", + "dtrace-parser", "libc", "proc-macro2", "quote", @@ -10698,32 +10607,18 @@ dependencies = [ "version_check", ] -[[package]] -name = "usdt-macro" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cb093f9653dc91632621c754f9ed4ee25d14e46e0239b6ccaf74a6c0c2788bd" -dependencies = [ - "dtrace-parser 0.1.14", - "proc-macro2", - "quote", - "serde_tokenstream 0.1.7", - "syn 1.0.109", - "usdt-impl 0.3.5", -] - [[package]] name = "usdt-macro" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ddd86f8f3abac0b7c87f59fe82446fc96a3854a413f176dd2797ed686b7af4c" dependencies = [ - "dtrace-parser 0.2.0", + "dtrace-parser", "proc-macro2", "quote", - "serde_tokenstream 0.2.0", + "serde_tokenstream", "syn 2.0.64", - "usdt-impl 0.5.0", + "usdt-impl", ] [[package]] @@ -11487,16 +11382,6 @@ dependencies = [ "time", ] -[[package]] -name = "zerocopy" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da091bab2bd35db397c46f5b81748b56f28f8fda837087fab9b6b07b6d66e3f1" -dependencies = [ - "byteorder", - "zerocopy-derive 0.2.0", -] - [[package]] name = "zerocopy" version = "0.6.6" @@ -11517,17 +11402,6 @@ dependencies = [ "zerocopy-derive 0.7.34", ] -[[package]] -name = "zerocopy-derive" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d498dbd1fd7beb83c86709ae1c33ca50942889473473d287d56ce4770a18edfb" -dependencies = [ - "proc-macro2", - "syn 1.0.109", - "synstructure", -] - [[package]] name = "zerocopy-derive" version = "0.6.6" diff --git a/Cargo.toml b/Cargo.toml index 4eb76f5859e..f9b4906779a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -275,8 +275,8 @@ foreign-types = "0.3.2" fs-err = "2.11.0" futures = "0.3.30" gateway-client = { path = "clients/gateway-client" } -gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "2739c18e80697aa6bc235c935176d14b4d757ee9", default-features = false, features = ["std"] } -gateway-sp-comms = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "2739c18e80697aa6bc235c935176d14b4d757ee9" } +gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "c85a4ca043aaa389df12aac5348d8a3feda28762", default-features = false, features = ["std"] } +gateway-sp-comms = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "c85a4ca043aaa389df12aac5348d8a3feda28762" } gateway-test-utils = { path = "gateway-test-utils" } gethostname = "0.4.3" glob = "0.3.1" diff --git a/clients/gateway-client/src/lib.rs b/clients/gateway-client/src/lib.rs index 6e932577a7f..9693c5e62a2 100644 --- a/clients/gateway-client/src/lib.rs +++ b/clients/gateway-client/src/lib.rs @@ -53,6 +53,7 @@ progenitor::generate_api!( HostPhase2RecoveryImageId = { derives = [PartialEq, Eq, PartialOrd, Ord] }, ImageVersion = { derives = [PartialEq, Eq, PartialOrd, Ord] }, RotImageDetails = { derives = [PartialEq, Eq, PartialOrd, Ord] }, + RotImageError = { derives = [ PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize] }, RotSlot = { derives = [PartialEq, Eq, PartialOrd, Ord] }, RotState = { derives = [PartialEq, Eq, PartialOrd, Ord] }, SpIdentifier = { derives = [Copy, PartialEq, Hash, Eq] }, diff --git a/dev-tools/omdb/src/bin/omdb/mgs.rs b/dev-tools/omdb/src/bin/omdb/mgs.rs index 7f33d5de155..6b7c8b26415 100644 --- a/dev-tools/omdb/src/bin/omdb/mgs.rs +++ b/dev-tools/omdb/src/bin/omdb/mgs.rs @@ -294,10 +294,16 @@ fn show_sp_states( RotState::CommunicationFailed { message } => { format!("error: {}", message) } - RotState::Enabled { active: RotSlot::A, .. } => { + RotState::V2 { active: RotSlot::A, .. } => { "slot A".to_string() } - RotState::Enabled { active: RotSlot::B, .. } => { + RotState::V2 { active: RotSlot::B, .. } => { + "slot B".to_string() + } + RotState::V3 { active: RotSlot::A, .. } => { + "slot A".to_string() + } + RotState::V3 { active: RotSlot::B, .. } => { "slot B".to_string() } }, @@ -332,7 +338,7 @@ async fn show_sp_details( RotState::CommunicationFailed { message } => { println!(" error: {}", message); } - RotState::Enabled { + RotState::V2 { active, pending_persistent_boot_preference, persistent_boot_preference, @@ -382,6 +388,85 @@ async fn show_sp_details( }, ]; + let table = tabled::Table::new(rows) + .with(tabled::settings::Style::empty()) + .with(tabled::settings::Padding::new(0, 1, 0, 0)) + .to_string(); + println!("{}", textwrap::indent(&table.to_string(), " ")); + println!(""); + } + RotState::V3 { + active, + pending_persistent_boot_preference, + persistent_boot_preference, + slot_a_fwid, + slot_b_fwid, + transient_boot_preference, + stage0_fwid, + stage0next_fwid, + slot_a_error, + slot_b_error, + stage0_error, + stage0next_error, + } => { + #[derive(Tabled)] + #[tabled(rename_all = "SCREAMING_SNAKE_CASE")] + struct Row { + name: &'static str, + value: String, + } + + let rows = vec![ + Row { + name: "active slot", + value: format!("slot {:?}", active), + }, + Row { + name: "persistent boot preference", + value: format!("slot {:?}", persistent_boot_preference), + }, + Row { + name: "pending persistent boot preference", + value: pending_persistent_boot_preference + .map(|s| format!("slot {:?}", s)) + .unwrap_or_else(|| "-".to_string()), + }, + Row { + name: "transient boot preference", + value: transient_boot_preference + .map(|s| format!("slot {:?}", s)) + .unwrap_or_else(|| "-".to_string()), + }, + Row { name: "slot A FWID", value: slot_a_fwid.clone() }, + Row { name: "slot B FWID", value: slot_b_fwid.clone() }, + Row { name: "Stage0 FWID", value: stage0_fwid.clone() }, + Row { name: "Stage0Next FWID", value: stage0next_fwid.clone() }, + Row { + name: "Slot A status", + value: (*slot_a_error) + .map(|x| format!("error: {:?}", x)) + .unwrap_or_else(|| "VALID".to_string()), + }, + Row { + name: "Slot B status", + value: (*slot_b_error) + .map(|x| format!("error: {:?}", x)) + .unwrap_or_else(|| "VALID".to_string()), + }, + Row { + name: "Stage0 status", + value: (*stage0_error) + .map(|x| format!("error: {:?}", x)) + .unwrap_or_else(|| "VALID".to_string()), + }, + Row { + name: "stage0next status", + value: (*stage0next_error) + .map(|x| format!("error: {:?}", x)) + .unwrap_or_else(|| "VALID".to_string()), + }, + ]; + let table = tabled::Table::new(rows) .with(tabled::settings::Style::empty()) .with(tabled::settings::Padding::new(0, 1, 0, 0)) diff --git a/dev-tools/omdb/tests/successes.out b/dev-tools/omdb/tests/successes.out index 07ebeb10bf7..22d613f8382 100644 --- a/dev-tools/omdb/tests/successes.out +++ b/dev-tools/omdb/tests/successes.out @@ -125,13 +125,19 @@ SP DETAILS: type "Sled" slot 0 ROOT OF TRUST - NAME VALUE - active slot slot A - persistent boot preference slot A - pending persistent boot preference - - transient boot preference - - slot A SHA3 256 digest - - slot B SHA3 256 digest - + NAME VALUE + active slot slot A + persistent boot preference slot A + pending persistent boot preference - + transient boot preference - + slot A FWID aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + slot B FWID bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + Stage0 FWID cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc + Stage0Next FWID dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd + Slot A status VALID + Slot B status VALID + Stage0 status VALID + stage0next status VALID COMPONENTS @@ -145,13 +151,19 @@ SP DETAILS: type "Sled" slot 1 ROOT OF TRUST - NAME VALUE - active slot slot A - persistent boot preference slot A - pending persistent boot preference - - transient boot preference - - slot A SHA3 256 digest - - slot B SHA3 256 digest - + NAME VALUE + active slot slot A + persistent boot preference slot A + pending persistent boot preference - + transient boot preference - + slot A FWID aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + slot B FWID bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + Stage0 FWID cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc + Stage0Next FWID dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd + Slot A status VALID + Slot B status VALID + Stage0 status VALID + stage0next status VALID COMPONENTS @@ -164,13 +176,19 @@ SP DETAILS: type "Switch" slot 0 ROOT OF TRUST - NAME VALUE - active slot slot A - persistent boot preference slot A - pending persistent boot preference - - transient boot preference - - slot A SHA3 256 digest - - slot B SHA3 256 digest - + NAME VALUE + active slot slot A + persistent boot preference slot A + pending persistent boot preference - + transient boot preference - + slot A FWID aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + slot B FWID bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + Stage0 FWID cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc + Stage0Next FWID dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd + Slot A status VALID + Slot B status VALID + Stage0 status VALID + stage0next status VALID COMPONENTS @@ -184,13 +202,19 @@ SP DETAILS: type "Switch" slot 1 ROOT OF TRUST - NAME VALUE - active slot slot A - persistent boot preference slot A - pending persistent boot preference - - transient boot preference - - slot A SHA3 256 digest - - slot B SHA3 256 digest - + NAME VALUE + active slot slot A + persistent boot preference slot A + pending persistent boot preference - + transient boot preference - + slot A FWID aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + slot B FWID bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + Stage0 FWID cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc + Stage0Next FWID dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd + Slot A status VALID + Slot B status VALID + Stage0 status VALID + stage0next status VALID COMPONENTS: none found diff --git a/gateway-test-utils/configs/sp_sim_config.test.toml b/gateway-test-utils/configs/sp_sim_config.test.toml index e1e3c0d057f..cc08eec30b7 100644 --- a/gateway-test-utils/configs/sp_sim_config.test.toml +++ b/gateway-test-utils/configs/sp_sim_config.test.toml @@ -18,14 +18,14 @@ device_id_cert_seed = "01de00000000000000000000000000000000000000000000000000000 id = "dev-0" device = "fake-tmp-sensor" description = "FAKE temperature sensor 1" -capabilities.bits = 0x2 +capabilities = 0x2 presence = "Present" [[simulated_sps.sidecar.components]] id = "dev-1" device = "fake-tmp-sensor" description = "FAKE temperature sensor 2" -capabilities.bits = 0x2 +capabilities = 0x2 presence = "Failed" [[simulated_sps.sidecar]] @@ -46,7 +46,7 @@ device_id_cert_seed = "01de00000000000000000000000000000000000000000000000000000 id = "sp3-host-cpu" device = "sp3-host-cpu" description = "FAKE host cpu" -capabilities.bits = 0 +capabilities = 0 presence = "Present" serial_console = "[::1]:0" @@ -54,7 +54,7 @@ serial_console = "[::1]:0" id = "dev-0" device = "fake-tmp-sensor" description = "FAKE temperature sensor" -capabilities.bits = 0x2 +capabilities = 0x2 presence = "Failed" [[simulated_sps.gimlet]] @@ -68,7 +68,7 @@ device_id_cert_seed = "01de00000000000000000000000000000000000000000000000000000 id = "sp3-host-cpu" device = "sp3-host-cpu" description = "FAKE host cpu" -capabilities.bits = 0 +capabilities = 0 presence = "Present" serial_console = "[::1]:0" diff --git a/gateway/src/http_entrypoints.rs b/gateway/src/http_entrypoints.rs index 727ba0950da..7e1c8a991e9 100644 --- a/gateway/src/http_entrypoints.rs +++ b/gateway/src/http_entrypoints.rs @@ -61,6 +61,34 @@ pub struct SpState { pub rot: RotState, } +#[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Deserialize, + Serialize, + JsonSchema, +)] +#[serde(rename_all = "snake_case")] +pub enum RotImageError { + Unchecked, + FirstPageErased, + PartiallyProgrammed, + InvalidLength, + HeaderNotProgrammed, + BootloaderTooSmall, + BadMagic, + HeaderImageSize, + UnalignedLength, + UnsupportedType, + ResetVectorNotThumb2, + ResetVector, + Signature, +} + #[derive( Debug, Clone, @@ -74,7 +102,7 @@ pub struct SpState { )] #[serde(tag = "state", rename_all = "snake_case")] pub enum RotState { - Enabled { + V2 { active: RotSlot, persistent_boot_preference: RotSlot, pending_persistent_boot_preference: Option, @@ -85,6 +113,22 @@ pub enum RotState { CommunicationFailed { message: String, }, + V3 { + active: RotSlot, + persistent_boot_preference: RotSlot, + pending_persistent_boot_preference: Option, + transient_boot_preference: Option, + + slot_a_fwid: String, + slot_b_fwid: String, + stage0_fwid: String, + stage0next_fwid: String, + + slot_a_error: Option, + slot_b_error: Option, + stage0_error: Option, + stage0next_error: Option, + }, } #[derive( @@ -184,6 +228,21 @@ pub struct RotCfpa { pub slot: RotCfpaSlot, } +#[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Deserialize, + Serialize, + JsonSchema, +)] +pub struct GetRotBootInfoParams { + pub version: u8, +} + #[derive( Debug, Clone, @@ -626,7 +685,12 @@ async fn sp_get( SpCommsError::SpCommunicationFailed { sp: sp_id, err } })?; - Ok(HttpResponseOk(state.into())) + let rot_state = sp + .rot_state(gateway_messages::RotBootInfo::HIGHEST_KNOWN_VERSION) + .await; + + let final_state = SpState::from((state, rot_state)); + Ok(HttpResponseOk(final_state)) } /// Get host startup options for a sled @@ -1044,7 +1108,9 @@ async fn sp_component_reset( let component = component_from_str(&component)?; sp.reset_component_prepare(component) - .and_then(|()| sp.reset_component_trigger(component)) + // We always want to run with the watchdog when resetting as + // disabling the watchdog should be considered a debug only feature + .and_then(|()| sp.reset_component_trigger(component, false)) .await .map_err(|err| SpCommsError::SpCommunicationFailed { sp: sp_id, @@ -1224,6 +1290,40 @@ async fn sp_rot_cfpa_get( Ok(HttpResponseOk(RotCfpa { base64_data, slot })) } +/// Read the RoT boot state from a root of trust +/// +/// This endpoint is only valid for the `rot` component. +#[endpoint { + method = GET, + path = "/sp/{type}/{slot}/component/{component}/rot-boot-info", +}] +async fn sp_rot_boot_info( + rqctx: RequestContext>, + path: Path, + params: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + + let PathSpComponent { sp, component } = path.into_inner(); + let GetRotBootInfoParams { version } = params.into_inner(); + let sp_id = sp.into(); + + // Ensure the caller knows they're asking for the RoT + if component_from_str(&component)? != SpComponent::ROT { + return Err(HttpError::for_bad_request( + Some("RequestUnsupportedForComponent".to_string()), + "rot_boot_info only makes sent for a RoT".into(), + )); + } + + let sp = apictx.mgmt_switch.sp(sp_id)?; + let state = sp.rot_state(version).await.map_err(|err| { + SpCommsError::SpCommunicationFailed { sp: sp_id, err } + })?; + + Ok(HttpResponseOk(state.into())) +} + /// List SPs via Ignition /// /// Retreive information for all SPs via the Ignition controller. This is lower @@ -1600,6 +1700,7 @@ pub fn api() -> GatewayApiDescription { api.register(sp_component_update_abort)?; api.register(sp_rot_cmpa_get)?; api.register(sp_rot_cfpa_get)?; + api.register(sp_rot_boot_info)?; api.register(sp_host_phase2_progress_get)?; api.register(sp_host_phase2_progress_delete)?; api.register(ignition_list)?; diff --git a/gateway/src/http_entrypoints/conversions.rs b/gateway/src/http_entrypoints/conversions.rs index df3d1c54369..c7fcb29922c 100644 --- a/gateway/src/http_entrypoints/conversions.rs +++ b/gateway/src/http_entrypoints/conversions.rs @@ -12,6 +12,7 @@ use super::ImageVersion; use super::InstallinatorImageId; use super::PowerState; use super::RotImageDetails; +use super::RotImageError; use super::RotSlot; use super::RotState; use super::SpComponentInfo; @@ -27,6 +28,7 @@ use super::SpType; use super::SpUpdateStatus; use super::UpdatePreparationProgress; use dropshot::HttpError; +use gateway_messages::RotBootInfo; use gateway_messages::SpComponent; use gateway_messages::StartupOptions; use gateway_messages::UpdateStatus; @@ -133,6 +135,45 @@ impl From for ImageVersion { } } +impl From for RotImageError { + fn from(error: gateway_messages::ImageError) -> Self { + match error { + gateway_messages::ImageError::Unchecked => RotImageError::Unchecked, + gateway_messages::ImageError::FirstPageErased => { + RotImageError::FirstPageErased + } + gateway_messages::ImageError::PartiallyProgrammed => { + RotImageError::PartiallyProgrammed + } + gateway_messages::ImageError::InvalidLength => { + RotImageError::InvalidLength + } + gateway_messages::ImageError::HeaderNotProgrammed => { + RotImageError::HeaderNotProgrammed + } + gateway_messages::ImageError::BootloaderTooSmall => { + RotImageError::BootloaderTooSmall + } + gateway_messages::ImageError::BadMagic => RotImageError::BadMagic, + gateway_messages::ImageError::HeaderImageSize => { + RotImageError::HeaderImageSize + } + gateway_messages::ImageError::UnalignedLength => { + RotImageError::UnalignedLength + } + gateway_messages::ImageError::UnsupportedType => { + RotImageError::UnsupportedType + } + gateway_messages::ImageError::ResetVectorNotThumb2 => { + RotImageError::ResetVectorNotThumb2 + } + gateway_messages::ImageError::ResetVector => { + RotImageError::ResetVector + } + gateway_messages::ImageError::Signature => RotImageError::Signature, + } + } +} // We expect serial and model numbers to be ASCII and 0-padded: find the first 0 // byte and convert to a string. If that fails, hexlify the entire slice. fn stringify_byte_string(bytes: &[u8]) -> String { @@ -143,8 +184,30 @@ fn stringify_byte_string(bytes: &[u8]) -> String { .unwrap_or_else(|_err| hex::encode(bytes)) } +impl From<(gateway_messages::SpStateV1, RotState)> for SpState { + fn from(all: (gateway_messages::SpStateV1, RotState)) -> Self { + let (state, rot) = all; + Self { + serial_number: stringify_byte_string(&state.serial_number), + model: stringify_byte_string(&state.model), + revision: state.revision, + hubris_archive_id: hex::encode(state.hubris_archive_id), + base_mac_address: state.base_mac_address, + power_state: PowerState::from(state.power_state), + rot, + } + } +} + impl From for SpState { fn from(state: gateway_messages::SpStateV1) -> Self { + Self::from((state, RotState::from(state.rot))) + } +} + +impl From<(gateway_messages::SpStateV2, RotState)> for SpState { + fn from(all: (gateway_messages::SpStateV2, RotState)) -> Self { + let (state, rot) = all; Self { serial_number: stringify_byte_string(&state.serial_number), model: stringify_byte_string(&state.model), @@ -152,13 +215,20 @@ impl From for SpState { hubris_archive_id: hex::encode(state.hubris_archive_id), base_mac_address: state.base_mac_address, power_state: PowerState::from(state.power_state), - rot: RotState::from(state.rot), + rot, } } } impl From for SpState { fn from(state: gateway_messages::SpStateV2) -> Self { + Self::from((state, RotState::from(state.rot))) + } +} + +impl From<(gateway_messages::SpStateV3, RotState)> for SpState { + fn from(all: (gateway_messages::SpStateV3, RotState)) -> Self { + let (state, rot) = all; Self { serial_number: stringify_byte_string(&state.serial_number), model: stringify_byte_string(&state.model), @@ -166,16 +236,55 @@ impl From for SpState { hubris_archive_id: hex::encode(state.hubris_archive_id), base_mac_address: state.base_mac_address, power_state: PowerState::from(state.power_state), - rot: RotState::from(state.rot), + rot, } } } -impl From for SpState { - fn from(value: gateway_sp_comms::VersionedSpState) -> Self { - match value { - gateway_sp_comms::VersionedSpState::V1(s) => Self::from(s), - gateway_sp_comms::VersionedSpState::V2(s) => Self::from(s), +impl + From<( + gateway_sp_comms::VersionedSpState, + Result< + gateway_messages::RotBootInfo, + gateway_sp_comms::error::CommunicationError, + >, + )> for SpState +{ + fn from( + all: ( + gateway_sp_comms::VersionedSpState, + Result< + gateway_messages::RotBootInfo, + gateway_sp_comms::error::CommunicationError, + >, + ), + ) -> Self { + // We need to keep this backwards compatible. If we get an error from reading `rot_state` + // it could be because the RoT/SP isn't updated or because we have failed for some + // other reason. If we're on V1/V2 SP info and we fail, just fall back to using the + // RoT info in that struct since any error will also be communicated there. + match (all.0, all.1) { + (gateway_sp_comms::VersionedSpState::V1(s), Err(_)) => { + Self::from(s) + } + (gateway_sp_comms::VersionedSpState::V1(s), Ok(r)) => { + Self::from((s, RotState::from(r))) + } + (gateway_sp_comms::VersionedSpState::V2(s), Err(_)) => { + Self::from(s) + } + (gateway_sp_comms::VersionedSpState::V2(s), Ok(r)) => { + Self::from((s, RotState::from(r))) + } + (gateway_sp_comms::VersionedSpState::V3(s), Ok(r)) => { + Self::from((s, RotState::from(r))) + } + (gateway_sp_comms::VersionedSpState::V3(s), Err(err)) => { + Self::from(( + s, + RotState::CommunicationFailed { message: err.to_string() }, + )) + } } } } @@ -187,25 +296,7 @@ impl From> result: Result, ) -> Self { match result { - Ok(state) => { - let boot_state = state.rot_updates.boot_state; - Self::Enabled { - active: boot_state.active.into(), - slot_a_sha3_256_digest: boot_state - .slot_a - .map(|details| hex::encode(details.digest)), - slot_b_sha3_256_digest: boot_state - .slot_b - .map(|details| hex::encode(details.digest)), - // RotState(V1) didn't have the following fields, so we make - // it up as best we can. This RoT version is pre-shipping - // and should only exist on (not updated recently) test - // systems. - persistent_boot_preference: boot_state.active.into(), - pending_persistent_boot_preference: None, - transient_boot_preference: None, - } - } + Ok(state) => Self::from(state), Err(err) => Self::CommunicationFailed { message: err.to_string() }, } } @@ -221,29 +312,99 @@ impl From> >, ) -> Self { match result { - Ok(state) => Self::Enabled { - active: state.active.into(), - persistent_boot_preference: state - .persistent_boot_preference - .into(), - pending_persistent_boot_preference: state - .pending_persistent_boot_preference - .map(Into::into), - transient_boot_preference: state - .transient_boot_preference - .map(Into::into), - slot_a_sha3_256_digest: state - .slot_a_sha3_256_digest - .map(hex::encode), - slot_b_sha3_256_digest: state - .slot_b_sha3_256_digest - .map(hex::encode), - }, + Ok(state) => Self::from(state), Err(err) => Self::CommunicationFailed { message: err.to_string() }, } } } +impl RotState { + fn fwid_to_string(fwid: gateway_messages::Fwid) -> String { + match fwid { + gateway_messages::Fwid::Sha3_256(digest) => hex::encode(digest), + } + } +} + +impl From for RotState { + fn from(state: gateway_messages::RotStateV3) -> Self { + Self::V3 { + active: state.active.into(), + persistent_boot_preference: state.persistent_boot_preference.into(), + pending_persistent_boot_preference: state + .pending_persistent_boot_preference + .map(Into::into), + transient_boot_preference: state + .transient_boot_preference + .map(Into::into), + slot_a_fwid: Self::fwid_to_string(state.slot_a_fwid), + slot_b_fwid: Self::fwid_to_string(state.slot_b_fwid), + + stage0_fwid: Self::fwid_to_string(state.stage0_fwid), + stage0next_fwid: Self::fwid_to_string(state.stage0next_fwid), + + slot_a_error: state.slot_a_status.err().map(From::from), + slot_b_error: state.slot_b_status.err().map(From::from), + + stage0_error: state.stage0_status.err().map(From::from), + stage0next_error: state.stage0next_status.err().map(From::from), + } + } +} + +impl From for RotState { + fn from(state: gateway_messages::RotStateV2) -> Self { + Self::V2 { + active: state.active.into(), + persistent_boot_preference: state.persistent_boot_preference.into(), + pending_persistent_boot_preference: state + .pending_persistent_boot_preference + .map(Into::into), + transient_boot_preference: state + .transient_boot_preference + .map(Into::into), + slot_a_sha3_256_digest: state + .slot_a_sha3_256_digest + .map(hex::encode), + slot_b_sha3_256_digest: state + .slot_b_sha3_256_digest + .map(hex::encode), + } + } +} + +impl From for RotState { + fn from(state: gateway_messages::RotState) -> Self { + let boot_state = state.rot_updates.boot_state; + Self::V2 { + active: boot_state.active.into(), + slot_a_sha3_256_digest: boot_state + .slot_a + .map(|details| hex::encode(details.digest)), + slot_b_sha3_256_digest: boot_state + .slot_b + .map(|details| hex::encode(details.digest)), + // RotState(V1) didn't have the following fields, so we make + // it up as best we can. This RoT version is pre-shipping + // and should only exist on (not updated recently) test + // systems. + persistent_boot_preference: boot_state.active.into(), + pending_persistent_boot_preference: None, + transient_boot_preference: None, + } + } +} + +impl From for RotState { + fn from(value: gateway_messages::RotBootInfo) -> Self { + match value { + RotBootInfo::V1(s) => Self::from(s), + RotBootInfo::V2(s) => Self::from(s), + RotBootInfo::V3(s) => Self::from(s), + } + } +} + impl From for RotSlot { fn from(slot: gateway_messages::RotSlotId) -> Self { match slot { diff --git a/nexus/db-model/src/inventory.rs b/nexus/db-model/src/inventory.rs index 456987f0ce8..4abd7fe9275 100644 --- a/nexus/db-model/src/inventory.rs +++ b/nexus/db-model/src/inventory.rs @@ -124,6 +124,8 @@ impl_enum_type!( SpSlot1 => b"sp_slot_1" RotSlotA => b"rot_slot_A" RotSlotB => b"rot_slot_B" + Stage0 => b"stage0" + Stage0Next => b"stage0next" ); impl From for CabooseWhich { @@ -134,6 +136,10 @@ impl From for CabooseWhich { nexus_inventory::CabooseWhich::SpSlot1 => CabooseWhich::SpSlot1, nexus_inventory::CabooseWhich::RotSlotA => CabooseWhich::RotSlotA, nexus_inventory::CabooseWhich::RotSlotB => CabooseWhich::RotSlotB, + nexus_inventory::CabooseWhich::Stage0 => CabooseWhich::Stage0, + nexus_inventory::CabooseWhich::Stage0Next => { + CabooseWhich::Stage0Next + } } } } @@ -146,6 +152,10 @@ impl From for nexus_types::inventory::CabooseWhich { CabooseWhich::SpSlot1 => nexus_inventory::CabooseWhich::SpSlot1, CabooseWhich::RotSlotA => nexus_inventory::CabooseWhich::RotSlotA, CabooseWhich::RotSlotB => nexus_inventory::CabooseWhich::RotSlotB, + CabooseWhich::Stage0 => nexus_inventory::CabooseWhich::Stage0, + CabooseWhich::Stage0Next => { + nexus_inventory::CabooseWhich::Stage0Next + } } } } @@ -203,6 +213,125 @@ impl From for nexus_types::inventory::RotPageWhich { } } +// See [`nexus_types::inventory::RotImageError`]. +impl_enum_type!( + #[derive(SqlType, Debug, QueryId)] + #[diesel(postgres_type(name = "rot_image_error", schema = "public"))] + pub struct RotImageErrorEnum; + + #[derive(Copy, Clone, Debug, AsExpression, FromSqlRow, PartialEq)] + #[diesel(sql_type = RotImageErrorEnum)] + pub enum RotImageError; + + // Enum values + Unchecked => b"unchecked" + FirstPageErased => b"first_page_erased" + PartiallyProgrammed => b"partially_programmed" + InvalidLength => b"invalid_length" + HeaderNotProgrammed => b"header_not_programmed" + BootloaderTooSmall => b"bootloader_too_small" + BadMagic => b"bad_magic" + HeaderImageSize => b"header_image_size" + UnalignedLength => b"unaligned_length" + UnsupportedType => b"unsupported_type" + ResetVectorNotThumb2 => b"not_thumb2" + ResetVector => b"reset_vector" + Signature => b"signature" + +); + +impl From for RotImageError { + fn from(c: nexus_types::inventory::RotImageError) -> Self { + match c { + nexus_types::inventory::RotImageError::Unchecked => { + RotImageError::Unchecked + } + nexus_types::inventory::RotImageError::FirstPageErased => { + RotImageError::FirstPageErased + } + nexus_types::inventory::RotImageError::PartiallyProgrammed => { + RotImageError::PartiallyProgrammed + } + nexus_types::inventory::RotImageError::InvalidLength => { + RotImageError::InvalidLength + } + nexus_types::inventory::RotImageError::HeaderNotProgrammed => { + RotImageError::HeaderNotProgrammed + } + nexus_types::inventory::RotImageError::BootloaderTooSmall => { + RotImageError::BootloaderTooSmall + } + nexus_types::inventory::RotImageError::BadMagic => { + RotImageError::BadMagic + } + nexus_types::inventory::RotImageError::HeaderImageSize => { + RotImageError::HeaderImageSize + } + nexus_types::inventory::RotImageError::UnalignedLength => { + RotImageError::UnalignedLength + } + nexus_types::inventory::RotImageError::UnsupportedType => { + RotImageError::UnsupportedType + } + nexus_types::inventory::RotImageError::ResetVectorNotThumb2 => { + RotImageError::ResetVectorNotThumb2 + } + nexus_types::inventory::RotImageError::ResetVector => { + RotImageError::ResetVector + } + nexus_types::inventory::RotImageError::Signature => { + RotImageError::Signature + } + } + } +} + +impl From for nexus_types::inventory::RotImageError { + fn from(row: RotImageError) -> Self { + match row { + RotImageError::Unchecked => { + nexus_types::inventory::RotImageError::Unchecked + } + RotImageError::FirstPageErased => { + nexus_types::inventory::RotImageError::FirstPageErased + } + RotImageError::PartiallyProgrammed => { + nexus_types::inventory::RotImageError::PartiallyProgrammed + } + RotImageError::InvalidLength => { + nexus_types::inventory::RotImageError::InvalidLength + } + RotImageError::HeaderNotProgrammed => { + nexus_types::inventory::RotImageError::HeaderNotProgrammed + } + RotImageError::BootloaderTooSmall => { + nexus_types::inventory::RotImageError::BootloaderTooSmall + } + RotImageError::BadMagic => { + nexus_types::inventory::RotImageError::BadMagic + } + RotImageError::HeaderImageSize => { + nexus_types::inventory::RotImageError::HeaderImageSize + } + RotImageError::UnalignedLength => { + nexus_types::inventory::RotImageError::UnalignedLength + } + RotImageError::UnsupportedType => { + nexus_types::inventory::RotImageError::UnsupportedType + } + RotImageError::ResetVectorNotThumb2 => { + nexus_types::inventory::RotImageError::ResetVectorNotThumb2 + } + RotImageError::ResetVector => { + nexus_types::inventory::RotImageError::ResetVector + } + RotImageError::Signature => { + nexus_types::inventory::RotImageError::Signature + } + } + } +} + // See [`nexus_types::inventory::SpType`]. impl_enum_type!( #[derive(SqlType, Debug, QueryId)] @@ -532,6 +661,13 @@ pub struct InvRootOfTrust { pub slot_boot_pref_persistent_pending: Option, pub slot_a_sha3_256: Option, pub slot_b_sha3_256: Option, + pub stage0_fwid: Option, + pub stage0next_fwid: Option, + + pub slot_a_error: Option, + pub slot_b_error: Option, + pub stage0_error: Option, + pub stage0next_error: Option, } impl From for nexus_types::inventory::RotState { @@ -551,6 +687,21 @@ impl From for nexus_types::inventory::RotState { .map(RotSlot::from), slot_a_sha3_256_digest: row.slot_a_sha3_256, slot_b_sha3_256_digest: row.slot_b_sha3_256, + stage0_digest: row.stage0_fwid, + stage0next_digest: row.stage0next_fwid, + + slot_a_error: row + .slot_a_error + .map(nexus_types::inventory::RotImageError::from), + slot_b_error: row + .slot_b_error + .map(nexus_types::inventory::RotImageError::from), + stage0_error: row + .stage0_error + .map(nexus_types::inventory::RotImageError::from), + stage0next_error: row + .stage0next_error + .map(nexus_types::inventory::RotImageError::from), } } } diff --git a/nexus/db-model/src/schema.rs b/nexus/db-model/src/schema.rs index 22ef60483d7..8a00ce6e37d 100644 --- a/nexus/db-model/src/schema.rs +++ b/nexus/db-model/src/schema.rs @@ -1370,6 +1370,13 @@ table! { slot_boot_pref_persistent_pending -> Nullable, slot_a_sha3_256 -> Nullable, slot_b_sha3_256 -> Nullable, + stage0_fwid -> Nullable, + stage0next_fwid -> Nullable, + + slot_a_error -> Nullable, + slot_b_error -> Nullable, + stage0_error -> Nullable, + stage0next_error -> Nullable, } } diff --git a/nexus/db-model/src/schema_versions.rs b/nexus/db-model/src/schema_versions.rs index ebc9d0173af..4465c3aacf5 100644 --- a/nexus/db-model/src/schema_versions.rs +++ b/nexus/db-model/src/schema_versions.rs @@ -17,7 +17,7 @@ use std::collections::BTreeMap; /// /// This must be updated when you change the database schema. Refer to /// schema/crdb/README.adoc in the root of this repository for details. -pub const SCHEMA_VERSION: SemverVersion = SemverVersion::new(68, 0, 0); +pub const SCHEMA_VERSION: SemverVersion = SemverVersion::new(69, 0, 0); /// List of all past database schema versions, in *reverse* order /// @@ -29,6 +29,7 @@ static KNOWN_VERSIONS: Lazy> = Lazy::new(|| { // | leaving the first copy as an example for the next person. // v // KnownVersion::new(next_int, "unique-dirname-with-the-sql-files"), + KnownVersion::new(69, "expose-stage0"), KnownVersion::new(68, "filter-v2p-mapping-by-instance-state"), KnownVersion::new(67, "add-instance-updater-lock"), KnownVersion::new(66, "blueprint-crdb-preserve-downgrade"), diff --git a/nexus/db-queries/src/db/datastore/inventory.rs b/nexus/db-queries/src/db/datastore/inventory.rs index 6faa8ea2513..289e4432139 100644 --- a/nexus/db-queries/src/db/datastore/inventory.rs +++ b/nexus/db-queries/src/db/datastore/inventory.rs @@ -47,6 +47,8 @@ use nexus_db_model::InvServiceProcessor; use nexus_db_model::InvSledAgent; use nexus_db_model::InvSledOmicronZones; use nexus_db_model::InvZpool; +use nexus_db_model::RotImageError; +use nexus_db_model::RotImageErrorEnum; use nexus_db_model::RotPageWhichEnum; use nexus_db_model::SledRole; use nexus_db_model::SledRoleEnum; @@ -404,6 +406,26 @@ impl DataStore { .clone() .into_sql::>( ), + rot.stage0_digest + .clone() + .into_sql::>( + ), + rot.stage0next_digest + .clone() + .into_sql::>( + ), + rot.slot_a_error + .map(RotImageError::from) + .into_sql::>(), + rot.slot_b_error + .map(RotImageError::from) + .into_sql::>(), + rot.stage0_error + .map(RotImageError::from) + .into_sql::>(), + rot.stage0next_error + .map(RotImageError::from) + .into_sql::>(), )) .filter( baseboard_dsl::part_number @@ -429,6 +451,12 @@ impl DataStore { rot_dsl::slot_boot_pref_transient, rot_dsl::slot_a_sha3_256, rot_dsl::slot_b_sha3_256, + rot_dsl::stage0_fwid, + rot_dsl::stage0next_fwid, + rot_dsl::slot_a_error, + rot_dsl::slot_b_error, + rot_dsl::stage0_error, + rot_dsl::stage0next_error, )) .execute_async(&conn) .await?; @@ -447,6 +475,12 @@ impl DataStore { _slot_boot_pref_transient, _slot_a_sha3_256, _slot_b_sha3_256, + _stage0_fwid, + _stage0next_fwid, + _slot_a_error, + _slot_b_error, + _stage0_error, + _stage0next_error, ) = rot_dsl::inv_root_of_trust::all_columns(); } } diff --git a/nexus/inventory/src/builder.rs b/nexus/inventory/src/builder.rs index bfa330669ff..65bdae63ce0 100644 --- a/nexus/inventory/src/builder.rs +++ b/nexus/inventory/src/builder.rs @@ -217,7 +217,7 @@ impl CollectionBuilder { }); match sp_state.rot { - gateway_client::types::RotState::Enabled { + gateway_client::types::RotState::V2 { active, pending_persistent_boot_preference, persistent_boot_preference, @@ -236,6 +236,12 @@ impl CollectionBuilder { transient_boot_preference, slot_a_sha3_256_digest, slot_b_sha3_256_digest, + stage0_digest: None, + stage0next_digest: None, + slot_a_error: None, + slot_b_error: None, + stage0_error: None, + stage0next_error: None, } }); } @@ -249,6 +255,40 @@ impl CollectionBuilder { message ))); } + gateway_client::types::RotState::V3 { + active, + pending_persistent_boot_preference, + persistent_boot_preference, + slot_a_fwid, + slot_b_fwid, + stage0_fwid, + stage0next_fwid, + transient_boot_preference, + slot_a_error, + slot_b_error, + stage0_error, + stage0next_error, + } => { + let _ = + self.rots.entry(baseboard.clone()).or_insert_with(|| { + RotState { + time_collected: now, + source: source.to_owned(), + active_slot: active, + persistent_boot_preference, + pending_persistent_boot_preference, + transient_boot_preference, + slot_a_sha3_256_digest: Some(slot_a_fwid), + slot_b_sha3_256_digest: Some(slot_b_fwid), + stage0_digest: Some(stage0_fwid), + stage0next_digest: Some(stage0next_fwid), + slot_a_error, + slot_b_error, + stage0_error, + stage0next_error, + } + }); + } } Some(baseboard) @@ -965,7 +1005,7 @@ mod test { model: String::from("model1"), power_state: PowerState::A0, revision: 0, - rot: RotState::Enabled { + rot: RotState::V2 { active: RotSlot::A, pending_persistent_boot_preference: None, persistent_boot_preference: RotSlot::A, @@ -990,7 +1030,7 @@ mod test { model: String::from("model1"), power_state: PowerState::A0, revision: 0, - rot: RotState::Enabled { + rot: RotState::V2 { active: RotSlot::A, pending_persistent_boot_preference: None, persistent_boot_preference: RotSlot::A, @@ -1016,7 +1056,7 @@ mod test { model: String::from("model1"), power_state: PowerState::A0, revision: 1, - rot: RotState::Enabled { + rot: RotState::V2 { active: RotSlot::A, pending_persistent_boot_preference: None, persistent_boot_preference: RotSlot::A, diff --git a/nexus/inventory/src/collector.rs b/nexus/inventory/src/collector.rs index 48761479b05..a64d092e1c1 100644 --- a/nexus/inventory/src/collector.rs +++ b/nexus/inventory/src/collector.rs @@ -174,6 +174,8 @@ impl<'a> Collector<'a> { CabooseWhich::SpSlot1 => ("sp", 1), CabooseWhich::RotSlotA => ("rot", 0), CabooseWhich::RotSlotB => ("rot", 1), + CabooseWhich::Stage0 => ("stage0", 0), + CabooseWhich::Stage0Next => ("stage0", 1), }; let result = client diff --git a/nexus/inventory/src/examples.rs b/nexus/inventory/src/examples.rs index 1a0c70f4566..c2e283a6408 100644 --- a/nexus/inventory/src/examples.rs +++ b/nexus/inventory/src/examples.rs @@ -49,7 +49,7 @@ pub fn representative() -> Representative { model: String::from("model1"), power_state: PowerState::A0, revision: 0, - rot: RotState::Enabled { + rot: RotState::V2 { active: RotSlot::A, pending_persistent_boot_preference: None, persistent_boot_preference: RotSlot::A, @@ -74,7 +74,7 @@ pub fn representative() -> Representative { model: String::from("model2"), power_state: PowerState::A2, revision: 1, - rot: RotState::Enabled { + rot: RotState::V2 { active: RotSlot::B, pending_persistent_boot_preference: Some(RotSlot::A), persistent_boot_preference: RotSlot::A, @@ -101,7 +101,7 @@ pub fn representative() -> Representative { model: String::from("model3"), power_state: PowerState::A1, revision: 2, - rot: RotState::Enabled { + rot: RotState::V2 { active: RotSlot::B, pending_persistent_boot_preference: None, persistent_boot_preference: RotSlot::A, @@ -128,7 +128,7 @@ pub fn representative() -> Representative { model: String::from("model4"), power_state: PowerState::A2, revision: 3, - rot: RotState::Enabled { + rot: RotState::V2 { active: RotSlot::B, pending_persistent_boot_preference: None, persistent_boot_preference: RotSlot::A, @@ -471,7 +471,7 @@ pub fn sp_state(unique: &str) -> SpState { model: format!("model{}", unique), power_state: PowerState::A2, revision: 0, - rot: RotState::Enabled { + rot: RotState::V2 { active: RotSlot::A, pending_persistent_boot_preference: None, persistent_boot_preference: RotSlot::A, diff --git a/nexus/inventory/tests/output/collector_basic.txt b/nexus/inventory/tests/output/collector_basic.txt index 0fc1c552aba..896cdcddbc6 100644 --- a/nexus/inventory/tests/output/collector_basic.txt +++ b/nexus/inventory/tests/output/collector_basic.txt @@ -7,10 +7,18 @@ baseboards: part "sim-gimlet" serial "sim-9cb9b78f-5614-440c-b66d-e8e81fab69b0" cabooses: - board "SimGimletSp" name "SimGimlet" version "0.0.1" git_commit "ffffffff" - board "SimRot" name "SimGimlet" version "0.0.1" git_commit "eeeeeeee" - board "SimRot" name "SimSidecar" version "0.0.1" git_commit "eeeeeeee" - board "SimSidecarSp" name "SimSidecar" version "0.0.1" git_commit "ffffffff" + board "SimGimletSp" name "SimGimlet" version "0.0.1" git_commit "fefefefe" + board "SimGimletSp" name "SimGimlet" version "0.0.2" git_commit "ffffffff" + board "SimRot" name "SimGimletRot" version "0.0.3" git_commit "edededed" + board "SimRot" name "SimSidecar" version "0.0.3" git_commit "edededed" + board "SimRot" name "SimGimletRot" version "0.0.4" git_commit "eeeeeeee" + board "SimRot" name "SimSidecar" version "0.0.4" git_commit "eeeeeeee" + board "SimRotStage0" name "SimSidecar" version "0.0.200" git_commit "dadadada" + board "SimRotStage0" name "SimGimletRot" version "0.0.200" git_commit "dadadadad" + board "SimRotStage0" name "SimSidecar" version "0.0.200" git_commit "dddddddd" + board "SimRotStage0" name "SimGimletRot" version "0.0.200" git_commit "ddddddddd" + board "SimSidecarSp" name "SimSidecar" version "0.0.1" git_commit "fefefefe" + board "SimSidecarSp" name "SimSidecar" version "0.0.2" git_commit "ffffffff" rot pages: data_base64 "Z2ltbGV0LWNmcGEtYWN0aXZlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" @@ -51,6 +59,14 @@ cabooses found: RotSlotB baseboard part "FAKE_SIM_SIDECAR" serial "SimSidecar1": board "SimRot" RotSlotB baseboard part "i86pc" serial "SimGimlet00": board "SimRot" RotSlotB baseboard part "i86pc" serial "SimGimlet01": board "SimRot" + Stage0 baseboard part "FAKE_SIM_SIDECAR" serial "SimSidecar0": board "SimRotStage0" + Stage0 baseboard part "FAKE_SIM_SIDECAR" serial "SimSidecar1": board "SimRotStage0" + Stage0 baseboard part "i86pc" serial "SimGimlet00": board "SimRotStage0" + Stage0 baseboard part "i86pc" serial "SimGimlet01": board "SimRotStage0" + Stage0Next baseboard part "FAKE_SIM_SIDECAR" serial "SimSidecar0": board "SimRotStage0" + Stage0Next baseboard part "FAKE_SIM_SIDECAR" serial "SimSidecar1": board "SimRotStage0" + Stage0Next baseboard part "i86pc" serial "SimGimlet00": board "SimRotStage0" + Stage0Next baseboard part "i86pc" serial "SimGimlet01": board "SimRotStage0" rot pages found: Cmpa baseboard part "FAKE_SIM_SIDECAR" serial "SimSidecar0": data_base64 "c2lkZWNhci1jbXBhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" diff --git a/nexus/inventory/tests/output/collector_errors.txt b/nexus/inventory/tests/output/collector_errors.txt index 20e9bb301e9..79d61567dd1 100644 --- a/nexus/inventory/tests/output/collector_errors.txt +++ b/nexus/inventory/tests/output/collector_errors.txt @@ -5,10 +5,18 @@ baseboards: part "i86pc" serial "SimGimlet01" cabooses: - board "SimGimletSp" name "SimGimlet" version "0.0.1" git_commit "ffffffff" - board "SimRot" name "SimGimlet" version "0.0.1" git_commit "eeeeeeee" - board "SimRot" name "SimSidecar" version "0.0.1" git_commit "eeeeeeee" - board "SimSidecarSp" name "SimSidecar" version "0.0.1" git_commit "ffffffff" + board "SimGimletSp" name "SimGimlet" version "0.0.1" git_commit "fefefefe" + board "SimGimletSp" name "SimGimlet" version "0.0.2" git_commit "ffffffff" + board "SimRot" name "SimGimletRot" version "0.0.3" git_commit "edededed" + board "SimRot" name "SimSidecar" version "0.0.3" git_commit "edededed" + board "SimRot" name "SimGimletRot" version "0.0.4" git_commit "eeeeeeee" + board "SimRot" name "SimSidecar" version "0.0.4" git_commit "eeeeeeee" + board "SimRotStage0" name "SimSidecar" version "0.0.200" git_commit "dadadada" + board "SimRotStage0" name "SimGimletRot" version "0.0.200" git_commit "dadadadad" + board "SimRotStage0" name "SimSidecar" version "0.0.200" git_commit "dddddddd" + board "SimRotStage0" name "SimGimletRot" version "0.0.200" git_commit "ddddddddd" + board "SimSidecarSp" name "SimSidecar" version "0.0.1" git_commit "fefefefe" + board "SimSidecarSp" name "SimSidecar" version "0.0.2" git_commit "ffffffff" rot pages: data_base64 "Z2ltbGV0LWNmcGEtYWN0aXZlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" @@ -49,6 +57,14 @@ cabooses found: RotSlotB baseboard part "FAKE_SIM_SIDECAR" serial "SimSidecar1": board "SimRot" RotSlotB baseboard part "i86pc" serial "SimGimlet00": board "SimRot" RotSlotB baseboard part "i86pc" serial "SimGimlet01": board "SimRot" + Stage0 baseboard part "FAKE_SIM_SIDECAR" serial "SimSidecar0": board "SimRotStage0" + Stage0 baseboard part "FAKE_SIM_SIDECAR" serial "SimSidecar1": board "SimRotStage0" + Stage0 baseboard part "i86pc" serial "SimGimlet00": board "SimRotStage0" + Stage0 baseboard part "i86pc" serial "SimGimlet01": board "SimRotStage0" + Stage0Next baseboard part "FAKE_SIM_SIDECAR" serial "SimSidecar0": board "SimRotStage0" + Stage0Next baseboard part "FAKE_SIM_SIDECAR" serial "SimSidecar1": board "SimRotStage0" + Stage0Next baseboard part "i86pc" serial "SimGimlet00": board "SimRotStage0" + Stage0Next baseboard part "i86pc" serial "SimGimlet01": board "SimRotStage0" rot pages found: Cmpa baseboard part "FAKE_SIM_SIDECAR" serial "SimSidecar0": data_base64 "c2lkZWNhci1jbXBhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" diff --git a/nexus/inventory/tests/output/collector_sled_agent_errors.txt b/nexus/inventory/tests/output/collector_sled_agent_errors.txt index 7b9bbce84eb..9e9c79aa92d 100644 --- a/nexus/inventory/tests/output/collector_sled_agent_errors.txt +++ b/nexus/inventory/tests/output/collector_sled_agent_errors.txt @@ -6,10 +6,18 @@ baseboards: part "sim-gimlet" serial "sim-9cb9b78f-5614-440c-b66d-e8e81fab69b0" cabooses: - board "SimGimletSp" name "SimGimlet" version "0.0.1" git_commit "ffffffff" - board "SimRot" name "SimGimlet" version "0.0.1" git_commit "eeeeeeee" - board "SimRot" name "SimSidecar" version "0.0.1" git_commit "eeeeeeee" - board "SimSidecarSp" name "SimSidecar" version "0.0.1" git_commit "ffffffff" + board "SimGimletSp" name "SimGimlet" version "0.0.1" git_commit "fefefefe" + board "SimGimletSp" name "SimGimlet" version "0.0.2" git_commit "ffffffff" + board "SimRot" name "SimGimletRot" version "0.0.3" git_commit "edededed" + board "SimRot" name "SimSidecar" version "0.0.3" git_commit "edededed" + board "SimRot" name "SimGimletRot" version "0.0.4" git_commit "eeeeeeee" + board "SimRot" name "SimSidecar" version "0.0.4" git_commit "eeeeeeee" + board "SimRotStage0" name "SimSidecar" version "0.0.200" git_commit "dadadada" + board "SimRotStage0" name "SimGimletRot" version "0.0.200" git_commit "dadadadad" + board "SimRotStage0" name "SimSidecar" version "0.0.200" git_commit "dddddddd" + board "SimRotStage0" name "SimGimletRot" version "0.0.200" git_commit "ddddddddd" + board "SimSidecarSp" name "SimSidecar" version "0.0.1" git_commit "fefefefe" + board "SimSidecarSp" name "SimSidecar" version "0.0.2" git_commit "ffffffff" rot pages: data_base64 "Z2ltbGV0LWNmcGEtYWN0aXZlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" @@ -50,6 +58,14 @@ cabooses found: RotSlotB baseboard part "FAKE_SIM_SIDECAR" serial "SimSidecar1": board "SimRot" RotSlotB baseboard part "i86pc" serial "SimGimlet00": board "SimRot" RotSlotB baseboard part "i86pc" serial "SimGimlet01": board "SimRot" + Stage0 baseboard part "FAKE_SIM_SIDECAR" serial "SimSidecar0": board "SimRotStage0" + Stage0 baseboard part "FAKE_SIM_SIDECAR" serial "SimSidecar1": board "SimRotStage0" + Stage0 baseboard part "i86pc" serial "SimGimlet00": board "SimRotStage0" + Stage0 baseboard part "i86pc" serial "SimGimlet01": board "SimRotStage0" + Stage0Next baseboard part "FAKE_SIM_SIDECAR" serial "SimSidecar0": board "SimRotStage0" + Stage0Next baseboard part "FAKE_SIM_SIDECAR" serial "SimSidecar1": board "SimRotStage0" + Stage0Next baseboard part "i86pc" serial "SimGimlet00": board "SimRotStage0" + Stage0Next baseboard part "i86pc" serial "SimGimlet01": board "SimRotStage0" rot pages found: Cmpa baseboard part "FAKE_SIM_SIDECAR" serial "SimSidecar0": data_base64 "c2lkZWNhci1jbXBhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" diff --git a/nexus/reconfigurator/planning/src/system.rs b/nexus/reconfigurator/planning/src/system.rs index 74c9313e05b..f2a979cb4a6 100644 --- a/nexus/reconfigurator/planning/src/system.rs +++ b/nexus/reconfigurator/planning/src/system.rs @@ -483,16 +483,18 @@ impl Sled { model: model.clone(), power_state: PowerState::A2, revision, - rot: RotState::Enabled { + rot: RotState::V3 { active: RotSlot::A, pending_persistent_boot_preference: None, persistent_boot_preference: RotSlot::A, - slot_a_sha3_256_digest: Some(String::from( - "slotAdigest1", - )), - slot_b_sha3_256_digest: Some(String::from( - "slotBdigest1", - )), + slot_a_fwid: String::from("slotAdigest1"), + slot_b_fwid: String::from("slotBdigest1"), + stage0_fwid: String::from("stage0_fwid"), + stage0next_fwid: String::from("stage0next_fwid"), + slot_a_error: None, + slot_b_error: None, + stage0_error: None, + stage0next_error: None, transient_boot_preference: None, }, serial_number: serial.clone(), @@ -582,35 +584,86 @@ impl Sled { .unwrap_or(sled_agent_client::types::Baseboard::Unknown); let inventory_sp = inventory_sp.map(|sledhw| { - let sp_state = SpState { - base_mac_address: [0; 6], - hubris_archive_id: sledhw.sp.hubris_archive.clone(), - model: sledhw.baseboard_id.part_number.clone(), - power_state: sledhw.sp.power_state, - revision: sledhw.sp.baseboard_revision, - rot: RotState::Enabled { - active: sledhw.rot.active_slot, - pending_persistent_boot_preference: sledhw - .rot - .pending_persistent_boot_preference, - persistent_boot_preference: sledhw - .rot - .persistent_boot_preference, - slot_a_sha3_256_digest: sledhw - .rot - .slot_a_sha3_256_digest - .clone(), - slot_b_sha3_256_digest: sledhw - .rot - .slot_b_sha3_256_digest - .clone(), - transient_boot_preference: sledhw - .rot - .transient_boot_preference, - }, - serial_number: sledhw.baseboard_id.serial_number.clone(), + // RotStateV3 unconditionally sets all of these + let sp_state = if sledhw.rot.slot_a_sha3_256_digest.is_some() + && sledhw.rot.slot_b_sha3_256_digest.is_some() + && sledhw.rot.stage0_digest.is_some() + && sledhw.rot.stage0next_digest.is_some() + { + SpState { + base_mac_address: [0; 6], + hubris_archive_id: sledhw.sp.hubris_archive.clone(), + model: sledhw.baseboard_id.part_number.clone(), + power_state: sledhw.sp.power_state, + revision: sledhw.sp.baseboard_revision, + rot: RotState::V3 { + active: sledhw.rot.active_slot, + pending_persistent_boot_preference: sledhw + .rot + .pending_persistent_boot_preference, + persistent_boot_preference: sledhw + .rot + .persistent_boot_preference, + slot_a_fwid: sledhw + .rot + .slot_a_sha3_256_digest + .clone() + .expect("slot_a_fwid should be set"), + slot_b_fwid: sledhw + .rot + .slot_b_sha3_256_digest + .clone() + .expect("slot_b_fwid should be set"), + stage0_fwid: sledhw + .rot + .stage0_digest + .clone() + .expect("stage0 fwid should be set"), + stage0next_fwid: sledhw + .rot + .stage0next_digest + .clone() + .expect("stage0 fwid should be set"), + transient_boot_preference: sledhw + .rot + .transient_boot_preference, + slot_a_error: sledhw.rot.slot_a_error, + slot_b_error: sledhw.rot.slot_b_error, + stage0_error: sledhw.rot.stage0_error, + stage0next_error: sledhw.rot.stage0next_error, + }, + serial_number: sledhw.baseboard_id.serial_number.clone(), + } + } else { + SpState { + base_mac_address: [0; 6], + hubris_archive_id: sledhw.sp.hubris_archive.clone(), + model: sledhw.baseboard_id.part_number.clone(), + power_state: sledhw.sp.power_state, + revision: sledhw.sp.baseboard_revision, + rot: RotState::V2 { + active: sledhw.rot.active_slot, + pending_persistent_boot_preference: sledhw + .rot + .pending_persistent_boot_preference, + persistent_boot_preference: sledhw + .rot + .persistent_boot_preference, + slot_a_sha3_256_digest: sledhw + .rot + .slot_a_sha3_256_digest + .clone(), + slot_b_sha3_256_digest: sledhw + .rot + .slot_b_sha3_256_digest + .clone(), + transient_boot_preference: sledhw + .rot + .transient_boot_preference, + }, + serial_number: sledhw.baseboard_id.serial_number.clone(), + } }; - (sledhw.sp.sp_slot, sp_state) }); diff --git a/nexus/types/src/inventory.rs b/nexus/types/src/inventory.rs index 6acbcaca6aa..661c4c088d7 100644 --- a/nexus/types/src/inventory.rs +++ b/nexus/types/src/inventory.rs @@ -15,6 +15,7 @@ use crate::external_api::shared::Baseboard; use chrono::DateTime; use chrono::Utc; pub use gateway_client::types::PowerState; +pub use gateway_client::types::RotImageError; pub use gateway_client::types::RotSlot; pub use gateway_client::types::SpType; use omicron_common::api::external::ByteCount; @@ -256,6 +257,13 @@ pub struct RotState { pub transient_boot_preference: Option, pub slot_a_sha3_256_digest: Option, pub slot_b_sha3_256_digest: Option, + pub stage0_digest: Option, + pub stage0next_digest: Option, + + pub slot_a_error: Option, + pub slot_b_error: Option, + pub stage0_error: Option, + pub stage0next_error: Option, } /// Describes which caboose this is (which component, which slot) @@ -276,6 +284,8 @@ pub enum CabooseWhich { SpSlot1, RotSlotA, RotSlotB, + Stage0, + Stage0Next, } /// Root of trust page contents found during a collection diff --git a/openapi/gateway.json b/openapi/gateway.json index c5d0eab0b13..8bd71e7c99e 100644 --- a/openapi/gateway.json +++ b/openapi/gateway.json @@ -762,6 +762,70 @@ } } }, + "/sp/{type}/{slot}/component/{component}/rot-boot-info": { + "get": { + "summary": "Read the RoT boot state from a root of trust", + "description": "This endpoint is only valid for the `rot` component.", + "operationId": "sp_rot_boot_info", + "parameters": [ + { + "in": "path", + "name": "component", + "description": "ID for the component of the SP; this is the internal identifier used by the SP itself to identify its components.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "slot", + "required": true, + "schema": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + { + "in": "path", + "name": "type", + "required": true, + "schema": { + "$ref": "#/components/schemas/SpType" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetRotBootInfoParams" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RotState" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/sp/{type}/{slot}/component/{component}/serial-console/attach": { "get": { "summary": "Upgrade into a websocket connection attached to the given SP component's", @@ -1499,6 +1563,19 @@ "slot" ] }, + "GetRotBootInfoParams": { + "type": "object", + "properties": { + "version": { + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "required": [ + "version" + ] + }, "HostPhase2Progress": { "oneOf": [ { @@ -2316,6 +2393,24 @@ "base64_data" ] }, + "RotImageError": { + "type": "string", + "enum": [ + "unchecked", + "first_page_erased", + "partially_programmed", + "invalid_length", + "header_not_programmed", + "bootloader_too_small", + "bad_magic", + "header_image_size", + "unaligned_length", + "unsupported_type", + "reset_vector_not_thumb2", + "reset_vector", + "signature" + ] + }, "RotSlot": { "oneOf": [ { @@ -2378,7 +2473,7 @@ "state": { "type": "string", "enum": [ - "enabled" + "v2" ] }, "transient_boot_preference": { @@ -2413,6 +2508,92 @@ "message", "state" ] + }, + { + "type": "object", + "properties": { + "active": { + "$ref": "#/components/schemas/RotSlot" + }, + "pending_persistent_boot_preference": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/RotSlot" + } + ] + }, + "persistent_boot_preference": { + "$ref": "#/components/schemas/RotSlot" + }, + "slot_a_error": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/RotImageError" + } + ] + }, + "slot_a_fwid": { + "type": "string" + }, + "slot_b_error": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/RotImageError" + } + ] + }, + "slot_b_fwid": { + "type": "string" + }, + "stage0_error": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/RotImageError" + } + ] + }, + "stage0_fwid": { + "type": "string" + }, + "stage0next_error": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/RotImageError" + } + ] + }, + "stage0next_fwid": { + "type": "string" + }, + "state": { + "type": "string", + "enum": [ + "v3" + ] + }, + "transient_boot_preference": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/RotSlot" + } + ] + } + }, + "required": [ + "active", + "persistent_boot_preference", + "slot_a_fwid", + "slot_b_fwid", + "stage0_fwid", + "stage0next_fwid", + "state" + ] } ] }, diff --git a/openapi/wicketd.json b/openapi/wicketd.json index fd8e49b6e30..edef5b98138 100644 --- a/openapi/wicketd.json +++ b/openapi/wicketd.json @@ -2584,6 +2584,25 @@ "sps" ] }, + "RotImageError": { + "description": "RotImageError\n\n
JSON schema\n\n```json { \"type\": \"string\", \"enum\": [ \"unchecked\", \"first_page_erased\", \"partially_programmed\", \"invalid_length\", \"header_not_programmed\", \"bootloader_too_small\", \"bad_magic\", \"header_image_size\", \"unaligned_length\", \"unsupported_type\", \"reset_vector_not_thumb2\", \"reset_vector\", \"signature\" ] } ```
", + "type": "string", + "enum": [ + "unchecked", + "first_page_erased", + "partially_programmed", + "invalid_length", + "header_not_programmed", + "bootloader_too_small", + "bad_magic", + "header_image_size", + "unaligned_length", + "unsupported_type", + "reset_vector_not_thumb2", + "reset_vector", + "signature" + ] + }, "RotInventory": { "description": "RoT-related data that isn't already supplied in [`SpState`].", "type": "object", @@ -2606,6 +2625,22 @@ "$ref": "#/components/schemas/SpComponentCaboose" } ] + }, + "caboose_stage0": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/SpComponentCaboose" + } + ] + }, + "caboose_stage0next": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/SpComponentCaboose" + } + ] } }, "required": [ @@ -2646,7 +2681,7 @@ ] }, "RotState": { - "description": "RotState\n\n
JSON schema\n\n```json { \"oneOf\": [ { \"type\": \"object\", \"required\": [ \"active\", \"persistent_boot_preference\", \"state\" ], \"properties\": { \"active\": { \"$ref\": \"#/components/schemas/RotSlot\" }, \"pending_persistent_boot_preference\": { \"allOf\": [ { \"$ref\": \"#/components/schemas/RotSlot\" } ] }, \"persistent_boot_preference\": { \"$ref\": \"#/components/schemas/RotSlot\" }, \"slot_a_sha3_256_digest\": { \"type\": [ \"string\", \"null\" ] }, \"slot_b_sha3_256_digest\": { \"type\": [ \"string\", \"null\" ] }, \"state\": { \"type\": \"string\", \"enum\": [ \"enabled\" ] }, \"transient_boot_preference\": { \"allOf\": [ { \"$ref\": \"#/components/schemas/RotSlot\" } ] } } }, { \"type\": \"object\", \"required\": [ \"message\", \"state\" ], \"properties\": { \"message\": { \"type\": \"string\" }, \"state\": { \"type\": \"string\", \"enum\": [ \"communication_failed\" ] } } } ] } ```
", + "description": "RotState\n\n
JSON schema\n\n```json { \"oneOf\": [ { \"type\": \"object\", \"required\": [ \"active\", \"persistent_boot_preference\", \"state\" ], \"properties\": { \"active\": { \"$ref\": \"#/components/schemas/RotSlot\" }, \"pending_persistent_boot_preference\": { \"allOf\": [ { \"$ref\": \"#/components/schemas/RotSlot\" } ] }, \"persistent_boot_preference\": { \"$ref\": \"#/components/schemas/RotSlot\" }, \"slot_a_sha3_256_digest\": { \"type\": [ \"string\", \"null\" ] }, \"slot_b_sha3_256_digest\": { \"type\": [ \"string\", \"null\" ] }, \"state\": { \"type\": \"string\", \"enum\": [ \"v2\" ] }, \"transient_boot_preference\": { \"allOf\": [ { \"$ref\": \"#/components/schemas/RotSlot\" } ] } } }, { \"type\": \"object\", \"required\": [ \"message\", \"state\" ], \"properties\": { \"message\": { \"type\": \"string\" }, \"state\": { \"type\": \"string\", \"enum\": [ \"communication_failed\" ] } } }, { \"type\": \"object\", \"required\": [ \"active\", \"persistent_boot_preference\", \"slot_a_fwid\", \"slot_b_fwid\", \"stage0_fwid\", \"stage0next_fwid\", \"state\" ], \"properties\": { \"active\": { \"$ref\": \"#/components/schemas/RotSlot\" }, \"pending_persistent_boot_preference\": { \"allOf\": [ { \"$ref\": \"#/components/schemas/RotSlot\" } ] }, \"persistent_boot_preference\": { \"$ref\": \"#/components/schemas/RotSlot\" }, \"slot_a_error\": { \"allOf\": [ { \"$ref\": \"#/components/schemas/RotImageError\" } ] }, \"slot_a_fwid\": { \"type\": \"string\" }, \"slot_b_error\": { \"allOf\": [ { \"$ref\": \"#/components/schemas/RotImageError\" } ] }, \"slot_b_fwid\": { \"type\": \"string\" }, \"stage0_error\": { \"allOf\": [ { \"$ref\": \"#/components/schemas/RotImageError\" } ] }, \"stage0_fwid\": { \"type\": \"string\" }, \"stage0next_error\": { \"allOf\": [ { \"$ref\": \"#/components/schemas/RotImageError\" } ] }, \"stage0next_fwid\": { \"type\": \"string\" }, \"state\": { \"type\": \"string\", \"enum\": [ \"v3\" ] }, \"transient_boot_preference\": { \"allOf\": [ { \"$ref\": \"#/components/schemas/RotSlot\" } ] } } } ] } ```
", "oneOf": [ { "type": "object", @@ -2676,7 +2711,7 @@ "state": { "type": "string", "enum": [ - "enabled" + "v2" ] }, "transient_boot_preference": { @@ -2711,6 +2746,92 @@ "message", "state" ] + }, + { + "type": "object", + "properties": { + "active": { + "$ref": "#/components/schemas/RotSlot" + }, + "pending_persistent_boot_preference": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/RotSlot" + } + ] + }, + "persistent_boot_preference": { + "$ref": "#/components/schemas/RotSlot" + }, + "slot_a_error": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/RotImageError" + } + ] + }, + "slot_a_fwid": { + "type": "string" + }, + "slot_b_error": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/RotImageError" + } + ] + }, + "slot_b_fwid": { + "type": "string" + }, + "stage0_error": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/RotImageError" + } + ] + }, + "stage0_fwid": { + "type": "string" + }, + "stage0next_error": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/RotImageError" + } + ] + }, + "stage0next_fwid": { + "type": "string" + }, + "state": { + "type": "string", + "enum": [ + "v3" + ] + }, + "transient_boot_preference": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/RotSlot" + } + ] + } + }, + "required": [ + "active", + "persistent_boot_preference", + "slot_a_fwid", + "slot_b_fwid", + "stage0_fwid", + "stage0next_fwid", + "state" + ] } ] }, diff --git a/oximeter/collector/tests/output/self-stat-schema.json b/oximeter/collector/tests/output/self-stat-schema.json index 286ac63405f..019e05b4946 100644 --- a/oximeter/collector/tests/output/self-stat-schema.json +++ b/oximeter/collector/tests/output/self-stat-schema.json @@ -39,7 +39,7 @@ } ], "datum_type": "cumulative_u64", - "created": "2024-05-21T18:32:24.199619581Z" + "created": "2024-06-04T20:49:05.675711686Z" }, "oximeter_collector:failed_collections": { "timeseries_name": "oximeter_collector:failed_collections", @@ -86,6 +86,6 @@ } ], "datum_type": "cumulative_u64", - "created": "2024-05-21T18:32:24.200514936Z" + "created": "2024-06-04T20:49:05.676050088Z" } } \ No newline at end of file diff --git a/schema/crdb/dbinit.sql b/schema/crdb/dbinit.sql index cf6bc2bf534..b759f86f1b6 100644 --- a/schema/crdb/dbinit.sql +++ b/schema/crdb/dbinit.sql @@ -2912,6 +2912,22 @@ CREATE TABLE IF NOT EXISTS omicron.public.inv_service_processor ( PRIMARY KEY (inv_collection_id, hw_baseboard_id) ); +CREATE TYPE IF NOT EXISTS omicron.public.rot_image_error AS ENUM ( + 'unchecked', + 'first_page_erased', + 'partially_programmed', + 'invalid_length', + 'header_not_programmed', + 'bootloader_too_small', + 'bad_magic', + 'header_image_size', + 'unaligned_length', + 'unsupported_type', + 'not_thumb2', + 'reset_vector', + 'signature' +); + -- root of trust information reported by SP -- There's usually one row here for each row in inv_service_processor, but not -- necessarily. @@ -2933,6 +2949,13 @@ CREATE TABLE IF NOT EXISTS omicron.public.inv_root_of_trust ( slot_boot_pref_persistent_pending omicron.public.hw_rot_slot, -- nullable slot_a_sha3_256 TEXT, -- nullable slot_b_sha3_256 TEXT, -- nullable + stage0_fwid TEXT, -- nullable + stage0next_fwid TEXT, -- nullable + + slot_a_error omicron.public.rot_image_error, -- nullable + slot_b_error omicron.public.rot_image_error, -- nullable + stage0_error omicron.public.rot_image_error, -- nullable + stage0next_error omicron.public.rot_image_error, -- nullable PRIMARY KEY (inv_collection_id, hw_baseboard_id) ); @@ -2941,7 +2964,9 @@ CREATE TYPE IF NOT EXISTS omicron.public.caboose_which AS ENUM ( 'sp_slot_0', 'sp_slot_1', 'rot_slot_A', - 'rot_slot_B' + 'rot_slot_B', + 'stage0', + 'stage0next' ); -- cabooses found @@ -4020,7 +4045,7 @@ INSERT INTO omicron.public.db_metadata ( version, target_version ) VALUES - (TRUE, NOW(), NOW(), '68.0.0', NULL) + (TRUE, NOW(), NOW(), '69.0.0', NULL) ON CONFLICT DO NOTHING; COMMIT; diff --git a/schema/crdb/expose-stage0/up01.sql b/schema/crdb/expose-stage0/up01.sql new file mode 100644 index 00000000000..f36e2c11e14 --- /dev/null +++ b/schema/crdb/expose-stage0/up01.sql @@ -0,0 +1,17 @@ +-- Create the `rot_image_error` type +CREATE TYPE IF NOT EXISTS omicron.public.rot_image_error AS ENUM ( + 'unchecked', + 'first_page_erased', + 'partially_programmed', + 'invalid_length', + 'header_not_programmed', + 'bootloader_too_small', + 'bad_magic', + 'header_image_size', + 'unaligned_length', + 'unsupported_type', + 'not_thumb2', + 'reset_vector', + 'signature' +); + diff --git a/schema/crdb/expose-stage0/up02.sql b/schema/crdb/expose-stage0/up02.sql new file mode 100644 index 00000000000..389b2ef7a6c --- /dev/null +++ b/schema/crdb/expose-stage0/up02.sql @@ -0,0 +1,8 @@ +-- Add new fields for stage0. These can all correctly be NULL +ALTER TABLE omicron.inv_root_of_trust + ADD COLUMN IF NOT EXISTS stage0_fwid TEXT, + ADD COLUMN IF NOT EXISTS stage0next_fwid TEXT, + ADD COLUMN IF NOT EXISTS slot_a_error omicron.public.rot_image_error, + ADD COLUMN IF NOT EXISTS slot_b_error omicron.public.rot_image_error, + ADD COLUMN IF NOT EXISTS stage0_error omicron.public.rot_image_error, + ADD COLUMN IF NOT EXISTS stage0next_error omicron.public.rot_image_error; diff --git a/schema/crdb/expose-stage0/up03.sql b/schema/crdb/expose-stage0/up03.sql new file mode 100644 index 00000000000..5f12436af30 --- /dev/null +++ b/schema/crdb/expose-stage0/up03.sql @@ -0,0 +1,2 @@ +-- add stage0/stage0next to the caboose targets +ALTER TYPE omicron.public.caboose_which ADD VALUE IF NOT EXISTS 'stage0'; diff --git a/schema/crdb/expose-stage0/up04.sql b/schema/crdb/expose-stage0/up04.sql new file mode 100644 index 00000000000..c72f43560f9 --- /dev/null +++ b/schema/crdb/expose-stage0/up04.sql @@ -0,0 +1,2 @@ +-- add stage0/stage0next to the caboose targets +ALTER TYPE omicron.public.caboose_which ADD VALUE IF NOT EXISTS 'stage0next'; diff --git a/smf/sp-sim/config.toml b/smf/sp-sim/config.toml index 9766be2f5c3..eda37959cd7 100644 --- a/smf/sp-sim/config.toml +++ b/smf/sp-sim/config.toml @@ -21,7 +21,7 @@ device_id_cert_seed = "01de00000000000000000000000000000000000000000000000000000 id = "sp3-host-cpu" device = "sp3-host-cpu" description = "FAKE host cpu" -capabilities.bits = 0 +capabilities = 0 presence = "Present" serial_console = "[::1]:33312" @@ -36,7 +36,7 @@ device_id_cert_seed = "01de00000000000000000000000000000000000000000000000000000 id = "sp3-host-cpu" device = "sp3-host-cpu" description = "FAKE host cpu" -capabilities.bits = 0 +capabilities = 0 presence = "Present" serial_console = "[::1]:33322" diff --git a/sp-sim/examples/config.toml b/sp-sim/examples/config.toml index 9766be2f5c3..cf338ecf2e8 100644 --- a/sp-sim/examples/config.toml +++ b/sp-sim/examples/config.toml @@ -9,7 +9,6 @@ serial_number = "SimSidecar0" manufacturing_root_cert_seed = "01de01de01de01de01de01de01de01de01de01de01de01de01de01de01de01de" device_id_cert_seed = "01de000000000000000000000000000000000000000000000000000000000000" - [[simulated_sps.gimlet]] multicast_addr = "ff15:0:1de::1" bind_addrs = ["[::]:33310", "[::]:33311"] @@ -21,7 +20,7 @@ device_id_cert_seed = "01de00000000000000000000000000000000000000000000000000000 id = "sp3-host-cpu" device = "sp3-host-cpu" description = "FAKE host cpu" -capabilities.bits = 0 +capabilities = 0 presence = "Present" serial_console = "[::1]:33312" @@ -36,7 +35,7 @@ device_id_cert_seed = "01de00000000000000000000000000000000000000000000000000000 id = "sp3-host-cpu" device = "sp3-host-cpu" description = "FAKE host cpu" -capabilities.bits = 0 +capabilities = 0 presence = "Present" serial_console = "[::1]:33322" diff --git a/sp-sim/src/config.rs b/sp-sim/src/config.rs index f2b63369538..b64953e5ed8 100644 --- a/sp-sim/src/config.rs +++ b/sp-sim/src/config.rs @@ -36,6 +36,13 @@ pub struct SpCommonConfig { /// Fake components. #[serde(skip_serializing_if = "Vec::is_empty", default)] pub components: Vec, + /// Return errors for `versioned_rot_boot_info` simulating + /// an older RoT + #[serde(default)] + pub old_rot_state: bool, + /// Simulate a RoT stage0 with no caboose + #[serde(default)] + pub no_stage0_caboose: bool, } /// Configuration of a simulated SP component diff --git a/sp-sim/src/gimlet.rs b/sp-sim/src/gimlet.rs index 0c109c1bd78..280248d0349 100644 --- a/sp-sim/src/gimlet.rs +++ b/sp-sim/src/gimlet.rs @@ -25,6 +25,7 @@ use gateway_messages::sp_impl::{BoundsChecked, DeviceDescription}; use gateway_messages::CfpaPage; use gateway_messages::ComponentAction; use gateway_messages::Header; +use gateway_messages::RotBootInfo; use gateway_messages::RotRequest; use gateway_messages::RotResponse; use gateway_messages::RotSlotId; @@ -56,6 +57,19 @@ use tokio::sync::Mutex as TokioMutex; use tokio::task::{self, JoinHandle}; pub const SIM_GIMLET_BOARD: &str = "SimGimletSp"; +const SP_GITC0: &[u8] = b"ffffffff"; +const SP_GITC1: &[u8] = b"fefefefe"; +const SP_BORD: &[u8] = SIM_GIMLET_BOARD.as_bytes(); +const SP_NAME: &[u8] = b"SimGimlet"; +const SP_VERS0: &[u8] = b"0.0.2"; +const SP_VERS1: &[u8] = b"0.0.1"; + +const ROT_GITC0: &[u8] = b"eeeeeeee"; +const ROT_GITC1: &[u8] = b"edededed"; +const ROT_BORD: &[u8] = SIM_ROT_BOARD.as_bytes(); +const ROT_NAME: &[u8] = b"SimGimletRot"; +const ROT_VERS0: &[u8] = b"0.0.4"; +const ROT_VERS1: &[u8] = b"0.0.3"; /// Type of request most recently handled by a simulated SP. /// @@ -278,6 +292,8 @@ impl Gimlet { commands_rx, Arc::clone(&last_request_handled), log, + gimlet.common.old_rot_state, + gimlet.common.no_stage0_caboose, ); inner_tasks .push(task::spawn(async move { inner.run().await.unwrap() })); @@ -498,6 +514,8 @@ impl UdpTask { commands: mpsc::UnboundedReceiver, last_request_handled: Arc>>, log: Logger, + old_rot_state: bool, + no_stage0_caboose: bool, ) -> (Self, Arc>, watch::Receiver) { let [udp0, udp1] = servers; let handler = Arc::new(TokioMutex::new(Handler::new( @@ -506,6 +524,8 @@ impl UdpTask { attached_mgs, incoming_serial_console, log, + old_rot_state, + no_stage0_caboose, ))); let responses_sent_count = watch::Sender::new(0); let responses_sent_count_rx = responses_sent_count.subscribe(); @@ -642,6 +662,8 @@ struct Handler { // this, our caller will pass us a function to call if they should ignore // whatever result we return and fail to respond at all. should_fail_to_respond_signal: Option>, + no_stage0_caboose: bool, + old_rot_state: bool, } impl Handler { @@ -651,6 +673,8 @@ impl Handler { attached_mgs: Arc>>, incoming_serial_console: HashMap>>, log: Logger, + old_rot_state: bool, + no_stage0_caboose: bool, ) -> Self { let mut leaked_component_device_strings = Vec::with_capacity(components.len()); @@ -679,6 +703,8 @@ impl Handler { reset_pending: None, last_request_handled: None, should_fail_to_respond_signal: None, + old_rot_state, + no_stage0_caboose, } } @@ -701,8 +727,8 @@ impl Handler { persistent_boot_preference: RotSlotId::A, pending_persistent_boot_preference: None, transient_boot_preference: None, - slot_a_sha3_256_digest: None, - slot_b_sha3_256_digest: None, + slot_a_sha3_256_digest: Some([0x55; 32]), + slot_b_sha3_256_digest: Some([0x66; 32]), }), } } @@ -1389,29 +1415,66 @@ impl SpHandler for Handler { fn get_component_caboose_value( &mut self, component: SpComponent, - _slot: u16, + slot: u16, key: [u8; 4], buf: &mut [u8], ) -> std::result::Result { - static SP_GITC: &[u8] = b"ffffffff"; - static SP_BORD: &[u8] = SIM_GIMLET_BOARD.as_bytes(); - static SP_NAME: &[u8] = b"SimGimlet"; - static SP_VERS: &[u8] = b"0.0.1"; - - static ROT_GITC: &[u8] = b"eeeeeeee"; - static ROT_BORD: &[u8] = SIM_ROT_BOARD.as_bytes(); - static ROT_NAME: &[u8] = b"SimGimlet"; - static ROT_VERS: &[u8] = b"0.0.1"; - - let val = match (component, &key) { - (SpComponent::SP_ITSELF, b"GITC") => SP_GITC, - (SpComponent::SP_ITSELF, b"BORD") => SP_BORD, - (SpComponent::SP_ITSELF, b"NAME") => SP_NAME, - (SpComponent::SP_ITSELF, b"VERS") => SP_VERS, - (SpComponent::ROT, b"GITC") => ROT_GITC, - (SpComponent::ROT, b"BORD") => ROT_BORD, - (SpComponent::ROT, b"NAME") => ROT_NAME, - (SpComponent::ROT, b"VERS") => ROT_VERS, + use crate::SIM_ROT_STAGE0_BOARD; + + const STAGE0_GITC0: &[u8] = b"ddddddddd"; + const STAGE0_GITC1: &[u8] = b"dadadadad"; + const STAGE0_BORD: &[u8] = SIM_ROT_STAGE0_BOARD.as_bytes(); + const STAGE0_NAME: &[u8] = b"SimGimletRot"; + const STAGE0_VERS0: &[u8] = b"0.0.200"; + const STAGE0_VERS1: &[u8] = b"0.0.200"; + + let val = match (component, &key, slot, self.no_stage0_caboose) { + (SpComponent::SP_ITSELF, b"GITC", 0, _) => SP_GITC0, + (SpComponent::SP_ITSELF, b"GITC", 1, _) => SP_GITC1, + (SpComponent::SP_ITSELF, b"BORD", _, _) => SP_BORD, + (SpComponent::SP_ITSELF, b"NAME", _, _) => SP_NAME, + (SpComponent::SP_ITSELF, b"VERS", 0, _) => SP_VERS0, + (SpComponent::SP_ITSELF, b"VERS", 1, _) => SP_VERS1, + (SpComponent::ROT, b"GITC", 0, _) => ROT_GITC0, + (SpComponent::ROT, b"GITC", 1, _) => ROT_GITC1, + (SpComponent::ROT, b"BORD", _, _) => ROT_BORD, + (SpComponent::ROT, b"NAME", _, _) => ROT_NAME, + (SpComponent::ROT, b"VERS", 0, _) => ROT_VERS0, + (SpComponent::ROT, b"VERS", 1, _) => ROT_VERS1, + (SpComponent::STAGE0, b"GITC", 0, false) => STAGE0_GITC0, + (SpComponent::STAGE0, b"GITC", 1, false) => STAGE0_GITC1, + (SpComponent::STAGE0, b"BORD", _, false) => STAGE0_BORD, + (SpComponent::STAGE0, b"NAME", _, false) => STAGE0_NAME, + (SpComponent::STAGE0, b"VERS", 0, false) => STAGE0_VERS0, + (SpComponent::STAGE0, b"VERS", 1, false) => STAGE0_VERS1, + _ => return Err(SpError::NoSuchCabooseKey(key)), + }; + + buf[..val.len()].copy_from_slice(val); + Ok(val.len()) + } + + #[cfg(any(feature = "no-caboose", feature = "old-state"))] + fn get_component_caboose_value( + &mut self, + component: SpComponent, + slot: u16, + key: [u8; 4], + buf: &mut [u8], + ) -> std::result::Result { + let val = match (component, &key, slot) { + (SpComponent::SP_ITSELF, b"GITC", 0) => SP_GITC0, + (SpComponent::SP_ITSELF, b"GITC", 1) => SP_GITC1, + (SpComponent::SP_ITSELF, b"BORD", _) => SP_BORD, + (SpComponent::SP_ITSELF, b"NAME", _) => SP_NAME, + (SpComponent::SP_ITSELF, b"VERS", 0) => SP_VERS0, + (SpComponent::SP_ITSELF, b"VERS", 1) => SP_VERS1, + (SpComponent::ROT, b"GITC", 0) => ROT_GITC0, + (SpComponent::ROT, b"GITC", 1) => ROT_GITC1, + (SpComponent::ROT, b"BORD", _) => ROT_BORD, + (SpComponent::ROT, b"NAME", _) => ROT_NAME, + (SpComponent::ROT, b"VERS", 0) => ROT_VERS0, + (SpComponent::ROT, b"VERS", 1) => ROT_VERS1, _ => return Err(SpError::NoSuchCabooseKey(key)), }; @@ -1445,6 +1508,114 @@ impl SpHandler for Handler { buf[dummy_page.len()..].fill(0); Ok(RotResponse::Ok) } + + fn vpd_lock_status_all( + &mut self, + _buf: &mut [u8], + ) -> Result { + Err(SpError::RequestUnsupportedForSp) + } + + fn reset_component_trigger_with_watchdog( + &mut self, + component: SpComponent, + _time_ms: u32, + ) -> Result<(), SpError> { + debug!( + &self.log, "received reset trigger with watchdog request"; + "component" => ?component, + ); + if component == SpComponent::SP_ITSELF { + if self.reset_pending == Some(SpComponent::SP_ITSELF) { + self.update_state.sp_reset(); + self.reset_pending = None; + if let Some(signal) = self.should_fail_to_respond_signal.take() + { + // Instruct `server::handle_request()` to _not_ respond to + // this request at all, simulating an SP actually resetting. + signal(); + } + Ok(()) + } else { + Err(SpError::ResetComponentTriggerWithoutPrepare) + } + } else if component == SpComponent::ROT { + if self.reset_pending == Some(SpComponent::ROT) { + self.update_state.rot_reset(); + self.reset_pending = None; + Ok(()) + } else { + Err(SpError::ResetComponentTriggerWithoutPrepare) + } + } else { + Err(SpError::RequestUnsupportedForComponent) + } + } + + fn disable_component_watchdog( + &mut self, + _component: SpComponent, + ) -> Result<(), SpError> { + Ok(()) + } + fn component_watchdog_supported( + &mut self, + _component: SpComponent, + ) -> Result<(), SpError> { + Ok(()) + } + + fn versioned_rot_boot_info( + &mut self, + _sender: SocketAddrV6, + _port: SpPort, + version: u8, + ) -> Result { + if self.old_rot_state { + Err(SpError::RequestUnsupportedForSp) + } else { + const SLOT_A_DIGEST: [u8; 32] = [0xaa; 32]; + const SLOT_B_DIGEST: [u8; 32] = [0xbb; 32]; + const STAGE0_DIGEST: [u8; 32] = [0xcc; 32]; + const STAGE0NEXT_DIGEST: [u8; 32] = [0xdd; 32]; + + match version { + 0 => Err(SpError::Update( + gateway_messages::UpdateError::VersionNotSupported, + )), + 1 => Ok(RotBootInfo::V2(gateway_messages::RotStateV2 { + active: RotSlotId::A, + persistent_boot_preference: RotSlotId::A, + pending_persistent_boot_preference: None, + transient_boot_preference: None, + slot_a_sha3_256_digest: Some(SLOT_A_DIGEST), + slot_b_sha3_256_digest: Some(SLOT_B_DIGEST), + })), + _ => Ok(RotBootInfo::V3(gateway_messages::RotStateV3 { + active: RotSlotId::A, + persistent_boot_preference: RotSlotId::A, + pending_persistent_boot_preference: None, + transient_boot_preference: None, + slot_a_fwid: gateway_messages::Fwid::Sha3_256( + SLOT_A_DIGEST, + ), + slot_b_fwid: gateway_messages::Fwid::Sha3_256( + SLOT_B_DIGEST, + ), + stage0_fwid: gateway_messages::Fwid::Sha3_256( + STAGE0_DIGEST, + ), + stage0next_fwid: gateway_messages::Fwid::Sha3_256( + STAGE0NEXT_DIGEST, + ), + slot_a_status: Ok(()), + slot_b_status: Ok(()), + stage0_status: Ok(()), + stage0next_status: Ok(()), + })), + } + } + } } impl SimSpHandler for Handler { diff --git a/sp-sim/src/lib.rs b/sp-sim/src/lib.rs index 8a8418b84de..ca9231bec01 100644 --- a/sp-sim/src/lib.rs +++ b/sp-sim/src/lib.rs @@ -30,6 +30,7 @@ use tokio::sync::mpsc; use tokio::sync::watch; pub const SIM_ROT_BOARD: &str = "SimRot"; +pub const SIM_ROT_STAGE0_BOARD: &str = "SimRotStage0"; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Responsiveness { diff --git a/sp-sim/src/sidecar.rs b/sp-sim/src/sidecar.rs index 1bd6fe49649..1b5b5d297b5 100644 --- a/sp-sim/src/sidecar.rs +++ b/sp-sim/src/sidecar.rs @@ -17,6 +17,7 @@ use crate::update::SimSpUpdate; use crate::Responsiveness; use crate::SimulatedSp; use crate::SIM_ROT_BOARD; +use crate::SIM_ROT_STAGE0_BOARD; use anyhow::Result; use async_trait::async_trait; use futures::future; @@ -35,6 +36,7 @@ use gateway_messages::IgnitionCommand; use gateway_messages::IgnitionState; use gateway_messages::MgsError; use gateway_messages::PowerState; +use gateway_messages::RotBootInfo; use gateway_messages::RotRequest; use gateway_messages::RotResponse; use gateway_messages::RotSlotId; @@ -206,6 +208,8 @@ impl Sidecar { FakeIgnition::new(&config.simulated_sps), commands_rx, log, + sidecar.common.old_rot_state, + sidecar.common.no_stage0_caboose, ); let inner_task = task::spawn(async move { inner.run().await.unwrap() }); @@ -269,6 +273,8 @@ impl Inner { ignition: FakeIgnition, commands: mpsc::UnboundedReceiver, log: Logger, + old_rot_state: bool, + no_stage0_caboose: bool, ) -> (Self, Arc>, watch::Receiver) { let [udp0, udp1] = servers; let handler = Arc::new(TokioMutex::new(Handler::new( @@ -276,6 +282,8 @@ impl Inner { components, ignition, log, + old_rot_state, + no_stage0_caboose, ))); let responses_sent_count = watch::Sender::new(0); let responses_sent_count_rx = responses_sent_count.subscribe(); @@ -406,6 +414,8 @@ struct Handler { // this, our caller will pass us a function to call if they should ignore // whatever result we return and fail to respond at all. should_fail_to_respond_signal: Option>, + no_stage0_caboose: bool, + old_rot_state: bool, } impl Handler { @@ -414,6 +424,8 @@ impl Handler { components: Vec, ignition: FakeIgnition, log: Logger, + old_rot_state: bool, + no_stage0_caboose: bool, ) -> Self { let mut leaked_component_device_strings = Vec::with_capacity(components.len()); @@ -439,6 +451,8 @@ impl Handler { update_state: SimSpUpdate::default(), reset_pending: None, should_fail_to_respond_signal: None, + old_rot_state, + no_stage0_caboose, } } @@ -1118,29 +1132,50 @@ impl SpHandler for Handler { fn get_component_caboose_value( &mut self, component: SpComponent, - _slot: u16, + slot: u16, key: [u8; 4], buf: &mut [u8], ) -> std::result::Result { - static SP_GITC: &[u8] = b"ffffffff"; + static SP_GITC0: &[u8] = b"ffffffff"; + static SP_GITC1: &[u8] = b"fefefefe"; static SP_BORD: &[u8] = SIM_SIDECAR_BOARD.as_bytes(); static SP_NAME: &[u8] = b"SimSidecar"; - static SP_VERS: &[u8] = b"0.0.1"; + static SP_VERS0: &[u8] = b"0.0.2"; + static SP_VERS1: &[u8] = b"0.0.1"; - static ROT_GITC: &[u8] = b"eeeeeeee"; + static ROT_GITC0: &[u8] = b"eeeeeeee"; + static ROT_GITC1: &[u8] = b"edededed"; static ROT_BORD: &[u8] = SIM_ROT_BOARD.as_bytes(); static ROT_NAME: &[u8] = b"SimSidecar"; - static ROT_VERS: &[u8] = b"0.0.1"; - - let val = match (component, &key) { - (SpComponent::SP_ITSELF, b"GITC") => SP_GITC, - (SpComponent::SP_ITSELF, b"BORD") => SP_BORD, - (SpComponent::SP_ITSELF, b"NAME") => SP_NAME, - (SpComponent::SP_ITSELF, b"VERS") => SP_VERS, - (SpComponent::ROT, b"GITC") => ROT_GITC, - (SpComponent::ROT, b"BORD") => ROT_BORD, - (SpComponent::ROT, b"NAME") => ROT_NAME, - (SpComponent::ROT, b"VERS") => ROT_VERS, + static ROT_VERS0: &[u8] = b"0.0.4"; + static ROT_VERS1: &[u8] = b"0.0.3"; + + static STAGE0_GITC0: &[u8] = b"dddddddd"; + static STAGE0_GITC1: &[u8] = b"dadadada"; + static STAGE0_BORD: &[u8] = SIM_ROT_STAGE0_BOARD.as_bytes(); + static STAGE0_NAME: &[u8] = b"SimSidecar"; + static STAGE0_VERS0: &[u8] = b"0.0.200"; + static STAGE0_VERS1: &[u8] = b"0.0.200"; + + let val = match (component, &key, slot, self.no_stage0_caboose) { + (SpComponent::SP_ITSELF, b"GITC", 0, _) => SP_GITC0, + (SpComponent::SP_ITSELF, b"GITC", 1, _) => SP_GITC1, + (SpComponent::SP_ITSELF, b"BORD", _, _) => SP_BORD, + (SpComponent::SP_ITSELF, b"NAME", _, _) => SP_NAME, + (SpComponent::SP_ITSELF, b"VERS", 0, _) => SP_VERS0, + (SpComponent::SP_ITSELF, b"VERS", 1, _) => SP_VERS1, + (SpComponent::ROT, b"GITC", 0, _) => ROT_GITC0, + (SpComponent::ROT, b"GITC", 1, _) => ROT_GITC1, + (SpComponent::ROT, b"BORD", _, _) => ROT_BORD, + (SpComponent::ROT, b"NAME", _, _) => ROT_NAME, + (SpComponent::ROT, b"VERS", 0, _) => ROT_VERS0, + (SpComponent::ROT, b"VERS", 1, _) => ROT_VERS1, + (SpComponent::STAGE0, b"GITC", 0, false) => STAGE0_GITC0, + (SpComponent::STAGE0, b"GITC", 1, false) => STAGE0_GITC1, + (SpComponent::STAGE0, b"BORD", _, false) => STAGE0_BORD, + (SpComponent::STAGE0, b"NAME", _, false) => STAGE0_NAME, + (SpComponent::STAGE0, b"VERS", 0, false) => STAGE0_VERS0, + (SpComponent::STAGE0, b"VERS", 1, false) => STAGE0_VERS1, _ => return Err(SpError::NoSuchCabooseKey(key)), }; @@ -1174,6 +1209,114 @@ impl SpHandler for Handler { buf[dummy_page.len()..].fill(0); Ok(RotResponse::Ok) } + + fn vpd_lock_status_all( + &mut self, + _buf: &mut [u8], + ) -> Result { + Err(SpError::RequestUnsupportedForSp) + } + + fn reset_component_trigger_with_watchdog( + &mut self, + component: SpComponent, + _time_ms: u32, + ) -> Result<(), SpError> { + debug!( + &self.log, "received sys-reset trigger with wathcdog request"; + "component" => ?component, + ); + if component == SpComponent::SP_ITSELF { + if self.reset_pending == Some(SpComponent::SP_ITSELF) { + self.update_state.sp_reset(); + self.reset_pending = None; + if let Some(signal) = self.should_fail_to_respond_signal.take() + { + // Instruct `server::handle_request()` to _not_ respond to + // this request at all, simulating an SP actually resetting. + signal(); + } + Ok(()) + } else { + Err(SpError::ResetComponentTriggerWithoutPrepare) + } + } else if component == SpComponent::ROT { + if self.reset_pending == Some(SpComponent::ROT) { + self.update_state.rot_reset(); + self.reset_pending = None; + Ok(()) + } else { + Err(SpError::ResetComponentTriggerWithoutPrepare) + } + } else { + Err(SpError::RequestUnsupportedForComponent) + } + } + + fn disable_component_watchdog( + &mut self, + _component: SpComponent, + ) -> Result<(), SpError> { + Ok(()) + } + fn component_watchdog_supported( + &mut self, + _component: SpComponent, + ) -> Result<(), SpError> { + Ok(()) + } + + fn versioned_rot_boot_info( + &mut self, + _sender: SocketAddrV6, + _port: SpPort, + version: u8, + ) -> Result { + if self.old_rot_state { + Err(SpError::RequestUnsupportedForSp) + } else { + const SLOT_A_DIGEST: [u8; 32] = [0xaa; 32]; + const SLOT_B_DIGEST: [u8; 32] = [0xbb; 32]; + const STAGE0_DIGEST: [u8; 32] = [0xcc; 32]; + const STAGE0NEXT_DIGEST: [u8; 32] = [0xdd; 32]; + + match version { + 0 => Err(SpError::Update( + gateway_messages::UpdateError::VersionNotSupported, + )), + 1 => Ok(RotBootInfo::V2(gateway_messages::RotStateV2 { + active: RotSlotId::A, + persistent_boot_preference: RotSlotId::A, + pending_persistent_boot_preference: None, + transient_boot_preference: None, + slot_a_sha3_256_digest: Some(SLOT_A_DIGEST), + slot_b_sha3_256_digest: Some(SLOT_B_DIGEST), + })), + _ => Ok(RotBootInfo::V3(gateway_messages::RotStateV3 { + active: RotSlotId::A, + persistent_boot_preference: RotSlotId::A, + pending_persistent_boot_preference: None, + transient_boot_preference: None, + slot_a_fwid: gateway_messages::Fwid::Sha3_256( + SLOT_A_DIGEST, + ), + slot_b_fwid: gateway_messages::Fwid::Sha3_256( + SLOT_B_DIGEST, + ), + stage0_fwid: gateway_messages::Fwid::Sha3_256( + STAGE0_DIGEST, + ), + stage0next_fwid: gateway_messages::Fwid::Sha3_256( + STAGE0NEXT_DIGEST, + ), + slot_a_status: Ok(()), + slot_b_status: Ok(()), + stage0_status: Ok(()), + stage0next_status: Ok(()), + })), + } + } + } } impl SimSpHandler for Handler { diff --git a/wicket/src/ui/panes/overview.rs b/wicket/src/ui/panes/overview.rs index f2d4d4a7aba..7d60c417725 100644 --- a/wicket/src/ui/panes/overview.rs +++ b/wicket/src/ui/panes/overview.rs @@ -675,7 +675,255 @@ fn inventory_description(component: &Component) -> Text { let mut label = vec![Span::styled("Root of Trust: ", label_style)]; if let Some(rot) = sp.state().map(|sp| &sp.rot) { match rot { - RotState::Enabled { + RotState::V3 { + active, + pending_persistent_boot_preference, + persistent_boot_preference, + slot_a_fwid, + slot_b_fwid, + stage0_fwid, + stage0next_fwid, + transient_boot_preference, + slot_a_error, + slot_b_error, + stage0_error, + stage0next_error, + } => { + spans.push(label.into()); + spans.push( + vec![ + bullet(), + Span::styled("Active Slot: ", label_style), + Span::styled(format!("{active:?}"), ok_style), + ] + .into(), + ); + spans.push( + vec![ + bullet(), + Span::styled( + "Persistent Boot Preference: ", + label_style, + ), + Span::styled( + format!("{persistent_boot_preference:?}"), + ok_style, + ), + ] + .into(), + ); + spans.push( + vec![ + bullet(), + Span::styled( + "Pending Persistent Boot Preference: ", + label_style, + ), + Span::styled( + match pending_persistent_boot_preference.as_ref() { + Some(pref) => Cow::from(format!("{pref:?}")), + None => Cow::from("None"), + }, + ok_style, + ), + ] + .into(), + ); + spans.push( + vec![ + bullet(), + Span::styled( + "Transient Boot Preference: ", + label_style, + ), + Span::styled( + match transient_boot_preference.as_ref() { + Some(pref) => Cow::from(format!("{pref:?}")), + None => Cow::from("None"), + }, + ok_style, + ), + ] + .into(), + ); + spans.push( + vec![bullet(), Span::styled("Slot A:", label_style)].into(), + ); + spans.push( + vec![ + nest_bullet(), + Span::styled("Image SHA3-256: ", label_style), + Span::styled(slot_a_fwid.clone(), ok_style), + ] + .into(), + ); + if let Some(caboose) = + sp.rot().and_then(|r| r.caboose_a.as_ref()) + { + append_caboose(&mut spans, nest_bullet(), caboose); + } else { + spans.push( + vec![ + nest_bullet(), + Span::styled("No further information", warn_style), + ] + .into(), + ); + } + if let Some(_) = slot_a_error { + spans.push( + vec![ + nest_bullet(), + Span::styled("Image status: ", label_style), + Span::styled("Error: ", bad_style), + ] + .into(), + ); + } else { + spans.push( + vec![ + nest_bullet(), + Span::styled("Image status: ", label_style), + Span::styled("Status Good", ok_style), + ] + .into(), + ); + } + spans.push( + vec![bullet(), Span::styled("Slot B:", label_style)].into(), + ); + spans.push( + vec![ + nest_bullet(), + Span::styled("Image SHA3-256: ", label_style), + Span::styled(slot_b_fwid.clone(), ok_style), + ] + .into(), + ); + if let Some(caboose) = + sp.rot().and_then(|r| r.caboose_b.as_ref()) + { + append_caboose(&mut spans, nest_bullet(), caboose); + } else { + spans.push( + vec![ + nest_bullet(), + Span::styled("No further information", warn_style), + ] + .into(), + ); + } + if let Some(_) = slot_b_error { + spans.push( + vec![ + nest_bullet(), + Span::styled("Image status: ", label_style), + Span::styled("Error: ", bad_style), + ] + .into(), + ); + } else { + spans.push( + vec![ + nest_bullet(), + Span::styled("Image status: ", label_style), + Span::styled("Status Good", ok_style), + ] + .into(), + ); + } + + spans.push( + vec![bullet(), Span::styled("Stage0:", label_style)].into(), + ); + spans.push( + vec![ + nest_bullet(), + Span::styled("Image SHA3-256: ", label_style), + Span::styled(stage0_fwid.clone(), ok_style), + ] + .into(), + ); + if let Some(caboose) = + sp.rot().and_then(|r| r.caboose_stage0.as_ref()) + { + append_caboose(&mut spans, nest_bullet(), caboose); + } else { + spans.push( + vec![ + nest_bullet(), + Span::styled("No further information", warn_style), + ] + .into(), + ); + } + if let Some(_) = stage0_error { + spans.push( + vec![ + nest_bullet(), + Span::styled("Image status: ", label_style), + Span::styled("Error: ", bad_style), + ] + .into(), + ); + } else { + spans.push( + vec![ + nest_bullet(), + Span::styled("Image status: ", label_style), + Span::styled("Status Good", ok_style), + ] + .into(), + ); + } + + spans.push( + vec![bullet(), Span::styled("Stage0Next:", label_style)] + .into(), + ); + spans.push( + vec![ + nest_bullet(), + Span::styled("Image SHA3-256: ", label_style), + Span::styled(stage0next_fwid.clone(), ok_style), + ] + .into(), + ); + if let Some(caboose) = + sp.rot().and_then(|r| r.caboose_stage0next.as_ref()) + { + append_caboose(&mut spans, nest_bullet(), caboose); + } else { + spans.push( + vec![ + nest_bullet(), + Span::styled("No further information", warn_style), + ] + .into(), + ); + } + if let Some(_) = stage0next_error { + spans.push( + vec![ + nest_bullet(), + Span::styled("Image status: ", label_style), + Span::styled("Error: ", bad_style), + ] + .into(), + ); + } else { + spans.push( + vec![ + nest_bullet(), + Span::styled("Image status: ", label_style), + Span::styled("Status Good", ok_style), + ] + .into(), + ); + } + } + + RotState::V2 { active, pending_persistent_boot_preference, persistent_boot_preference, diff --git a/wicketd/src/inventory.rs b/wicketd/src/inventory.rs index f6bf5c2984b..e1465147b5f 100644 --- a/wicketd/src/inventory.rs +++ b/wicketd/src/inventory.rs @@ -48,6 +48,10 @@ pub struct RotInventory { pub active: RotSlot, pub caboose_a: Option, pub caboose_b: Option, + // stage0 information is not available on all RoT versions + // `None` indicates we don't need to read + pub caboose_stage0: Option>, + pub caboose_stage0next: Option>, } /// The current state of the v1 Rack as known to wicketd diff --git a/wicketd/src/mgs/inventory.rs b/wicketd/src/mgs/inventory.rs index 5334c08a40e..a9805b4c1da 100644 --- a/wicketd/src/mgs/inventory.rs +++ b/wicketd/src/mgs/inventory.rs @@ -316,11 +316,22 @@ async fn sp_fetching_task( if rot.is_none() || prev_state.as_ref() != Some(&state) { match &state.rot { - RotState::Enabled { active, .. } => { + RotState::V2 { active, .. } => { rot = Some(RotInventory { active: *active, caboose_a: None, caboose_b: None, + caboose_stage0: None, + caboose_stage0next: None, + }); + } + RotState::V3 { active, .. } => { + rot = Some(RotInventory { + active: *active, + caboose_a: None, + caboose_b: None, + caboose_stage0: Some(None), + caboose_stage0next: Some(None), }); } RotState::CommunicationFailed { message } => { @@ -456,6 +467,60 @@ async fn sp_fetching_task( } }; } + + if let Some(v) = &rot.caboose_stage0 { + if prev_state.as_ref() != Some(&state) || v.is_none() { + rot.caboose_stage0 = match mgs_client + .sp_component_caboose_get( + id.type_, + id.slot, + SpComponent::STAGE0.const_as_str(), + 0, + ) + .await + { + Ok(response) => { + mgs_received = Instant::now(); + Some(Some(response.into_inner())) + } + Err(err) => { + warn!( + log, "Failed to get RoT caboose (stage0) for sp"; + "sp" => ?id, + "err" => %err, + ); + Some(None) + } + }; + } + } + + if let Some(v) = &rot.caboose_stage0next { + if prev_state.as_ref() != Some(&state) || v.is_none() { + rot.caboose_stage0next = match mgs_client + .sp_component_caboose_get( + id.type_, + id.slot, + SpComponent::STAGE0.const_as_str(), + 1, + ) + .await + { + Ok(response) => { + mgs_received = Instant::now(); + Some(Some(response.into_inner())) + } + Err(err) => { + warn!( + log, "Failed to get RoT caboose (stage0next) for sp"; + "sp" => ?id, + "err" => %err, + ); + Some(None) + } + }; + } + } } let emit = FetchedSpData { diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index 7880422c47a..1b21b724955 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -53,7 +53,7 @@ futures-io = { version = "0.3.30", default-features = false, features = ["std"] futures-sink = { version = "0.3.30" } futures-task = { version = "0.3.30", default-features = false, features = ["std"] } futures-util = { version = "0.3.30", features = ["channel", "io", "sink"] } -gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "2739c18e80697aa6bc235c935176d14b4d757ee9", features = ["std"] } +gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "c85a4ca043aaa389df12aac5348d8a3feda28762", features = ["std"] } generic-array = { version = "0.14.7", default-features = false, features = ["more_lengths", "zeroize"] } getrandom = { version = "0.2.14", default-features = false, features = ["js", "rdrand", "std"] } group = { version = "0.13.0", default-features = false, features = ["alloc"] } @@ -101,7 +101,6 @@ smallvec = { version = "1.13.2", default-features = false, features = ["const_ne spin = { version = "0.9.8" } string_cache = { version = "0.8.7" } subtle = { version = "2.5.0" } -syn-dff4ba8e3ae991db = { package = "syn", version = "1.0.109", features = ["extra-traits", "fold", "full", "visit"] } syn-f595c2ba2a3f28df = { package = "syn", version = "2.0.64", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] } time = { version = "0.3.36", features = ["formatting", "local-offset", "macros", "parsing"] } tokio = { version = "1.37.0", features = ["full", "test-util"] } @@ -158,7 +157,7 @@ futures-io = { version = "0.3.30", default-features = false, features = ["std"] futures-sink = { version = "0.3.30" } futures-task = { version = "0.3.30", default-features = false, features = ["std"] } futures-util = { version = "0.3.30", features = ["channel", "io", "sink"] } -gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "2739c18e80697aa6bc235c935176d14b4d757ee9", features = ["std"] } +gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "c85a4ca043aaa389df12aac5348d8a3feda28762", features = ["std"] } generic-array = { version = "0.14.7", default-features = false, features = ["more_lengths", "zeroize"] } getrandom = { version = "0.2.14", default-features = false, features = ["js", "rdrand", "std"] } group = { version = "0.13.0", default-features = false, features = ["alloc"] }