blob: ffe50263bf40307679a4f83ef8b30ecf340335f3 [file]
#ifndef THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_TRUSTED_COMPONENT_H_
#define THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_TRUSTED_COMPONENT_H_
/*
// Copyright (c) 2023 Google
/
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/
#include <algorithm>
#include <array>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>
#include "boost/container/flat_map.hpp" // NOLINT
#include "boost/system/error_code.hpp" // NOLINT
#include "boost/url/format.hpp" // NOLINT
#include "app.hpp"
#include "http_request.hpp"
#include "logging.hpp"
#include "utility.hpp"
#include "async_resp.hpp"
#include "dbus_utility.hpp"
#include "error_messages.hpp"
#include "query.hpp"
#include "registries/privilege_registry.hpp"
#include "chassis_utils.hpp"
#include "collection.hpp"
#include "dbus_utils.hpp"
#include "system_utils.hpp"
#include "component_integrity.hpp"
#include <nlohmann/json.hpp>
#include "managed_store.hpp"
#include "managed_store_types.hpp"
#include "sdbusplus/message/native_types.hpp"
#include "sdbusplus/unpack_properties.hpp"
#ifdef UNIT_TEST_BUILD
#include "test/g3/mock_managed_store.hpp" // NOLINT
#endif
namespace redfish {
// Interfaces which imply a D-Bus object represents a TrustedComponent
constexpr std::array<std::string_view, 1> trustedComponentInterfaces = {
"xyz.openbmc_project.Inventory.Item.TrustedComponent"};
constexpr char const* discreteAttachType =
"xyz.openbmc_project.Inventory.Item.TrustedComponent.ComponentAttachType."
"Discrete";
constexpr char const* integratedAttachType =
"xyz.openbmc_project.Inventory.Item.TrustedComponent.ComponentAttachType."
"Integrated";
constexpr char const* decoratorAssetIntf =
"xyz.openbmc_project.Inventory.Decorator.Asset";
constexpr char const* uuidIntf = "xyz.openbmc_project.Common.UUID";
constexpr char const* trustedComponentIntf =
"xyz.openbmc_project.Inventory.Item.TrustedComponent";
/**
* Map a given vector of D-Bus object path to an array of Redfish
* URI.
*
* @param[in] property1 Redfish property name.
* @param[in] property2 Optional second Redfish property name.
* @param[in] nodeList vector of path string.
*
* @return entities Array of Redfish URI in json.
*/
inline std::optional<nlohmann::json> mapObjectPathVector(
const std::string& property1, const std::string& property2,
const std::vector<std::string>& pathList) {
nlohmann::json entities = nlohmann::json::array();
for (const std::string& node : pathList) {
sdbusplus::message::object_path path(node);
std::string entityId = path.filename();
if (entityId.empty()) {
BMCWEB_LOG_ERROR << "Invalid object path:" << property1 << " "
<< property2 << ":" << path.str;
return std::nullopt;
}
nlohmann::json::object_t entity;
if (property2.empty()) {
entity["@odata.id"] =
crow::utility::urlFromPieces("redfish", "v1", property1, entityId);
} else {
entity["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", property1, property2, entityId);
}
entities.push_back(std::move(entity));
}
return {std::move(entities)};
}
/**
* Find the D-Bus object representing the requested TrustedComponent, and call
* the handler with the results. If matching object is not found, add 404 error
* to response and don't call the handler.
*
* @param[in,out] resp Async HTTP response.
* @param[in] chassisId Redfish Chassis Id.
* @param[in] componentId Redfish TrustedComponent Id.
* @param[in] handler Callback to continue processing request upon
* successfully finding object.
*/
template <typename Handler>
inline void getTrustedComponentObject(
const std::shared_ptr<bmcweb::AsyncResp>& resp,
const std::string& chassisId, const std::string& componentId,
Handler&& handler) {
BMCWEB_LOG_DEBUG << "Get available chassis trusted_component resources.";
// GetSubTree on all interfaces which provide info about TrustedComponent
constexpr std::array<std::string_view, 1> interfaces = {trustedComponentIntf};
std::string trustedComponentsPath =
sdbusplus::message::object_path("/xyz/openbmc_project/Chassis") /
chassisId / "TrustedComponents";
managedStore::ManagedObjectStoreContext context(resp);
managedStore::GetManagedObjectStore()->getSubTree(
trustedComponentsPath, 0, interfaces, context,
[resp, chassisId, componentId, handler](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& subtree) {
if (ec) {
BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
messages::internalError(resp->res);
return;
}
for (const auto& [objectPath, serviceMap] : subtree) {
// Ignore any objects which don't end with our desired component
// name
if (!objectPath.ends_with(componentId)) {
continue;
}
bool found = false;
for (const auto& [serviceName, interfaceList] : serviceMap) {
if (std::find_first_of(interfaceList.begin(), interfaceList.end(),
trustedComponentInterfaces.begin(),
trustedComponentInterfaces.end()) !=
interfaceList.end()) {
found = true;
break;
}
}
if (!found) {
continue;
}
// Found something, populate Name and Id before get Interface info
resp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Chassis", chassisId, "TrustedComponents",
componentId);
resp->res.jsonValue["Name"] = componentId;
resp->res.jsonValue["Id"] = componentId;
handler(objectPath, serviceMap);
return;
}
messages::resourceNotFound(resp->res, "TrustedComponent", componentId);
});
}
inline void getTrustedComponentIntegratedIntoInfo(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& trustedComponentObjPath,
const boost::system::error_code& ec,
const dbus::utility::MapperEndPoints& integratedIntoObjPaths) {
if (ec) {
if (ec.value() != EBADR) {
BMCWEB_LOG_ERROR << "DBUS response error " << ec;
messages::internalError(asyncResp->res);
}
return;
}
if (integratedIntoObjPaths.empty()) {
return;
}
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
for (const auto& objpath : integratedIntoObjPaths) {
sdbusplus::message::object_path integratedIntoObjPath(objpath);
const std::string objName = integratedIntoObjPath.filename();
if (objName.empty()) {
BMCWEB_LOG_WARNING << "Malformed integratedInto object path "
<< integratedIntoObjPath.str << " for "
<< trustedComponentObjPath;
continue;
}
managedStore::GetManagedObjectStore()->getDbusObject(
objpath, {}, requestContext,
[asyncResp, integratedIntoObjPath, trustedComponentObjPath,
requestContext,
objName](const boost::system::error_code& ec2,
const dbus::utility::MapperGetObject& object) {
if (ec2) {
BMCWEB_LOG_ERROR << "DBUS response error";
messages::internalError(asyncResp->res);
return;
}
nlohmann::json::object_t integratedPath;
// Link can be Chassis or Board, Processor, and NetworkAdapters
// handle Chassis or Board case
if (redfish::dbus_utils::findInterfacesInServiceMap(
object, redfish::chassis_utils::chassisInterfaces)) {
integratedPath["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Chassis", objName);
asyncResp->res.jsonValue["Links"]["IntegratedInto"] =
std::move(integratedPath);
return;
}
// handle NetworkAdaptors: parent resource can only be Chassis.
if (redfish::dbus_utils::findInterfacesInServiceMap(
object, networkAdapterInterfaces)) {
const std::string parentObj =
integratedIntoObjPath.parent_path().filename();
if (parentObj.empty()) {
BMCWEB_LOG_WARNING << "Malformed integratedInto object path "
<< integratedIntoObjPath.str << " for "
<< trustedComponentObjPath;
return;
}
integratedPath["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Chassis", parentObj, "NetworkAdapters",
objName);
asyncResp->res.jsonValue["Links"]["IntegratedInto"] =
std::move(integratedPath);
return;
}
if (redfish::dbus_utils::findInterfacesInServiceMap(
object, processorsInterfaces)) {
std::string parentPath = integratedIntoObjPath.parent_path().str;
std::string parentObj =
integratedIntoObjPath.parent_path().filename();
// e.g.,
// /xyz/openbmc_project/inventory/system/board/BoardName/system1/chassis/motherboard/cpu0
// filter out chassis|motherboard in object path.
while (parentObj == "motherboard" || parentObj == "chassis") {
sdbusplus::message::object_path tempObjPath(parentPath);
parentPath = tempObjPath.parent_path().str;
parentObj = tempObjPath.parent_path().filename();
}
if (parentObj.empty()) {
BMCWEB_LOG_WARNING << "Malformed integratedInto object path "
<< integratedIntoObjPath.str << " for "
<< trustedComponentObjPath;
return;
}
managedStore::GetManagedObjectStore()->getDbusObject(
parentPath, {}, requestContext,
[asyncResp, objName, parentObj](
const boost::system::error_code& ec2,
const dbus::utility::MapperGetObject& object) {
if (ec2) {
BMCWEB_LOG_ERROR << "DBUS response error";
messages::internalError(asyncResp->res);
return;
}
// handle Processor: parent resource has to be Systems as
// OpenBMC only search for Processor under Systems though
// Redfish SPEC allows Processors to be modeled under Chassis
// starting from DSP2046_2022.3.pdf.
nlohmann::json::object_t integratedProcessorPath;
std::string parentResourceType;
std::string parentResourceName;
if (redfish::dbus_utils::findInterfacesInServiceMap(
object, redfish::chassis_utils::chassisInterfaces)) {
parentResourceType = "Systems";
parentResourceName = "system";
} else if (redfish::dbus_utils::findInterfacesInServiceMap(
object,
redfish::system_utils::systemInterfaces)) {
parentResourceType = "Systems";
parentResourceName = parentObj;
} else {
BMCWEB_LOG_ERROR << "Parent resource type not "
"supported for Processor resource";
messages::internalError(asyncResp->res);
return;
}
integratedProcessorPath["@odata.id"] =
crow::utility::urlFromPieces(
"redfish", "v1", parentResourceType,
parentResourceName, "Processors", objName);
asyncResp->res.jsonValue["Links"]["IntegratedInto"] =
std::move(integratedProcessorPath);
return;
});
return;
} // end of Processor interfaces handling
// Unsupported resource type
BMCWEB_LOG_ERROR << "Unsupported IntegratedInto resource for "
<< integratedIntoObjPath.str;
messages::internalError(asyncResp->res);
return;
});
}
}
/**
* @brief Fill out TrustedComponent interface related info by
* requesting data from the given D-Bus object.
*
* @param[in,out] aResp Async HTTP response.
* @param[in] service D-Bus service to query.
* @param[in] objPath D-Bus object to query.
*/
inline void getTrustedComponentInterfaceData(
const std::shared_ptr<bmcweb::AsyncResp>& aResp, const std::string& service,
const std::string& objPath) {
BMCWEB_LOG_DEBUG << "Get TrustedComponent Interface Data";
managedStore::ManagedObjectStoreContext context(aResp);
managedStore::GetManagedObjectStore()->getAllProperties(
service, objPath, trustedComponentIntf, context,
[objPath, aResp{aResp}, context](
const boost::system::error_code& ec,
const std::vector<std::pair<
std::string, dbus::utility::DbusVariantType>>& properties) {
if (ec) {
BMCWEB_LOG_ERROR << "DBUS response error";
messages::internalError(aResp->res);
return;
}
const std::string* type = nullptr;
const std::string* firmwareVersion = nullptr;
const bool success = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), properties, "FirmwareVersion",
firmwareVersion, "TrustedComponentType", type);
if (!success) {
messages::internalError(aResp->res);
return;
}
if ((firmwareVersion != nullptr) && !firmwareVersion->empty()) {
aResp->res.jsonValue["FirmwareVersion"] = *firmwareVersion;
}
// For Root-of-Trust, it is safe to hardcode it as Embedded.
aResp->res.jsonValue["Location"]["PartLocation"]["LocationType"] =
"Embedded";
// Get active software image info from associations.
std::string activeSoftwarePath = objPath + "/actively_running";
dbus_utils::getAssociationEndPoints(
activeSoftwarePath, context,
[aResp](
const boost::system::error_code& e,
const dbus::utility::MapperEndPoints& nodeActiveSoftwareList) {
if (e) {
if (e.value() != EBADR) {
messages::internalError(aResp->res);
return;
}
// No resource found.
return;
}
// One TrustedComponent object should be associated with one
// activeSoftwareImage object
if (nodeActiveSoftwareList.size() != 1) {
BMCWEB_LOG_ERROR
<< "Unexpected ActiveSoftwareImage #objs (expecting 1): "
<< nodeActiveSoftwareList.size();
messages::internalError(aResp->res);
return;
}
const std::string& softwareImageURI =
nodeActiveSoftwareList.at(0);
sdbusplus::message::object_path activeSoftwareImage(
softwareImageURI);
std::string softwareId = activeSoftwareImage.filename();
nlohmann::json::object_t imagePath;
if (softwareId.empty()) {
BMCWEB_LOG_ERROR
<< "TrustedComponent contains invalid activeSoftwareImage:"
<< activeSoftwareImage.str;
messages::internalError(aResp->res);
return;
}
imagePath["@odata.id"] =
crow::utility::urlFromPieces("redfish", "v1", "UpdateService",
"FirmwareInventory", softwareId);
aResp->res.jsonValue["Links"]["ActiveSoftwareImage"] =
std::move(imagePath);
});
// Get ComponentIntegrity info from associations.
std::string componentIntegrityPath = objPath + "/reported_by";
dbus_utils::getAssociationEndPoints(
componentIntegrityPath, context,
[aResp](const boost::system::error_code& e,
const dbus::utility::MapperEndPoints&
nodeComponentIntegrityList) {
if (e) {
if (e.value() != EBADR) {
messages::internalError(aResp->res);
return;
}
// No resource found.
return;
}
if (nodeComponentIntegrityList.empty()) {
BMCWEB_LOG_DEBUG << "No ComponentIntegrity objects found!";
return;
}
std::optional<nlohmann::json> components = mapObjectPathVector(
"ComponentIntegrity", "", nodeComponentIntegrityList);
if (!components) {
BMCWEB_LOG_ERROR << "ComponentIntegrity is invalid.";
messages::internalError(aResp->res);
return;
}
aResp->res.jsonValue["Links"]["ComponentIntegrity"] = *components;
});
// Get protected components info from associations.
std::string protectedComponentPath = objPath + "/protecting";
dbus_utils::getAssociationEndPoints(
protectedComponentPath, context,
std::bind_front(getProtectedComponentsInfo, aResp, objPath));
// Get integratedInto info from associations.
std::string integratedInto = objPath + "/integrated_into";
dbus_utils::getAssociationEndPoints(
integratedInto, context,
std::bind_front(getTrustedComponentIntegratedIntoInfo, aResp,
objPath));
// Get software images info from associations.
std::string softwareImagesPath = objPath + "/runs";
dbus_utils::getAssociationEndPoints(
softwareImagesPath, context,
[aResp](
const boost::system::error_code& e,
const dbus::utility::MapperEndPoints& nodeSoftwareImagesList) {
if (e) {
if (e.value() != EBADR) {
messages::internalError(aResp->res);
return;
}
// No resource found.
return;
}
// One TrustedComponent object may be associated with one
// or more SoftwareImage objects
if (nodeSoftwareImagesList.empty()) {
BMCWEB_LOG_DEBUG << "No software image objects found!";
return;
}
std::optional<nlohmann::json> images = mapObjectPathVector(
"UpdateService", "FirmwareInventory", nodeSoftwareImagesList);
if (!images) {
BMCWEB_LOG_ERROR << "SoftwareImages is invalid.";
messages::internalError(aResp->res);
return;
}
aResp->res.jsonValue["Links"]["SoftwareImages"] = *images;
});
if ((type != nullptr) && !type->empty()) {
if (*type == discreteAttachType) {
aResp->res.jsonValue["TrustedComponentType"] = "Discrete";
} else if (*type == integratedAttachType) {
aResp->res.jsonValue["TrustedComponentType"] = "Integrated";
} else {
BMCWEB_LOG_ERROR << "Unrecognized ComponentAttachType!";
messages::internalError(aResp->res);
return;
}
} else {
BMCWEB_LOG_ERROR << "No ComponentAttachType found!";
messages::internalError(aResp->res);
return;
}
});
}
/**
* @brief Fill out Inventory.Decorator.Asset interface related info by
* requesting data from the given D-Bus object.
*
* @param[in,out] aResp Async HTTP response.
* @param[in] service D-Bus service to query.
* @param[in] objPath D-Bus object to query.
*/
inline void getDecoratorAssetInterfaceData(
const std::shared_ptr<bmcweb::AsyncResp>& aResp, const std::string& service,
const std::string& objPath) {
BMCWEB_LOG_DEBUG << "Get Decorator.Asset Interface Data";
managedStore::ManagedObjectStoreContext context(aResp);
managedStore::GetManagedObjectStore()->getAllProperties(
service, objPath, decoratorAssetIntf, context,
[objPath, aResp{aResp}, context](
const boost::system::error_code& ec,
const std::vector<std::pair<
std::string, dbus::utility::DbusVariantType>>& properties) {
if (ec) {
BMCWEB_LOG_ERROR << "DBUS response error for DecoratorAssetIntf!";
messages::internalError(aResp->res);
return;
}
const std::string* manufacturer = nullptr;
const std::string* serialNumber = nullptr;
const std::string* sku = nullptr;
const std::string* partNumber = nullptr;
const bool success = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), properties, "Manufacturer",
manufacturer, "SerialNumber", serialNumber, "PartNumber",
partNumber, "SKU", sku);
if (!success) {
BMCWEB_LOG_ERROR << "Unpacking Inventory.Decorator.Asset failed!";
messages::internalError(aResp->res);
return;
}
if ((manufacturer != nullptr) && !manufacturer->empty()) {
aResp->res.jsonValue["Manufacturer"] = *manufacturer;
}
if ((serialNumber != nullptr) && !serialNumber->empty()) {
aResp->res.jsonValue["SerialNumber"] = *serialNumber;
}
if ((sku != nullptr) && !sku->empty()) {
aResp->res.jsonValue["SKU"] = *sku;
}
if ((partNumber != nullptr) && !partNumber->empty()) {
aResp->res.jsonValue["PartNumber"] = *partNumber;
}
});
}
/**
* @brief Fill out Common.UUID interface related info by
* requesting data from the given D-Bus object.
*
* @param[in,out] aResp Async HTTP response.
* @param[in] service D-Bus service to query.
* @param[in] objPath D-Bus object to query.
*/
inline void getCommonUUIDInterfaceData(
const std::shared_ptr<bmcweb::AsyncResp>& aResp, const std::string& service,
const std::string& objPath) {
BMCWEB_LOG_DEBUG << "Get Common.UUID Interface Data";
managedStore::ManagedObjectStoreContext context(aResp);
managedStore::GetManagedObjectStore()->getAllProperties(
service, objPath, uuidIntf, context,
[objPath, aResp{aResp}, context](
const boost::system::error_code& ec,
const std::vector<std::pair<
std::string, dbus::utility::DbusVariantType>>& properties) {
if (ec) {
BMCWEB_LOG_DEBUG << "DBUS response error";
messages::internalError(aResp->res);
return;
}
const std::string* uuid = nullptr;
const bool success = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), properties, "UUID", uuid);
if (!success) {
messages::internalError(aResp->res);
return;
}
if ((uuid != nullptr) && !uuid->empty()) {
aResp->res.jsonValue["UUID"] = *uuid;
}
});
}
inline void getTrustedComponentData(
const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const std::string& objectPath,
const dbus::utility::MapperServiceMap& serviceMap) {
for (const auto& [serviceName, interfaceList] : serviceMap) {
for (const auto& interface : interfaceList) {
if (interface == trustedComponentIntf) {
getTrustedComponentInterfaceData(aResp, serviceName, objectPath);
} else if (interface == decoratorAssetIntf) {
getDecoratorAssetInterfaceData(aResp, serviceName, objectPath);
} else if (interface == uuidIntf) {
getCommonUUIDInterfaceData(aResp, serviceName, objectPath);
}
}
}
}
inline void handleTrustedComponentGet(
crow::App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& chassisId, const std::string& componentId) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp)) {
return;
}
asyncResp->res.addHeader(boost::beast::http::field::link,
"</redfish/v1/JsonSchemas/TrustedComponent/"
"TrustedComponent.json>; rel=describedby");
asyncResp->res.jsonValue["@odata.type"] =
"#TrustedComponent.v1_0_0.TrustedComponent";
getTrustedComponentObject(
asyncResp, chassisId, componentId,
std::bind_front(getTrustedComponentData, asyncResp));
}
inline void handleTrustedComponentCollectionGet(
crow::App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& chassisId) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp)) {
return;
}
boost::urls::url url = crow::utility::urlFromPieces(
"redfish", "v1", "Chassis", chassisId, "TrustedComponents");
asyncResp->res.jsonValue["@odata.type"] =
"#TrustedComponentCollection.TrustedComponentCollection";
asyncResp->res.jsonValue["@odata.id"] = url.buffer();
asyncResp->res.jsonValue["Name"] = "Trusted Component Collection";
asyncResp->res.jsonValue["Description"] = "Collection of Trusted Components";
std::string trustedComponentsPath =
sdbusplus::message::object_path("/xyz/openbmc_project/Chassis") /
chassisId / "TrustedComponents";
collection_util::getCollectionMembers(asyncResp, url,
trustedComponentInterfaces,
trustedComponentsPath.c_str());
}
inline void requestRoutesTrustedComponent(App& app) {
BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/TrustedComponents/<str>/")
.privileges(redfish::privileges::getTrustedComponent)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleTrustedComponentGet, std::ref(app)));
}
inline void requestRoutesTrustedComponentCollection(App& app) {
BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/TrustedComponents/")
.privileges(redfish::privileges::getTrustedComponentCollection)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleTrustedComponentCollectionGet, std::ref(app)));
}
} // namespace redfish
#endif // THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_TRUSTED_COMPONENT_H_