blob: 90a1de93de5757061e3c46bdce61ca3ec6bc4d3a [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 Tanous8ed41c32021-05-09 23:51:31 -05005#include "user_role_map.hpp"
Ed Tanous04e438c2020-10-03 08:06:26 -07006#include "utility.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08007#include "utils/ip_utils.hpp"
Ed Tanousfc76b8a2020-09-28 17:21:52 -07008
Ed Tanous1abe55e2018-09-05 08:30:59 -07009#include <nlohmann/json.hpp>
Ed Tanous8ed41c32021-05-09 23:51:31 -050010#include <sdbusplus/message.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +053011
Xie Ning9fa06f12022-06-29 18:27:47 +080012#include <algorithm>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050013#include <csignal>
Ed Tanousbb759e32022-08-02 17:07:54 -070014#include <optional>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050015#include <random>
Ratan Gupta07386c62019-12-14 14:06:09 +053016#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080017#include "ibm/locks.hpp"
Ratan Gupta07386c62019-12-14 14:06:09 +053018#endif
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010019
Ed Tanous1abe55e2018-09-05 08:30:59 -070020namespace persistent_data
21{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010022
Ed Tanous51dae672018-09-05 16:07:32 -070023// entropy: 20 characters, 62 possibilities. log2(62^20) = 119 bits of
24// entropy. OWASP recommends at least 64
25// https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy
26constexpr std::size_t sessionTokenSize = 20;
27
Ed Tanous1abe55e2018-09-05 08:30:59 -070028enum class PersistenceType
29{
30 TIMEOUT, // User session times out after a predetermined amount of time
31 SINGLE_REQUEST // User times out once this request is completed.
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010032};
33
Ed Tanous1abe55e2018-09-05 08:30:59 -070034struct UserSession
35{
36 std::string uniqueId;
37 std::string sessionToken;
38 std::string username;
39 std::string csrfToken;
Ed Tanousbb759e32022-08-02 17:07:54 -070040 std::optional<std::string> clientId;
Sunitha Harish92f68222020-05-28 05:09:09 -050041 std::string clientIp;
Ed Tanous1abe55e2018-09-05 08:30:59 -070042 std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
Ed Tanousd3a9e082022-01-07 09:30:41 -080043 PersistenceType persistence{PersistenceType::TIMEOUT};
Ed Tanous7e9c08e2023-06-16 11:29:37 -070044 bool cookieAuth = false;
Joseph Reynolds3bf4e632020-02-06 14:44:32 -060045 bool isConfigureSelfOnly = false;
Ninad Palsule3e72c202023-03-27 17:19:55 -050046 std::string userRole{};
47 std::vector<std::string> userGroups{};
Joseph Reynolds3bf4e632020-02-06 14:44:32 -060048
49 // There are two sources of truth for isConfigureSelfOnly:
50 // 1. When pamAuthenticateUser() returns PAM_NEW_AUTHTOK_REQD.
51 // 2. D-Bus User.Manager.GetUserInfo property UserPasswordExpired.
52 // These should be in sync, but the underlying condition can change at any
53 // time. For example, a password can expire or be changed outside of
54 // bmcweb. The value stored here is updated at the start of each
55 // operation and used as the truth within bmcweb.
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010056
Ed Tanous1abe55e2018-09-05 08:30:59 -070057 /**
58 * @brief Fills object with data from UserSession's JSON representation
59 *
60 * This replaces nlohmann's from_json to ensure no-throw approach
61 *
62 * @param[in] j JSON object from which data should be loaded
63 *
64 * @return a shared pointer if data has been loaded properly, nullptr
65 * otherwise
66 */
67 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
68 {
69 std::shared_ptr<UserSession> userSession =
70 std::make_shared<UserSession>();
71 for (const auto& element : j.items())
72 {
73 const std::string* thisValue =
74 element.value().get_ptr<const std::string*>();
75 if (thisValue == nullptr)
76 {
Ed Tanous62598e32023-07-17 17:06:25 -070077 BMCWEB_LOG_ERROR(
78 "Error reading persistent store. Property {} was not of type string",
79 element.key());
Ed Tanousdc511aa2020-10-21 12:33:42 -070080 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -070081 }
82 if (element.key() == "unique_id")
83 {
84 userSession->uniqueId = *thisValue;
85 }
86 else if (element.key() == "session_token")
87 {
88 userSession->sessionToken = *thisValue;
89 }
90 else if (element.key() == "csrf_token")
91 {
92 userSession->csrfToken = *thisValue;
93 }
94 else if (element.key() == "username")
95 {
96 userSession->username = *thisValue;
97 }
Sunitha Harish08bdcc72020-05-12 05:17:57 -050098 else if (element.key() == "client_id")
99 {
100 userSession->clientId = *thisValue;
101 }
Sunitha Harish92f68222020-05-28 05:09:09 -0500102 else if (element.key() == "client_ip")
103 {
104 userSession->clientIp = *thisValue;
105 }
106
Ed Tanous1abe55e2018-09-05 08:30:59 -0700107 else
108 {
Ed Tanous62598e32023-07-17 17:06:25 -0700109 BMCWEB_LOG_ERROR(
110 "Got unexpected property reading persistent file: {}",
111 element.key());
Ed Tanousdc511aa2020-10-21 12:33:42 -0700112 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700113 }
114 }
Ed Tanousdc511aa2020-10-21 12:33:42 -0700115 // If any of these fields are missing, we can't restore the session, as
116 // we don't have enough information. These 4 fields have been present
117 // in every version of this file in bmcwebs history, so any file, even
118 // on upgrade, should have these present
119 if (userSession->uniqueId.empty() || userSession->username.empty() ||
120 userSession->sessionToken.empty() || userSession->csrfToken.empty())
121 {
Ed Tanous62598e32023-07-17 17:06:25 -0700122 BMCWEB_LOG_DEBUG("Session missing required security "
123 "information, refusing to restore");
Ed Tanousdc511aa2020-10-21 12:33:42 -0700124 return nullptr;
125 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700126
127 // For now, sessions that were persisted through a reboot get their idle
128 // timer reset. This could probably be overcome with a better
129 // understanding of wall clock time and steady timer time, possibly
130 // persisting values with wall clock time instead of steady timer, but
131 // the tradeoffs of all the corner cases involved are non-trivial, so
132 // this is done temporarily
133 userSession->lastUpdated = std::chrono::steady_clock::now();
134 userSession->persistence = PersistenceType::TIMEOUT;
135
136 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100137 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100138};
139
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100140struct AuthConfigMethods
141{
Alan Kuof16f6262020-12-08 19:29:59 +0800142#ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100143 bool basic = true;
Alan Kuof16f6262020-12-08 19:29:59 +0800144#else
145 bool basic = false;
146#endif
147
148#ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
149 bool sessionToken = true;
150#else
151 bool sessionToken = false;
152#endif
153
154#ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
155 bool xtoken = true;
156#else
157 bool xtoken = false;
158#endif
159
160#ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
161 bool cookie = true;
162#else
163 bool cookie = false;
164#endif
165
166#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
167 bool tls = true;
168#else
Zbigniew Kurzynskicac94c52019-11-07 12:55:04 +0100169 bool tls = false;
Alan Kuof16f6262020-12-08 19:29:59 +0800170#endif
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100171
172 void fromJson(const nlohmann::json& j)
173 {
174 for (const auto& element : j.items())
175 {
176 const bool* value = element.value().get_ptr<const bool*>();
177 if (value == nullptr)
178 {
179 continue;
180 }
181
182 if (element.key() == "XToken")
183 {
184 xtoken = *value;
185 }
186 else if (element.key() == "Cookie")
187 {
188 cookie = *value;
189 }
190 else if (element.key() == "SessionToken")
191 {
192 sessionToken = *value;
193 }
194 else if (element.key() == "BasicAuth")
195 {
196 basic = *value;
197 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200198 else if (element.key() == "TLS")
199 {
200 tls = *value;
201 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100202 }
203 }
204};
205
Ed Tanous1abe55e2018-09-05 08:30:59 -0700206class SessionStore
207{
208 public:
209 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous26ccae32023-02-16 10:28:44 -0800210 std::string_view username, const boost::asio::ip::address& clientIp,
Ed Tanousbb759e32022-08-02 17:07:54 -0700211 const std::optional<std::string>& clientId,
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600212 PersistenceType persistence = PersistenceType::TIMEOUT,
Sunitha Harishd3239222021-02-24 15:33:29 +0530213 bool isConfigureSelfOnly = false)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700214 {
215 // TODO(ed) find a secure way to not generate session identifiers if
216 // persistence is set to SINGLE_REQUEST
217 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500218 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
219 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
220 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700221 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
222 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100223
Ed Tanous1abe55e2018-09-05 08:30:59 -0700224 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700225 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700226 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
James Feista68a8042020-04-15 15:46:44 -0700227
Ed Tanousfc76b8a2020-09-28 17:21:52 -0700228 bmcweb::OpenSSLGenerator gen;
James Feista68a8042020-04-15 15:46:44 -0700229
Ed Tanous0dfeda62019-10-24 11:21:38 -0700230 for (char& sessionChar : sessionToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700231 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700232 sessionChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700233 if (gen.error())
234 {
235 return nullptr;
236 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100237 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700238 // Only need csrf tokens for cookie based auth, token doesn't matter
239 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700240 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700241 for (char& csrfChar : csrfToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700242 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700243 csrfChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700244 if (gen.error())
245 {
246 return nullptr;
247 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700248 }
249
250 std::string uniqueId;
251 uniqueId.resize(10, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700252 for (char& uidChar : uniqueId)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700253 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700254 uidChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700255 if (gen.error())
256 {
257 return nullptr;
258 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700259 }
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800260
Ed Tanous8ed41c32021-05-09 23:51:31 -0500261 std::string userRole = crow::UserRoleMap::getInstance()
262 .getUserRole(username)
263 .userRole.value_or("");
264
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800265 auto session = std::make_shared<UserSession>(UserSession{
Ed Tanousbb759e32022-08-02 17:07:54 -0700266 uniqueId, sessionToken, std::string(username), csrfToken, clientId,
267 redfish::ip_util::toString(clientIp),
Ed Tanous7e9c08e2023-06-16 11:29:37 -0700268 std::chrono::steady_clock::now(), persistence, false,
Ed Tanous8ed41c32021-05-09 23:51:31 -0500269 isConfigureSelfOnly, userRole});
Patrick Williams41713dd2022-09-28 06:48:07 -0500270 auto it = authTokens.emplace(sessionToken, session);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700271 // Only need to write to disk if session isn't about to be destroyed.
272 needWrite = persistence == PersistenceType::TIMEOUT;
273 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100274 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700275
Ed Tanous26ccae32023-02-16 10:28:44 -0800276 std::shared_ptr<UserSession> loginSessionByToken(std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700277 {
278 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700279 if (token.size() != sessionTokenSize)
280 {
281 return nullptr;
282 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700283 auto sessionIt = authTokens.find(std::string(token));
284 if (sessionIt == authTokens.end())
285 {
286 return nullptr;
287 }
288 std::shared_ptr<UserSession> userSession = sessionIt->second;
289 userSession->lastUpdated = std::chrono::steady_clock::now();
290 return userSession;
291 }
292
Ed Tanous26ccae32023-02-16 10:28:44 -0800293 std::shared_ptr<UserSession> getSessionByUid(std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700294 {
295 applySessionTimeouts();
296 // TODO(Ed) this is inefficient
297 auto sessionIt = authTokens.begin();
298 while (sessionIt != authTokens.end())
299 {
300 if (sessionIt->second->uniqueId == uid)
301 {
302 return sessionIt->second;
303 }
304 sessionIt++;
305 }
306 return nullptr;
307 }
308
Ed Tanousb5a76932020-09-29 16:16:58 -0700309 void removeSession(const std::shared_ptr<UserSession>& session)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700310 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530311#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
312 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
313#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700314 authTokens.erase(session->sessionToken);
315 needWrite = true;
316 }
317
318 std::vector<const std::string*> getUniqueIds(
319 bool getAll = true,
320 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
321 {
322 applySessionTimeouts();
323
324 std::vector<const std::string*> ret;
325 ret.reserve(authTokens.size());
326 for (auto& session : authTokens)
327 {
328 if (getAll || type == session.second->persistence)
329 {
330 ret.push_back(&session.second->uniqueId);
331 }
332 }
333 return ret;
334 }
335
Xie Ning9fa06f12022-06-29 18:27:47 +0800336 void removeSessionsByUsername(std::string_view username)
337 {
338 std::erase_if(authTokens, [username](const auto& value) {
339 if (value.second == nullptr)
340 {
341 return false;
342 }
343 return value.second->username == username;
344 });
345 }
346
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100347 void updateAuthMethodsConfig(const AuthConfigMethods& config)
348 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100349 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100350 authMethodsConfig = config;
351 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100352 if (isTLSchanged)
353 {
354 // recreate socket connections with new settings
355 std::raise(SIGHUP);
356 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100357 }
358
359 AuthConfigMethods& getAuthMethodsConfig()
360 {
361 return authMethodsConfig;
362 }
363
Ed Tanous9eb808c2022-01-25 10:19:23 -0800364 bool needsWrite() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700365 {
366 return needWrite;
367 }
Ed Tanous271584a2019-07-09 16:24:22 -0700368 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700369 {
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530370 return std::chrono::seconds(timeoutInSeconds).count();
371 }
372
373 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds)
374 {
375 timeoutInSeconds = newTimeoutInSeconds;
376 needWrite = true;
Ed Tanous23a21a12020-07-25 04:45:05 +0000377 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700378
Ed Tanous1abe55e2018-09-05 08:30:59 -0700379 static SessionStore& getInstance()
380 {
381 static SessionStore sessionStore;
382 return sessionStore;
383 }
384
Ed Tanous1abe55e2018-09-05 08:30:59 -0700385 void applySessionTimeouts()
386 {
387 auto timeNow = std::chrono::steady_clock::now();
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530388 if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1))
Ed Tanous1abe55e2018-09-05 08:30:59 -0700389 {
390 lastTimeoutUpdate = timeNow;
391 auto authTokensIt = authTokens.begin();
392 while (authTokensIt != authTokens.end())
393 {
394 if (timeNow - authTokensIt->second->lastUpdated >=
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530395 timeoutInSeconds)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700396 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530397#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
398 crow::ibm_mc_lock::Lock::getInstance().releaseLock(
399 authTokensIt->second->uniqueId);
400#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700401 authTokensIt = authTokens.erase(authTokensIt);
Ratan Gupta07386c62019-12-14 14:06:09 +0530402
Ed Tanous1abe55e2018-09-05 08:30:59 -0700403 needWrite = true;
404 }
405 else
406 {
407 authTokensIt++;
408 }
409 }
410 }
411 }
Gunnar Mills83cf8182020-11-11 15:37:34 -0600412
413 SessionStore(const SessionStore&) = delete;
414 SessionStore& operator=(const SessionStore&) = delete;
Ed Tanousecd6a3a2022-01-07 09:18:40 -0800415 SessionStore(SessionStore&&) = delete;
416 SessionStore& operator=(const SessionStore&&) = delete;
417 ~SessionStore() = default;
Gunnar Mills83cf8182020-11-11 15:37:34 -0600418
419 std::unordered_map<std::string, std::shared_ptr<UserSession>,
420 std::hash<std::string>,
421 crow::utility::ConstantTimeCompare>
422 authTokens;
423
424 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
425 bool needWrite{false};
426 std::chrono::seconds timeoutInSeconds;
427 AuthConfigMethods authMethodsConfig;
428
429 private:
Patrick Williams89492a12023-05-10 07:51:34 -0500430 SessionStore() : timeoutInSeconds(1800) {}
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100431};
432
Ed Tanous1abe55e2018-09-05 08:30:59 -0700433} // namespace persistent_data