xref: /openbmc/bmcweb/include/persistent_data.hpp (revision a17005271588105a802825fa646c7064fd3b4512)
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                         for (const auto& elem : item.second)
163                         {
164                             const nlohmann::json::object_t* jObj =
165                                 elem.get_ptr<const nlohmann::json::object_t*>();
166                             if (jObj == nullptr)
167                             {
168                                 continue;
169                             }
170                             std::shared_ptr<UserSession> newSession =
171                                 UserSession::fromJson(*jObj);
172 
173                             if (newSession == nullptr)
174                             {
175                                 BMCWEB_LOG_ERROR("Problem reading session "
176                                                  "from persistent store");
177                                 continue;
178                             }
179 
180                             BMCWEB_LOG_DEBUG("Restored session: {} {} {}",
181                                              newSession->csrfToken,
182                                              newSession->uniqueId,
183                                              newSession->sessionToken);
184                             SessionStore::getInstance().authTokens.emplace(
185                                 newSession->sessionToken, newSession);
186                         }
187                     }
188                     else if (item.first == "timeout")
189                     {
190                         const int64_t* jTimeout =
191                             item.second.get_ptr<const int64_t*>();
192                         if (jTimeout == nullptr)
193                         {
194                             BMCWEB_LOG_DEBUG(
195                                 "Problem reading session timeout value");
196                             continue;
197                         }
198                         std::chrono::seconds sessionTimeoutInseconds(*jTimeout);
199                         BMCWEB_LOG_DEBUG("Restored Session Timeout: {}",
200                                          sessionTimeoutInseconds.count());
201                         SessionStore::getInstance().updateSessionTimeout(
202                             sessionTimeoutInseconds);
203                     }
204                     else if (item.first == "eventservice_config")
205                     {
206                         const nlohmann::json::object_t* esobj =
207                             item.second
208                                 .get_ptr<const nlohmann::json::object_t*>();
209                         if (esobj == nullptr)
210                         {
211                             BMCWEB_LOG_DEBUG(
212                                 "Problem reading EventService value");
213                             continue;
214                         }
215 
216                         EventServiceStore::getInstance()
217                             .getEventServiceConfig()
218                             .fromJson(*esobj);
219                     }
220                     else if (item.first == "subscriptions")
221                     {
222                         for (const auto& elem : item.second)
223                         {
224                             const nlohmann::json::object_t* subobj =
225                                 elem.get_ptr<const nlohmann::json::object_t*>();
226                             if (subobj == nullptr)
227                             {
228                                 continue;
229                             }
230 
231                             std::optional<UserSubscription> newSub =
232                                 UserSubscription::fromJson(*subobj);
233 
234                             if (!newSub)
235                             {
236                                 BMCWEB_LOG_ERROR(
237                                     "Problem reading subscription from persistent store");
238                                 continue;
239                             }
240 
241                             std::string id = newSub->id;
242                             BMCWEB_LOG_DEBUG("Restored subscription: {} {}", id,
243                                              newSub->customText);
244 
245                             EventServiceStore::getInstance()
246                                 .subscriptionsConfigMap.emplace(
247                                     id, std::make_shared<UserSubscription>(
248                                             std::move(*newSub)));
249                         }
250                     }
251                     else
252                     {
253                         // Do nothing in the case of extra fields.  We may have
254                         // cases where fields are added in the future, and we
255                         // want to at least attempt to gracefully support
256                         // downgrades in that case, even if we don't officially
257                         // support it
258                     }
259                 }
260             }
261         }
262         bool needWrite = false;
263 
264         if (systemUuid.empty())
265         {
266             systemUuid = bmcweb::getRandomUUID();
267             needWrite = true;
268         }
269         if (fileRevision < jsonRevision)
270         {
271             needWrite = true;
272         }
273         // write revision changes or system uuid changes immediately
274         if (needWrite)
275         {
276             writeData();
277         }
278     }
279 
writeData()280     void writeData()
281     {
282         const std::string& fname = filename();
283         std::filesystem::path path(fname);
284         path = path.parent_path();
285         if (!path.empty())
286         {
287             std::error_code ecDir;
288             std::filesystem::create_directories(path, ecDir);
289             if (ecDir)
290             {
291                 BMCWEB_LOG_CRITICAL("Can't create persistent folders {}",
292                                     ecDir.message());
293                 return;
294             }
295         }
296         boost::beast::file_posix persistentFile;
297         boost::system::error_code ec;
298         persistentFile.open(fname.c_str(), boost::beast::file_mode::write, ec);
299         if (ec)
300         {
301             BMCWEB_LOG_CRITICAL("Unable to store persistent data to file {}",
302                                 ec.message());
303             return;
304         }
305 
306         // set the permission of the file to 640
307         std::filesystem::perms permission =
308             std::filesystem::perms::owner_read |
309             std::filesystem::perms::owner_write |
310             std::filesystem::perms::group_read;
311         std::filesystem::permissions(fname, permission, ec);
312         if (ec)
313         {
314             BMCWEB_LOG_CRITICAL("Failed to set filesystem permissions {}",
315                                 ec.message());
316             return;
317         }
318         const AuthConfigMethods& c =
319             SessionStore::getInstance().getAuthMethodsConfig();
320         const auto& eventServiceConfig =
321             EventServiceStore::getInstance().getEventServiceConfig();
322         nlohmann::json::object_t data;
323         nlohmann::json& authConfig = data["auth_config"];
324 
325         authConfig["XToken"] = c.xtoken;
326         authConfig["Cookie"] = c.cookie;
327         authConfig["SessionToken"] = c.sessionToken;
328         authConfig["BasicAuth"] = c.basic;
329         authConfig["TLS"] = c.tls;
330         authConfig["TLSStrict"] = c.tlsStrict;
331         authConfig["MTLSCommonNameParseMode"] =
332             static_cast<int>(c.mTLSCommonNameParsingMode);
333 
334         nlohmann::json& eventserviceConfig = data["eventservice_config"];
335         eventserviceConfig["ServiceEnabled"] = eventServiceConfig.enabled;
336         eventserviceConfig["DeliveryRetryAttempts"] =
337             eventServiceConfig.retryAttempts;
338         eventserviceConfig["DeliveryRetryIntervalSeconds"] =
339             eventServiceConfig.retryTimeoutInterval;
340 
341         data["system_uuid"] = systemUuid;
342         data["service_identification"] = serviceIdentification;
343         data["revision"] = jsonRevision;
344         data["timeout"] = SessionStore::getInstance().getTimeoutInSeconds();
345 
346         nlohmann::json& sessions = data["sessions"];
347         sessions = nlohmann::json::array();
348         for (const auto& p : SessionStore::getInstance().authTokens)
349         {
350             if (p.second->sessionType != persistent_data::SessionType::Basic &&
351                 p.second->sessionType !=
352                     persistent_data::SessionType::MutualTLS)
353             {
354                 nlohmann::json::object_t session;
355                 session["unique_id"] = p.second->uniqueId;
356                 session["session_token"] = p.second->sessionToken;
357                 session["username"] = p.second->username;
358                 session["csrf_token"] = p.second->csrfToken;
359                 session["client_ip"] = p.second->clientIp;
360                 const std::optional<std::string>& clientId = p.second->clientId;
361                 if (clientId)
362                 {
363                     session["client_id"] = *clientId;
364                 }
365                 sessions.emplace_back(std::move(session));
366             }
367         }
368         nlohmann::json& subscriptions = data["subscriptions"];
369         subscriptions = nlohmann::json::array();
370         for (const auto& it :
371              EventServiceStore::getInstance().subscriptionsConfigMap)
372         {
373             if (it.second == nullptr)
374             {
375                 continue;
376             }
377             const UserSubscription& subValue = *it.second;
378             if (subValue.subscriptionType == "SSE")
379             {
380                 BMCWEB_LOG_DEBUG("The subscription type is SSE, so skipping.");
381                 continue;
382             }
383             nlohmann::json::object_t headers;
384             for (const boost::beast::http::fields::value_type& header :
385                  subValue.httpHeaders)
386             {
387                 // Note, these are technically copies because nlohmann doesn't
388                 // support key lookup by std::string_view.  At least the
389                 // following code can use move
390                 // https://github.com/nlohmann/json/issues/1529
391                 std::string name(header.name_string());
392                 headers[std::move(name)] = header.value();
393             }
394 
395             nlohmann::json::object_t subscription;
396 
397             subscription["Id"] = subValue.id;
398             subscription["Context"] = subValue.customText;
399             subscription["DeliveryRetryPolicy"] = subValue.retryPolicy;
400             subscription["SendHeartbeat"] = subValue.sendHeartbeat;
401             subscription["HeartbeatIntervalMinutes"] =
402                 subValue.hbIntervalMinutes;
403             subscription["Destination"] = subValue.destinationUrl;
404             subscription["EventFormatType"] = subValue.eventFormatType;
405             subscription["HttpHeaders"] = std::move(headers);
406             subscription["MessageIds"] = subValue.registryMsgIds;
407             subscription["Protocol"] = subValue.protocol;
408             subscription["RegistryPrefixes"] = subValue.registryPrefixes;
409             subscription["OriginResources"] = subValue.originResources;
410             subscription["ResourceTypes"] = subValue.resourceTypes;
411             subscription["SubscriptionType"] = subValue.subscriptionType;
412             subscription["MetricReportDefinitions"] =
413                 subValue.metricReportDefinitions;
414             subscription["VerifyCertificate"] = subValue.verifyCertificate;
415 
416             subscriptions.emplace_back(std::move(subscription));
417         }
418         std::string out = nlohmann::json(data).dump(
419             -1, ' ', true, nlohmann::json::error_handler_t::replace);
420         persistentFile.write(out.data(), out.size(), ec);
421         if (ec)
422         {
423             BMCWEB_LOG_ERROR("Failed to write file {}", ec.message());
424         }
425     }
426 
427     std::string systemUuid;
428     std::string serviceIdentification;
429 };
430 
getConfig()431 inline ConfigFile& getConfig()
432 {
433     static ConfigFile f;
434     return f;
435 }
436 
437 } // namespace persistent_data
438