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