blob: 98912e827a7b3ce1e1ccb2525fad35e4979f7cc3 [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};
Ed Tanous7e9c08e2023-06-16 11:29:37 -070042 bool cookieAuth = false;
Joseph Reynolds3bf4e632020-02-06 14:44:32 -060043 bool isConfigureSelfOnly = false;
Ninad Palsule3e72c202023-03-27 17:19:55 -050044 std::string userRole{};
45 std::vector<std::string> userGroups{};
Joseph Reynolds3bf4e632020-02-06 14:44:32 -060046
47 // There are two sources of truth for isConfigureSelfOnly:
48 // 1. When pamAuthenticateUser() returns PAM_NEW_AUTHTOK_REQD.
49 // 2. D-Bus User.Manager.GetUserInfo property UserPasswordExpired.
50 // These should be in sync, but the underlying condition can change at any
51 // time. For example, a password can expire or be changed outside of
52 // bmcweb. The value stored here is updated at the start of each
53 // operation and used as the truth within bmcweb.
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010054
Ed Tanous1abe55e2018-09-05 08:30:59 -070055 /**
56 * @brief Fills object with data from UserSession's JSON representation
57 *
58 * This replaces nlohmann's from_json to ensure no-throw approach
59 *
60 * @param[in] j JSON object from which data should be loaded
61 *
62 * @return a shared pointer if data has been loaded properly, nullptr
63 * otherwise
64 */
65 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
66 {
67 std::shared_ptr<UserSession> userSession =
68 std::make_shared<UserSession>();
69 for (const auto& element : j.items())
70 {
71 const std::string* thisValue =
72 element.value().get_ptr<const std::string*>();
73 if (thisValue == nullptr)
74 {
75 BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
76 << element.key() << " was not of type string";
Ed Tanousdc511aa2020-10-21 12:33:42 -070077 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -070078 }
79 if (element.key() == "unique_id")
80 {
81 userSession->uniqueId = *thisValue;
82 }
83 else if (element.key() == "session_token")
84 {
85 userSession->sessionToken = *thisValue;
86 }
87 else if (element.key() == "csrf_token")
88 {
89 userSession->csrfToken = *thisValue;
90 }
91 else if (element.key() == "username")
92 {
93 userSession->username = *thisValue;
94 }
Sunitha Harish08bdcc72020-05-12 05:17:57 -050095 else if (element.key() == "client_id")
96 {
97 userSession->clientId = *thisValue;
98 }
Sunitha Harish92f68222020-05-28 05:09:09 -050099 else if (element.key() == "client_ip")
100 {
101 userSession->clientIp = *thisValue;
102 }
103
Ed Tanous1abe55e2018-09-05 08:30:59 -0700104 else
105 {
106 BMCWEB_LOG_ERROR
107 << "Got unexpected property reading persistent file: "
108 << element.key();
Ed Tanousdc511aa2020-10-21 12:33:42 -0700109 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700110 }
111 }
Ed Tanousdc511aa2020-10-21 12:33:42 -0700112 // If any of these fields are missing, we can't restore the session, as
113 // we don't have enough information. These 4 fields have been present
114 // in every version of this file in bmcwebs history, so any file, even
115 // on upgrade, should have these present
116 if (userSession->uniqueId.empty() || userSession->username.empty() ||
117 userSession->sessionToken.empty() || userSession->csrfToken.empty())
118 {
119 BMCWEB_LOG_DEBUG << "Session missing required security "
120 "information, refusing to restore";
121 return nullptr;
122 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700123
124 // For now, sessions that were persisted through a reboot get their idle
125 // timer reset. This could probably be overcome with a better
126 // understanding of wall clock time and steady timer time, possibly
127 // persisting values with wall clock time instead of steady timer, but
128 // the tradeoffs of all the corner cases involved are non-trivial, so
129 // this is done temporarily
130 userSession->lastUpdated = std::chrono::steady_clock::now();
131 userSession->persistence = PersistenceType::TIMEOUT;
132
133 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100134 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100135};
136
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100137struct AuthConfigMethods
138{
Alan Kuof16f6262020-12-08 19:29:59 +0800139#ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100140 bool basic = true;
Alan Kuof16f6262020-12-08 19:29:59 +0800141#else
142 bool basic = false;
143#endif
144
145#ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
146 bool sessionToken = true;
147#else
148 bool sessionToken = false;
149#endif
150
151#ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
152 bool xtoken = true;
153#else
154 bool xtoken = false;
155#endif
156
157#ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
158 bool cookie = true;
159#else
160 bool cookie = false;
161#endif
162
163#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
164 bool tls = true;
165#else
Zbigniew Kurzynskicac94c52019-11-07 12:55:04 +0100166 bool tls = false;
Alan Kuof16f6262020-12-08 19:29:59 +0800167#endif
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100168
169 void fromJson(const nlohmann::json& j)
170 {
171 for (const auto& element : j.items())
172 {
173 const bool* value = element.value().get_ptr<const bool*>();
174 if (value == nullptr)
175 {
176 continue;
177 }
178
179 if (element.key() == "XToken")
180 {
181 xtoken = *value;
182 }
183 else if (element.key() == "Cookie")
184 {
185 cookie = *value;
186 }
187 else if (element.key() == "SessionToken")
188 {
189 sessionToken = *value;
190 }
191 else if (element.key() == "BasicAuth")
192 {
193 basic = *value;
194 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200195 else if (element.key() == "TLS")
196 {
197 tls = *value;
198 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100199 }
200 }
201};
202
Ed Tanous1abe55e2018-09-05 08:30:59 -0700203class SessionStore
204{
205 public:
206 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous26ccae32023-02-16 10:28:44 -0800207 std::string_view username, const boost::asio::ip::address& clientIp,
Ed Tanousbb759e32022-08-02 17:07:54 -0700208 const std::optional<std::string>& clientId,
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600209 PersistenceType persistence = PersistenceType::TIMEOUT,
Sunitha Harishd3239222021-02-24 15:33:29 +0530210 bool isConfigureSelfOnly = false)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700211 {
212 // TODO(ed) find a secure way to not generate session identifiers if
213 // persistence is set to SINGLE_REQUEST
214 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500215 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
216 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
217 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700218 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
219 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100220
Ed Tanous1abe55e2018-09-05 08:30:59 -0700221 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700222 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700223 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
James Feista68a8042020-04-15 15:46:44 -0700224
Ed Tanousfc76b8a2020-09-28 17:21:52 -0700225 bmcweb::OpenSSLGenerator gen;
James Feista68a8042020-04-15 15:46:44 -0700226
Ed Tanous0dfeda62019-10-24 11:21:38 -0700227 for (char& sessionChar : sessionToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700228 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700229 sessionChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700230 if (gen.error())
231 {
232 return nullptr;
233 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100234 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700235 // Only need csrf tokens for cookie based auth, token doesn't matter
236 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700237 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700238 for (char& csrfChar : csrfToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700239 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700240 csrfChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700241 if (gen.error())
242 {
243 return nullptr;
244 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700245 }
246
247 std::string uniqueId;
248 uniqueId.resize(10, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700249 for (char& uidChar : uniqueId)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700250 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700251 uidChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700252 if (gen.error())
253 {
254 return nullptr;
255 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700256 }
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800257
258 auto session = std::make_shared<UserSession>(UserSession{
Ed Tanousbb759e32022-08-02 17:07:54 -0700259 uniqueId, sessionToken, std::string(username), csrfToken, clientId,
260 redfish::ip_util::toString(clientIp),
Ed Tanous7e9c08e2023-06-16 11:29:37 -0700261 std::chrono::steady_clock::now(), persistence, false,
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800262 isConfigureSelfOnly});
Patrick Williams41713dd2022-09-28 06:48:07 -0500263 auto it = authTokens.emplace(sessionToken, session);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700264 // Only need to write to disk if session isn't about to be destroyed.
265 needWrite = persistence == PersistenceType::TIMEOUT;
266 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100267 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700268
Ed Tanous26ccae32023-02-16 10:28:44 -0800269 std::shared_ptr<UserSession> loginSessionByToken(std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700270 {
271 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700272 if (token.size() != sessionTokenSize)
273 {
274 return nullptr;
275 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700276 auto sessionIt = authTokens.find(std::string(token));
277 if (sessionIt == authTokens.end())
278 {
279 return nullptr;
280 }
281 std::shared_ptr<UserSession> userSession = sessionIt->second;
282 userSession->lastUpdated = std::chrono::steady_clock::now();
283 return userSession;
284 }
285
Ed Tanous26ccae32023-02-16 10:28:44 -0800286 std::shared_ptr<UserSession> getSessionByUid(std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700287 {
288 applySessionTimeouts();
289 // TODO(Ed) this is inefficient
290 auto sessionIt = authTokens.begin();
291 while (sessionIt != authTokens.end())
292 {
293 if (sessionIt->second->uniqueId == uid)
294 {
295 return sessionIt->second;
296 }
297 sessionIt++;
298 }
299 return nullptr;
300 }
301
Ed Tanousb5a76932020-09-29 16:16:58 -0700302 void removeSession(const std::shared_ptr<UserSession>& session)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700303 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530304#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
305 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
306#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700307 authTokens.erase(session->sessionToken);
308 needWrite = true;
309 }
310
311 std::vector<const std::string*> getUniqueIds(
312 bool getAll = true,
313 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
314 {
315 applySessionTimeouts();
316
317 std::vector<const std::string*> ret;
318 ret.reserve(authTokens.size());
319 for (auto& session : authTokens)
320 {
321 if (getAll || type == session.second->persistence)
322 {
323 ret.push_back(&session.second->uniqueId);
324 }
325 }
326 return ret;
327 }
328
Xie Ning9fa06f12022-06-29 18:27:47 +0800329 void removeSessionsByUsername(std::string_view username)
330 {
331 std::erase_if(authTokens, [username](const auto& value) {
332 if (value.second == nullptr)
333 {
334 return false;
335 }
336 return value.second->username == username;
337 });
338 }
339
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100340 void updateAuthMethodsConfig(const AuthConfigMethods& config)
341 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100342 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100343 authMethodsConfig = config;
344 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100345 if (isTLSchanged)
346 {
347 // recreate socket connections with new settings
348 std::raise(SIGHUP);
349 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100350 }
351
352 AuthConfigMethods& getAuthMethodsConfig()
353 {
354 return authMethodsConfig;
355 }
356
Ed Tanous9eb808c2022-01-25 10:19:23 -0800357 bool needsWrite() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700358 {
359 return needWrite;
360 }
Ed Tanous271584a2019-07-09 16:24:22 -0700361 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700362 {
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530363 return std::chrono::seconds(timeoutInSeconds).count();
364 }
365
366 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds)
367 {
368 timeoutInSeconds = newTimeoutInSeconds;
369 needWrite = true;
Ed Tanous23a21a12020-07-25 04:45:05 +0000370 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700371
Ed Tanous1abe55e2018-09-05 08:30:59 -0700372 static SessionStore& getInstance()
373 {
374 static SessionStore sessionStore;
375 return sessionStore;
376 }
377
Ed Tanous1abe55e2018-09-05 08:30:59 -0700378 void applySessionTimeouts()
379 {
380 auto timeNow = std::chrono::steady_clock::now();
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530381 if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1))
Ed Tanous1abe55e2018-09-05 08:30:59 -0700382 {
383 lastTimeoutUpdate = timeNow;
384 auto authTokensIt = authTokens.begin();
385 while (authTokensIt != authTokens.end())
386 {
387 if (timeNow - authTokensIt->second->lastUpdated >=
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530388 timeoutInSeconds)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700389 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530390#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
391 crow::ibm_mc_lock::Lock::getInstance().releaseLock(
392 authTokensIt->second->uniqueId);
393#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700394 authTokensIt = authTokens.erase(authTokensIt);
Ratan Gupta07386c62019-12-14 14:06:09 +0530395
Ed Tanous1abe55e2018-09-05 08:30:59 -0700396 needWrite = true;
397 }
398 else
399 {
400 authTokensIt++;
401 }
402 }
403 }
404 }
Gunnar Mills83cf8182020-11-11 15:37:34 -0600405
406 SessionStore(const SessionStore&) = delete;
407 SessionStore& operator=(const SessionStore&) = delete;
Ed Tanousecd6a3a2022-01-07 09:18:40 -0800408 SessionStore(SessionStore&&) = delete;
409 SessionStore& operator=(const SessionStore&&) = delete;
410 ~SessionStore() = default;
Gunnar Mills83cf8182020-11-11 15:37:34 -0600411
412 std::unordered_map<std::string, std::shared_ptr<UserSession>,
413 std::hash<std::string>,
414 crow::utility::ConstantTimeCompare>
415 authTokens;
416
417 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
418 bool needWrite{false};
419 std::chrono::seconds timeoutInSeconds;
420 AuthConfigMethods authMethodsConfig;
421
422 private:
Patrick Williams89492a12023-05-10 07:51:34 -0500423 SessionStore() : timeoutInSeconds(1800) {}
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100424};
425
Ed Tanous1abe55e2018-09-05 08:30:59 -0700426} // namespace persistent_data