xref: /openbmc/bmcweb/features/redfish/lib/network_protocol.hpp (revision 1e1e598df6d1d9530dde6e92d8f74f8143f60e50)
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 <registries/privilege_registry.hpp>
25 #include <sdbusplus/asio/property.hpp>
26 #include <utils/json_utils.hpp>
27 #include <utils/stl_utils.hpp>
28 
29 #include <optional>
30 #include <variant>
31 namespace redfish
32 {
33 
34 void getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp);
35 std::string getHostName();
36 
37 const static std::array<std::pair<std::string, std::string>, 3> protocolToDBus{
38     {{"SSH", "dropbear"}, {"HTTPS", "bmcweb"}, {"IPMI", "phosphor-ipmi-net"}}};
39 
40 inline void
41     extractNTPServersAndDomainNamesData(const GetManagedObjects& dbusData,
42                                         std::vector<std::string>& ntpData,
43                                         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::move(callback)}](
87             const boost::system::error_code errorCode,
88             const GetManagedObjects& 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                              const 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         asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers;
145         if (hostName.empty() == false)
146         {
147             std::string fqdn = hostName;
148             if (domainNames.empty() == false)
149             {
150                 fqdn += ".";
151                 fqdn += domainNames[0];
152             }
153             asyncResp->res.jsonValue["FQDN"] = std::move(fqdn);
154         }
155     });
156 
157     Privileges effectiveUserPrivileges =
158         redfish::getUserPrivileges(req.userRole);
159 
160     // /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates is
161     // something only ConfigureManager can access then only display when
162     // the user has permissions ConfigureManager
163     if (isOperationAllowedWithPrivileges({{"ConfigureManager"}},
164                                          effectiveUserPrivileges))
165     {
166         asyncResp->res.jsonValue["HTTPS"]["Certificates"] = {
167             {"@odata.id",
168              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"}};
169     }
170 
171     for (const auto& protocol : protocolToDBus)
172     {
173         const std::string& protocolName = protocol.first;
174         const std::string& serviceName = protocol.second;
175         getPortStatusAndPath(
176             serviceName,
177             [asyncResp, protocolName](const boost::system::error_code ec,
178                                       const std::string& socketPath,
179                                       bool isProtocolEnabled) {
180                 // If the service is not installed, that is not an error
181                 if (ec == boost::system::errc::no_such_process)
182                 {
183                     asyncResp->res.jsonValue[protocolName]["Port"] =
184                         nlohmann::detail::value_t::null;
185                     asyncResp->res.jsonValue[protocolName]["ProtocolEnabled"] =
186                         false;
187                     return;
188                 }
189                 if (ec)
190                 {
191                     messages::internalError(asyncResp->res);
192                     return;
193                 }
194                 asyncResp->res.jsonValue[protocolName]["ProtocolEnabled"] =
195                     isProtocolEnabled;
196                 getPortNumber(
197                     socketPath,
198                     [asyncResp, protocolName](
199                         const boost::system::error_code ec, int portNumber) {
200                         if (ec)
201                         {
202                             messages::internalError(asyncResp->res);
203                             return;
204                         }
205                         asyncResp->res.jsonValue[protocolName]["Port"] =
206                             portNumber;
207                     });
208             });
209     }
210 } // namespace redfish
211 
212 inline void handleNTPProtocolEnabled(
213     const bool& ntpEnabled, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
214 {
215     std::string timeSyncMethod;
216     if (ntpEnabled)
217     {
218         timeSyncMethod = "xyz.openbmc_project.Time.Synchronization.Method.NTP";
219     }
220     else
221     {
222         timeSyncMethod =
223             "xyz.openbmc_project.Time.Synchronization.Method.Manual";
224     }
225 
226     crow::connections::systemBus->async_method_call(
227         [asyncResp](const boost::system::error_code errorCode) {
228             if (errorCode)
229             {
230                 messages::internalError(asyncResp->res);
231             }
232         },
233         "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method",
234         "org.freedesktop.DBus.Properties", "Set",
235         "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod",
236         dbus::utility::DbusVariantType{timeSyncMethod});
237 }
238 
239 inline void
240     handleNTPServersPatch(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
241                           std::vector<std::string>& ntpServers)
242 {
243     auto iter = stl_utils::firstDuplicate(ntpServers.begin(), ntpServers.end());
244     if (iter != ntpServers.end())
245     {
246         std::string pointer =
247             "NTPServers/" +
248             std::to_string(std::distance(ntpServers.begin(), iter));
249         messages::propertyValueIncorrect(asyncResp->res, pointer, *iter);
250         return;
251     }
252 
253     crow::connections::systemBus->async_method_call(
254         [asyncResp,
255          ntpServers](boost::system::error_code ec,
256                      const crow::openbmc_mapper::GetSubTreeType& subtree) {
257             if (ec)
258             {
259                 BMCWEB_LOG_WARNING << "D-Bus error: " << ec << ", "
260                                    << ec.message();
261                 messages::internalError(asyncResp->res);
262                 return;
263             }
264 
265             for (const auto& [objectPath, serviceMap] : subtree)
266             {
267                 for (const auto& [service, interfaces] : serviceMap)
268                 {
269                     for (const auto& interface : interfaces)
270                     {
271                         if (interface !=
272                             "xyz.openbmc_project.Network.EthernetInterface")
273                         {
274                             continue;
275                         }
276 
277                         crow::connections::systemBus->async_method_call(
278                             [asyncResp](const boost::system::error_code ec) {
279                                 if (ec)
280                                 {
281                                     messages::internalError(asyncResp->res);
282                                     return;
283                                 }
284                             },
285                             service, objectPath,
286                             "org.freedesktop.DBus.Properties", "Set", interface,
287                             "NTPServers",
288                             dbus::utility::DbusVariantType{ntpServers});
289                     }
290                 }
291             }
292         },
293         "xyz.openbmc_project.ObjectMapper",
294         "/xyz/openbmc_project/object_mapper",
295         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
296         "/xyz/openbmc_project", 0,
297         std::array<const char*, 1>{
298             "xyz.openbmc_project.Network.EthernetInterface"});
299 }
300 
301 inline void
302     handleProtocolEnabled(const bool protocolEnabled,
303                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
304                           const std::string_view netBasePath)
305 {
306     crow::connections::systemBus->async_method_call(
307         [protocolEnabled, asyncResp,
308          netBasePath](const boost::system::error_code ec,
309                       const crow::openbmc_mapper::GetSubTreeType& subtree) {
310             if (ec)
311             {
312                 messages::internalError(asyncResp->res);
313                 return;
314             }
315 
316             for (const auto& entry : subtree)
317             {
318                 if (boost::algorithm::starts_with(entry.first, netBasePath))
319                 {
320                     crow::connections::systemBus->async_method_call(
321                         [asyncResp](const boost::system::error_code ec2) {
322                             if (ec2)
323                             {
324                                 messages::internalError(asyncResp->res);
325                                 return;
326                             }
327                         },
328                         entry.second.begin()->first, entry.first,
329                         "org.freedesktop.DBus.Properties", "Set",
330                         "xyz.openbmc_project.Control.Service.Attributes",
331                         "Running",
332                         dbus::utility::DbusVariantType{protocolEnabled});
333 
334                     crow::connections::systemBus->async_method_call(
335                         [asyncResp](const boost::system::error_code ec2) {
336                             if (ec2)
337                             {
338                                 messages::internalError(asyncResp->res);
339                                 return;
340                             }
341                         },
342                         entry.second.begin()->first, entry.first,
343                         "org.freedesktop.DBus.Properties", "Set",
344                         "xyz.openbmc_project.Control.Service.Attributes",
345                         "Enabled",
346                         dbus::utility::DbusVariantType{protocolEnabled});
347                 }
348             }
349         },
350         "xyz.openbmc_project.ObjectMapper",
351         "/xyz/openbmc_project/object_mapper",
352         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
353         "/xyz/openbmc_project/control/service", 0,
354         std::array<const char*, 1>{
355             "xyz.openbmc_project.Control.Service.Attributes"});
356 }
357 
358 inline std::string getHostName()
359 {
360     std::string hostName;
361 
362     std::array<char, HOST_NAME_MAX> hostNameCStr;
363     if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0)
364     {
365         hostName = hostNameCStr.data();
366     }
367     return hostName;
368 }
369 
370 inline void
371     getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
372 {
373     sdbusplus::asio::getProperty<std::string>(
374         *crow::connections::systemBus, "xyz.openbmc_project.Settings",
375         "/xyz/openbmc_project/time/sync_method",
376         "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod",
377         [asyncResp](const boost::system::error_code errorCode,
378                     const std::string& timeSyncMethod) {
379             if (errorCode)
380             {
381                 return;
382             }
383 
384             if (timeSyncMethod ==
385                 "xyz.openbmc_project.Time.Synchronization.Method.NTP")
386             {
387                 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true;
388             }
389             else if (timeSyncMethod ==
390                      "xyz.openbmc_project.Time.Synchronization."
391                      "Method.Manual")
392             {
393                 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false;
394             }
395         });
396 }
397 
398 inline void requestRoutesNetworkProtocol(App& app)
399 {
400     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
401         .privileges(redfish::privileges::patchManagerNetworkProtocol)
402         .methods(
403             boost::beast::http::verb::
404                 patch)([](const crow::Request& req,
405                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
406             std::optional<std::string> newHostName;
407             std::optional<nlohmann::json> ntp;
408             std::optional<nlohmann::json> ipmi;
409             std::optional<nlohmann::json> ssh;
410 
411             if (!json_util::readJson(req, asyncResp->res, "NTP", ntp,
412                                      "HostName", newHostName, "IPMI", ipmi,
413                                      "SSH", ssh))
414             {
415                 return;
416             }
417 
418             asyncResp->res.result(boost::beast::http::status::no_content);
419             if (newHostName)
420             {
421                 messages::propertyNotWritable(asyncResp->res, "HostName");
422                 return;
423             }
424 
425             if (ntp)
426             {
427                 std::optional<std::vector<std::string>> ntpServers;
428                 std::optional<bool> ntpEnabled;
429                 if (!json_util::readJson(*ntp, asyncResp->res, "NTPServers",
430                                          ntpServers, "ProtocolEnabled",
431                                          ntpEnabled))
432                 {
433                     return;
434                 }
435 
436                 if (ntpEnabled)
437                 {
438                     handleNTPProtocolEnabled(*ntpEnabled, asyncResp);
439                 }
440 
441                 if (ntpServers)
442                 {
443                     stl_utils::removeDuplicate(*ntpServers);
444                     handleNTPServersPatch(asyncResp, *ntpServers);
445                 }
446             }
447 
448             if (ipmi)
449             {
450                 std::optional<bool> ipmiProtocolEnabled;
451                 if (!json_util::readJson(*ipmi, asyncResp->res,
452                                          "ProtocolEnabled",
453                                          ipmiProtocolEnabled))
454                 {
455                     return;
456                 }
457 
458                 if (ipmiProtocolEnabled)
459                 {
460                     handleProtocolEnabled(
461                         *ipmiProtocolEnabled, asyncResp,
462                         "/xyz/openbmc_project/control/service/phosphor_2dipmi_2dnet_40");
463                 }
464             }
465 
466             if (ssh)
467             {
468                 std::optional<bool> sshProtocolEnabled;
469                 if (!json_util::readJson(*ssh, asyncResp->res,
470                                          "ProtocolEnabled", sshProtocolEnabled))
471                 {
472                     return;
473                 }
474 
475                 if (sshProtocolEnabled)
476                 {
477                     handleProtocolEnabled(
478                         *sshProtocolEnabled, asyncResp,
479                         "/xyz/openbmc_project/control/service/dropbear");
480                 }
481             }
482         });
483 
484     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
485         .privileges(redfish::privileges::getManagerNetworkProtocol)
486         .methods(boost::beast::http::verb::get)(
487             [](const crow::Request& req,
488                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
489                 getNetworkData(asyncResp, req);
490             });
491 }
492 
493 } // namespace redfish
494