blob: 0fa3f002b57bf8b90596e7f0dec25d857c70eb88 [file] [log] [blame]
/*
// Copyright (c) 2018 Intel Corporation
//
// 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 "dbus_utility.hpp"
#include "error_messages.hpp"
#include "managed_store.hpp"
#include "managed_store_types.hpp"
#include "openbmc_dbus_rest.hpp"
#include "query.hpp"
#include "redfish_util.hpp"
#include "registries/privilege_registry.hpp"
#include "utils/json_utils.hpp"
#include "utils/stl_utils.hpp"
#include <boost/system/error_code.hpp>
#include <sdbusplus/asio/property.hpp>
#include <array>
#include <optional>
#include <string_view>
#include <variant>
#include "managed_store.hpp"
#ifdef UNIT_TEST_BUILD
#include "test/g3/mock_managed_store.hpp" // NOLINT
#endif
namespace redfish
{
void getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp);
std::string getHostName();
static constexpr std::string_view sshServiceName = "dropbear";
static constexpr std::string_view httpsServiceName = "bmcweb";
static constexpr std::string_view ipmiServiceName = "phosphor-ipmi-net";
// Mapping from Redfish NetworkProtocol key name to backend service that hosts
// that protocol.
static constexpr std::array<std::pair<std::string_view, std::string_view>, 3>
networkProtocolToDbus = {{{"SSH", sshServiceName},
{"HTTPS", httpsServiceName},
{"IPMI", ipmiServiceName}}};
inline void extractNTPServersAndDomainNamesData(
const dbus::utility::ManagedObjectType& dbusData,
std::vector<std::string>& ntpData, std::vector<std::string>& dnData)
{
for (const auto& obj : dbusData)
{
for (const auto& ifacePair : obj.second)
{
if (ifacePair.first !=
"xyz.openbmc_project.Network.EthernetInterface")
{
continue;
}
for (const auto& propertyPair : ifacePair.second)
{
if (propertyPair.first == "StaticNTPServers")
{
const std::vector<std::string>* ntpServers =
std::get_if<std::vector<std::string>>(
&propertyPair.second);
if (ntpServers != nullptr)
{
ntpData.insert(ntpData.end(), ntpServers->begin(),
ntpServers->end());
}
}
else if (propertyPair.first == "DomainName")
{
const std::vector<std::string>* domainNames =
std::get_if<std::vector<std::string>>(
&propertyPair.second);
if (domainNames != nullptr)
{
dnData.insert(dnData.end(), domainNames->begin(),
domainNames->end());
}
}
}
}
}
stl_utils::removeDuplicate(ntpData);
stl_utils::removeDuplicate(dnData);
}
template <typename CallbackFunc>
void getEthernetIfaceData(
const managedStore::ManagedObjectStoreContext& context,
CallbackFunc&& callback)
{
managedStore::GetManagedObjectStore()->getManagedObjectsWithContext(
"xyz.openbmc_project.Network", {"/xyz/openbmc_project/network"},
context,
[callback{std::forward<CallbackFunc>(callback)}](
const boost::system::error_code& errorCode,
const dbus::utility::ManagedObjectType& dbusData) {
std::vector<std::string> ntpServers;
std::vector<std::string> domainNames;
if (errorCode)
{
callback(false, ntpServers, domainNames);
return;
}
extractNTPServersAndDomainNamesData(dbusData, ntpServers, domainNames);
callback(true, ntpServers, domainNames);
});
}
inline void afterNetworkPortRequest(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const boost::system::error_code& ec,
const std::vector<std::tuple<std::string, std::string, bool>>& socketData)
{
if (ec)
{
messages::internalError(asyncResp->res);
return;
}
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
for (const auto& data : socketData)
{
const std::string& socketPath = get<0>(data);
const std::string& protocolName = get<1>(data);
bool isProtocolEnabled = get<2>(data);
asyncResp->res.jsonValue[protocolName]["ProtocolEnabled"] =
isProtocolEnabled;
asyncResp->res.jsonValue[protocolName]["Port"] = nullptr;
getPortNumber(
socketPath, requestContext,
[asyncResp, protocolName](const boost::system::error_code& ec2,
int portNumber) {
if (ec2)
{
messages::internalError(asyncResp->res);
return;
}
asyncResp->res.jsonValue[protocolName]["Port"] = portNumber;
});
}
}
inline void getNetworkData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const crow::Request& req)
{
asyncResp->res.addHeader(
boost::beast::http::field::link,
"</redfish/v1/JsonSchemas/ManagerNetworkProtocol/NetworkProtocol.json>; rel=describedby");
asyncResp->res.jsonValue["@odata.type"] =
"#ManagerNetworkProtocol.v1_5_0.ManagerNetworkProtocol";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Managers/bmc/NetworkProtocol";
asyncResp->res.jsonValue["Id"] = "NetworkProtocol";
asyncResp->res.jsonValue["Name"] = "Manager Network Protocol";
asyncResp->res.jsonValue["Description"] = "Manager Network Service";
asyncResp->res.jsonValue["Status"]["Health"] = "OK";
asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
// HTTP is Mandatory attribute as per OCP Baseline Profile - v1.0.0,
// but from security perspective it is not recommended to use.
// Hence using protocolEnabled as false to make it OCP and security-wise
// compliant
asyncResp->res.jsonValue["HTTP"]["Port"] = nullptr;
asyncResp->res.jsonValue["HTTP"]["ProtocolEnabled"] = false;
std::string hostName = getHostName();
asyncResp->res.jsonValue["HostName"] = hostName;
getNTPProtocolEnabled(asyncResp);
managedStore::ManagedObjectStoreContext context(asyncResp);
getEthernetIfaceData(
context,
[hostName, asyncResp](const bool& success,
const std::vector<std::string>& ntpServers,
const std::vector<std::string>& domainNames) {
if (!success)
{
messages::resourceNotFound(asyncResp->res, "ManagerNetworkProtocol",
"NetworkProtocol");
return;
}
asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers;
if (!hostName.empty())
{
std::string fqdn = hostName;
if (!domainNames.empty())
{
fqdn += ".";
fqdn += domainNames[0];
}
asyncResp->res.jsonValue["FQDN"] = std::move(fqdn);
}
});
Privileges effectiveUserPrivileges =
redfish::getUserPrivileges(req.userRole);
// /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates is
// something only ConfigureManager can access then only display when
// the user has permissions ConfigureManager
if (isOperationAllowedWithPrivileges({{"ConfigureManager"}},
effectiveUserPrivileges))
{
asyncResp->res.jsonValue["HTTPS"]["Certificates"]["@odata.id"] =
"/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates";
}
getPortStatusAndPath(std::span(networkProtocolToDbus), context,
std::bind_front(afterNetworkPortRequest, asyncResp));
} // namespace redfish
inline void handleNTPProtocolEnabled(
const bool& ntpEnabled, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
std::string timeSyncMethod;
if (ntpEnabled)
{
timeSyncMethod = "xyz.openbmc_project.Time.Synchronization.Method.NTP";
}
else
{
timeSyncMethod =
"xyz.openbmc_project.Time.Synchronization.Method.Manual";
}
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[asyncResp](const boost::system::error_code& errorCode) {
if (errorCode)
{
messages::internalError(asyncResp->res);
}
},
"xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method",
"org.freedesktop.DBus.Properties", "Set",
"xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod",
dbus::utility::DbusVariantType{timeSyncMethod});
}
inline void
handleNTPServersPatch(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::vector<nlohmann::json>& ntpServerObjects,
std::vector<std::string> currentNtpServers)
{
std::vector<std::string>::iterator currentNtpServer =
currentNtpServers.begin();
for (size_t index = 0; index < ntpServerObjects.size(); index++)
{
const nlohmann::json& ntpServer = ntpServerObjects[index];
if (ntpServer.is_null())
{
// Can't delete an item that doesn't exist
if (currentNtpServer == currentNtpServers.end())
{
messages::propertyValueNotInList(asyncResp->res, "null",
"NTP/NTPServers/" +
std::to_string(index));
return;
}
currentNtpServer = currentNtpServers.erase(currentNtpServer);
continue;
}
const nlohmann::json::object_t* ntpServerObject =
ntpServer.get_ptr<const nlohmann::json::object_t*>();
if (ntpServerObject != nullptr)
{
if (!ntpServerObject->empty())
{
messages::propertyValueNotInList(
asyncResp->res,
ntpServer.dump(2, ' ', true,
nlohmann::json::error_handler_t::replace),
"NTP/NTPServers/" + std::to_string(index));
return;
}
// Can't retain an item that doesn't exist
if (currentNtpServer == currentNtpServers.end())
{
messages::propertyValueOutOfRange(
asyncResp->res,
ntpServer.dump(2, ' ', true,
nlohmann::json::error_handler_t::replace),
"NTP/NTPServers/" + std::to_string(index));
return;
}
// empty objects should leave the NtpServer unmodified
currentNtpServer++;
continue;
}
const std::string* ntpServerStr =
ntpServer.get_ptr<const std::string*>();
if (ntpServerStr == nullptr)
{
messages::propertyValueTypeError(
asyncResp->res,
ntpServer.dump(2, ' ', true,
nlohmann::json::error_handler_t::replace),
"NTP/NTPServers/" + std::to_string(index));
return;
}
if (currentNtpServer == currentNtpServers.end())
{
// if we're at the end of the list, append to the end
currentNtpServers.push_back(*ntpServerStr);
currentNtpServer = currentNtpServers.end();
continue;
}
*currentNtpServer = *ntpServerStr;
currentNtpServer++;
}
// Any remaining array elements should be removed
currentNtpServers.erase(currentNtpServer, currentNtpServers.end());
constexpr std::array<std::string_view, 1> ethInterfaces = {
"xyz.openbmc_project.Network.EthernetInterface"};
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getSubTree(
"/xyz/openbmc_project", 0, ethInterfaces, requestContext,
[asyncResp, currentNtpServers](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& subtree) {
if (ec)
{
BMCWEB_LOG_WARNING << "D-Bus error: " << ec << ", " << ec.message();
messages::internalError(asyncResp->res);
return;
}
for (const auto& [objectPath, serviceMap] : subtree)
{
for (const auto& [service, interfaces] : serviceMap)
{
for (const auto& interface : interfaces)
{
if (interface !=
"xyz.openbmc_project.Network.EthernetInterface")
{
continue;
}
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[asyncResp](const boost::system::error_code& ec2) {
if (ec2)
{
messages::internalError(asyncResp->res);
return;
}
},
service, objectPath, "org.freedesktop.DBus.Properties",
"Set", interface, "StaticNTPServers",
dbus::utility::DbusVariantType{currentNtpServers});
}
}
}
});
}
inline void
handleProtocolEnabled(const bool protocolEnabled,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& netBasePath)
{
constexpr std::array<std::string_view, 1> interfaces = {
"xyz.openbmc_project.Control.Service.Attributes"};
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getSubTree(
"/xyz/openbmc_project/control/service", 0, interfaces, requestContext,
[protocolEnabled, asyncResp,
netBasePath](const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& subtree) {
if (ec)
{
messages::internalError(asyncResp->res);
return;
}
for (const auto& entry : subtree)
{
if (boost::algorithm::starts_with(entry.first, netBasePath))
{
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[asyncResp](const boost::system::error_code& ec2) {
if (ec2)
{
messages::internalError(asyncResp->res);
return;
}
},
entry.second.begin()->first, entry.first,
"org.freedesktop.DBus.Properties", "Set",
"xyz.openbmc_project.Control.Service.Attributes", "Running",
dbus::utility::DbusVariantType{protocolEnabled});
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[asyncResp](const boost::system::error_code& ec2) {
if (ec2)
{
messages::internalError(asyncResp->res);
return;
}
},
entry.second.begin()->first, entry.first,
"org.freedesktop.DBus.Properties", "Set",
"xyz.openbmc_project.Control.Service.Attributes", "Enabled",
dbus::utility::DbusVariantType{protocolEnabled});
}
}
});
}
inline std::string getHostName()
{
std::string hostName;
std::array<char, HOST_NAME_MAX> hostNameCStr{};
if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0)
{
hostName = hostNameCStr.data();
}
return hostName;
}
inline void
getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
dbus_utils::getProperty<std::string>(
"xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method",
"xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod",
requestContext,
[asyncResp](const boost::system::error_code& errorCode,
const std::string& timeSyncMethod) {
if (errorCode)
{
return;
}
if (timeSyncMethod ==
"xyz.openbmc_project.Time.Synchronization.Method.NTP")
{
asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true;
}
else if (timeSyncMethod == "xyz.openbmc_project.Time.Synchronization."
"Method.Manual")
{
asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false;
}
});
}
inline std::string encodeServiceObjectPath(std::string_view serviceName)
{
sdbusplus::message::object_path objPath(
"/xyz/openbmc_project/control/service");
objPath /= serviceName;
return objPath.str;
}
inline void handleBmcNetworkProtocolHead(
crow::App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
asyncResp->res.addHeader(
boost::beast::http::field::link,
"</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby");
}
inline void handleManagersNetworkProtocolPatch(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
std::optional<std::string> newHostName;
std::optional<std::vector<nlohmann::json>> ntpServerObjects;
std::optional<bool> ntpEnabled;
std::optional<bool> ipmiEnabled;
std::optional<bool> sshEnabled;
// clang-format off
if (!json_util::readJsonPatch(
req, asyncResp->res,
"HostName", newHostName,
"NTP/NTPServers", ntpServerObjects,
"NTP/ProtocolEnabled", ntpEnabled,
"IPMI/ProtocolEnabled", ipmiEnabled,
"SSH/ProtocolEnabled", sshEnabled))
{
return;
}
// clang-format on
asyncResp->res.result(boost::beast::http::status::no_content);
if (newHostName)
{
messages::propertyNotWritable(asyncResp->res, "HostName");
return;
}
if (ntpEnabled)
{
handleNTPProtocolEnabled(*ntpEnabled, asyncResp);
}
if (ntpServerObjects)
{
managedStore::ManagedObjectStoreContext context(asyncResp);
getEthernetIfaceData(
context,
[asyncResp, ntpServerObjects](
const bool success, std::vector<std::string>& currentNtpServers,
const std::vector<std::string>& /*domainNames*/) {
if (!success)
{
messages::internalError(asyncResp->res);
return;
}
handleNTPServersPatch(asyncResp, *ntpServerObjects,
std::move(currentNtpServers));
});
}
if (ipmiEnabled)
{
handleProtocolEnabled(
*ipmiEnabled, asyncResp,
encodeServiceObjectPath(std::string(ipmiServiceName) + '@'));
}
if (sshEnabled)
{
handleProtocolEnabled(*sshEnabled, asyncResp,
encodeServiceObjectPath(sshServiceName));
}
}
inline void handleManagersNetworkProtocolHead(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
asyncResp->res.addHeader(
boost::beast::http::field::link,
"</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby");
}
inline void handleManagersNetworkProtocolGet(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
handleManagersNetworkProtocolHead(app, req, asyncResp);
getNetworkData(asyncResp, req);
}
inline void requestRoutesNetworkProtocol(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
.privileges(redfish::privileges::patchManagerNetworkProtocol)
.methods(boost::beast::http::verb::patch)(
std::bind_front(handleManagersNetworkProtocolPatch, std::ref(app)));
BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
.privileges(redfish::privileges::headManagerNetworkProtocol)
.methods(boost::beast::http::verb::head)(
std::bind_front(handleManagersNetworkProtocolHead, std::ref(app)));
BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
.privileges(redfish::privileges::getManagerNetworkProtocol)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleManagersNetworkProtocolGet, std::ref(app)));
}
} // namespace redfish