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