blob: 62700bec56aa0013f0e73a5a1d38e379acd77daa [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};
James Feistf8aa3d22020-04-08 18:32:33 -070042 bool cookieAuth = false;
Joseph Reynolds3bf4e632020-02-06 14:44:32 -060043 bool isConfigureSelfOnly = false;
44
45 // There are two sources of truth for isConfigureSelfOnly:
46 // 1. When pamAuthenticateUser() returns PAM_NEW_AUTHTOK_REQD.
47 // 2. D-Bus User.Manager.GetUserInfo property UserPasswordExpired.
48 // These should be in sync, but the underlying condition can change at any
49 // time. For example, a password can expire or be changed outside of
50 // bmcweb. The value stored here is updated at the start of each
51 // operation and used as the truth within bmcweb.
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010052
Ed Tanous1abe55e2018-09-05 08:30:59 -070053 /**
54 * @brief Fills object with data from UserSession's JSON representation
55 *
56 * This replaces nlohmann's from_json to ensure no-throw approach
57 *
58 * @param[in] j JSON object from which data should be loaded
59 *
60 * @return a shared pointer if data has been loaded properly, nullptr
61 * otherwise
62 */
63 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
64 {
65 std::shared_ptr<UserSession> userSession =
66 std::make_shared<UserSession>();
67 for (const auto& element : j.items())
68 {
69 const std::string* thisValue =
70 element.value().get_ptr<const std::string*>();
71 if (thisValue == nullptr)
72 {
73 BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
74 << element.key() << " was not of type string";
Ed Tanousdc511aa2020-10-21 12:33:42 -070075 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -070076 }
77 if (element.key() == "unique_id")
78 {
79 userSession->uniqueId = *thisValue;
80 }
81 else if (element.key() == "session_token")
82 {
83 userSession->sessionToken = *thisValue;
84 }
85 else if (element.key() == "csrf_token")
86 {
87 userSession->csrfToken = *thisValue;
88 }
89 else if (element.key() == "username")
90 {
91 userSession->username = *thisValue;
92 }
Sunitha Harish08bdcc72020-05-12 05:17:57 -050093 else if (element.key() == "client_id")
94 {
95 userSession->clientId = *thisValue;
96 }
Sunitha Harish92f68222020-05-28 05:09:09 -050097 else if (element.key() == "client_ip")
98 {
99 userSession->clientIp = *thisValue;
100 }
101
Ed Tanous1abe55e2018-09-05 08:30:59 -0700102 else
103 {
104 BMCWEB_LOG_ERROR
105 << "Got unexpected property reading persistent file: "
106 << element.key();
Ed Tanousdc511aa2020-10-21 12:33:42 -0700107 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700108 }
109 }
Ed Tanousdc511aa2020-10-21 12:33:42 -0700110 // If any of these fields are missing, we can't restore the session, as
111 // we don't have enough information. These 4 fields have been present
112 // in every version of this file in bmcwebs history, so any file, even
113 // on upgrade, should have these present
114 if (userSession->uniqueId.empty() || userSession->username.empty() ||
115 userSession->sessionToken.empty() || userSession->csrfToken.empty())
116 {
117 BMCWEB_LOG_DEBUG << "Session missing required security "
118 "information, refusing to restore";
119 return nullptr;
120 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700121
122 // For now, sessions that were persisted through a reboot get their idle
123 // timer reset. This could probably be overcome with a better
124 // understanding of wall clock time and steady timer time, possibly
125 // persisting values with wall clock time instead of steady timer, but
126 // the tradeoffs of all the corner cases involved are non-trivial, so
127 // this is done temporarily
128 userSession->lastUpdated = std::chrono::steady_clock::now();
129 userSession->persistence = PersistenceType::TIMEOUT;
130
131 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100132 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100133};
134
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100135struct AuthConfigMethods
136{
Alan Kuof16f6262020-12-08 19:29:59 +0800137#ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100138 bool basic = true;
Alan Kuof16f6262020-12-08 19:29:59 +0800139#else
140 bool basic = false;
141#endif
142
143#ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
144 bool sessionToken = true;
145#else
146 bool sessionToken = false;
147#endif
148
149#ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
150 bool xtoken = true;
151#else
152 bool xtoken = false;
153#endif
154
155#ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
156 bool cookie = true;
157#else
158 bool cookie = false;
159#endif
160
161#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
162 bool tls = true;
163#else
Zbigniew Kurzynskicac94c52019-11-07 12:55:04 +0100164 bool tls = false;
Alan Kuof16f6262020-12-08 19:29:59 +0800165#endif
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100166
167 void fromJson(const nlohmann::json& j)
168 {
169 for (const auto& element : j.items())
170 {
171 const bool* value = element.value().get_ptr<const bool*>();
172 if (value == nullptr)
173 {
174 continue;
175 }
176
177 if (element.key() == "XToken")
178 {
179 xtoken = *value;
180 }
181 else if (element.key() == "Cookie")
182 {
183 cookie = *value;
184 }
185 else if (element.key() == "SessionToken")
186 {
187 sessionToken = *value;
188 }
189 else if (element.key() == "BasicAuth")
190 {
191 basic = *value;
192 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200193 else if (element.key() == "TLS")
194 {
195 tls = *value;
196 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100197 }
198 }
199};
200
Ed Tanous1abe55e2018-09-05 08:30:59 -0700201class SessionStore
202{
203 public:
204 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous26ccae32023-02-16 10:28:44 -0800205 std::string_view username, const boost::asio::ip::address& clientIp,
Ed Tanousbb759e32022-08-02 17:07:54 -0700206 const std::optional<std::string>& clientId,
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600207 PersistenceType persistence = PersistenceType::TIMEOUT,
Sunitha Harishd3239222021-02-24 15:33:29 +0530208 bool isConfigureSelfOnly = false)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700209 {
210 // TODO(ed) find a secure way to not generate session identifiers if
211 // persistence is set to SINGLE_REQUEST
212 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500213 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
214 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
215 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700216 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
217 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100218
Ed Tanous1abe55e2018-09-05 08:30:59 -0700219 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700220 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700221 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
James Feista68a8042020-04-15 15:46:44 -0700222
Ed Tanousfc76b8a2020-09-28 17:21:52 -0700223 bmcweb::OpenSSLGenerator gen;
James Feista68a8042020-04-15 15:46:44 -0700224
Ed Tanous0dfeda62019-10-24 11:21:38 -0700225 for (char& sessionChar : sessionToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700226 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700227 sessionChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700228 if (gen.error())
229 {
230 return nullptr;
231 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100232 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700233 // Only need csrf tokens for cookie based auth, token doesn't matter
234 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700235 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700236 for (char& csrfChar : csrfToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700237 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700238 csrfChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700239 if (gen.error())
240 {
241 return nullptr;
242 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700243 }
244
245 std::string uniqueId;
246 uniqueId.resize(10, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700247 for (char& uidChar : uniqueId)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700248 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700249 uidChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700250 if (gen.error())
251 {
252 return nullptr;
253 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700254 }
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800255
256 auto session = std::make_shared<UserSession>(UserSession{
Ed Tanousbb759e32022-08-02 17:07:54 -0700257 uniqueId, sessionToken, std::string(username), csrfToken, clientId,
258 redfish::ip_util::toString(clientIp),
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800259 std::chrono::steady_clock::now(), persistence, false,
260 isConfigureSelfOnly});
Patrick Williams41713dd2022-09-28 06:48:07 -0500261 auto it = authTokens.emplace(sessionToken, session);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700262 // Only need to write to disk if session isn't about to be destroyed.
263 needWrite = persistence == PersistenceType::TIMEOUT;
264 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100265 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700266
Ed Tanous26ccae32023-02-16 10:28:44 -0800267 std::shared_ptr<UserSession> loginSessionByToken(std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700268 {
269 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700270 if (token.size() != sessionTokenSize)
271 {
272 return nullptr;
273 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700274 auto sessionIt = authTokens.find(std::string(token));
275 if (sessionIt == authTokens.end())
276 {
277 return nullptr;
278 }
279 std::shared_ptr<UserSession> userSession = sessionIt->second;
280 userSession->lastUpdated = std::chrono::steady_clock::now();
281 return userSession;
282 }
283
Ed Tanous26ccae32023-02-16 10:28:44 -0800284 std::shared_ptr<UserSession> getSessionByUid(std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700285 {
286 applySessionTimeouts();
287 // TODO(Ed) this is inefficient
288 auto sessionIt = authTokens.begin();
289 while (sessionIt != authTokens.end())
290 {
291 if (sessionIt->second->uniqueId == uid)
292 {
293 return sessionIt->second;
294 }
295 sessionIt++;
296 }
297 return nullptr;
298 }
299
Ed Tanousb5a76932020-09-29 16:16:58 -0700300 void removeSession(const std::shared_ptr<UserSession>& session)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700301 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530302#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
303 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
304#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700305 authTokens.erase(session->sessionToken);
306 needWrite = true;
307 }
308
309 std::vector<const std::string*> getUniqueIds(
310 bool getAll = true,
311 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
312 {
313 applySessionTimeouts();
314
315 std::vector<const std::string*> ret;
316 ret.reserve(authTokens.size());
317 for (auto& session : authTokens)
318 {
319 if (getAll || type == session.second->persistence)
320 {
321 ret.push_back(&session.second->uniqueId);
322 }
323 }
324 return ret;
325 }
326
Xie Ning9fa06f12022-06-29 18:27:47 +0800327 void removeSessionsByUsername(std::string_view username)
328 {
329 std::erase_if(authTokens, [username](const auto& value) {
330 if (value.second == nullptr)
331 {
332 return false;
333 }
334 return value.second->username == username;
335 });
336 }
337
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100338 void updateAuthMethodsConfig(const AuthConfigMethods& config)
339 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100340 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100341 authMethodsConfig = config;
342 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100343 if (isTLSchanged)
344 {
345 // recreate socket connections with new settings
346 std::raise(SIGHUP);
347 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100348 }
349
350 AuthConfigMethods& getAuthMethodsConfig()
351 {
352 return authMethodsConfig;
353 }
354
Ed Tanous9eb808c2022-01-25 10:19:23 -0800355 bool needsWrite() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700356 {
357 return needWrite;
358 }
Ed Tanous271584a2019-07-09 16:24:22 -0700359 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700360 {
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530361 return std::chrono::seconds(timeoutInSeconds).count();
362 }
363
364 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds)
365 {
366 timeoutInSeconds = newTimeoutInSeconds;
367 needWrite = true;
Ed Tanous23a21a12020-07-25 04:45:05 +0000368 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700369
Ed Tanous1abe55e2018-09-05 08:30:59 -0700370 static SessionStore& getInstance()
371 {
372 static SessionStore sessionStore;
373 return sessionStore;
374 }
375
Ed Tanous1abe55e2018-09-05 08:30:59 -0700376 void applySessionTimeouts()
377 {
378 auto timeNow = std::chrono::steady_clock::now();
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530379 if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1))
Ed Tanous1abe55e2018-09-05 08:30:59 -0700380 {
381 lastTimeoutUpdate = timeNow;
382 auto authTokensIt = authTokens.begin();
383 while (authTokensIt != authTokens.end())
384 {
385 if (timeNow - authTokensIt->second->lastUpdated >=
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530386 timeoutInSeconds)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700387 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530388#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
389 crow::ibm_mc_lock::Lock::getInstance().releaseLock(
390 authTokensIt->second->uniqueId);
391#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700392 authTokensIt = authTokens.erase(authTokensIt);
Ratan Gupta07386c62019-12-14 14:06:09 +0530393
Ed Tanous1abe55e2018-09-05 08:30:59 -0700394 needWrite = true;
395 }
396 else
397 {
398 authTokensIt++;
399 }
400 }
401 }
402 }
Gunnar Mills83cf8182020-11-11 15:37:34 -0600403
404 SessionStore(const SessionStore&) = delete;
405 SessionStore& operator=(const SessionStore&) = delete;
Ed Tanousecd6a3a2022-01-07 09:18:40 -0800406 SessionStore(SessionStore&&) = delete;
407 SessionStore& operator=(const SessionStore&&) = delete;
408 ~SessionStore() = default;
Gunnar Mills83cf8182020-11-11 15:37:34 -0600409
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:
Jason M. Billsdc414b52021-08-05 15:20:25 -0700421 SessionStore() : timeoutInSeconds(1800)
Gunnar Mills83cf8182020-11-11 15:37:34 -0600422 {}
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100423};
424
Ed Tanous1abe55e2018-09-05 08:30:59 -0700425} // namespace persistent_data