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