blob: 99a1a0e9626264c38c15db8c101321bbce1065dc [file] [log] [blame]
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +01001#pragma once
2
Ed Tanous04e438c2020-10-03 08:06:26 -07003#include "logging.hpp"
Ed Tanousfc76b8a2020-09-28 17:21:52 -07004#include "random.hpp"
Ed Tanous04e438c2020-10-03 08:06:26 -07005#include "utility.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08006#include "utils/ip_utils.hpp"
Ed Tanousfc76b8a2020-09-28 17:21:52 -07007
Ed Tanous1abe55e2018-09-05 08:30:59 -07008#include <nlohmann/json.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +05309
Xie Ning9fa06f12022-06-29 18:27:47 +080010#include <algorithm>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050011#include <csignal>
Ed Tanousbb759e32022-08-02 17:07:54 -070012#include <optional>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050013#include <random>
Ratan Gupta07386c62019-12-14 14:06:09 +053014#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080015#include "ibm/locks.hpp"
Ratan Gupta07386c62019-12-14 14:06:09 +053016#endif
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010017
Ed Tanous1abe55e2018-09-05 08:30:59 -070018namespace persistent_data
19{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010020
Ed Tanous51dae672018-09-05 16:07:32 -070021// 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
24constexpr std::size_t sessionTokenSize = 20;
25
Ed Tanous1abe55e2018-09-05 08:30:59 -070026enum class PersistenceType
27{
28 TIMEOUT, // User session times out after a predetermined amount of time
29 SINGLE_REQUEST // User times out once this request is completed.
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010030};
31
Ed Tanous1abe55e2018-09-05 08:30:59 -070032struct UserSession
33{
34 std::string uniqueId;
35 std::string sessionToken;
36 std::string username;
37 std::string csrfToken;
Ed Tanousbb759e32022-08-02 17:07:54 -070038 std::optional<std::string> clientId;
Sunitha Harish92f68222020-05-28 05:09:09 -050039 std::string clientIp;
Ed Tanous1abe55e2018-09-05 08:30:59 -070040 std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
Ed Tanousd3a9e082022-01-07 09:30:41 -080041 PersistenceType persistence{PersistenceType::TIMEOUT};
Joseph Reynolds3bf4e632020-02-06 14:44:32 -060042 bool isConfigureSelfOnly = false;
43
44 // There are two sources of truth for isConfigureSelfOnly:
45 // 1. When pamAuthenticateUser() returns PAM_NEW_AUTHTOK_REQD.
46 // 2. D-Bus User.Manager.GetUserInfo property UserPasswordExpired.
47 // These should be in sync, but the underlying condition can change at any
48 // time. For example, a password can expire or be changed outside of
49 // bmcweb. The value stored here is updated at the start of each
50 // operation and used as the truth within bmcweb.
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010051
Ed Tanous1abe55e2018-09-05 08:30:59 -070052 /**
53 * @brief Fills object with data from UserSession's JSON representation
54 *
55 * This replaces nlohmann's from_json to ensure no-throw approach
56 *
57 * @param[in] j JSON object from which data should be loaded
58 *
59 * @return a shared pointer if data has been loaded properly, nullptr
60 * otherwise
61 */
62 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
63 {
64 std::shared_ptr<UserSession> userSession =
65 std::make_shared<UserSession>();
66 for (const auto& element : j.items())
67 {
68 const std::string* thisValue =
69 element.value().get_ptr<const std::string*>();
70 if (thisValue == nullptr)
71 {
72 BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
73 << element.key() << " was not of type string";
Ed Tanousdc511aa2020-10-21 12:33:42 -070074 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -070075 }
76 if (element.key() == "unique_id")
77 {
78 userSession->uniqueId = *thisValue;
79 }
80 else if (element.key() == "session_token")
81 {
82 userSession->sessionToken = *thisValue;
83 }
84 else if (element.key() == "csrf_token")
85 {
86 userSession->csrfToken = *thisValue;
87 }
88 else if (element.key() == "username")
89 {
90 userSession->username = *thisValue;
91 }
Sunitha Harish08bdcc72020-05-12 05:17:57 -050092 else if (element.key() == "client_id")
93 {
94 userSession->clientId = *thisValue;
95 }
Sunitha Harish92f68222020-05-28 05:09:09 -050096 else if (element.key() == "client_ip")
97 {
98 userSession->clientIp = *thisValue;
99 }
100
Ed Tanous1abe55e2018-09-05 08:30:59 -0700101 else
102 {
103 BMCWEB_LOG_ERROR
104 << "Got unexpected property reading persistent file: "
105 << element.key();
Ed Tanousdc511aa2020-10-21 12:33:42 -0700106 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700107 }
108 }
Ed Tanousdc511aa2020-10-21 12:33:42 -0700109 // If any of these fields are missing, we can't restore the session, as
110 // we don't have enough information. These 4 fields have been present
111 // in every version of this file in bmcwebs history, so any file, even
112 // on upgrade, should have these present
113 if (userSession->uniqueId.empty() || userSession->username.empty() ||
114 userSession->sessionToken.empty() || userSession->csrfToken.empty())
115 {
116 BMCWEB_LOG_DEBUG << "Session missing required security "
117 "information, refusing to restore";
118 return nullptr;
119 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700120
121 // For now, sessions that were persisted through a reboot get their idle
122 // timer reset. This could probably be overcome with a better
123 // understanding of wall clock time and steady timer time, possibly
124 // persisting values with wall clock time instead of steady timer, but
125 // the tradeoffs of all the corner cases involved are non-trivial, so
126 // this is done temporarily
127 userSession->lastUpdated = std::chrono::steady_clock::now();
128 userSession->persistence = PersistenceType::TIMEOUT;
129
130 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100131 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100132};
133
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100134struct AuthConfigMethods
135{
Alan Kuof16f6262020-12-08 19:29:59 +0800136#ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100137 bool basic = true;
Alan Kuof16f6262020-12-08 19:29:59 +0800138#else
139 bool basic = false;
140#endif
141
142#ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
143 bool sessionToken = true;
144#else
145 bool sessionToken = false;
146#endif
147
148#ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
149 bool xtoken = true;
150#else
151 bool xtoken = false;
152#endif
153
154#ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
155 bool cookie = true;
156#else
157 bool cookie = false;
158#endif
159
160#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
161 bool tls = true;
162#else
Zbigniew Kurzynskicac94c52019-11-07 12:55:04 +0100163 bool tls = false;
Alan Kuof16f6262020-12-08 19:29:59 +0800164#endif
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100165
166 void fromJson(const nlohmann::json& j)
167 {
168 for (const auto& element : j.items())
169 {
170 const bool* value = element.value().get_ptr<const bool*>();
171 if (value == nullptr)
172 {
173 continue;
174 }
175
176 if (element.key() == "XToken")
177 {
178 xtoken = *value;
179 }
180 else if (element.key() == "Cookie")
181 {
182 cookie = *value;
183 }
184 else if (element.key() == "SessionToken")
185 {
186 sessionToken = *value;
187 }
188 else if (element.key() == "BasicAuth")
189 {
190 basic = *value;
191 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200192 else if (element.key() == "TLS")
193 {
194 tls = *value;
195 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100196 }
197 }
198};
199
Ed Tanous1abe55e2018-09-05 08:30:59 -0700200class SessionStore
201{
202 public:
203 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous26ccae32023-02-16 10:28:44 -0800204 std::string_view username, const boost::asio::ip::address& clientIp,
Ed Tanousbb759e32022-08-02 17:07:54 -0700205 const std::optional<std::string>& clientId,
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600206 PersistenceType persistence = PersistenceType::TIMEOUT,
Sunitha Harishd3239222021-02-24 15:33:29 +0530207 bool isConfigureSelfOnly = false)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700208 {
209 // TODO(ed) find a secure way to not generate session identifiers if
210 // persistence is set to SINGLE_REQUEST
211 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500212 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
213 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
214 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700215 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
216 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100217
Ed Tanous1abe55e2018-09-05 08:30:59 -0700218 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700219 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700220 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
James Feista68a8042020-04-15 15:46:44 -0700221
Ed Tanousfc76b8a2020-09-28 17:21:52 -0700222 bmcweb::OpenSSLGenerator gen;
James Feista68a8042020-04-15 15:46:44 -0700223
Ed Tanous0dfeda62019-10-24 11:21:38 -0700224 for (char& sessionChar : sessionToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700225 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700226 sessionChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700227 if (gen.error())
228 {
229 return nullptr;
230 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100231 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700232 // Only need csrf tokens for cookie based auth, token doesn't matter
233 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700234 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700235 for (char& csrfChar : csrfToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700236 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700237 csrfChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700238 if (gen.error())
239 {
240 return nullptr;
241 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700242 }
243
244 std::string uniqueId;
245 uniqueId.resize(10, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700246 for (char& uidChar : uniqueId)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700247 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700248 uidChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700249 if (gen.error())
250 {
251 return nullptr;
252 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700253 }
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800254
255 auto session = std::make_shared<UserSession>(UserSession{
Ed Tanousbb759e32022-08-02 17:07:54 -0700256 uniqueId, sessionToken, std::string(username), csrfToken, clientId,
257 redfish::ip_util::toString(clientIp),
Gunnar Millse628df82023-04-04 10:15:42 -0500258 std::chrono::steady_clock::now(), persistence,
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800259 isConfigureSelfOnly});
Patrick Williams41713dd2022-09-28 06:48:07 -0500260 auto it = authTokens.emplace(sessionToken, session);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700261 // Only need to write to disk if session isn't about to be destroyed.
262 needWrite = persistence == PersistenceType::TIMEOUT;
263 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100264 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700265
Ed Tanous26ccae32023-02-16 10:28:44 -0800266 std::shared_ptr<UserSession> loginSessionByToken(std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700267 {
268 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700269 if (token.size() != sessionTokenSize)
270 {
271 return nullptr;
272 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700273 auto sessionIt = authTokens.find(std::string(token));
274 if (sessionIt == authTokens.end())
275 {
276 return nullptr;
277 }
278 std::shared_ptr<UserSession> userSession = sessionIt->second;
279 userSession->lastUpdated = std::chrono::steady_clock::now();
280 return userSession;
281 }
282
Ed Tanous26ccae32023-02-16 10:28:44 -0800283 std::shared_ptr<UserSession> getSessionByUid(std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700284 {
285 applySessionTimeouts();
286 // TODO(Ed) this is inefficient
287 auto sessionIt = authTokens.begin();
288 while (sessionIt != authTokens.end())
289 {
290 if (sessionIt->second->uniqueId == uid)
291 {
292 return sessionIt->second;
293 }
294 sessionIt++;
295 }
296 return nullptr;
297 }
298
Ed Tanousb5a76932020-09-29 16:16:58 -0700299 void removeSession(const std::shared_ptr<UserSession>& session)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700300 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530301#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
302 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
303#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700304 authTokens.erase(session->sessionToken);
305 needWrite = true;
306 }
307
308 std::vector<const std::string*> getUniqueIds(
309 bool getAll = true,
310 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
311 {
312 applySessionTimeouts();
313
314 std::vector<const std::string*> ret;
315 ret.reserve(authTokens.size());
316 for (auto& session : authTokens)
317 {
318 if (getAll || type == session.second->persistence)
319 {
320 ret.push_back(&session.second->uniqueId);
321 }
322 }
323 return ret;
324 }
325
Xie Ning9fa06f12022-06-29 18:27:47 +0800326 void removeSessionsByUsername(std::string_view username)
327 {
328 std::erase_if(authTokens, [username](const auto& value) {
329 if (value.second == nullptr)
330 {
331 return false;
332 }
333 return value.second->username == username;
334 });
335 }
336
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100337 void updateAuthMethodsConfig(const AuthConfigMethods& config)
338 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100339 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100340 authMethodsConfig = config;
341 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100342 if (isTLSchanged)
343 {
344 // recreate socket connections with new settings
345 std::raise(SIGHUP);
346 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100347 }
348
349 AuthConfigMethods& getAuthMethodsConfig()
350 {
351 return authMethodsConfig;
352 }
353
Ed Tanous9eb808c2022-01-25 10:19:23 -0800354 bool needsWrite() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700355 {
356 return needWrite;
357 }
Ed Tanous271584a2019-07-09 16:24:22 -0700358 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700359 {
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530360 return std::chrono::seconds(timeoutInSeconds).count();
361 }
362
363 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds)
364 {
365 timeoutInSeconds = newTimeoutInSeconds;
366 needWrite = true;
Ed Tanous23a21a12020-07-25 04:45:05 +0000367 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700368
Ed Tanous1abe55e2018-09-05 08:30:59 -0700369 static SessionStore& getInstance()
370 {
371 static SessionStore sessionStore;
372 return sessionStore;
373 }
374
Ed Tanous1abe55e2018-09-05 08:30:59 -0700375 void applySessionTimeouts()
376 {
377 auto timeNow = std::chrono::steady_clock::now();
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530378 if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1))
Ed Tanous1abe55e2018-09-05 08:30:59 -0700379 {
380 lastTimeoutUpdate = timeNow;
381 auto authTokensIt = authTokens.begin();
382 while (authTokensIt != authTokens.end())
383 {
384 if (timeNow - authTokensIt->second->lastUpdated >=
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530385 timeoutInSeconds)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700386 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530387#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
388 crow::ibm_mc_lock::Lock::getInstance().releaseLock(
389 authTokensIt->second->uniqueId);
390#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700391 authTokensIt = authTokens.erase(authTokensIt);
Ratan Gupta07386c62019-12-14 14:06:09 +0530392
Ed Tanous1abe55e2018-09-05 08:30:59 -0700393 needWrite = true;
394 }
395 else
396 {
397 authTokensIt++;
398 }
399 }
400 }
401 }
Gunnar Mills83cf8182020-11-11 15:37:34 -0600402
403 SessionStore(const SessionStore&) = delete;
404 SessionStore& operator=(const SessionStore&) = delete;
Ed Tanousecd6a3a2022-01-07 09:18:40 -0800405 SessionStore(SessionStore&&) = delete;
406 SessionStore& operator=(const SessionStore&&) = delete;
407 ~SessionStore() = default;
Gunnar Mills83cf8182020-11-11 15:37:34 -0600408
409 std::unordered_map<std::string, std::shared_ptr<UserSession>,
410 std::hash<std::string>,
411 crow::utility::ConstantTimeCompare>
412 authTokens;
413
414 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
415 bool needWrite{false};
416 std::chrono::seconds timeoutInSeconds;
417 AuthConfigMethods authMethodsConfig;
418
419 private:
Jason M. Billsdc414b52021-08-05 15:20:25 -0700420 SessionStore() : timeoutInSeconds(1800)
Gunnar Mills83cf8182020-11-11 15:37:34 -0600421 {}
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100422};
423
Ed Tanous1abe55e2018-09-05 08:30:59 -0700424} // namespace persistent_data