xref: /openbmc/bmcweb/include/persistent_data.hpp (revision 76c2ad64440e2864a0cc3d66a3b8bb0f5ac2f288)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #pragma once
4 
5 #include "event_service_store.hpp"
6 #include "logging.hpp"
7 #include "ossl_random.hpp"
8 #include "sessions.hpp"
9 // NOLINTNEXTLINE(misc-include-cleaner)
10 #include "utility.hpp"
11 
12 #include <boost/beast/core/file_base.hpp>
13 #include <boost/beast/core/file_posix.hpp>
14 #include <boost/beast/http/fields.hpp>
15 #include <nlohmann/json.hpp>
16 
17 #include <chrono>
18 #include <cstddef>
19 #include <cstdint>
20 #include <filesystem>
21 #include <memory>
22 #include <optional>
23 #include <string>
24 #include <system_error>
25 #include <utility>
26 
27 namespace persistent_data
28 {
29 
30 class ConfigFile
31 {
32     uint64_t jsonRevision = 1;
33 
34   public:
getStateFile()35     static std::string getStateFile()
36     {
37         // NOLINTNEXTLINE(concurrency-mt-unsafe)
38         const char* stateDir = std::getenv("STATE_DIRECTORY");
39         if (stateDir == nullptr)
40         {
41             stateDir = ".";
42         }
43         return std::string(stateDir) + "/bmcweb_persistent_data.json";
44     }
45 
filename()46     static const std::string& filename()
47     {
48         const static std::string fname = getStateFile();
49         return fname;
50     }
51 
ConfigFile()52     ConfigFile()
53     {
54         readData();
55     }
56 
~ConfigFile()57     ~ConfigFile()
58     {
59         // Make sure we aren't writing stale sessions
60         persistent_data::SessionStore::getInstance().applySessionTimeouts();
61         if (persistent_data::SessionStore::getInstance().needsWrite())
62         {
63             writeData();
64         }
65     }
66 
67     ConfigFile(const ConfigFile&) = delete;
68     ConfigFile(ConfigFile&&) = delete;
69     ConfigFile& operator=(const ConfigFile&) = delete;
70     ConfigFile& operator=(ConfigFile&&) = delete;
71 
72     // TODO(ed) this should really use protobuf, or some other serialization
73     // library, but adding another dependency is somewhat outside the scope of
74     // this application for the moment
readData()75     void readData()
76     {
77         boost::beast::file_posix persistentFile;
78         boost::system::error_code ec;
79         const std::string& file = filename();
80         persistentFile.open(file.c_str(), boost::beast::file_mode::read, ec);
81         uint64_t fileRevision = 0;
82         if (!ec)
83         {
84             uint64_t size = persistentFile.size(ec);
85             if (ec)
86             {
87                 BMCWEB_LOG_CRITICAL("Can't get filesize of {}", file);
88                 return;
89             }
90             std::string str;
91             str.resize(static_cast<size_t>(size), '\0');
92             persistentFile.read(str.data(), str.size(), ec);
93             if (ec)
94             {
95                 BMCWEB_LOG_CRITICAL("Failed to read file {}", file);
96                 return;
97             }
98             // call with exceptions disabled
99             auto data = nlohmann::json::parse(str, nullptr, false);
100             if (data.is_discarded())
101             {
102                 BMCWEB_LOG_ERROR("Error parsing persistent data in json file.");
103             }
104             else
105             {
106                 const nlohmann::json::object_t* obj =
107                     data.get_ptr<nlohmann::json::object_t*>();
108                 if (obj == nullptr)
109                 {
110                     return;
111                 }
112                 for (const auto& item : *obj)
113                 {
114                     if (item.first == "revision")
115                     {
116                         fileRevision = 0;
117 
118                         const uint64_t* uintPtr =
119                             item.second.get_ptr<const uint64_t*>();
120                         if (uintPtr == nullptr)
121                         {
122                             BMCWEB_LOG_ERROR("Failed to read revision flag");
123                         }
124                         else
125                         {
126                             fileRevision = *uintPtr;
127                         }
128                     }
129                     else if (item.first == "system_uuid")
130                     {
131                         const std::string* jSystemUuid =
132                             item.second.get_ptr<const std::string*>();
133                         if (jSystemUuid != nullptr)
134                         {
135                             systemUuid = *jSystemUuid;
136                         }
137                     }
138                     else if (item.first == "service_identification")
139                     {
140                         const std::string* jServiceIdentification =
141                             item.second.get_ptr<const std::string*>();
142                         if (jServiceIdentification != nullptr)
143                         {
144                             serviceIdentification = *jServiceIdentification;
145                         }
146                     }
147                     else if (item.first == "auth_config")
148                     {
149                         const nlohmann::json::object_t* jObj =
150                             item.second
151                                 .get_ptr<const nlohmann::json::object_t*>();
152                         if (jObj == nullptr)
153                         {
154                             continue;
155                         }
156                         SessionStore::getInstance()
157                             .getAuthMethodsConfig()
158                             .fromJson(*jObj);
159                     }
160                     else if (item.first == "sessions")
161                     {
162                         const nlohmann::json::array_t* arr =
163                             item.second
164                                 .get_ptr<const nlohmann::json::array_t*>();
165                         if (arr == nullptr)
166                         {
167                             BMCWEB_LOG_ERROR("Sessions wasn't an array");
168                             continue;
169                         }
170                         for (const auto& elem : *arr)
171                         {
172                             const nlohmann::json::object_t* jObj =
173                                 elem.get_ptr<const nlohmann::json::object_t*>();
174                             if (jObj == nullptr)
175                             {
176                                 continue;
177                             }
178                             std::shared_ptr<UserSession> newSession =
179                                 UserSession::fromJson(*jObj);
180 
181                             if (newSession == nullptr)
182                             {
183                                 BMCWEB_LOG_ERROR("Problem reading session "
184                                                  "from persistent store");
185                                 continue;
186                             }
187 
188                             BMCWEB_LOG_DEBUG("Restored session: {} {} {}",
189                                              newSession->csrfToken,
190                                              newSession->uniqueId,
191                                              newSession->sessionToken);
192                             SessionStore::getInstance().authTokens.emplace(
193                                 newSession->sessionToken, newSession);
194                         }
195                     }
196                     else if (item.first == "timeout")
197                     {
198                         const int64_t* jTimeout =
199                             item.second.get_ptr<const int64_t*>();
200                         if (jTimeout == nullptr)
201                         {
202                             BMCWEB_LOG_DEBUG(
203                                 "Problem reading session timeout value");
204                             continue;
205                         }
206                         std::chrono::seconds sessionTimeoutInseconds(*jTimeout);
207                         BMCWEB_LOG_DEBUG("Restored Session Timeout: {}",
208                                          sessionTimeoutInseconds.count());
209                         SessionStore::getInstance().updateSessionTimeout(
210                             sessionTimeoutInseconds);
211                     }
212                     else if (item.first == "eventservice_config")
213                     {
214                         const nlohmann::json::object_t* esobj =
215                             item.second
216                                 .get_ptr<const nlohmann::json::object_t*>();
217                         if (esobj == nullptr)
218                         {
219                             BMCWEB_LOG_DEBUG(
220                                 "Problem reading EventService value");
221                             continue;
222                         }
223 
224                         EventServiceStore::getInstance()
225                             .getEventServiceConfig()
226                             .fromJson(*esobj);
227                     }
228                     else if (item.first == "subscriptions")
229                     {
230                         const nlohmann::json::array_t* subarr =
231                             item.second
232                                 .get_ptr<const nlohmann::json::array_t*>();
233                         if (subarr == nullptr)
234                         {
235                             BMCWEB_LOG_ERROR("Problem reading Subscriptions");
236                             continue;
237                         }
238                         for (const auto& elem : *subarr)
239                         {
240                             const nlohmann::json::object_t* subobj =
241                                 elem.get_ptr<const nlohmann::json::object_t*>();
242                             if (subobj == nullptr)
243                             {
244                                 continue;
245                             }
246 
247                             std::optional<UserSubscription> newSub =
248                                 UserSubscription::fromJson(*subobj);
249 
250                             if (!newSub)
251                             {
252                                 BMCWEB_LOG_ERROR(
253                                     "Problem reading subscription from persistent store");
254                                 continue;
255                             }
256 
257                             std::string id = newSub->id;
258                             BMCWEB_LOG_DEBUG("Restored subscription: {} {}", id,
259                                              newSub->customText);
260 
261                             EventServiceStore::getInstance()
262                                 .subscriptionsConfigMap.emplace(
263                                     id, std::make_shared<UserSubscription>(
264                                             std::move(*newSub)));
265                         }
266                     }
267                     else
268                     {
269                         // Do nothing in the case of extra fields.  We may have
270                         // cases where fields are added in the future, and we
271                         // want to at least attempt to gracefully support
272                         // downgrades in that case, even if we don't officially
273                         // support it
274                     }
275                 }
276             }
277         }
278         bool needWrite = false;
279 
280         if (systemUuid.empty())
281         {
282             systemUuid = bmcweb::getRandomUUID();
283             needWrite = true;
284         }
285         if (fileRevision < jsonRevision)
286         {
287             needWrite = true;
288         }
289         // write revision changes or system uuid changes immediately
290         if (needWrite)
291         {
292             writeData();
293         }
294     }
295 
writeData()296     void writeData()
297     {
298         const std::string& fname = filename();
299         std::filesystem::path path(fname);
300         path = path.parent_path();
301         if (!path.empty())
302         {
303             std::error_code ecDir;
304             std::filesystem::create_directories(path, ecDir);
305             if (ecDir)
306             {
307                 BMCWEB_LOG_CRITICAL("Can't create persistent folders {}",
308                                     ecDir.message());
309                 return;
310             }
311         }
312         boost::beast::file_posix persistentFile;
313         boost::system::error_code ec;
314         persistentFile.open(fname.c_str(), boost::beast::file_mode::write, ec);
315         if (ec)
316         {
317             BMCWEB_LOG_CRITICAL("Unable to store persistent data to file {}",
318                                 ec.message());
319             return;
320         }
321 
322         // set the permission of the file to 640
323         std::filesystem::perms permission =
324             std::filesystem::perms::owner_read |
325             std::filesystem::perms::owner_write |
326             std::filesystem::perms::group_read;
327         std::filesystem::permissions(fname, permission, ec);
328         if (ec)
329         {
330             BMCWEB_LOG_CRITICAL("Failed to set filesystem permissions {}",
331                                 ec.message());
332             return;
333         }
334         const AuthConfigMethods& c =
335             SessionStore::getInstance().getAuthMethodsConfig();
336         const auto& eventServiceConfig =
337             EventServiceStore::getInstance().getEventServiceConfig();
338         nlohmann::json::object_t data;
339         nlohmann::json& authConfig = data["auth_config"];
340 
341         authConfig["XToken"] = c.xtoken;
342         authConfig["Cookie"] = c.cookie;
343         authConfig["SessionToken"] = c.sessionToken;
344         authConfig["BasicAuth"] = c.basic;
345         authConfig["TLS"] = c.tls;
346         authConfig["TLSStrict"] = c.tlsStrict;
347         authConfig["MTLSCommonNameParseMode"] =
348             static_cast<int>(c.mTLSCommonNameParsingMode);
349 
350         nlohmann::json& eventserviceConfig = data["eventservice_config"];
351         eventserviceConfig["ServiceEnabled"] = eventServiceConfig.enabled;
352         eventserviceConfig["DeliveryRetryAttempts"] =
353             eventServiceConfig.retryAttempts;
354         eventserviceConfig["DeliveryRetryIntervalSeconds"] =
355             eventServiceConfig.retryTimeoutInterval;
356 
357         data["system_uuid"] = systemUuid;
358         data["service_identification"] = serviceIdentification;
359         data["revision"] = jsonRevision;
360         data["timeout"] = SessionStore::getInstance().getTimeoutInSeconds();
361 
362         nlohmann::json& sessions = data["sessions"];
363         sessions = nlohmann::json::array();
364         for (const auto& p : SessionStore::getInstance().authTokens)
365         {
366             if (p.second->sessionType != persistent_data::SessionType::Basic &&
367                 p.second->sessionType !=
368                     persistent_data::SessionType::MutualTLS)
369             {
370                 nlohmann::json::object_t session;
371                 session["unique_id"] = p.second->uniqueId;
372                 session["session_token"] = p.second->sessionToken;
373                 session["username"] = p.second->username;
374                 session["csrf_token"] = p.second->csrfToken;
375                 session["client_ip"] = p.second->clientIp;
376                 const std::optional<std::string>& clientId = p.second->clientId;
377                 if (clientId)
378                 {
379                     session["client_id"] = *clientId;
380                 }
381                 sessions.emplace_back(std::move(session));
382             }
383         }
384         nlohmann::json& subscriptions = data["subscriptions"];
385         subscriptions = nlohmann::json::array();
386         for (const auto& it :
387              EventServiceStore::getInstance().subscriptionsConfigMap)
388         {
389             if (it.second == nullptr)
390             {
391                 continue;
392             }
393             const UserSubscription& subValue = *it.second;
394             if (subValue.subscriptionType == "SSE")
395             {
396                 BMCWEB_LOG_DEBUG("The subscription type is SSE, so skipping.");
397                 continue;
398             }
399             nlohmann::json::object_t headers;
400             for (const boost::beast::http::fields::value_type& header :
401                  subValue.httpHeaders)
402             {
403                 // Note, these are technically copies because nlohmann doesn't
404                 // support key lookup by std::string_view.  At least the
405                 // following code can use move
406                 // https://github.com/nlohmann/json/issues/1529
407                 std::string name(header.name_string());
408                 headers[std::move(name)] = header.value();
409             }
410 
411             nlohmann::json::object_t subscription;
412 
413             subscription["Id"] = subValue.id;
414             subscription["Context"] = subValue.customText;
415             subscription["DeliveryRetryPolicy"] = subValue.retryPolicy;
416             subscription["SendHeartbeat"] = subValue.sendHeartbeat;
417             subscription["HeartbeatIntervalMinutes"] =
418                 subValue.hbIntervalMinutes;
419             subscription["Destination"] = subValue.destinationUrl;
420             subscription["EventFormatType"] = subValue.eventFormatType;
421             subscription["HttpHeaders"] = std::move(headers);
422             subscription["MessageIds"] = subValue.registryMsgIds;
423             subscription["Protocol"] = subValue.protocol;
424             subscription["RegistryPrefixes"] = subValue.registryPrefixes;
425             subscription["OriginResources"] = subValue.originResources;
426             subscription["ResourceTypes"] = subValue.resourceTypes;
427             subscription["SubscriptionType"] = subValue.subscriptionType;
428             subscription["MetricReportDefinitions"] =
429                 subValue.metricReportDefinitions;
430             subscription["VerifyCertificate"] = subValue.verifyCertificate;
431 
432             subscriptions.emplace_back(std::move(subscription));
433         }
434         std::string out = nlohmann::json(data).dump(
435             -1, ' ', true, nlohmann::json::error_handler_t::replace);
436         persistentFile.write(out.data(), out.size(), ec);
437         if (ec)
438         {
439             BMCWEB_LOG_ERROR("Failed to write file {}", ec.message());
440         }
441     }
442 
443     std::string systemUuid;
444     std::string serviceIdentification;
445 };
446 
getConfig()447 inline ConfigFile& getConfig()
448 {
449     static ConfigFile f;
450     return f;
451 }
452 
453 } // namespace persistent_data
454