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