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