| #include "component_integrity.hpp" | |
| #include <memory> | |
| #include <string> | |
| #include <system_error> // NOLINT | |
| #include <unordered_set> | |
| #include <vector> | |
| #include <gmock/gmock.h> | |
| #include <gtest/gtest.h> | |
| #include "boost/beast/http/message.hpp" // NOLINT | |
| #include "dbus_utility.hpp" | |
| #include "test/redfish-core/lib/snapshot_fixture.hpp" | |
| #include <nlohmann/json.hpp> | |
| #include "managed_store.hpp" | |
| #include "managed_store_types.hpp" | |
| #include "test/g3/mock_managed_store.hpp" | |
| #include "test/g3/mock_managed_store_test.hpp" | |
| #include "sdbusplus/message/native_types.hpp" | |
| #ifdef SNAPSHOT_FIXTURE_G3 | |
| #include "devtools/build/runtime/get_runfiles_dir.h" | |
| #endif | |
| namespace redfish { | |
| namespace { | |
| using ::testing::Contains; | |
| using ::testing::NotNull; | |
| constexpr const char* fakeComponentIntegrity = "fakeComponentIntegrity"; | |
| class SnapshotFixture_ComponentIntegrity : public redfish::SnapshotFixture { | |
| protected: | |
| static void SetUpTestSuite() { | |
| ::managedStore::MockManagedStoreTest::SetUpTestSuite(); | |
| #ifdef SNAPSHOT_FIXTURE_G3 | |
| std::string snapshotFilepath_platform22Local = | |
| ::devtools_build::GetDataDependencyFilepath( | |
| "google3/third_party/openbmc_build_scripts/additional/" | |
| "scrubbed_gBMCwebManagedStore_platform22_0_2024_08_13.json"); | |
| EXPECT_TRUE(managedStore::GetManagedObjectStore()->deserialize( | |
| snapshotFilepath_platform22Local)); | |
| #else | |
| EXPECT_TRUE(managedStore::GetManagedObjectStore()->deserialize( | |
| snapshotFilepath_platform22)); | |
| #endif | |
| } | |
| }; | |
| TEST_F( | |
| SnapshotFixture_ComponentIntegrity, | |
| GetComponentIntegritySPDMGetSignedMeasurementsActionInfoReturnsCorrectResponse) { // NOLINT | |
| handleSPDMGetSignedMeasurementsActionInfoGet( | |
| app_, CreateRequest(), share_async_resp_, fakeComponentIntegrity); | |
| RunIoUntilDone(); | |
| nlohmann::json& json = share_async_resp_->res.jsonValue; | |
| EXPECT_EQ(json["@odata.id"], | |
| "/redfish/v1/ComponentIntegrity/fakeComponentIntegrity/" | |
| "SPDMGetSignedMeasurementsActionInfo"); | |
| EXPECT_EQ(json["@odata.type"], "#ActionInfo.v1_3_0.ActionInfo"); | |
| EXPECT_EQ(json["Name"], "SPDMGetSignedMeasurements Action Info"); | |
| EXPECT_EQ(json["Id"], "SPDMGetSignedMeasurementsActionInfo"); | |
| EXPECT_EQ(json["Parameters"][0]["Name"], "MeasurementIndices"); | |
| EXPECT_EQ(json["Parameters"][0]["Required"], false); | |
| EXPECT_EQ(json["Parameters"][0]["DataType"], "NumberArray"); | |
| EXPECT_EQ(json["Parameters"][1]["Name"], "Nonce"); | |
| EXPECT_EQ(json["Parameters"][1]["Required"], false); | |
| EXPECT_EQ(json["Parameters"][1]["DataType"], "String"); | |
| EXPECT_EQ(json["Parameters"][2]["Name"], "SlotId"); | |
| EXPECT_EQ(json["Parameters"][2]["Required"], false); | |
| EXPECT_EQ(json["Parameters"][2]["DataType"], "Number"); | |
| EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); | |
| } | |
| TEST_F(SnapshotFixture_ComponentIntegrity, | |
| GetComponentIntegrityCollectionTestReturnsCorrectResponse) { | |
| handleComponentIntegrityCollectionGet(app_, CreateRequest(), | |
| share_async_resp_); | |
| RunIoUntilDone(); | |
| nlohmann::json& json = share_async_resp_->res.jsonValue; | |
| EXPECT_EQ(json["@odata.id"], "/redfish/v1/ComponentIntegrity"); | |
| EXPECT_EQ(json["@odata.type"], | |
| "#ComponentIntegrityCollection.ComponentIntegrityCollection"); | |
| EXPECT_EQ(json["Name"], "Component Integrity Collection"); | |
| EXPECT_EQ(json["Members@odata.count"], json["Members"].size()); | |
| std::unordered_set<std::string> component_integrity_set{{ | |
| "/redfish/v1/ComponentIntegrity/CI_name6_7_1_name7_1_NIC_1", | |
| "/redfish/v1/ComponentIntegrity/CI_name6_7_2_name7_1_NIC_1", | |
| "/redfish/v1/ComponentIntegrity/CI_name6_7_3_name7_1_NIC_1", | |
| "/redfish/v1/ComponentIntegrity/CI_name6_7_4_name7_1_NIC_1", | |
| "/redfish/v1/ComponentIntegrity/CI_name6_7_5_name7_1_NIC_1", | |
| "/redfish/v1/ComponentIntegrity/CI_name6_7_6_name7_1_NIC_1", | |
| "/redfish/v1/ComponentIntegrity/CI_name6_7_7_name7_1_NIC_1", | |
| "/redfish/v1/ComponentIntegrity/CI_name6_7_8_name7_1_NIC_1", | |
| }}; | |
| for (const auto& member : json["Members"]) { | |
| ASSERT_TRUE(member.contains("@odata.id")); | |
| const std::string* ci = member["@odata.id"].get_ptr<const std::string*>(); | |
| ASSERT_THAT(ci, NotNull()); | |
| EXPECT_THAT(component_integrity_set, Contains(*ci)); | |
| } | |
| EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); | |
| } | |
| TEST_F(SnapshotFixture_ComponentIntegrity, | |
| GetComponentIntegrityTestReturnsCorrectResponse) { | |
| handleComponentIntegrityGet(app_, CreateRequest(), share_async_resp_, | |
| "CI_name6_7_1_name7_1_NIC_1"); | |
| RunIoUntilDone(); | |
| nlohmann::json& json = share_async_resp_->res.jsonValue; | |
| EXPECT_EQ(json["@odata.id"], | |
| "/redfish/v1/ComponentIntegrity/CI_name6_7_1_name7_1_NIC_1"); | |
| EXPECT_EQ(json["@odata.type"], | |
| "#ComponentIntegrity.v1_2_0.ComponentIntegrity"); | |
| EXPECT_EQ(json["Actions"]["#ComponentIntegrity.SPDMGetSignedMeasurements"] | |
| ["@Redfish.ActionInfo"], | |
| "/redfish/v1/ComponentIntegrity/CI_name6_7_1_name7_1_NIC_1/" | |
| "SPDMGetSignedMeasurementsActionInfo"); | |
| EXPECT_EQ(json["Actions"]["#ComponentIntegrity.SPDMGetSignedMeasurements"] | |
| ["target"], | |
| "/redfish/v1/ComponentIntegrity/CI_name6_7_1_name7_1_NIC_1/Actions/" | |
| "ComponentIntegrity.SPDMGetSignedMeasurements"); | |
| EXPECT_EQ(json["Name"], "Component Integrity"); | |
| EXPECT_EQ(json["Id"], "CI_name6_7_1_name7_1_NIC_1"); | |
| EXPECT_EQ(json["LastUpdated"], "20230308T201000Z"); | |
| EXPECT_EQ(json["ComponentIntegrityType"], "SPDM"); | |
| EXPECT_EQ(json["SPDM"]["Requester"]["@odata.id"], "/redfish/v1/Managers/bmc"); | |
| EXPECT_EQ( | |
| json["SPDM"]["IdentityAuthentication"]["ResponderAuthentication"] | |
| ["ComponentCertificate"]["@odata.id"], | |
| "/redfish/v1/Chassis/name6_7_1/Certificates/name7_1_NIC_1_PemCertChain"); | |
| EXPECT_EQ(json["SPDM"]["IdentityAuthentication"]["ResponderAuthentication"] | |
| ["VerificationStatus"], | |
| "Success"); | |
| EXPECT_EQ(json["TargetComponentURI"], | |
| "/redfish/v1/Chassis/name6_7_1/TrustedComponents/TC_name7_1_NIC_1"); | |
| EXPECT_EQ(json["ComponentIntegrityTypeVersion"], "1.1"); | |
| EXPECT_EQ(json["Description"], | |
| "SPDM-capable RoT physically attached to a device."); | |
| EXPECT_EQ(json["Links"]["ComponentsProtected"][0]["@odata.id"], | |
| "/redfish/v1/Chassis/name6_7_1/NetworkAdapters/name7_1_NIC_1"); | |
| EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); | |
| } | |
| TEST_F(SnapshotFixture_ComponentIntegrity, | |
| GetComponentIntegrityComponentItemReturnsCorrectResponse) { | |
| dbus::utility::MapperGetObject componentMapperObj{ | |
| {{"xyz.openbmc_project.SPDM", | |
| {"xyz.openbmc_project.Inventory.Item.Component"}}}}; | |
| // Use name6_7_2 rather than name6_7_1. The mock managed store is a | |
| // process-wide static created in MockManagedStoreTest::SetUpTestSuite() and | |
| // is never reset between tests, so this upsert is permanent for the rest of | |
| // the binary. name6_7_1 is the object | |
| // GetComponentIntegrityTestReturnsCorrectResponse expects to still be a | |
| // NetworkAdapter, and overriding it here made that test fail whenever it | |
| // happened to run afterwards | |
| // (repro: --gtest_shuffle --gtest_random_seed=1). Nothing asserts on the | |
| // contents of name6_7_2. | |
| managedStore::KeyType keyGetObject( | |
| managedStore::ManagedType::kManagedMapperObject, | |
| "xyz.openbmc_project.SPDM", | |
| sdbusplus::message::object_path( | |
| "/xyz/openbmc_project/inventory/system/board/name6_7_2/" | |
| "name7_1_NIC_1"), | |
| "xyz.openbmc_project.Inventory.Item.Component", "Component"); | |
| std::shared_ptr<managedStore::ValueType> mockedObj = | |
| CreateValueType(componentMapperObj); | |
| EXPECT_TRUE( | |
| dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()) | |
| ->upsertMockObjectIntoManagedStore(keyGetObject, mockedObj) | |
| .ok()); | |
| managedStore::KeyType keyContainedBy( | |
| managedStore::ManagedType::kManagedProperty, | |
| "xyz.openbmc_project.ObjectMapper", | |
| sdbusplus::message::object_path( | |
| "/xyz/openbmc_project/inventory/system/board/name6_7_2/" | |
| "name7_1_NIC_1/contained_by"), | |
| "xyz.openbmc_project.Association", "endpoints"); | |
| // keyContainedBy is a kManagedProperty key, so the value has to be built | |
| // from a DbusVariantType. Passing a bare std::vector<std::string> selects | |
| // the MapperGetSubTreePathsResponse overload of ValueType instead, and the | |
| // upsert is then rejected with "Key and Value type are not compatible". | |
| std::shared_ptr<managedStore::ValueType> mockedContainedBy = | |
| CreateValueType(dbus::utility::DbusVariantType( | |
| dbus::utility::MapperEndPoints{ | |
| "/xyz/openbmc_project/inventory/system/board/chassis_name"})); | |
| EXPECT_TRUE( | |
| dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()) | |
| ->upsertMockObjectIntoManagedStore(keyContainedBy, mockedContainedBy) | |
| .ok()); | |
| handleComponentIntegrityGet(app_, CreateRequest(), share_async_resp_, | |
| "CI_name6_7_2_name7_1_NIC_1"); | |
| RunIoUntilDone(); | |
| nlohmann::json& json = share_async_resp_->res.jsonValue; | |
| EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); | |
| EXPECT_EQ(json["Links"]["ComponentsProtected"][0]["@odata.id"], | |
| "/redfish/v1/Chassis/chassis_name"); | |
| } | |
| TEST_F(SnapshotFixture_ComponentIntegrity, | |
| GetComponentIntegrityComponentItemWithoutContainedByUsesParentPath) { | |
| dbus::utility::MapperGetObject componentMapperObj{ | |
| {{"xyz.openbmc_project.SPDM", | |
| {"xyz.openbmc_project.Inventory.Item.Component"}}}}; | |
| // name6_7_3 is used here for the same reason name6_7_2 is used above: the | |
| // mock managed store is never reset between tests, so this upsert is | |
| // permanent. Nothing asserts on the contents of name6_7_3. | |
| managedStore::KeyType keyGetObject( | |
| managedStore::ManagedType::kManagedMapperObject, | |
| "xyz.openbmc_project.SPDM", | |
| sdbusplus::message::object_path( | |
| "/xyz/openbmc_project/inventory/system/board/name6_7_3/" | |
| "name7_1_NIC_1"), | |
| "xyz.openbmc_project.Inventory.Item.Component", "Component"); | |
| std::shared_ptr<managedStore::ValueType> mockedObj = | |
| CreateValueType(componentMapperObj); | |
| EXPECT_TRUE( | |
| dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()) | |
| ->upsertMockObjectIntoManagedStore(keyGetObject, mockedObj) | |
| .ok()); | |
| // No contained_by association is upserted for this object, and the snapshot | |
| // does not carry one either. The store runs in snapshot mode, so the lookup | |
| // fails with an error instead of reading through to D-Bus, which is the | |
| // inventory-without-association case the fallback exists for. The chassis id | |
| // must then come from the parent of the component's own inventory path. | |
| handleComponentIntegrityGet(app_, CreateRequest(), share_async_resp_, | |
| "CI_name6_7_3_name7_1_NIC_1"); | |
| RunIoUntilDone(); | |
| nlohmann::json& json = share_async_resp_->res.jsonValue; | |
| EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); | |
| EXPECT_EQ(json["Links"]["ComponentsProtected"][0]["@odata.id"], | |
| "/redfish/v1/Chassis/name6_7_3"); | |
| } | |
| // Verifies that when a ComponentIntegrity D-Bus object authenticates an | |
| // inventory item implementing xyz.openbmc_project.Inventory.Item.Drive, the | |
| // Redfish response populates Links.ComponentsProtected with the Drive Redfish | |
| // URI. | |
| TEST_F( | |
| SnapshotFixture_ComponentIntegrity, | |
| GetComponentIntegrityDriveProtectedComponentsTestReturnsCorrectResponse) { | |
| using ::dbus::utility::DbusVariantType; | |
| using ::dbus::utility::MapperGetObject; | |
| using ::managedStore::KeyType; | |
| using ::managedStore::ManagedType; | |
| using ::managedStore::ValueType; | |
| // Use CI_name6_7_4_name7_1_NIC_1 / name6_7_4 so we do not mutate the shared | |
| // static mock entries used by CI_name6_7_1..3 in sibling tests. | |
| KeyType keyDriveMapperObject( | |
| ManagedType::kManagedMapperObject, | |
| "/xyz/openbmc_project/inventory/system/board/name6_7_4/Drive_1", | |
| std::vector<std::string>{}); | |
| std::shared_ptr<ValueType> driveMapperValue = | |
| CreateValueType(MapperGetObject({{ | |
| "xyz.openbmc_project.SPDM", | |
| {"xyz.openbmc_project.Inventory.Item.Drive"}, | |
| }})); | |
| EXPECT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()) | |
| ->upsertMockObjectIntoManagedStore(keyDriveMapperObject, | |
| driveMapperValue) | |
| .ok()); | |
| // Override the authenticating association endpoints to point to the Drive | |
| // object path. | |
| KeyType keyAuthenticating(ManagedType::kManagedProperty, | |
| "xyz.openbmc_project.ObjectMapper", | |
| sdbusplus::message::object_path( | |
| "/xyz/openbmc_project/ComponentIntegrity/" | |
| "CI_name6_7_4_name7_1_NIC_1/authenticating"), | |
| "xyz.openbmc_project.Association", "endpoints"); | |
| std::shared_ptr<ValueType> authenticatingValue = | |
| CreateValueType(DbusVariantType(std::vector<std::string>{ | |
| {"/xyz/openbmc_project/inventory/system/board/name6_7_4/Drive_1"}})); | |
| EXPECT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()) | |
| ->upsertMockObjectIntoManagedStore(keyAuthenticating, | |
| authenticatingValue) | |
| .ok()); | |
| handleComponentIntegrityGet(app_, CreateRequest(), share_async_resp_, | |
| "CI_name6_7_4_name7_1_NIC_1"); | |
| RunIoUntilDone(); | |
| // Verify that ComponentsProtected links to the expected Redfish Drive | |
| // resource. | |
| nlohmann::json& json = share_async_resp_->res.jsonValue; | |
| EXPECT_EQ(json["@odata.id"], | |
| "/redfish/v1/ComponentIntegrity/CI_name6_7_4_name7_1_NIC_1"); | |
| ASSERT_TRUE(json["Links"].contains("ComponentsProtected")); | |
| ASSERT_EQ(json["Links"]["ComponentsProtected"].size(), 1); | |
| EXPECT_EQ(json["Links"]["ComponentsProtected"][0]["@odata.id"], | |
| "/redfish/v1/Chassis/name6_7_4/Drives/Drive_1"); | |
| EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); | |
| } | |
| TEST_F(SnapshotFixture_ComponentIntegrity, | |
| GetComponentIntegrityMctpUsbDeviceReturnsCorrectResponse) { | |
| dbus::utility::MapperGetObject mctpMapperObj{ | |
| {{"xyz.openbmc_project.EntityManager", | |
| {"xyz.openbmc_project.Configuration.MCTPUSBDevice"}}}}; | |
| managedStore::KeyType keyGetObject( | |
| managedStore::ManagedType::kManagedMapperObject, | |
| "xyz.openbmc_project.EntityManager", | |
| sdbusplus::message::object_path( | |
| "/xyz/openbmc_project/inventory/system/board/name6_7_5/" | |
| "name7_1_NIC_1"), | |
| "xyz.openbmc_project.Configuration.MCTPUSBDevice", "MCTPUSBDevice"); | |
| std::shared_ptr<managedStore::ValueType> mockedObj = | |
| CreateValueType(mctpMapperObj); | |
| EXPECT_TRUE( | |
| dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()) | |
| ->upsertMockObjectIntoManagedStore(keyGetObject, mockedObj) | |
| .ok()); | |
| managedStore::KeyType keyContainedBy( | |
| managedStore::ManagedType::kManagedProperty, | |
| "xyz.openbmc_project.ObjectMapper", | |
| sdbusplus::message::object_path( | |
| "/xyz/openbmc_project/inventory/system/board/name6_7_5/" | |
| "name7_1_NIC_1/contained_by"), | |
| "xyz.openbmc_project.Association", "endpoints"); | |
| std::shared_ptr<managedStore::ValueType> mockedContainedBy = | |
| CreateValueType(dbus::utility::DbusVariantType( | |
| dbus::utility::MapperEndPoints{ | |
| "/xyz/openbmc_project/inventory/system/board/chassis_name"})); | |
| EXPECT_TRUE( | |
| dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()) | |
| ->upsertMockObjectIntoManagedStore(keyContainedBy, mockedContainedBy) | |
| .ok()); | |
| handleComponentIntegrityGet(app_, CreateRequest(), share_async_resp_, | |
| "CI_name6_7_5_name7_1_NIC_1"); | |
| RunIoUntilDone(); | |
| nlohmann::json& json = share_async_resp_->res.jsonValue; | |
| EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); | |
| EXPECT_EQ(json["Links"]["ComponentsProtected"][0]["@odata.id"], | |
| "/redfish/v1/Chassis/chassis_name"); | |
| } | |
| TEST_F(SnapshotFixture_ComponentIntegrity, | |
| GetComponentIntegrityMctpUsbDeviceWithoutContainedByUsesParentPath) { | |
| dbus::utility::MapperGetObject mctpMapperObj{ | |
| {{"xyz.openbmc_project.EntityManager", | |
| {"xyz.openbmc_project.Configuration.MCTPUSBDevice"}}}}; | |
| managedStore::KeyType keyGetObject( | |
| managedStore::ManagedType::kManagedMapperObject, | |
| "xyz.openbmc_project.EntityManager", | |
| sdbusplus::message::object_path( | |
| "/xyz/openbmc_project/inventory/system/board/name6_7_6/" | |
| "name7_1_NIC_1"), | |
| "xyz.openbmc_project.Configuration.MCTPUSBDevice", "MCTPUSBDevice"); | |
| std::shared_ptr<managedStore::ValueType> mockedObj = | |
| CreateValueType(mctpMapperObj); | |
| EXPECT_TRUE( | |
| dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()) | |
| ->upsertMockObjectIntoManagedStore(keyGetObject, mockedObj) | |
| .ok()); | |
| handleComponentIntegrityGet(app_, CreateRequest(), share_async_resp_, | |
| "CI_name6_7_6_name7_1_NIC_1"); | |
| RunIoUntilDone(); | |
| nlohmann::json& json = share_async_resp_->res.jsonValue; | |
| EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); | |
| EXPECT_EQ(json["Links"]["ComponentsProtected"][0]["@odata.id"], | |
| "/redfish/v1/Chassis/name6_7_6"); | |
| } | |
| } // namespace | |
| } // namespace redfish |