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 // Authentication paths 188 bool basic = BMCWEB_BASIC_AUTH; 189 bool sessionToken = BMCWEB_SESSION_AUTH; 190 bool xtoken = BMCWEB_XTOKEN_AUTH; 191 bool cookie = BMCWEB_COOKIE_AUTH; 192 bool tls = BMCWEB_MUTUAL_TLS_AUTH; 193 194 // Whether or not unauthenticated TLS should be accepted 195 // true = reject connections if mutual tls is not provided 196 // false = allow connection, and allow user to use other auth method 197 // Always default to false, because root certificates will not 198 // be provisioned at startup 199 bool tlsStrict = false; 200 201 MTLSCommonNameParseMode mTLSCommonNameParsingMode = 202 getMTLSCommonNameParseMode( 203 BMCWEB_MUTUAL_TLS_COMMON_NAME_PARSING_DEFAULT); 204 205 void fromJson(const nlohmann::json::object_t& j) 206 { 207 for (const auto& element : j) 208 { 209 const bool* value = element.second.get_ptr<const bool*>(); 210 if (value != nullptr) 211 { 212 if (element.first == "XToken") 213 { 214 xtoken = *value; 215 } 216 else if (element.first == "Cookie") 217 { 218 cookie = *value; 219 } 220 else if (element.first == "SessionToken") 221 { 222 sessionToken = *value; 223 } 224 else if (element.first == "BasicAuth") 225 { 226 basic = *value; 227 } 228 else if (element.first == "TLS") 229 { 230 tls = *value; 231 } 232 else if (element.first == "TLSStrict") 233 { 234 tlsStrict = *value; 235 } 236 } 237 const uint64_t* intValue = 238 element.second.get_ptr<const uint64_t*>(); 239 if (intValue != nullptr) 240 { 241 if (element.first == "MTLSCommonNameParseMode") 242 { 243 if (*intValue <= 2 || *intValue == 100) 244 { 245 mTLSCommonNameParsingMode = 246 static_cast<MTLSCommonNameParseMode>(*intValue); 247 } 248 else 249 { 250 BMCWEB_LOG_ERROR( 251 "Json value of {} was out of range of the enum. Ignoring", 252 *intValue); 253 } 254 } 255 } 256 } 257 } 258 }; 259 260 class SessionStore 261 { 262 public: 263 std::shared_ptr<UserSession> generateUserSession( 264 std::string_view username, const boost::asio::ip::address& clientIp, 265 const std::optional<std::string>& clientId, SessionType sessionType, 266 bool isConfigureSelfOnly = false) 267 { 268 // Only need csrf tokens for cookie based auth, token doesn't matter 269 std::string sessionToken = 270 bmcweb::getRandomIdOfLength(sessionTokenSize); 271 std::string csrfToken = bmcweb::getRandomIdOfLength(sessionTokenSize); 272 std::string uniqueId = bmcweb::getRandomIdOfLength(10); 273 274 // 275 if (sessionToken.empty() || csrfToken.empty() || uniqueId.empty()) 276 { 277 BMCWEB_LOG_ERROR("Failed to generate session tokens"); 278 return nullptr; 279 } 280 281 auto session = std::make_shared<UserSession>( 282 UserSession{uniqueId, 283 sessionToken, 284 std::string(username), 285 csrfToken, 286 clientId, 287 redfish::ip_util::toString(clientIp), 288 std::chrono::steady_clock::now(), 289 sessionType, 290 false, 291 isConfigureSelfOnly, 292 "", 293 {}}); 294 auto it = authTokens.emplace(sessionToken, session); 295 // Only need to write to disk if session isn't about to be destroyed. 296 needWrite = sessionType != SessionType::Basic && 297 sessionType != SessionType::MutualTLS; 298 return it.first->second; 299 } 300 301 std::shared_ptr<UserSession> loginSessionByToken(std::string_view token) 302 { 303 applySessionTimeouts(); 304 if (token.size() != sessionTokenSize) 305 { 306 return nullptr; 307 } 308 auto sessionIt = authTokens.find(std::string(token)); 309 if (sessionIt == authTokens.end()) 310 { 311 return nullptr; 312 } 313 std::shared_ptr<UserSession> userSession = sessionIt->second; 314 userSession->lastUpdated = std::chrono::steady_clock::now(); 315 return userSession; 316 } 317 318 std::shared_ptr<UserSession> getSessionByUid(std::string_view uid) 319 { 320 applySessionTimeouts(); 321 // TODO(Ed) this is inefficient 322 auto sessionIt = authTokens.begin(); 323 while (sessionIt != authTokens.end()) 324 { 325 if (sessionIt->second->uniqueId == uid) 326 { 327 return sessionIt->second; 328 } 329 sessionIt++; 330 } 331 return nullptr; 332 } 333 334 void removeSession(const std::shared_ptr<UserSession>& session) 335 { 336 authTokens.erase(session->sessionToken); 337 needWrite = true; 338 } 339 340 std::vector<std::string> getAllUniqueIds() 341 { 342 applySessionTimeouts(); 343 std::vector<std::string> ret; 344 ret.reserve(authTokens.size()); 345 for (auto& session : authTokens) 346 { 347 ret.push_back(session.second->uniqueId); 348 } 349 return ret; 350 } 351 352 std::vector<std::string> getUniqueIdsBySessionType(SessionType type) 353 { 354 applySessionTimeouts(); 355 356 std::vector<std::string> ret; 357 ret.reserve(authTokens.size()); 358 for (auto& session : authTokens) 359 { 360 if (type == session.second->sessionType) 361 { 362 ret.push_back(session.second->uniqueId); 363 } 364 } 365 return ret; 366 } 367 368 std::vector<std::shared_ptr<UserSession>> getSessions() 369 { 370 std::vector<std::shared_ptr<UserSession>> sessions; 371 sessions.reserve(authTokens.size()); 372 for (auto& session : authTokens) 373 { 374 sessions.push_back(session.second); 375 } 376 return sessions; 377 } 378 379 void removeSessionsByUsername(std::string_view username) 380 { 381 std::erase_if(authTokens, [username](const auto& value) { 382 if (value.second == nullptr) 383 { 384 return false; 385 } 386 return value.second->username == username; 387 }); 388 } 389 390 void removeSessionsByUsernameExceptSession( 391 std::string_view username, const std::shared_ptr<UserSession>& session) 392 { 393 std::erase_if(authTokens, [username, session](const auto& value) { 394 if (value.second == nullptr) 395 { 396 return false; 397 } 398 399 return value.second->username == username && 400 value.second->uniqueId != session->uniqueId; 401 }); 402 } 403 404 void updateAuthMethodsConfig(const AuthConfigMethods& config) 405 { 406 bool isTLSchanged = (authMethodsConfig.tls != config.tls); 407 authMethodsConfig = config; 408 needWrite = true; 409 if (isTLSchanged) 410 { 411 // recreate socket connections with new settings 412 std::raise(SIGHUP); 413 } 414 } 415 416 AuthConfigMethods& getAuthMethodsConfig() 417 { 418 return authMethodsConfig; 419 } 420 421 bool needsWrite() const 422 { 423 return needWrite; 424 } 425 int64_t getTimeoutInSeconds() const 426 { 427 return std::chrono::seconds(timeoutInSeconds).count(); 428 } 429 430 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds) 431 { 432 timeoutInSeconds = newTimeoutInSeconds; 433 needWrite = true; 434 } 435 436 static SessionStore& getInstance() 437 { 438 static SessionStore sessionStore; 439 return sessionStore; 440 } 441 442 void applySessionTimeouts() 443 { 444 auto timeNow = std::chrono::steady_clock::now(); 445 if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1)) 446 { 447 lastTimeoutUpdate = timeNow; 448 auto authTokensIt = authTokens.begin(); 449 while (authTokensIt != authTokens.end()) 450 { 451 if (timeNow - authTokensIt->second->lastUpdated >= 452 timeoutInSeconds) 453 { 454 authTokensIt = authTokens.erase(authTokensIt); 455 456 needWrite = true; 457 } 458 else 459 { 460 authTokensIt++; 461 } 462 } 463 } 464 } 465 466 SessionStore(const SessionStore&) = delete; 467 SessionStore& operator=(const SessionStore&) = delete; 468 SessionStore(SessionStore&&) = delete; 469 SessionStore& operator=(const SessionStore&&) = delete; 470 ~SessionStore() = default; 471 472 std::unordered_map<std::string, std::shared_ptr<UserSession>, 473 std::hash<std::string>, bmcweb::ConstantTimeCompare> 474 authTokens; 475 476 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate; 477 bool needWrite{false}; 478 std::chrono::seconds timeoutInSeconds; 479 AuthConfigMethods authMethodsConfig; 480 481 private: 482 SessionStore() : timeoutInSeconds(1800) {} 483 }; 484 485 } // namespace persistent_data 486