diff --git a/crates/api-db/src/attestation/spdm.rs b/crates/api-db/src/attestation/spdm.rs index ee117083e0..c16c433a83 100644 --- a/crates/api-db/src/attestation/spdm.rs +++ b/crates/api-db/src/attestation/spdm.rs @@ -220,6 +220,13 @@ pub async fn get_attestation_status_for_machine_id( .await .map_err(|e| DatabaseError::query(query, e))?; + if attestation_status_rows.is_empty() { + return Err(DatabaseError::NotFoundError { + kind: "SPDM Attestation Record", + id: machine_id.to_string(), + }); + } + // if all passed - PASSED // if all cancelled - CANCELLED // if any failed && none not in progress - FAILED diff --git a/crates/api-model/src/attestation.rs b/crates/api-model/src/attestation.rs index 202c6a02d5..437e449eca 100644 --- a/crates/api-model/src/attestation.rs +++ b/crates/api-model/src/attestation.rs @@ -54,6 +54,7 @@ pub mod spdm { use itertools::Itertools; use nras::{NrasError, NrasVerifierClient, ProcessedAttestationOutcome, RawAttestationOutcome}; use serde::{Deserialize, Serialize}; + use sha2::{Digest, Sha256}; use sqlx::Row; use sqlx::postgres::PgRow; @@ -96,6 +97,12 @@ pub mod spdm { pub completed_at: Option>, } + impl SpdmDeviceAttestation { + pub fn nonce_hex(&self) -> String { + hex::encode(Sha256::digest(self.nonce.as_bytes())) + } + } + /// Major state, associated with Machine. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum SpdmAttestationState { diff --git a/crates/api/src/handlers/attestation.rs b/crates/api/src/handlers/attestation.rs index 88844799ab..b09f27e94f 100644 --- a/crates/api/src/handlers/attestation.rs +++ b/crates/api/src/handlers/attestation.rs @@ -14,6 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +use std::collections::BTreeMap; + use ::rpc::common::MachineIdList; use ::rpc::forge::{self as rpc}; use carbide_uuid::machine::MachineId; @@ -32,6 +34,9 @@ use tonic::{Request, Response, Status}; use crate::CarbideError; use crate::api::{Api, log_machine_id, log_request_data}; +const PRODUCT_GB200: &str = "GB200 NVL"; +const PRODUCT_GB300: &str = "GB300 NVL"; + pub(crate) async fn trigger_machine_attestation( api: &Api, request: Request, @@ -130,7 +135,7 @@ pub async fn trigger_attestation( Err(_) => { return Err(CarbideError::Internal { message: format!( - "redfish service_root could not finish in {} secods", + "redfish service_root could not finish in {} seconds", redfish_timeout_duration.as_secs() ), }); @@ -142,6 +147,28 @@ pub async fn trigger_attestation( return Ok(0); } + // do we support attestation for a given machine type? + // check the ServiceRoot.Product + let product = match service_root.product { + Some(product_name) => product_name, + None => { + tracing::info!( + "ServiceRoot.Product is None, not scheduling SPDM attestation for machine: {}", + machine_id + ); + return Ok(0); + } + }; + + if !is_supported_product(&product) { + tracing::info!( + "ServiceRoot.Product - {} - is not supported, not scheduling SPDM attestation for machine: {}", + product, + machine_id + ); + return Ok(0); + } + let component_integrities_future = redfish_client.get_component_integrities(); let component_integrities = @@ -155,14 +182,14 @@ pub async fn trigger_attestation( Err(_) => { return Err(CarbideError::Internal { message: format!( - "redfish get_component_integrities could not finish in {} secods", + "redfish get_component_integrities could not finish in {} seconds", redfish_timeout_duration.as_secs() ), }); } }; - let components = get_components_supporting_spdm(&component_integrities); + let components = get_supported_components(&product, &component_integrities); if components.is_empty() { // let's treat 0 devices under attestation as NotSupported @@ -177,6 +204,7 @@ pub async fn trigger_attestation( // Remove existing device list and over-write with this list. let time_now = Utc::now(); let device_attestations = components + .clone() .into_iter() .map(|x| from_component_integrity(x.clone(), machine_id, &time_now, bmc_info)) .collect_vec(); @@ -204,9 +232,12 @@ pub async fn trigger_attestation( .map_err(|e| AnnotatedSqlxError::new("trigger_attestation commit", e))?; tracing::info!( - "SPDM attestation commenced for machine {}, scheduled {} SPDM device attestations", + "SPDM attestation commenced for machine {}, scheduled {} SPDM device attestations. Attestations scheduled are: {}", machine_id, - records_inserted + records_inserted, + components.iter().fold("".to_string(), |acc, &elem| { + format!("{} {}", acc, elem.id) + }) ); Ok(records_inserted) @@ -419,8 +450,14 @@ pub(crate) async fn attest_quote( // ComponentIntegrityTypeVersion should be >= 1.1.0. // ComponentIntegrityType should be SPDM. // ComponentIntegrityEnabled should be true. +// A device must be of supported type. // Once these all conditions are true, a device can be proceed with attestation. -fn get_components_supporting_spdm(integrities: &ComponentIntegrities) -> Vec<&ComponentIntegrity> { +fn get_supported_components<'a>( + product: &str, + integrities: &'a ComponentIntegrities, +) -> Vec<&'a ComponentIntegrity> { + let supported_devices = BTreeMap::from([(PRODUCT_GB200, ["HGX_IRoT_GPU"])]); + let supported_versions = ["1.1.0"]; // This can be configurable value. let mut supported_components = vec![]; @@ -439,6 +476,17 @@ fn get_components_supporting_spdm(integrities: &ComponentIntegrities) -> Vec<&Co continue; } + let is_supported = match supported_devices.get(product) { + Some(device_id_stems) => device_id_stems + .iter() + .any(|device_id_stem| component.id.contains(device_id_stem)), + None => false, + }; + + if !is_supported { + continue; + } + supported_components.push(component); } @@ -454,6 +502,7 @@ fn from_component_integrity( let ca_certificate_link = integrity .spdm .map(|x| x.identity_authentication) + .map(|x| x.responder_authentication) .map(|x| x.component_certificate) .map(|x| x.odata_id); @@ -483,6 +532,10 @@ fn from_component_integrity( } } +fn is_supported_product(product: &str) -> bool { + matches!(product, PRODUCT_GB200 | PRODUCT_GB300) +} + #[cfg(not(feature = "linux-build"))] pub(crate) async fn attest_quote( _api: &Api, diff --git a/crates/api/src/state_controller/spdm/handler.rs b/crates/api/src/state_controller/spdm/handler.rs index 9c99fe275a..86d57b0d71 100644 --- a/crates/api/src/state_controller/spdm/handler.rs +++ b/crates/api/src/state_controller/spdm/handler.rs @@ -178,8 +178,9 @@ impl StateHandler for SpdmAttestationStateHandler { ), )); }; + let nonce = snapshot.nonce_hex(); let task = redfish_client - .trigger_evidence_collection(url.as_str(), snapshot.nonce.to_string().as_str()) + .trigger_evidence_collection(url.as_str(), nonce.as_str()) .await .map_err(|error| redfish_error("trigger measurement collection", error))?; @@ -348,7 +349,7 @@ async fn perform_attestation( firmware_version, }], architecture: nras::MachineArchitecture::Blackwell, - nonce: device.nonce.to_string(), + nonce: device.nonce_hex(), }; let device_type: DeviceType = device.device_id.parse()?; diff --git a/crates/nras/src/client.rs b/crates/nras/src/client.rs index 43b477158e..fcaf349326 100644 --- a/crates/nras/src/client.rs +++ b/crates/nras/src/client.rs @@ -83,8 +83,8 @@ impl VerifierClient for NrasVerifierClient { if status_code != reqwest::StatusCode::OK { return Err(NrasError::Communication(format!( - "NRAS returned status code {} and message {}", - status_code, response_text + "NRAS returned status code {} and message {}.\n Config is {:?}", + status_code, response_text, self.config ))); } diff --git a/crates/redfish/src/libredfish/test_support.rs b/crates/redfish/src/libredfish/test_support.rs index bf98d5c3e7..f733039e83 100644 --- a/crates/redfish/src/libredfish/test_support.rs +++ b/crates/redfish/src/libredfish/test_support.rs @@ -1397,13 +1397,16 @@ impl Redfish for RedfishSimClient { target_component_uri: Some("/redfish/v1/Chassis/ERoT_BMC_0".to_string()), spdm: Some(libredfish::model::component_integrity::SPDMData { identity_authentication: - libredfish::model::component_integrity::ResponderAuthentication { - component_certificate: ODataId { - odata_id: - "/redfish/v1/Chassis/ERoT_BMC_0/Certificates/CertChain" - .to_string(), + libredfish::model::component_integrity::IdentityAuthentication { + responder_authentication: + libredfish::model::component_integrity::ResponderAuthentication { + component_certificate: ODataId { + odata_id: + "/redfish/v1/Chassis/ERoT_BMC_0/Certificates/CertChain" + .to_string(), + }, + }, }, - }, requester: ODataId { odata_id: "/redfish/v1/Managers/BMC_0".to_string(), }, @@ -1431,13 +1434,16 @@ impl Redfish for RedfishSimClient { target_component_uri: Some("/redfish/v1/Chassis/HGX_IRoT_GPU_0".to_string()), spdm: Some(libredfish::model::component_integrity::SPDMData { identity_authentication: - libredfish::model::component_integrity::ResponderAuthentication { - component_certificate: ODataId { - odata_id: - "/redfish/v1/Chassis/HGX_IRoT_GPU_0/Certificates/CertChain" - .to_string(), + libredfish::model::component_integrity::IdentityAuthentication { + responder_authentication: + libredfish::model::component_integrity::ResponderAuthentication { + component_certificate: ODataId { + odata_id: + "/redfish/v1/Chassis/HGX_IRoT_GPU_0/Certificates/CertChain" + .to_string(), + }, + }, }, - }, requester: ODataId { odata_id: "/redfish/v1/Managers/BMC_0".to_string(), }, @@ -1465,13 +1471,16 @@ impl Redfish for RedfishSimClient { target_component_uri: Some("/redfish/v1/Chassis/HGX_IRoT_GPU_1".to_string()), spdm: Some(libredfish::model::component_integrity::SPDMData { identity_authentication: - libredfish::model::component_integrity::ResponderAuthentication { - component_certificate: ODataId { - odata_id: - "/redfish/v1/Chassis/HGX_IRoT_GPU_1/Certificates/CertChain" - .to_string(), + libredfish::model::component_integrity::IdentityAuthentication { + responder_authentication: + libredfish::model::component_integrity::ResponderAuthentication { + component_certificate: ODataId { + odata_id: + "/redfish/v1/Chassis/HGX_IRoT_GPU_1/Certificates/CertChain" + .to_string(), + }, + }, }, - }, requester: ODataId { odata_id: "/redfish/v1/Managers/BMC_0".to_string(), }, @@ -1499,13 +1508,16 @@ impl Redfish for RedfishSimClient { target_component_uri: Some("/redfish/v1/Chassis/HGX_IRoT_GPU_1".to_string()), spdm: Some(libredfish::model::component_integrity::SPDMData { identity_authentication: - libredfish::model::component_integrity::ResponderAuthentication { - component_certificate: ODataId { - odata_id: - "/redfish/v1/Chassis/HGX_IRoT_GPU_1/Certificates/CertChain" - .to_string(), + libredfish::model::component_integrity::IdentityAuthentication { + responder_authentication: + libredfish::model::component_integrity::ResponderAuthentication { + component_certificate: ODataId { + odata_id: + "/redfish/v1/Chassis/HGX_IRoT_GPU_1/Certificates/CertChain" + .to_string(), + }, + }, }, - }, requester: ODataId { odata_id: "/redfish/v1/Managers/BMC_0".to_string(), }, @@ -1533,13 +1545,16 @@ impl Redfish for RedfishSimClient { target_component_uri: Some("/redfish/v1/Chassis/HGX_IRoT_GPU_1".to_string()), spdm: Some(libredfish::model::component_integrity::SPDMData { identity_authentication: - libredfish::model::component_integrity::ResponderAuthentication { - component_certificate: ODataId { - odata_id: - "/redfish/v1/Chassis/HGX_IRoT_GPU_1/Certificates/CertChain" - .to_string(), + libredfish::model::component_integrity::IdentityAuthentication { + responder_authentication: + libredfish::model::component_integrity::ResponderAuthentication { + component_certificate: ODataId { + odata_id: + "/redfish/v1/Chassis/HGX_IRoT_GPU_1/Certificates/CertChain" + .to_string(), + }, + }, }, - }, requester: ODataId { odata_id: "/redfish/v1/Managers/BMC_0".to_string(), }, @@ -1567,13 +1582,16 @@ impl Redfish for RedfishSimClient { target_component_uri: Some("/redfish/v1/Chassis/HGX_IRoT_GPU_1".to_string()), spdm: Some(libredfish::model::component_integrity::SPDMData { identity_authentication: - libredfish::model::component_integrity::ResponderAuthentication { - component_certificate: ODataId { - odata_id: - "/redfish/v1/Chassis/HGX_IRoT_GPU_1/Certificates/CertChain" - .to_string(), + libredfish::model::component_integrity::IdentityAuthentication { + responder_authentication: + libredfish::model::component_integrity::ResponderAuthentication { + component_certificate: ODataId { + odata_id: + "/redfish/v1/Chassis/HGX_IRoT_GPU_1/Certificates/CertChain" + .to_string(), + }, + }, }, - }, requester: ODataId { odata_id: "/redfish/v1/Managers/BMC_0".to_string(), },