1 /* 2 // Copyright (c) 2018 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 #pragma once 17 18 #include "app.hpp" 19 #include "dbus_utility.hpp" 20 #include "error_messages.hpp" 21 #include "generated/enums/resource.hpp" 22 #include "query.hpp" 23 #include "redfish_util.hpp" 24 #include "registries/privilege_registry.hpp" 25 #include "utils/json_utils.hpp" 26 #include "utils/stl_utils.hpp" 27 28 #include <boost/system/error_code.hpp> 29 #include <boost/url/format.hpp> 30 #include <sdbusplus/asio/property.hpp> 31 32 #include <array> 33 #include <optional> 34 #include <string_view> 35 #include <variant> 36 #include <vector> 37 38 namespace redfish 39 { 40 41 void getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp); 42 std::string getHostName(); 43 44 static constexpr std::string_view sshServiceName = "dropbear"; 45 static constexpr std::string_view httpsServiceName = "bmcweb"; 46 static constexpr std::string_view ipmiServiceName = "phosphor-ipmi-net"; 47 48 // Mapping from Redfish NetworkProtocol key name to backend service that hosts 49 // that protocol. 50 static constexpr std::array<std::pair<std::string_view, std::string_view>, 3> 51 networkProtocolToDbus = {{{"SSH", sshServiceName}, 52 {"HTTPS", httpsServiceName}, 53 {"IPMI", ipmiServiceName}}}; 54 55 inline void extractNTPServersAndDomainNamesData( 56 const dbus::utility::ManagedObjectType& dbusData, 57 std::vector<std::string>& ntpData, std::vector<std::string>& dynamicNtpData, 58 std::vector<std::string>& dnData) 59 { 60 for (const auto& obj : dbusData) 61 { 62 for (const auto& ifacePair : obj.second) 63 { 64 if (ifacePair.first != 65 "xyz.openbmc_project.Network.EthernetInterface") 66 { 67 continue; 68 } 69 70 for (const auto& propertyPair : ifacePair.second) 71 { 72 if (propertyPair.first == "StaticNTPServers") 73 { 74 const std::vector<std::string>* ntpServers = 75 std::get_if<std::vector<std::string>>( 76 &propertyPair.second); 77 if (ntpServers != nullptr) 78 { 79 ntpData.insert(ntpData.end(), ntpServers->begin(), 80 ntpServers->end()); 81 } 82 } 83 else if (propertyPair.first == "NTPServers") 84 { 85 const std::vector<std::string>* dynamicNtpServers = 86 std::get_if<std::vector<std::string>>( 87 &propertyPair.second); 88 if (dynamicNtpServers != nullptr) 89 { 90 dynamicNtpData = *dynamicNtpServers; 91 } 92 } 93 else if (propertyPair.first == "DomainName") 94 { 95 const std::vector<std::string>* domainNames = 96 std::get_if<std::vector<std::string>>( 97 &propertyPair.second); 98 if (domainNames != nullptr) 99 { 100 dnData.insert(dnData.end(), domainNames->begin(), 101 domainNames->end()); 102 } 103 } 104 } 105 } 106 } 107 stl_utils::removeDuplicate(ntpData); 108 stl_utils::removeDuplicate(dnData); 109 } 110 111 template <typename CallbackFunc> 112 void getEthernetIfaceData(CallbackFunc&& callback) 113 { 114 sdbusplus::message::object_path path("/xyz/openbmc_project/network"); 115 dbus::utility::getManagedObjects( 116 "xyz.openbmc_project.Network", path, 117 [callback = std::forward<CallbackFunc>(callback)]( 118 const boost::system::error_code& ec, 119 const dbus::utility::ManagedObjectType& dbusData) { 120 std::vector<std::string> ntpServers; 121 std::vector<std::string> dynamicNtpServers; 122 std::vector<std::string> domainNames; 123 124 if (ec) 125 { 126 callback(false, ntpServers, dynamicNtpServers, domainNames); 127 return; 128 } 129 130 extractNTPServersAndDomainNamesData(dbusData, ntpServers, 131 dynamicNtpServers, domainNames); 132 133 callback(true, ntpServers, dynamicNtpServers, domainNames); 134 }); 135 } 136 137 inline void afterNetworkPortRequest( 138 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 139 const boost::system::error_code& ec, 140 const std::vector<std::tuple<std::string, std::string, bool>>& socketData) 141 { 142 if (ec) 143 { 144 messages::internalError(asyncResp->res); 145 return; 146 } 147 for (const auto& data : socketData) 148 { 149 const std::string& socketPath = get<0>(data); 150 const std::string& protocolName = get<1>(data); 151 bool isProtocolEnabled = get<2>(data); 152 153 asyncResp->res.jsonValue[protocolName]["ProtocolEnabled"] = 154 isProtocolEnabled; 155 asyncResp->res.jsonValue[protocolName]["Port"] = nullptr; 156 getPortNumber(socketPath, [asyncResp, protocolName]( 157 const boost::system::error_code& ec2, 158 int portNumber) { 159 if (ec2) 160 { 161 messages::internalError(asyncResp->res); 162 return; 163 } 164 asyncResp->res.jsonValue[protocolName]["Port"] = portNumber; 165 }); 166 } 167 } 168 169 inline void getNetworkData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 170 const crow::Request& req) 171 { 172 if (req.session == nullptr) 173 { 174 messages::internalError(asyncResp->res); 175 return; 176 } 177 178 asyncResp->res.addHeader( 179 boost::beast::http::field::link, 180 "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/NetworkProtocol.json>; rel=describedby"); 181 asyncResp->res.jsonValue["@odata.type"] = 182 "#ManagerNetworkProtocol.v1_9_0.ManagerNetworkProtocol"; 183 asyncResp->res.jsonValue["@odata.id"] = 184 boost::urls::format("/redfish/v1/Managers/{}/NetworkProtocol", 185 BMCWEB_REDFISH_MANAGER_URI_NAME); 186 asyncResp->res.jsonValue["Id"] = "NetworkProtocol"; 187 asyncResp->res.jsonValue["Name"] = "Manager Network Protocol"; 188 asyncResp->res.jsonValue["Description"] = "Manager Network Service"; 189 asyncResp->res.jsonValue["Status"]["Health"] = resource::Health::OK; 190 asyncResp->res.jsonValue["Status"]["HealthRollup"] = resource::Health::OK; 191 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled; 192 193 // HTTP is Mandatory attribute as per OCP Baseline Profile - v1.0.0, 194 // but from security perspective it is not recommended to use. 195 // Hence using protocolEnabled as false to make it OCP and security-wise 196 // compliant 197 asyncResp->res.jsonValue["HTTP"]["Port"] = nullptr; 198 asyncResp->res.jsonValue["HTTP"]["ProtocolEnabled"] = false; 199 200 // The ProtocolEnabled of the following protocols is determined by 201 // inspecting the state of associated systemd sockets. If these protocols 202 // have been disabled, then the systemd socket unit files will not be found 203 // and the protocols will not be returned in this Redfish query. Set some 204 // defaults to ensure something is always returned. 205 for (const auto& nwkProtocol : networkProtocolToDbus) 206 { 207 asyncResp->res.jsonValue[nwkProtocol.first]["Port"] = nullptr; 208 asyncResp->res.jsonValue[nwkProtocol.first]["ProtocolEnabled"] = false; 209 } 210 211 std::string hostName = getHostName(); 212 213 asyncResp->res.jsonValue["HostName"] = hostName; 214 215 getNTPProtocolEnabled(asyncResp); 216 217 getEthernetIfaceData([hostName, asyncResp]( 218 const bool& success, 219 const std::vector<std::string>& ntpServers, 220 const std::vector<std::string>& dynamicNtpServers, 221 const std::vector<std::string>& domainNames) { 222 if (!success) 223 { 224 messages::resourceNotFound(asyncResp->res, "ManagerNetworkProtocol", 225 "NetworkProtocol"); 226 return; 227 } 228 asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers; 229 asyncResp->res.jsonValue["NTP"]["NetworkSuppliedServers"] = 230 dynamicNtpServers; 231 if (!hostName.empty()) 232 { 233 std::string fqdn = hostName; 234 if (!domainNames.empty()) 235 { 236 fqdn += "."; 237 fqdn += domainNames[0]; 238 } 239 asyncResp->res.jsonValue["FQDN"] = std::move(fqdn); 240 } 241 }); 242 243 Privileges effectiveUserPrivileges = 244 redfish::getUserPrivileges(*req.session); 245 246 // /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates is 247 // something only ConfigureManager can access then only display when 248 // the user has permissions ConfigureManager 249 if (isOperationAllowedWithPrivileges({{"ConfigureManager"}}, 250 effectiveUserPrivileges)) 251 { 252 asyncResp->res.jsonValue["HTTPS"]["Certificates"]["@odata.id"] = 253 boost::urls::format( 254 "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates", 255 BMCWEB_REDFISH_MANAGER_URI_NAME); 256 } 257 258 getPortStatusAndPath(std::span(networkProtocolToDbus), 259 std::bind_front(afterNetworkPortRequest, asyncResp)); 260 } // namespace redfish 261 262 inline void afterSetNTP(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 263 const boost::system::error_code& ec) 264 { 265 if (ec) 266 { 267 BMCWEB_LOG_DEBUG("Failed to set elapsed time. DBUS response error {}", 268 ec); 269 messages::internalError(asyncResp->res); 270 return; 271 } 272 asyncResp->res.result(boost::beast::http::status::no_content); 273 } 274 275 inline void handleNTPProtocolEnabled( 276 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, bool ntpEnabled) 277 { 278 bool interactive = false; 279 auto callback = [asyncResp](const boost::system::error_code& ec) { 280 afterSetNTP(asyncResp, ec); 281 }; 282 crow::connections::systemBus->async_method_call( 283 std::move(callback), "org.freedesktop.timedate1", 284 "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "SetNTP", 285 ntpEnabled, interactive); 286 } 287 288 // Redfish states that ip addresses can be 289 // string, to set a value 290 // null, to delete the value 291 // object_t, empty json object, to ignore the value 292 using IpAddress = 293 std::variant<std::string, nlohmann::json::object_t, std::nullptr_t>; 294 295 inline void 296 handleNTPServersPatch(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 297 const std::vector<IpAddress>& ntpServerObjects, 298 std::vector<std::string> currentNtpServers) 299 { 300 std::vector<std::string>::iterator currentNtpServer = 301 currentNtpServers.begin(); 302 for (size_t index = 0; index < ntpServerObjects.size(); index++) 303 { 304 const IpAddress& ntpServer = ntpServerObjects[index]; 305 if (std::holds_alternative<std::nullptr_t>(ntpServer)) 306 { 307 // Can't delete an item that doesn't exist 308 if (currentNtpServer == currentNtpServers.end()) 309 { 310 messages::propertyValueNotInList( 311 asyncResp->res, "null", 312 "NTP/NTPServers/" + std::to_string(index)); 313 314 return; 315 } 316 currentNtpServer = currentNtpServers.erase(currentNtpServer); 317 continue; 318 } 319 const nlohmann::json::object_t* ntpServerObject = 320 std::get_if<nlohmann::json::object_t>(&ntpServer); 321 if (ntpServerObject != nullptr) 322 { 323 if (!ntpServerObject->empty()) 324 { 325 messages::propertyValueNotInList( 326 asyncResp->res, *ntpServerObject, 327 "NTP/NTPServers/" + std::to_string(index)); 328 return; 329 } 330 // Can't retain an item that doesn't exist 331 if (currentNtpServer == currentNtpServers.end()) 332 { 333 messages::propertyValueOutOfRange( 334 asyncResp->res, *ntpServerObject, 335 "NTP/NTPServers/" + std::to_string(index)); 336 337 return; 338 } 339 // empty objects should leave the NtpServer unmodified 340 currentNtpServer++; 341 continue; 342 } 343 344 const std::string* ntpServerStr = std::get_if<std::string>(&ntpServer); 345 if (ntpServerStr == nullptr) 346 { 347 messages::internalError(asyncResp->res); 348 return; 349 } 350 if (currentNtpServer == currentNtpServers.end()) 351 { 352 // if we're at the end of the list, append to the end 353 currentNtpServers.push_back(*ntpServerStr); 354 currentNtpServer = currentNtpServers.end(); 355 continue; 356 } 357 *currentNtpServer = *ntpServerStr; 358 currentNtpServer++; 359 } 360 361 // Any remaining array elements should be removed 362 currentNtpServers.erase(currentNtpServer, currentNtpServers.end()); 363 364 constexpr std::array<std::string_view, 1> ethInterfaces = { 365 "xyz.openbmc_project.Network.EthernetInterface"}; 366 dbus::utility::getSubTree( 367 "/xyz/openbmc_project", 0, ethInterfaces, 368 [asyncResp, currentNtpServers]( 369 const boost::system::error_code& ec, 370 const dbus::utility::MapperGetSubTreeResponse& subtree) { 371 if (ec) 372 { 373 BMCWEB_LOG_WARNING("D-Bus error: {}, {}", ec, ec.message()); 374 messages::internalError(asyncResp->res); 375 return; 376 } 377 378 for (const auto& [objectPath, serviceMap] : subtree) 379 { 380 for (const auto& [service, interfaces] : serviceMap) 381 { 382 for (const auto& interface : interfaces) 383 { 384 if (interface != 385 "xyz.openbmc_project.Network.EthernetInterface") 386 { 387 continue; 388 } 389 390 setDbusProperty(asyncResp, "NTP/NTPServers/", service, 391 objectPath, interface, 392 "StaticNTPServers", currentNtpServers); 393 } 394 } 395 } 396 }); 397 } 398 399 inline void 400 handleProtocolEnabled(const bool protocolEnabled, 401 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 402 const std::string& netBasePath) 403 { 404 constexpr std::array<std::string_view, 1> interfaces = { 405 "xyz.openbmc_project.Control.Service.Attributes"}; 406 dbus::utility::getSubTree( 407 "/xyz/openbmc_project/control/service", 0, interfaces, 408 [protocolEnabled, asyncResp, 409 netBasePath](const boost::system::error_code& ec, 410 const dbus::utility::MapperGetSubTreeResponse& subtree) { 411 if (ec) 412 { 413 messages::internalError(asyncResp->res); 414 return; 415 } 416 417 for (const auto& entry : subtree) 418 { 419 if (entry.first.starts_with(netBasePath)) 420 { 421 setDbusProperty( 422 asyncResp, "IPMI/ProtocolEnabled", 423 entry.second.begin()->first, entry.first, 424 "xyz.openbmc_project.Control.Service.Attributes", 425 "Running", protocolEnabled); 426 setDbusProperty( 427 asyncResp, "IPMI/ProtocolEnabled", 428 entry.second.begin()->first, entry.first, 429 "xyz.openbmc_project.Control.Service.Attributes", 430 "Enabled", protocolEnabled); 431 } 432 } 433 }); 434 } 435 436 inline std::string getHostName() 437 { 438 std::string hostName; 439 440 std::array<char, HOST_NAME_MAX> hostNameCStr{}; 441 if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0) 442 { 443 hostName = hostNameCStr.data(); 444 } 445 return hostName; 446 } 447 448 inline void 449 getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 450 { 451 sdbusplus::asio::getProperty<bool>( 452 *crow::connections::systemBus, "org.freedesktop.timedate1", 453 "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", 454 [asyncResp](const boost::system::error_code& ec, bool enabled) { 455 if (ec) 456 { 457 BMCWEB_LOG_WARNING( 458 "Failed to get NTP status, assuming not supported"); 459 return; 460 } 461 462 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = enabled; 463 }); 464 } 465 466 inline std::string encodeServiceObjectPath(std::string_view serviceName) 467 { 468 sdbusplus::message::object_path objPath( 469 "/xyz/openbmc_project/control/service"); 470 objPath /= serviceName; 471 return objPath.str; 472 } 473 474 inline void handleBmcNetworkProtocolHead( 475 crow::App& app, const crow::Request& req, 476 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 477 { 478 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 479 { 480 return; 481 } 482 asyncResp->res.addHeader( 483 boost::beast::http::field::link, 484 "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby"); 485 } 486 487 inline void handleManagersNetworkProtocolPatch( 488 App& app, const crow::Request& req, 489 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 490 const std::string& managerId) 491 { 492 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 493 { 494 return; 495 } 496 497 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 498 { 499 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 500 return; 501 } 502 503 std::optional<std::string> newHostName; 504 505 std::optional<std::vector<IpAddress>> ntpServerObjects; 506 std::optional<bool> ntpEnabled; 507 std::optional<bool> ipmiEnabled; 508 std::optional<bool> sshEnabled; 509 510 // clang-format off 511 if (!json_util::readJsonPatch( 512 req, asyncResp->res, 513 "HostName", newHostName, 514 "NTP/NTPServers", ntpServerObjects, 515 "NTP/ProtocolEnabled", ntpEnabled, 516 "IPMI/ProtocolEnabled", ipmiEnabled, 517 "SSH/ProtocolEnabled", sshEnabled)) 518 { 519 return; 520 } 521 // clang-format on 522 523 asyncResp->res.result(boost::beast::http::status::no_content); 524 if (newHostName) 525 { 526 messages::propertyNotWritable(asyncResp->res, "HostName"); 527 return; 528 } 529 530 if (ntpEnabled) 531 { 532 handleNTPProtocolEnabled(asyncResp, *ntpEnabled); 533 } 534 if (ntpServerObjects) 535 { 536 getEthernetIfaceData( 537 [asyncResp, ntpServerObjects]( 538 const bool success, std::vector<std::string>& currentNtpServers, 539 const std::vector<std::string>& /*dynamicNtpServers*/, 540 const std::vector<std::string>& /*domainNames*/) { 541 if (!success) 542 { 543 messages::internalError(asyncResp->res); 544 return; 545 } 546 handleNTPServersPatch(asyncResp, *ntpServerObjects, 547 std::move(currentNtpServers)); 548 }); 549 } 550 551 if (ipmiEnabled) 552 { 553 handleProtocolEnabled( 554 *ipmiEnabled, asyncResp, 555 encodeServiceObjectPath(std::string(ipmiServiceName) + '@')); 556 } 557 558 if (sshEnabled) 559 { 560 handleProtocolEnabled(*sshEnabled, asyncResp, 561 encodeServiceObjectPath(sshServiceName)); 562 } 563 } 564 565 inline void handleManagersNetworkProtocolHead( 566 App& app, const crow::Request& req, 567 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 568 const std::string& managerId) 569 { 570 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 571 { 572 return; 573 } 574 asyncResp->res.addHeader( 575 boost::beast::http::field::link, 576 "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby"); 577 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 578 { 579 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 580 return; 581 } 582 } 583 584 inline void handleManagersNetworkProtocolGet( 585 App& app, const crow::Request& req, 586 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 587 const std::string& managerId) 588 { 589 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 590 { 591 return; 592 } 593 asyncResp->res.addHeader( 594 boost::beast::http::field::link, 595 "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby"); 596 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 597 { 598 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 599 return; 600 } 601 602 getNetworkData(asyncResp, req); 603 } 604 605 inline void requestRoutesNetworkProtocol(App& app) 606 { 607 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/") 608 .privileges(redfish::privileges::patchManagerNetworkProtocol) 609 .methods(boost::beast::http::verb::patch)( 610 std::bind_front(handleManagersNetworkProtocolPatch, std::ref(app))); 611 612 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/") 613 .privileges(redfish::privileges::headManagerNetworkProtocol) 614 .methods(boost::beast::http::verb::head)( 615 std::bind_front(handleManagersNetworkProtocolHead, std::ref(app))); 616 617 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/") 618 .privileges(redfish::privileges::getManagerNetworkProtocol) 619 .methods(boost::beast::http::verb::get)( 620 std::bind_front(handleManagersNetworkProtocolGet, std::ref(app))); 621 } 622 623 } // namespace redfish 624