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