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