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