blob: cb7f78e78ab8317f97baaafb22e7c7656bea24a7 [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 Tanous2c6ffdb2023-06-28 11:28:38 -07004#include "ossl_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 {
Ed Tanous62598e32023-07-17 17:06:25 -070075 BMCWEB_LOG_ERROR(
76 "Error reading persistent store. Property {} was not of type string",
77 element.key());
Ed Tanousdc511aa2020-10-21 12:33:42 -070078 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -070079 }
80 if (element.key() == "unique_id")
81 {
82 userSession->uniqueId = *thisValue;
83 }
84 else if (element.key() == "session_token")
85 {
86 userSession->sessionToken = *thisValue;
87 }
88 else if (element.key() == "csrf_token")
89 {
90 userSession->csrfToken = *thisValue;
91 }
92 else if (element.key() == "username")
93 {
94 userSession->username = *thisValue;
95 }
Sunitha Harish08bdcc72020-05-12 05:17:57 -050096 else if (element.key() == "client_id")
97 {
98 userSession->clientId = *thisValue;
99 }
Sunitha Harish92f68222020-05-28 05:09:09 -0500100 else if (element.key() == "client_ip")
101 {
102 userSession->clientIp = *thisValue;
103 }
104
Ed Tanous1abe55e2018-09-05 08:30:59 -0700105 else
106 {
Ed Tanous62598e32023-07-17 17:06:25 -0700107 BMCWEB_LOG_ERROR(
108 "Got unexpected property reading persistent file: {}",
109 element.key());
Ed Tanousdc511aa2020-10-21 12:33:42 -0700110 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700111 }
112 }
Ed Tanousdc511aa2020-10-21 12:33:42 -0700113 // If any of these fields are missing, we can't restore the session, as
114 // we don't have enough information. These 4 fields have been present
115 // in every version of this file in bmcwebs history, so any file, even
116 // on upgrade, should have these present
117 if (userSession->uniqueId.empty() || userSession->username.empty() ||
118 userSession->sessionToken.empty() || userSession->csrfToken.empty())
119 {
Ed Tanous62598e32023-07-17 17:06:25 -0700120 BMCWEB_LOG_DEBUG("Session missing required security "
121 "information, refusing to restore");
Ed Tanousdc511aa2020-10-21 12:33:42 -0700122 return nullptr;
123 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700124
125 // For now, sessions that were persisted through a reboot get their idle
126 // timer reset. This could probably be overcome with a better
127 // understanding of wall clock time and steady timer time, possibly
128 // persisting values with wall clock time instead of steady timer, but
129 // the tradeoffs of all the corner cases involved are non-trivial, so
130 // this is done temporarily
131 userSession->lastUpdated = std::chrono::steady_clock::now();
132 userSession->persistence = PersistenceType::TIMEOUT;
133
134 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100135 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100136};
137
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100138struct AuthConfigMethods
139{
Alan Kuof16f6262020-12-08 19:29:59 +0800140#ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100141 bool basic = true;
Alan Kuof16f6262020-12-08 19:29:59 +0800142#else
143 bool basic = false;
144#endif
145
146#ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
147 bool sessionToken = true;
148#else
149 bool sessionToken = false;
150#endif
151
152#ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
153 bool xtoken = true;
154#else
155 bool xtoken = false;
156#endif
157
158#ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
159 bool cookie = true;
160#else
161 bool cookie = false;
162#endif
163
164#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
165 bool tls = true;
166#else
Zbigniew Kurzynskicac94c52019-11-07 12:55:04 +0100167 bool tls = false;
Alan Kuof16f6262020-12-08 19:29:59 +0800168#endif
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100169
170 void fromJson(const nlohmann::json& j)
171 {
172 for (const auto& element : j.items())
173 {
174 const bool* value = element.value().get_ptr<const bool*>();
175 if (value == nullptr)
176 {
177 continue;
178 }
179
180 if (element.key() == "XToken")
181 {
182 xtoken = *value;
183 }
184 else if (element.key() == "Cookie")
185 {
186 cookie = *value;
187 }
188 else if (element.key() == "SessionToken")
189 {
190 sessionToken = *value;
191 }
192 else if (element.key() == "BasicAuth")
193 {
194 basic = *value;
195 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200196 else if (element.key() == "TLS")
197 {
198 tls = *value;
199 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100200 }
201 }
202};
203
Ed Tanous1abe55e2018-09-05 08:30:59 -0700204class SessionStore
205{
206 public:
207 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous26ccae32023-02-16 10:28:44 -0800208 std::string_view username, const boost::asio::ip::address& clientIp,
Ed Tanousbb759e32022-08-02 17:07:54 -0700209 const std::optional<std::string>& clientId,
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600210 PersistenceType persistence = PersistenceType::TIMEOUT,
Sunitha Harishd3239222021-02-24 15:33:29 +0530211 bool isConfigureSelfOnly = false)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700212 {
213 // TODO(ed) find a secure way to not generate session identifiers if
214 // persistence is set to SINGLE_REQUEST
215 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500216 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
217 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
218 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700219 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
220 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100221
Ed Tanous1abe55e2018-09-05 08:30:59 -0700222 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700223 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700224 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
James Feista68a8042020-04-15 15:46:44 -0700225
Ed Tanousfc76b8a2020-09-28 17:21:52 -0700226 bmcweb::OpenSSLGenerator gen;
James Feista68a8042020-04-15 15:46:44 -0700227
Ed Tanous0dfeda62019-10-24 11:21:38 -0700228 for (char& sessionChar : sessionToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700229 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700230 sessionChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700231 if (gen.error())
232 {
233 return nullptr;
234 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100235 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700236 // Only need csrf tokens for cookie based auth, token doesn't matter
237 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700238 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700239 for (char& csrfChar : csrfToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700240 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700241 csrfChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700242 if (gen.error())
243 {
244 return nullptr;
245 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700246 }
247
248 std::string uniqueId;
249 uniqueId.resize(10, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700250 for (char& uidChar : uniqueId)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700251 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700252 uidChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700253 if (gen.error())
254 {
255 return nullptr;
256 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700257 }
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800258
259 auto session = std::make_shared<UserSession>(UserSession{
Ed Tanousbb759e32022-08-02 17:07:54 -0700260 uniqueId, sessionToken, std::string(username), csrfToken, clientId,
261 redfish::ip_util::toString(clientIp),
Ed Tanous7e9c08e2023-06-16 11:29:37 -0700262 std::chrono::steady_clock::now(), persistence, false,
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800263 isConfigureSelfOnly});
Patrick Williams41713dd2022-09-28 06:48:07 -0500264 auto it = authTokens.emplace(sessionToken, session);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700265 // Only need to write to disk if session isn't about to be destroyed.
266 needWrite = persistence == PersistenceType::TIMEOUT;
267 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100268 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700269
Ed Tanous26ccae32023-02-16 10:28:44 -0800270 std::shared_ptr<UserSession> loginSessionByToken(std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700271 {
272 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700273 if (token.size() != sessionTokenSize)
274 {
275 return nullptr;
276 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700277 auto sessionIt = authTokens.find(std::string(token));
278 if (sessionIt == authTokens.end())
279 {
280 return nullptr;
281 }
282 std::shared_ptr<UserSession> userSession = sessionIt->second;
283 userSession->lastUpdated = std::chrono::steady_clock::now();
284 return userSession;
285 }
286
Ed Tanous26ccae32023-02-16 10:28:44 -0800287 std::shared_ptr<UserSession> getSessionByUid(std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700288 {
289 applySessionTimeouts();
290 // TODO(Ed) this is inefficient
291 auto sessionIt = authTokens.begin();
292 while (sessionIt != authTokens.end())
293 {
294 if (sessionIt->second->uniqueId == uid)
295 {
296 return sessionIt->second;
297 }
298 sessionIt++;
299 }
300 return nullptr;
301 }
302
Ed Tanousb5a76932020-09-29 16:16:58 -0700303 void removeSession(const std::shared_ptr<UserSession>& session)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700304 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530305#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
306 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
307#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700308 authTokens.erase(session->sessionToken);
309 needWrite = true;
310 }
311
312 std::vector<const std::string*> getUniqueIds(
313 bool getAll = true,
314 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
315 {
316 applySessionTimeouts();
317
318 std::vector<const std::string*> ret;
319 ret.reserve(authTokens.size());
320 for (auto& session : authTokens)
321 {
322 if (getAll || type == session.second->persistence)
323 {
324 ret.push_back(&session.second->uniqueId);
325 }
326 }
327 return ret;
328 }
329
Xie Ning9fa06f12022-06-29 18:27:47 +0800330 void removeSessionsByUsername(std::string_view username)
331 {
332 std::erase_if(authTokens, [username](const auto& value) {
333 if (value.second == nullptr)
334 {
335 return false;
336 }
337 return value.second->username == username;
338 });
339 }
340
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100341 void updateAuthMethodsConfig(const AuthConfigMethods& config)
342 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100343 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100344 authMethodsConfig = config;
345 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100346 if (isTLSchanged)
347 {
348 // recreate socket connections with new settings
349 std::raise(SIGHUP);
350 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100351 }
352
353 AuthConfigMethods& getAuthMethodsConfig()
354 {
355 return authMethodsConfig;
356 }
357
Ed Tanous9eb808c2022-01-25 10:19:23 -0800358 bool needsWrite() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700359 {
360 return needWrite;
361 }
Ed Tanous271584a2019-07-09 16:24:22 -0700362 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700363 {
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530364 return std::chrono::seconds(timeoutInSeconds).count();
365 }
366
367 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds)
368 {
369 timeoutInSeconds = newTimeoutInSeconds;
370 needWrite = true;
Ed Tanous23a21a12020-07-25 04:45:05 +0000371 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700372
Ed Tanous1abe55e2018-09-05 08:30:59 -0700373 static SessionStore& getInstance()
374 {
375 static SessionStore sessionStore;
376 return sessionStore;
377 }
378
Ed Tanous1abe55e2018-09-05 08:30:59 -0700379 void applySessionTimeouts()
380 {
381 auto timeNow = std::chrono::steady_clock::now();
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530382 if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1))
Ed Tanous1abe55e2018-09-05 08:30:59 -0700383 {
384 lastTimeoutUpdate = timeNow;
385 auto authTokensIt = authTokens.begin();
386 while (authTokensIt != authTokens.end())
387 {
388 if (timeNow - authTokensIt->second->lastUpdated >=
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530389 timeoutInSeconds)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700390 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530391#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
392 crow::ibm_mc_lock::Lock::getInstance().releaseLock(
393 authTokensIt->second->uniqueId);
394#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700395 authTokensIt = authTokens.erase(authTokensIt);
Ratan Gupta07386c62019-12-14 14:06:09 +0530396
Ed Tanous1abe55e2018-09-05 08:30:59 -0700397 needWrite = true;
398 }
399 else
400 {
401 authTokensIt++;
402 }
403 }
404 }
405 }
Gunnar Mills83cf8182020-11-11 15:37:34 -0600406
407 SessionStore(const SessionStore&) = delete;
408 SessionStore& operator=(const SessionStore&) = delete;
Ed Tanousecd6a3a2022-01-07 09:18:40 -0800409 SessionStore(SessionStore&&) = delete;
410 SessionStore& operator=(const SessionStore&&) = delete;
411 ~SessionStore() = default;
Gunnar Mills83cf8182020-11-11 15:37:34 -0600412
413 std::unordered_map<std::string, std::shared_ptr<UserSession>,
414 std::hash<std::string>,
415 crow::utility::ConstantTimeCompare>
416 authTokens;
417
418 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
419 bool needWrite{false};
420 std::chrono::seconds timeoutInSeconds;
421 AuthConfigMethods authMethodsConfig;
422
423 private:
Patrick Williams89492a12023-05-10 07:51:34 -0500424 SessionStore() : timeoutInSeconds(1800) {}
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100425};
426
Ed Tanous1abe55e2018-09-05 08:30:59 -0700427} // namespace persistent_data