blob: 3dabff4b5d366265bc61449a20b43fa4e5f5f9a5 [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 Tanousfc76b8a2020-09-28 17:21:52 -07006
Ed Tanous1abe55e2018-09-05 08:30:59 -07007#include <nlohmann/json.hpp>
Jiaqing Zhao41d61c82021-12-07 13:21:47 +08008#include <utils/ip_utils.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +05309
Gunnar Mills1214b7e2020-06-04 10:11:30 -050010#include <csignal>
11#include <random>
Ratan Gupta07386c62019-12-14 14:06:09 +053012#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
13#include <ibm/locks.hpp>
14#endif
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010015
Ed Tanous1abe55e2018-09-05 08:30:59 -070016namespace persistent_data
17{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010018
Ed Tanous51dae672018-09-05 16:07:32 -070019// entropy: 20 characters, 62 possibilities. log2(62^20) = 119 bits of
20// entropy. OWASP recommends at least 64
21// https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy
22constexpr std::size_t sessionTokenSize = 20;
23
Ed Tanous1abe55e2018-09-05 08:30:59 -070024enum class PersistenceType
25{
26 TIMEOUT, // User session times out after a predetermined amount of time
27 SINGLE_REQUEST // User times out once this request is completed.
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010028};
29
Ed Tanous1abe55e2018-09-05 08:30:59 -070030struct UserSession
31{
32 std::string uniqueId;
33 std::string sessionToken;
34 std::string username;
35 std::string csrfToken;
Sunitha Harish08bdcc72020-05-12 05:17:57 -050036 std::string clientId;
Sunitha Harish92f68222020-05-28 05:09:09 -050037 std::string clientIp;
Ed Tanous1abe55e2018-09-05 08:30:59 -070038 std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
Ed Tanousd3a9e082022-01-07 09:30:41 -080039 PersistenceType persistence{PersistenceType::TIMEOUT};
James Feistf8aa3d22020-04-08 18:32:33 -070040 bool cookieAuth = false;
Joseph Reynolds3bf4e632020-02-06 14:44:32 -060041 bool isConfigureSelfOnly = false;
42
43 // There are two sources of truth for isConfigureSelfOnly:
44 // 1. When pamAuthenticateUser() returns PAM_NEW_AUTHTOK_REQD.
45 // 2. D-Bus User.Manager.GetUserInfo property UserPasswordExpired.
46 // These should be in sync, but the underlying condition can change at any
47 // time. For example, a password can expire or be changed outside of
48 // bmcweb. The value stored here is updated at the start of each
49 // operation and used as the truth within bmcweb.
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010050
Ed Tanous1abe55e2018-09-05 08:30:59 -070051 /**
52 * @brief Fills object with data from UserSession's JSON representation
53 *
54 * This replaces nlohmann's from_json to ensure no-throw approach
55 *
56 * @param[in] j JSON object from which data should be loaded
57 *
58 * @return a shared pointer if data has been loaded properly, nullptr
59 * otherwise
60 */
61 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
62 {
63 std::shared_ptr<UserSession> userSession =
64 std::make_shared<UserSession>();
65 for (const auto& element : j.items())
66 {
67 const std::string* thisValue =
68 element.value().get_ptr<const std::string*>();
69 if (thisValue == nullptr)
70 {
71 BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
72 << element.key() << " was not of type string";
Ed Tanousdc511aa2020-10-21 12:33:42 -070073 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -070074 }
75 if (element.key() == "unique_id")
76 {
77 userSession->uniqueId = *thisValue;
78 }
79 else if (element.key() == "session_token")
80 {
81 userSession->sessionToken = *thisValue;
82 }
83 else if (element.key() == "csrf_token")
84 {
85 userSession->csrfToken = *thisValue;
86 }
87 else if (element.key() == "username")
88 {
89 userSession->username = *thisValue;
90 }
Ed Tanousdc511aa2020-10-21 12:33:42 -070091#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
Sunitha Harish08bdcc72020-05-12 05:17:57 -050092 else if (element.key() == "client_id")
93 {
94 userSession->clientId = *thisValue;
95 }
Ed Tanousdc511aa2020-10-21 12:33:42 -070096#endif
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(
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800205 const std::string_view username,
206 const boost::asio::ip::address& clientIp,
Sunitha Harishd3239222021-02-24 15:33:29 +0530207 const std::string_view clientId,
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600208 PersistenceType persistence = PersistenceType::TIMEOUT,
Sunitha Harishd3239222021-02-24 15:33:29 +0530209 bool isConfigureSelfOnly = false)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700210 {
211 // TODO(ed) find a secure way to not generate session identifiers if
212 // persistence is set to SINGLE_REQUEST
213 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500214 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
215 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
216 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700217 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
218 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100219
Ed Tanous1abe55e2018-09-05 08:30:59 -0700220 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700221 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700222 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
James Feista68a8042020-04-15 15:46:44 -0700223
Ed Tanousfc76b8a2020-09-28 17:21:52 -0700224 bmcweb::OpenSSLGenerator gen;
James Feista68a8042020-04-15 15:46:44 -0700225
Ed Tanous0dfeda62019-10-24 11:21:38 -0700226 for (char& sessionChar : sessionToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700227 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700228 sessionChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700229 if (gen.error())
230 {
231 return nullptr;
232 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100233 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700234 // Only need csrf tokens for cookie based auth, token doesn't matter
235 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700236 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700237 for (char& csrfChar : csrfToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700238 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700239 csrfChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700240 if (gen.error())
241 {
242 return nullptr;
243 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700244 }
245
246 std::string uniqueId;
247 uniqueId.resize(10, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700248 for (char& uidChar : uniqueId)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700249 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700250 uidChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700251 if (gen.error())
252 {
253 return nullptr;
254 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700255 }
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800256
257 auto session = std::make_shared<UserSession>(UserSession{
258 uniqueId, sessionToken, std::string(username), csrfToken,
259 std::string(clientId), redfish::ip_util::toString(clientIp),
260 std::chrono::steady_clock::now(), persistence, false,
261 isConfigureSelfOnly});
Ed Tanous1abe55e2018-09-05 08:30:59 -0700262 auto it = authTokens.emplace(std::make_pair(sessionToken, session));
263 // Only need to write to disk if session isn't about to be destroyed.
264 needWrite = persistence == PersistenceType::TIMEOUT;
265 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100266 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700267
268 std::shared_ptr<UserSession>
Ed Tanous39e77502019-03-04 17:35:53 -0800269 loginSessionByToken(const 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 Tanous39e77502019-03-04 17:35:53 -0800286 std::shared_ptr<UserSession> getSessionByUid(const 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
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100329 void updateAuthMethodsConfig(const AuthConfigMethods& config)
330 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100331 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100332 authMethodsConfig = config;
333 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100334 if (isTLSchanged)
335 {
336 // recreate socket connections with new settings
337 std::raise(SIGHUP);
338 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100339 }
340
341 AuthConfigMethods& getAuthMethodsConfig()
342 {
343 return authMethodsConfig;
344 }
345
Ed Tanous9eb808c2022-01-25 10:19:23 -0800346 bool needsWrite() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700347 {
348 return needWrite;
349 }
Ed Tanous271584a2019-07-09 16:24:22 -0700350 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700351 {
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530352 return std::chrono::seconds(timeoutInSeconds).count();
353 }
354
355 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds)
356 {
357 timeoutInSeconds = newTimeoutInSeconds;
358 needWrite = true;
Ed Tanous23a21a12020-07-25 04:45:05 +0000359 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700360
Ed Tanous1abe55e2018-09-05 08:30:59 -0700361 static SessionStore& getInstance()
362 {
363 static SessionStore sessionStore;
364 return sessionStore;
365 }
366
Ed Tanous1abe55e2018-09-05 08:30:59 -0700367 void applySessionTimeouts()
368 {
369 auto timeNow = std::chrono::steady_clock::now();
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530370 if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1))
Ed Tanous1abe55e2018-09-05 08:30:59 -0700371 {
372 lastTimeoutUpdate = timeNow;
373 auto authTokensIt = authTokens.begin();
374 while (authTokensIt != authTokens.end())
375 {
376 if (timeNow - authTokensIt->second->lastUpdated >=
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530377 timeoutInSeconds)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700378 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530379#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
380 crow::ibm_mc_lock::Lock::getInstance().releaseLock(
381 authTokensIt->second->uniqueId);
382#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700383 authTokensIt = authTokens.erase(authTokensIt);
Ratan Gupta07386c62019-12-14 14:06:09 +0530384
Ed Tanous1abe55e2018-09-05 08:30:59 -0700385 needWrite = true;
386 }
387 else
388 {
389 authTokensIt++;
390 }
391 }
392 }
393 }
Gunnar Mills83cf8182020-11-11 15:37:34 -0600394
395 SessionStore(const SessionStore&) = delete;
396 SessionStore& operator=(const SessionStore&) = delete;
Ed Tanousecd6a3a2022-01-07 09:18:40 -0800397 SessionStore(SessionStore&&) = delete;
398 SessionStore& operator=(const SessionStore&&) = delete;
399 ~SessionStore() = default;
Gunnar Mills83cf8182020-11-11 15:37:34 -0600400
401 std::unordered_map<std::string, std::shared_ptr<UserSession>,
402 std::hash<std::string>,
403 crow::utility::ConstantTimeCompare>
404 authTokens;
405
406 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
407 bool needWrite{false};
408 std::chrono::seconds timeoutInSeconds;
409 AuthConfigMethods authMethodsConfig;
410
411 private:
Jason M. Billsdc414b52021-08-05 15:20:25 -0700412 SessionStore() : timeoutInSeconds(1800)
Gunnar Mills83cf8182020-11-11 15:37:34 -0600413 {}
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100414};
415
Ed Tanous1abe55e2018-09-05 08:30:59 -0700416} // namespace persistent_data