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