1 #pragma once 2 3 #include "logging.hpp" 4 #include "random.hpp" 5 #include "utility.hpp" 6 #include "utils/ip_utils.hpp" 7 8 #include <nlohmann/json.hpp> 9 10 #include <csignal> 11 #include <optional> 12 #include <random> 13 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 14 #include "ibm/locks.hpp" 15 #endif 16 17 namespace persistent_data 18 { 19 20 // entropy: 20 characters, 62 possibilities. log2(62^20) = 119 bits of 21 // entropy. OWASP recommends at least 64 22 // https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy 23 constexpr std::size_t sessionTokenSize = 20; 24 25 enum class PersistenceType 26 { 27 TIMEOUT, // User session times out after a predetermined amount of time 28 SINGLE_REQUEST // User times out once this request is completed. 29 }; 30 31 struct UserSession 32 { 33 std::string uniqueId; 34 std::string sessionToken; 35 std::string username; 36 std::string csrfToken; 37 std::optional<std::string> clientId; 38 std::string clientIp; 39 std::chrono::time_point<std::chrono::steady_clock> lastUpdated; 40 PersistenceType persistence{PersistenceType::TIMEOUT}; 41 bool cookieAuth = false; 42 bool isConfigureSelfOnly = false; 43 44 // There are two sources of truth for isConfigureSelfOnly: 45 // 1. When pamAuthenticateUser() returns PAM_NEW_AUTHTOK_REQD. 46 // 2. D-Bus User.Manager.GetUserInfo property UserPasswordExpired. 47 // These should be in sync, but the underlying condition can change at any 48 // time. For example, a password can expire or be changed outside of 49 // bmcweb. The value stored here is updated at the start of each 50 // operation and used as the truth within bmcweb. 51 52 /** 53 * @brief Fills object with data from UserSession's JSON representation 54 * 55 * This replaces nlohmann's from_json to ensure no-throw approach 56 * 57 * @param[in] j JSON object from which data should be loaded 58 * 59 * @return a shared pointer if data has been loaded properly, nullptr 60 * otherwise 61 */ 62 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j) 63 { 64 std::shared_ptr<UserSession> userSession = 65 std::make_shared<UserSession>(); 66 for (const auto& element : j.items()) 67 { 68 const std::string* thisValue = 69 element.value().get_ptr<const std::string*>(); 70 if (thisValue == nullptr) 71 { 72 BMCWEB_LOG_ERROR << "Error reading persistent store. Property " 73 << element.key() << " was not of type string"; 74 continue; 75 } 76 if (element.key() == "unique_id") 77 { 78 userSession->uniqueId = *thisValue; 79 } 80 else if (element.key() == "session_token") 81 { 82 userSession->sessionToken = *thisValue; 83 } 84 else if (element.key() == "csrf_token") 85 { 86 userSession->csrfToken = *thisValue; 87 } 88 else if (element.key() == "username") 89 { 90 userSession->username = *thisValue; 91 } 92 else if (element.key() == "client_id") 93 { 94 userSession->clientId = *thisValue; 95 } 96 else if (element.key() == "client_ip") 97 { 98 userSession->clientIp = *thisValue; 99 } 100 101 else 102 { 103 BMCWEB_LOG_ERROR 104 << "Got unexpected property reading persistent file: " 105 << element.key(); 106 continue; 107 } 108 } 109 // If any of these fields are missing, we can't restore the session, as 110 // we don't have enough information. These 4 fields have been present 111 // in every version of this file in bmcwebs history, so any file, even 112 // on upgrade, should have these present 113 if (userSession->uniqueId.empty() || userSession->username.empty() || 114 userSession->sessionToken.empty() || userSession->csrfToken.empty()) 115 { 116 BMCWEB_LOG_DEBUG << "Session missing required security " 117 "information, refusing to restore"; 118 return nullptr; 119 } 120 121 // For now, sessions that were persisted through a reboot get their idle 122 // timer reset. This could probably be overcome with a better 123 // understanding of wall clock time and steady timer time, possibly 124 // persisting values with wall clock time instead of steady timer, but 125 // the tradeoffs of all the corner cases involved are non-trivial, so 126 // this is done temporarily 127 userSession->lastUpdated = std::chrono::steady_clock::now(); 128 userSession->persistence = PersistenceType::TIMEOUT; 129 130 return userSession; 131 } 132 }; 133 134 struct AuthConfigMethods 135 { 136 #ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION 137 bool basic = true; 138 #else 139 bool basic = false; 140 #endif 141 142 #ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION 143 bool sessionToken = true; 144 #else 145 bool sessionToken = false; 146 #endif 147 148 #ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION 149 bool xtoken = true; 150 #else 151 bool xtoken = false; 152 #endif 153 154 #ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION 155 bool cookie = true; 156 #else 157 bool cookie = false; 158 #endif 159 160 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 161 bool tls = true; 162 #else 163 bool tls = false; 164 #endif 165 166 void fromJson(const nlohmann::json& j) 167 { 168 for (const auto& element : j.items()) 169 { 170 const bool* value = element.value().get_ptr<const bool*>(); 171 if (value == nullptr) 172 { 173 continue; 174 } 175 176 if (element.key() == "XToken") 177 { 178 xtoken = *value; 179 } 180 else if (element.key() == "Cookie") 181 { 182 cookie = *value; 183 } 184 else if (element.key() == "SessionToken") 185 { 186 sessionToken = *value; 187 } 188 else if (element.key() == "BasicAuth") 189 { 190 basic = *value; 191 } 192 else if (element.key() == "TLS") 193 { 194 tls = *value; 195 } 196 } 197 } 198 }; 199 200 class SessionStore 201 { 202 public: 203 std::shared_ptr<UserSession> generateUserSession( 204 std::string_view username, const boost::asio::ip::address& clientIp, 205 const std::optional<std::string>& clientId, 206 PersistenceType persistence = PersistenceType::TIMEOUT, 207 bool isConfigureSelfOnly = false) 208 { 209 // TODO(ed) find a secure way to not generate session identifiers if 210 // persistence is set to SINGLE_REQUEST 211 static constexpr std::array<char, 62> alphanum = { 212 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 213 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 214 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 215 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 216 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; 217 218 std::string sessionToken; 219 sessionToken.resize(sessionTokenSize, '0'); 220 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1); 221 222 bmcweb::OpenSSLGenerator gen; 223 224 for (char& sessionChar : sessionToken) 225 { 226 sessionChar = alphanum[dist(gen)]; 227 if (gen.error()) 228 { 229 return nullptr; 230 } 231 } 232 // Only need csrf tokens for cookie based auth, token doesn't matter 233 std::string csrfToken; 234 csrfToken.resize(sessionTokenSize, '0'); 235 for (char& csrfChar : csrfToken) 236 { 237 csrfChar = alphanum[dist(gen)]; 238 if (gen.error()) 239 { 240 return nullptr; 241 } 242 } 243 244 std::string uniqueId; 245 uniqueId.resize(10, '0'); 246 for (char& uidChar : uniqueId) 247 { 248 uidChar = alphanum[dist(gen)]; 249 if (gen.error()) 250 { 251 return nullptr; 252 } 253 } 254 255 auto session = std::make_shared<UserSession>(UserSession{ 256 uniqueId, sessionToken, std::string(username), csrfToken, clientId, 257 redfish::ip_util::toString(clientIp), 258 std::chrono::steady_clock::now(), persistence, false, 259 isConfigureSelfOnly}); 260 auto it = authTokens.emplace(sessionToken, session); 261 // Only need to write to disk if session isn't about to be destroyed. 262 needWrite = persistence == PersistenceType::TIMEOUT; 263 return it.first->second; 264 } 265 266 std::shared_ptr<UserSession> loginSessionByToken(std::string_view token) 267 { 268 applySessionTimeouts(); 269 if (token.size() != sessionTokenSize) 270 { 271 return nullptr; 272 } 273 auto sessionIt = authTokens.find(std::string(token)); 274 if (sessionIt == authTokens.end()) 275 { 276 return nullptr; 277 } 278 std::shared_ptr<UserSession> userSession = sessionIt->second; 279 userSession->lastUpdated = std::chrono::steady_clock::now(); 280 return userSession; 281 } 282 283 std::shared_ptr<UserSession> getSessionByUid(std::string_view uid) 284 { 285 applySessionTimeouts(); 286 // TODO(Ed) this is inefficient 287 auto sessionIt = authTokens.begin(); 288 while (sessionIt != authTokens.end()) 289 { 290 if (sessionIt->second->uniqueId == uid) 291 { 292 return sessionIt->second; 293 } 294 sessionIt++; 295 } 296 return nullptr; 297 } 298 299 void removeSession(const std::shared_ptr<UserSession>& session) 300 { 301 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 302 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId); 303 #endif 304 authTokens.erase(session->sessionToken); 305 needWrite = true; 306 } 307 308 std::vector<const std::string*> getUniqueIds( 309 bool getAll = true, 310 const PersistenceType& type = PersistenceType::SINGLE_REQUEST) 311 { 312 applySessionTimeouts(); 313 314 std::vector<const std::string*> ret; 315 ret.reserve(authTokens.size()); 316 for (auto& session : authTokens) 317 { 318 if (getAll || type == session.second->persistence) 319 { 320 ret.push_back(&session.second->uniqueId); 321 } 322 } 323 return ret; 324 } 325 326 void updateAuthMethodsConfig(const AuthConfigMethods& config) 327 { 328 bool isTLSchanged = (authMethodsConfig.tls != config.tls); 329 authMethodsConfig = config; 330 needWrite = true; 331 if (isTLSchanged) 332 { 333 // recreate socket connections with new settings 334 std::raise(SIGHUP); 335 } 336 } 337 338 AuthConfigMethods& getAuthMethodsConfig() 339 { 340 return authMethodsConfig; 341 } 342 343 bool needsWrite() const 344 { 345 return needWrite; 346 } 347 int64_t getTimeoutInSeconds() const 348 { 349 return std::chrono::seconds(timeoutInSeconds).count(); 350 } 351 352 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds) 353 { 354 timeoutInSeconds = newTimeoutInSeconds; 355 needWrite = true; 356 } 357 358 static SessionStore& getInstance() 359 { 360 static SessionStore sessionStore; 361 return sessionStore; 362 } 363 364 void applySessionTimeouts() 365 { 366 auto timeNow = std::chrono::steady_clock::now(); 367 if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1)) 368 { 369 lastTimeoutUpdate = timeNow; 370 auto authTokensIt = authTokens.begin(); 371 while (authTokensIt != authTokens.end()) 372 { 373 if (timeNow - authTokensIt->second->lastUpdated >= 374 timeoutInSeconds) 375 { 376 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 377 crow::ibm_mc_lock::Lock::getInstance().releaseLock( 378 authTokensIt->second->uniqueId); 379 #endif 380 authTokensIt = authTokens.erase(authTokensIt); 381 382 needWrite = true; 383 } 384 else 385 { 386 authTokensIt++; 387 } 388 } 389 } 390 } 391 392 SessionStore(const SessionStore&) = delete; 393 SessionStore& operator=(const SessionStore&) = delete; 394 SessionStore(SessionStore&&) = delete; 395 SessionStore& operator=(const SessionStore&&) = delete; 396 ~SessionStore() = default; 397 398 std::unordered_map<std::string, std::shared_ptr<UserSession>, 399 std::hash<std::string>, 400 crow::utility::ConstantTimeCompare> 401 authTokens; 402 403 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate; 404 bool needWrite{false}; 405 std::chrono::seconds timeoutInSeconds; 406 AuthConfigMethods authMethodsConfig; 407 408 private: 409 SessionStore() : timeoutInSeconds(1800) 410 {} 411 }; 412 413 } // namespace persistent_data 414