xref: /openbmc/bmcweb/redfish-core/lib/network_protocol.hpp (revision 6be832e2963e9d720dd95543358eca380c5e52d2)
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