xref: /openbmc/bmcweb/features/redfish/lib/network_protocol.hpp (revision 44fad2aa3ba9e28bb52c0ff21ba2cdbd2812b199)
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 <registries/privilege_registry.hpp>
24 #include <utils/json_utils.hpp>
25 
26 #include <optional>
27 #include <variant>
28 namespace redfish
29 {
30 
31 void getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp);
32 std::string getHostName();
33 
34 const static std::array<std::pair<std::string, std::string>, 3> protocolToDBus{
35     {{"SSH", "dropbear"}, {"HTTPS", "bmcweb"}, {"IPMI", "phosphor-ipmi-net"}}};
36 
37 inline void
38     extractNTPServersAndDomainNamesData(const GetManagedObjects& dbusData,
39                                         std::vector<std::string>& ntpData,
40                                         std::vector<std::string>& dnData)
41 {
42     for (const auto& obj : dbusData)
43     {
44         for (const auto& ifacePair : obj.second)
45         {
46             if (obj.first == "/xyz/openbmc_project/network/eth0")
47             {
48                 if (ifacePair.first ==
49                     "xyz.openbmc_project.Network.EthernetInterface")
50                 {
51                     for (const auto& propertyPair : ifacePair.second)
52                     {
53                         if (propertyPair.first == "NTPServers")
54                         {
55                             const std::vector<std::string>* ntpServers =
56                                 std::get_if<std::vector<std::string>>(
57                                     &propertyPair.second);
58                             if (ntpServers != nullptr)
59                             {
60                                 ntpData = *ntpServers;
61                             }
62                         }
63                         else if (propertyPair.first == "DomainName")
64                         {
65                             const std::vector<std::string>* domainNames =
66                                 std::get_if<std::vector<std::string>>(
67                                     &propertyPair.second);
68                             if (domainNames != nullptr)
69                             {
70                                 dnData = *domainNames;
71                             }
72                         }
73                     }
74                 }
75             }
76         }
77     }
78 }
79 
80 template <typename CallbackFunc>
81 void getEthernetIfaceData(CallbackFunc&& callback)
82 {
83     crow::connections::systemBus->async_method_call(
84         [callback{std::move(callback)}](
85             const boost::system::error_code errorCode,
86             const GetManagedObjects& dbusData) {
87             std::vector<std::string> ntpServers;
88             std::vector<std::string> domainNames;
89 
90             if (errorCode)
91             {
92                 callback(false, ntpServers, domainNames);
93                 return;
94             }
95 
96             extractNTPServersAndDomainNamesData(dbusData, ntpServers,
97                                                 domainNames);
98 
99             callback(true, ntpServers, domainNames);
100         },
101         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
102         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
103 }
104 
105 inline void getNetworkData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
106                            const crow::Request& req)
107 {
108     asyncResp->res.jsonValue["@odata.type"] =
109         "#ManagerNetworkProtocol.v1_5_0.ManagerNetworkProtocol";
110     asyncResp->res.jsonValue["@odata.id"] =
111         "/redfish/v1/Managers/bmc/NetworkProtocol";
112     asyncResp->res.jsonValue["Id"] = "NetworkProtocol";
113     asyncResp->res.jsonValue["Name"] = "Manager Network Protocol";
114     asyncResp->res.jsonValue["Description"] = "Manager Network Service";
115     asyncResp->res.jsonValue["Status"]["Health"] = "OK";
116     asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
117     asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
118 
119     // HTTP is Mandatory attribute as per OCP Baseline Profile - v1.0.0,
120     // but from security perspective it is not recommended to use.
121     // Hence using protocolEnabled as false to make it OCP and security-wise
122     // compliant
123     asyncResp->res.jsonValue["HTTP"]["Port"] = 0;
124     asyncResp->res.jsonValue["HTTP"]["ProtocolEnabled"] = false;
125 
126     std::string hostName = getHostName();
127 
128     asyncResp->res.jsonValue["HostName"] = hostName;
129 
130     getNTPProtocolEnabled(asyncResp);
131 
132     // TODO Get eth0 interface data, and call the below callback for JSON
133     // preparation
134     getEthernetIfaceData(
135         [hostName, asyncResp](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, "EthernetInterface",
141                                            "eth0");
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         std::variant<std::string>{timeSyncMethod});
237 }
238 
239 inline void
240     handleNTPServersPatch(const std::vector<std::string>& ntpServers,
241                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
242 {
243     crow::connections::systemBus->async_method_call(
244         [asyncResp](const boost::system::error_code ec) {
245             if (ec)
246             {
247                 messages::internalError(asyncResp->res);
248                 return;
249             }
250         },
251         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/eth0",
252         "org.freedesktop.DBus.Properties", "Set",
253         "xyz.openbmc_project.Network.EthernetInterface", "NTPServers",
254         std::variant<std::vector<std::string>>{ntpServers});
255 }
256 
257 inline void
258     handleProtocolEnabled(const bool protocolEnabled,
259                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
260                           const std::string_view netBasePath)
261 {
262     crow::connections::systemBus->async_method_call(
263         [protocolEnabled, asyncResp,
264          netBasePath](const boost::system::error_code ec,
265                       const crow::openbmc_mapper::GetSubTreeType& subtree) {
266             if (ec)
267             {
268                 messages::internalError(asyncResp->res);
269                 return;
270             }
271 
272             for (const auto& entry : subtree)
273             {
274                 if (boost::algorithm::starts_with(entry.first, netBasePath))
275                 {
276                     crow::connections::systemBus->async_method_call(
277                         [asyncResp](const boost::system::error_code ec2) {
278                             if (ec2)
279                             {
280                                 messages::internalError(asyncResp->res);
281                                 return;
282                             }
283                         },
284                         entry.second.begin()->first, entry.first,
285                         "org.freedesktop.DBus.Properties", "Set",
286                         "xyz.openbmc_project.Control.Service.Attributes",
287                         "Running", std::variant<bool>{protocolEnabled});
288 
289                     crow::connections::systemBus->async_method_call(
290                         [asyncResp](const boost::system::error_code ec2) {
291                             if (ec2)
292                             {
293                                 messages::internalError(asyncResp->res);
294                                 return;
295                             }
296                         },
297                         entry.second.begin()->first, entry.first,
298                         "org.freedesktop.DBus.Properties", "Set",
299                         "xyz.openbmc_project.Control.Service.Attributes",
300                         "Enabled", std::variant<bool>{protocolEnabled});
301                 }
302             }
303         },
304         "xyz.openbmc_project.ObjectMapper",
305         "/xyz/openbmc_project/object_mapper",
306         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
307         "/xyz/openbmc_project/control/service", 0,
308         std::array<const char*, 1>{
309             "xyz.openbmc_project.Control.Service.Attributes"});
310 }
311 
312 inline std::string getHostName()
313 {
314     std::string hostName;
315 
316     std::array<char, HOST_NAME_MAX> hostNameCStr;
317     if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0)
318     {
319         hostName = hostNameCStr.data();
320     }
321     return hostName;
322 }
323 
324 inline void
325     getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
326 {
327     crow::connections::systemBus->async_method_call(
328         [asyncResp](const boost::system::error_code errorCode,
329                     const std::variant<std::string>& timeSyncMethod) {
330             if (errorCode)
331             {
332                 return;
333             }
334 
335             const std::string* s = std::get_if<std::string>(&timeSyncMethod);
336 
337             if (*s == "xyz.openbmc_project.Time.Synchronization.Method.NTP")
338             {
339                 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true;
340             }
341             else if (*s == "xyz.openbmc_project.Time.Synchronization."
342                            "Method.Manual")
343             {
344                 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false;
345             }
346         },
347         "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method",
348         "org.freedesktop.DBus.Properties", "Get",
349         "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod");
350 }
351 
352 inline void requestRoutesNetworkProtocol(App& app)
353 {
354     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
355         .privileges(redfish::privileges::patchManagerNetworkProtocol)
356         .methods(boost::beast::http::verb::patch)(
357             [](const crow::Request& req,
358                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
359                 std::optional<std::string> newHostName;
360                 std::optional<nlohmann::json> ntp;
361                 std::optional<nlohmann::json> ipmi;
362                 std::optional<nlohmann::json> ssh;
363 
364                 if (!json_util::readJson(req, asyncResp->res, "NTP", ntp,
365                                          "HostName", newHostName, "IPMI", ipmi,
366                                          "SSH", ssh))
367                 {
368                     return;
369                 }
370 
371                 asyncResp->res.result(boost::beast::http::status::no_content);
372                 if (newHostName)
373                 {
374                     messages::propertyNotWritable(asyncResp->res, "HostName");
375                     return;
376                 }
377 
378                 if (ntp)
379                 {
380                     std::optional<std::vector<std::string>> ntpServers;
381                     std::optional<bool> ntpEnabled;
382                     if (!json_util::readJson(*ntp, asyncResp->res, "NTPServers",
383                                              ntpServers, "ProtocolEnabled",
384                                              ntpEnabled))
385                     {
386                         return;
387                     }
388 
389                     if (ntpEnabled)
390                     {
391                         handleNTPProtocolEnabled(*ntpEnabled, asyncResp);
392                     }
393 
394                     if (ntpServers)
395                     {
396                         std::sort((*ntpServers).begin(), (*ntpServers).end());
397                         (*ntpServers)
398                             .erase(std::unique((*ntpServers).begin(),
399                                                (*ntpServers).end()),
400                                    (*ntpServers).end());
401                         handleNTPServersPatch(*ntpServers, asyncResp);
402                     }
403                 }
404 
405                 if (ipmi)
406                 {
407                     std::optional<bool> ipmiProtocolEnabled;
408                     if (!json_util::readJson(*ipmi, asyncResp->res,
409                                              "ProtocolEnabled",
410                                              ipmiProtocolEnabled))
411                     {
412                         return;
413                     }
414 
415                     if (ipmiProtocolEnabled)
416                     {
417                         handleProtocolEnabled(
418                             *ipmiProtocolEnabled, asyncResp,
419                             "/xyz/openbmc_project/control/service/"
420                             "phosphor_2dipmi_2dnet_40");
421                     }
422                 }
423 
424                 if (ssh)
425                 {
426                     std::optional<bool> sshProtocolEnabled;
427                     if (!json_util::readJson(*ssh, asyncResp->res,
428                                              "ProtocolEnabled",
429                                              sshProtocolEnabled))
430                     {
431                         return;
432                     }
433 
434                     if (sshProtocolEnabled)
435                     {
436                         handleProtocolEnabled(
437                             *sshProtocolEnabled, asyncResp,
438                             "/xyz/openbmc_project/control/service/dropbear");
439                     }
440                 }
441             });
442 
443     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
444         .privileges(redfish::privileges::getManagerNetworkProtocol)
445         .methods(boost::beast::http::verb::get)(
446             [](const crow::Request& req,
447                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
448                 getNetworkData(asyncResp, req);
449             });
450 }
451 
452 } // namespace redfish
453