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