blob: 1e262427e7cca6729eef3f1ea12f6658907a0130 [file] [log] [blame]
/*
// 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.
*/
#pragma once
#include "app.hpp"
#include "component_integrity.hpp"
#include "dbus_utility.hpp"
#include "managed_store.hpp"
#include "managed_store_types.hpp"
#include "processor.hpp"
#include "query.hpp"
#include "registries/privilege_registry.hpp"
#include "utils/chassis_utils.hpp"
#include "utils/system_utils.hpp"
#include "utils/collection.hpp"
#include "utils/dbus_utils.hpp"
#include "utils/json_utils.hpp"
#include "utils/system_utils.hpp"
#include <boost/container/flat_map.hpp>
#include <boost/system/error_code.hpp>
#include <boost/url/format.hpp>
#include <sdbusplus/asio/property.hpp>
#include <sdbusplus/message/native_types.hpp>
#include <sdbusplus/unpack_properties.hpp>
#include <sdbusplus/utility/dedup_variant.hpp>
#include <array>
#include <limits>
#include <regex>
#include <string_view>
#include <tuple>
#include <variant>
#include <vector>
#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;
}
// handle Processor: parent resource can be Systems or Chassis.
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;
}
nlohmann::json::object_t integratedProcessorPath;
std::string parentResourceType;
if (redfish::dbus_utils::findInterfacesInServiceMap(
object, redfish::chassis_utils::chassisInterfaces))
{
parentResourceType = "Chassis";
} else if (redfish::dbus_utils::findInterfacesInServiceMap(
object, redfish::system_utils::systemInterfaces))
{
parentResourceType = "Systems";
} 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, parentObj,
"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