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/uuid/uuid.hpp> 10 #include <boost/uuid/uuid_generators.hpp> 11 #include <boost/uuid/uuid_io.hpp> 12 #include <dbus_singleton.hpp> 13 #include <nlohmann/json.hpp> 14 #include <pam_authenticate.hpp> 15 #include <random.hpp> 16 #include <sdbusplus/bus/match.hpp> 17 #include <utils/ip_utils.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{PersistenceType::TIMEOUT}; 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, 215 const boost::asio::ip::address& clientIp, 216 const std::string_view clientId, 217 PersistenceType persistence = PersistenceType::TIMEOUT, 218 bool isConfigureSelfOnly = false) 219 { 220 // TODO(ed) find a secure way to not generate session identifiers if 221 // persistence is set to SINGLE_REQUEST 222 static constexpr std::array<char, 62> alphanum = { 223 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 224 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 225 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 226 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 227 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; 228 229 std::string sessionToken; 230 sessionToken.resize(sessionTokenSize, '0'); 231 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1); 232 233 bmcweb::OpenSSLGenerator gen; 234 235 for (char& sessionChar : sessionToken) 236 { 237 sessionChar = alphanum[dist(gen)]; 238 if (gen.error()) 239 { 240 return nullptr; 241 } 242 } 243 // Only need csrf tokens for cookie based auth, token doesn't matter 244 std::string csrfToken; 245 csrfToken.resize(sessionTokenSize, '0'); 246 for (char& csrfChar : csrfToken) 247 { 248 csrfChar = alphanum[dist(gen)]; 249 if (gen.error()) 250 { 251 return nullptr; 252 } 253 } 254 255 std::string uniqueId; 256 uniqueId.resize(10, '0'); 257 for (char& uidChar : uniqueId) 258 { 259 uidChar = alphanum[dist(gen)]; 260 if (gen.error()) 261 { 262 return nullptr; 263 } 264 } 265 266 auto session = std::make_shared<UserSession>(UserSession{ 267 uniqueId, sessionToken, std::string(username), csrfToken, 268 std::string(clientId), redfish::ip_util::toString(clientIp), 269 std::chrono::steady_clock::now(), persistence, false, 270 isConfigureSelfOnly}); 271 auto it = authTokens.emplace(std::make_pair(sessionToken, session)); 272 // Only need to write to disk if session isn't about to be destroyed. 273 needWrite = persistence == PersistenceType::TIMEOUT; 274 return it.first->second; 275 } 276 277 std::shared_ptr<UserSession> 278 loginSessionByToken(const std::string_view token) 279 { 280 applySessionTimeouts(); 281 if (token.size() != sessionTokenSize) 282 { 283 return nullptr; 284 } 285 auto sessionIt = authTokens.find(std::string(token)); 286 if (sessionIt == authTokens.end()) 287 { 288 return nullptr; 289 } 290 std::shared_ptr<UserSession> userSession = sessionIt->second; 291 userSession->lastUpdated = std::chrono::steady_clock::now(); 292 return userSession; 293 } 294 295 std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid) 296 { 297 applySessionTimeouts(); 298 // TODO(Ed) this is inefficient 299 auto sessionIt = authTokens.begin(); 300 while (sessionIt != authTokens.end()) 301 { 302 if (sessionIt->second->uniqueId == uid) 303 { 304 return sessionIt->second; 305 } 306 sessionIt++; 307 } 308 return nullptr; 309 } 310 311 void removeSession(const std::shared_ptr<UserSession>& session) 312 { 313 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 314 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId); 315 #endif 316 authTokens.erase(session->sessionToken); 317 needWrite = true; 318 } 319 320 std::vector<const std::string*> getUniqueIds( 321 bool getAll = true, 322 const PersistenceType& type = PersistenceType::SINGLE_REQUEST) 323 { 324 applySessionTimeouts(); 325 326 std::vector<const std::string*> ret; 327 ret.reserve(authTokens.size()); 328 for (auto& session : authTokens) 329 { 330 if (getAll || type == session.second->persistence) 331 { 332 ret.push_back(&session.second->uniqueId); 333 } 334 } 335 return ret; 336 } 337 338 void updateAuthMethodsConfig(const AuthConfigMethods& config) 339 { 340 bool isTLSchanged = (authMethodsConfig.tls != config.tls); 341 authMethodsConfig = config; 342 needWrite = true; 343 if (isTLSchanged) 344 { 345 // recreate socket connections with new settings 346 std::raise(SIGHUP); 347 } 348 } 349 350 AuthConfigMethods& getAuthMethodsConfig() 351 { 352 return authMethodsConfig; 353 } 354 355 bool needsWrite() const 356 { 357 return needWrite; 358 } 359 int64_t getTimeoutInSeconds() const 360 { 361 return std::chrono::seconds(timeoutInSeconds).count(); 362 } 363 364 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds) 365 { 366 timeoutInSeconds = newTimeoutInSeconds; 367 needWrite = true; 368 } 369 370 static SessionStore& getInstance() 371 { 372 static SessionStore sessionStore; 373 return sessionStore; 374 } 375 376 void applySessionTimeouts() 377 { 378 auto timeNow = std::chrono::steady_clock::now(); 379 if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1)) 380 { 381 lastTimeoutUpdate = timeNow; 382 auto authTokensIt = authTokens.begin(); 383 while (authTokensIt != authTokens.end()) 384 { 385 if (timeNow - authTokensIt->second->lastUpdated >= 386 timeoutInSeconds) 387 { 388 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 389 crow::ibm_mc_lock::Lock::getInstance().releaseLock( 390 authTokensIt->second->uniqueId); 391 #endif 392 authTokensIt = authTokens.erase(authTokensIt); 393 394 needWrite = true; 395 } 396 else 397 { 398 authTokensIt++; 399 } 400 } 401 } 402 } 403 404 SessionStore(const SessionStore&) = delete; 405 SessionStore& operator=(const SessionStore&) = delete; 406 SessionStore(SessionStore&&) = delete; 407 SessionStore& operator=(const SessionStore&&) = delete; 408 ~SessionStore() = default; 409 410 std::unordered_map<std::string, std::shared_ptr<UserSession>, 411 std::hash<std::string>, 412 crow::utility::ConstantTimeCompare> 413 authTokens; 414 415 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate; 416 bool needWrite{false}; 417 std::chrono::seconds timeoutInSeconds; 418 AuthConfigMethods authMethodsConfig; 419 420 private: 421 SessionStore() : timeoutInSeconds(1800) 422 {} 423 }; 424 425 } // namespace persistent_data 426