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