1 #pragma once 2 3 #include "logging.hpp" 4 #include "ossl_random.hpp" 5 #include "utility.hpp" 6 #include "utils/ip_utils.hpp" 7 8 #include <nlohmann/json.hpp> 9 10 #include <algorithm> 11 #include <csignal> 12 #include <memory> 13 #include <optional> 14 #include <random> 15 #include <string> 16 #include <vector> 17 18 namespace persistent_data 19 { 20 21 // entropy: 20 characters, 62 possibilities. log2(62^20) = 119 bits of 22 // entropy. OWASP recommends at least 64 23 // https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy 24 constexpr std::size_t sessionTokenSize = 20; 25 26 enum class SessionType 27 { 28 None, 29 Basic, 30 Session, 31 Cookie, 32 MutualTLS 33 }; 34 35 struct UserSession 36 { 37 std::string uniqueId; 38 std::string sessionToken; 39 std::string username; 40 std::string csrfToken; 41 std::optional<std::string> clientId; 42 std::string clientIp; 43 std::chrono::time_point<std::chrono::steady_clock> lastUpdated; 44 SessionType sessionType{SessionType::None}; 45 bool cookieAuth = false; 46 bool isConfigureSelfOnly = false; 47 std::string userRole; 48 std::vector<std::string> userGroups; 49 50 // There are two sources of truth for isConfigureSelfOnly: 51 // 1. When pamAuthenticateUser() returns PAM_NEW_AUTHTOK_REQD. 52 // 2. D-Bus User.Manager.GetUserInfo property UserPasswordExpired. 53 // These should be in sync, but the underlying condition can change at any 54 // time. For example, a password can expire or be changed outside of 55 // bmcweb. The value stored here is updated at the start of each 56 // operation and used as the truth within bmcweb. 57 58 /** 59 * @brief Fills object with data from UserSession's JSON representation 60 * 61 * This replaces nlohmann's from_json to ensure no-throw approach 62 * 63 * @param[in] j JSON object from which data should be loaded 64 * 65 * @return a shared pointer if data has been loaded properly, nullptr 66 * otherwise 67 */ 68 static std::shared_ptr<UserSession> 69 fromJson(const nlohmann::json::object_t& j) 70 { 71 std::shared_ptr<UserSession> userSession = 72 std::make_shared<UserSession>(); 73 for (const auto& element : j) 74 { 75 const std::string* thisValue = 76 element.second.get_ptr<const std::string*>(); 77 if (thisValue == nullptr) 78 { 79 BMCWEB_LOG_ERROR( 80 "Error reading persistent store. Property {} was not of type string", 81 element.first); 82 continue; 83 } 84 if (element.first == "unique_id") 85 { 86 userSession->uniqueId = *thisValue; 87 } 88 else if (element.first == "session_token") 89 { 90 userSession->sessionToken = *thisValue; 91 } 92 else if (element.first == "csrf_token") 93 { 94 userSession->csrfToken = *thisValue; 95 } 96 else if (element.first == "username") 97 { 98 userSession->username = *thisValue; 99 } 100 else if (element.first == "client_id") 101 { 102 userSession->clientId = *thisValue; 103 } 104 else if (element.first == "client_ip") 105 { 106 userSession->clientIp = *thisValue; 107 } 108 109 else 110 { 111 BMCWEB_LOG_ERROR( 112 "Got unexpected property reading persistent file: {}", 113 element.first); 114 continue; 115 } 116 } 117 // If any of these fields are missing, we can't restore the session, as 118 // we don't have enough information. These 4 fields have been present 119 // in every version of this file in bmcwebs history, so any file, even 120 // on upgrade, should have these present 121 if (userSession->uniqueId.empty() || userSession->username.empty() || 122 userSession->sessionToken.empty() || userSession->csrfToken.empty()) 123 { 124 BMCWEB_LOG_DEBUG("Session missing required security " 125 "information, refusing to restore"); 126 return nullptr; 127 } 128 129 // For now, sessions that were persisted through a reboot get their idle 130 // timer reset. This could probably be overcome with a better 131 // understanding of wall clock time and steady timer time, possibly 132 // persisting values with wall clock time instead of steady timer, but 133 // the tradeoffs of all the corner cases involved are non-trivial, so 134 // this is done temporarily 135 userSession->lastUpdated = std::chrono::steady_clock::now(); 136 userSession->sessionType = SessionType::Session; 137 138 return userSession; 139 } 140 }; 141 142 enum class MTLSCommonNameParseMode 143 { 144 Invalid = 0, 145 // This section approximately matches Redfish AccountService 146 // CertificateMappingAttribute, plus bmcweb defined OEM ones. 147 // Note, IDs in this enum must be maintained between versions, as they are 148 // persisted to disk 149 Whole = 1, 150 CommonName = 2, 151 UserPrincipalName = 3, 152 153 // Intentional gap for future DMTF-defined enums 154 155 // OEM parsing modes for various OEMs 156 Meta = 100, 157 }; 158 159 inline MTLSCommonNameParseMode getMTLSCommonNameParseMode(std::string_view name) 160 { 161 if (name == "CommonName") 162 { 163 return MTLSCommonNameParseMode::CommonName; 164 } 165 if (name == "Whole") 166 { 167 // Not yet supported 168 // return MTLSCommonNameParseMode::Whole; 169 } 170 if (name == "UserPrincipalName") 171 { 172 // Not yet supported 173 // return MTLSCommonNameParseMode::UserPrincipalName; 174 } 175 if constexpr (BMCWEB_META_TLS_COMMON_NAME_PARSING) 176 { 177 if (name == "Meta") 178 { 179 return MTLSCommonNameParseMode::Meta; 180 } 181 } 182 return MTLSCommonNameParseMode::Invalid; 183 } 184 185 struct AuthConfigMethods 186 { 187 bool basic = BMCWEB_BASIC_AUTH; 188 bool sessionToken = BMCWEB_SESSION_AUTH; 189 bool xtoken = BMCWEB_XTOKEN_AUTH; 190 bool cookie = BMCWEB_COOKIE_AUTH; 191 bool tls = BMCWEB_MUTUAL_TLS_AUTH; 192 193 MTLSCommonNameParseMode mTLSCommonNameParsingMode = 194 getMTLSCommonNameParseMode( 195 BMCWEB_MUTUAL_TLS_COMMON_NAME_PARSING_DEFAULT); 196 197 void fromJson(const nlohmann::json::object_t& j) 198 { 199 for (const auto& element : j) 200 { 201 const bool* value = element.second.get_ptr<const bool*>(); 202 if (value != nullptr) 203 { 204 if (element.first == "XToken") 205 { 206 xtoken = *value; 207 } 208 else if (element.first == "Cookie") 209 { 210 cookie = *value; 211 } 212 else if (element.first == "SessionToken") 213 { 214 sessionToken = *value; 215 } 216 else if (element.first == "BasicAuth") 217 { 218 basic = *value; 219 } 220 else if (element.first == "TLS") 221 { 222 tls = *value; 223 } 224 } 225 const uint64_t* intValue = 226 element.second.get_ptr<const uint64_t*>(); 227 if (intValue != nullptr) 228 { 229 if (element.first == "MTLSCommonNameParseMode") 230 { 231 if (*intValue <= 2 || *intValue == 100) 232 { 233 mTLSCommonNameParsingMode = 234 static_cast<MTLSCommonNameParseMode>(*intValue); 235 } 236 else 237 { 238 BMCWEB_LOG_ERROR( 239 "Json value of {} was out of range of the enum. Ignoring", 240 *intValue); 241 } 242 } 243 } 244 } 245 } 246 }; 247 248 class SessionStore 249 { 250 public: 251 std::shared_ptr<UserSession> generateUserSession( 252 std::string_view username, const boost::asio::ip::address& clientIp, 253 const std::optional<std::string>& clientId, SessionType sessionType, 254 bool isConfigureSelfOnly = false) 255 { 256 // Only need csrf tokens for cookie based auth, token doesn't matter 257 std::string sessionToken = 258 bmcweb::getRandomIdOfLength(sessionTokenSize); 259 std::string csrfToken = bmcweb::getRandomIdOfLength(sessionTokenSize); 260 std::string uniqueId = bmcweb::getRandomIdOfLength(10); 261 262 // 263 if (sessionToken.empty() || csrfToken.empty() || uniqueId.empty()) 264 { 265 BMCWEB_LOG_ERROR("Failed to generate session tokens"); 266 return nullptr; 267 } 268 269 auto session = std::make_shared<UserSession>( 270 UserSession{uniqueId, 271 sessionToken, 272 std::string(username), 273 csrfToken, 274 clientId, 275 redfish::ip_util::toString(clientIp), 276 std::chrono::steady_clock::now(), 277 sessionType, 278 false, 279 isConfigureSelfOnly, 280 "", 281 {}}); 282 auto it = authTokens.emplace(sessionToken, session); 283 // Only need to write to disk if session isn't about to be destroyed. 284 needWrite = sessionType != SessionType::Basic && 285 sessionType != SessionType::MutualTLS; 286 return it.first->second; 287 } 288 289 std::shared_ptr<UserSession> loginSessionByToken(std::string_view token) 290 { 291 applySessionTimeouts(); 292 if (token.size() != sessionTokenSize) 293 { 294 return nullptr; 295 } 296 auto sessionIt = authTokens.find(std::string(token)); 297 if (sessionIt == authTokens.end()) 298 { 299 return nullptr; 300 } 301 std::shared_ptr<UserSession> userSession = sessionIt->second; 302 userSession->lastUpdated = std::chrono::steady_clock::now(); 303 return userSession; 304 } 305 306 std::shared_ptr<UserSession> getSessionByUid(std::string_view uid) 307 { 308 applySessionTimeouts(); 309 // TODO(Ed) this is inefficient 310 auto sessionIt = authTokens.begin(); 311 while (sessionIt != authTokens.end()) 312 { 313 if (sessionIt->second->uniqueId == uid) 314 { 315 return sessionIt->second; 316 } 317 sessionIt++; 318 } 319 return nullptr; 320 } 321 322 void removeSession(const std::shared_ptr<UserSession>& session) 323 { 324 authTokens.erase(session->sessionToken); 325 needWrite = true; 326 } 327 328 std::vector<std::string> getAllUniqueIds() 329 { 330 applySessionTimeouts(); 331 std::vector<std::string> ret; 332 ret.reserve(authTokens.size()); 333 for (auto& session : authTokens) 334 { 335 ret.push_back(session.second->uniqueId); 336 } 337 return ret; 338 } 339 340 std::vector<std::string> getUniqueIdsBySessionType(SessionType type) 341 { 342 applySessionTimeouts(); 343 344 std::vector<std::string> ret; 345 ret.reserve(authTokens.size()); 346 for (auto& session : authTokens) 347 { 348 if (type == session.second->sessionType) 349 { 350 ret.push_back(session.second->uniqueId); 351 } 352 } 353 return ret; 354 } 355 356 std::vector<std::shared_ptr<UserSession>> getSessions() 357 { 358 std::vector<std::shared_ptr<UserSession>> sessions; 359 sessions.reserve(authTokens.size()); 360 for (auto& session : authTokens) 361 { 362 sessions.push_back(session.second); 363 } 364 return sessions; 365 } 366 367 void removeSessionsByUsername(std::string_view username) 368 { 369 std::erase_if(authTokens, [username](const auto& value) { 370 if (value.second == nullptr) 371 { 372 return false; 373 } 374 return value.second->username == username; 375 }); 376 } 377 378 void removeSessionsByUsernameExceptSession( 379 std::string_view username, const std::shared_ptr<UserSession>& session) 380 { 381 std::erase_if(authTokens, [username, session](const auto& value) { 382 if (value.second == nullptr) 383 { 384 return false; 385 } 386 387 return value.second->username == username && 388 value.second->uniqueId != session->uniqueId; 389 }); 390 } 391 392 void updateAuthMethodsConfig(const AuthConfigMethods& config) 393 { 394 bool isTLSchanged = (authMethodsConfig.tls != config.tls); 395 authMethodsConfig = config; 396 needWrite = true; 397 if (isTLSchanged) 398 { 399 // recreate socket connections with new settings 400 std::raise(SIGHUP); 401 } 402 } 403 404 AuthConfigMethods& getAuthMethodsConfig() 405 { 406 return authMethodsConfig; 407 } 408 409 bool needsWrite() const 410 { 411 return needWrite; 412 } 413 int64_t getTimeoutInSeconds() const 414 { 415 return std::chrono::seconds(timeoutInSeconds).count(); 416 } 417 418 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds) 419 { 420 timeoutInSeconds = newTimeoutInSeconds; 421 needWrite = true; 422 } 423 424 static SessionStore& getInstance() 425 { 426 static SessionStore sessionStore; 427 return sessionStore; 428 } 429 430 void applySessionTimeouts() 431 { 432 auto timeNow = std::chrono::steady_clock::now(); 433 if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1)) 434 { 435 lastTimeoutUpdate = timeNow; 436 auto authTokensIt = authTokens.begin(); 437 while (authTokensIt != authTokens.end()) 438 { 439 if (timeNow - authTokensIt->second->lastUpdated >= 440 timeoutInSeconds) 441 { 442 authTokensIt = authTokens.erase(authTokensIt); 443 444 needWrite = true; 445 } 446 else 447 { 448 authTokensIt++; 449 } 450 } 451 } 452 } 453 454 SessionStore(const SessionStore&) = delete; 455 SessionStore& operator=(const SessionStore&) = delete; 456 SessionStore(SessionStore&&) = delete; 457 SessionStore& operator=(const SessionStore&&) = delete; 458 ~SessionStore() = default; 459 460 std::unordered_map<std::string, std::shared_ptr<UserSession>, 461 std::hash<std::string>, 462 crow::utility::ConstantTimeCompare> 463 authTokens; 464 465 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate; 466 bool needWrite{false}; 467 std::chrono::seconds timeoutInSeconds; 468 AuthConfigMethods authMethodsConfig; 469 470 private: 471 SessionStore() : timeoutInSeconds(1800) {} 472 }; 473 474 } // namespace persistent_data 475