blob: 85c0cb9208a8e59ee91de6920fcd1653233f99f5 [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
James Feista68a8042020-04-15 15:46:44 -07007#include <openssl/rand.h>
8
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +01009#include <boost/container/flat_map.hpp>
10#include <boost/uuid/uuid.hpp>
11#include <boost/uuid/uuid_generators.hpp>
12#include <boost/uuid/uuid_io.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +053013#include <dbus_singleton.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -070014#include <nlohmann/json.hpp>
15#include <pam_authenticate.hpp>
Nan Zhou97128e92021-08-17 12:01:55 -070016#include <random.hpp>
RAJESWARAN THILLAIGOVINDAN70525172019-07-09 13:15:05 -050017#include <sdbusplus/bus/match.hpp>
Jiaqing Zhao41d61c82021-12-07 13:21:47 +080018#include <utils/ip_utils.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +053019
Gunnar Mills1214b7e2020-06-04 10:11:30 -050020#include <csignal>
21#include <random>
Ratan Gupta07386c62019-12-14 14:06:09 +053022#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
23#include <ibm/locks.hpp>
24#endif
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010025
Ed Tanous1abe55e2018-09-05 08:30:59 -070026namespace persistent_data
27{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010028
Ed Tanous51dae672018-09-05 16:07:32 -070029// entropy: 20 characters, 62 possibilities. log2(62^20) = 119 bits of
30// entropy. OWASP recommends at least 64
31// https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy
32constexpr std::size_t sessionTokenSize = 20;
33
Ed Tanous1abe55e2018-09-05 08:30:59 -070034enum class PersistenceType
35{
36 TIMEOUT, // User session times out after a predetermined amount of time
37 SINGLE_REQUEST // User times out once this request is completed.
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010038};
39
Ed Tanous1abe55e2018-09-05 08:30:59 -070040struct UserSession
41{
42 std::string uniqueId;
43 std::string sessionToken;
44 std::string username;
45 std::string csrfToken;
Sunitha Harish08bdcc72020-05-12 05:17:57 -050046 std::string clientId;
Sunitha Harish92f68222020-05-28 05:09:09 -050047 std::string clientIp;
Ed Tanous1abe55e2018-09-05 08:30:59 -070048 std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
Ed Tanousd3a9e082022-01-07 09:30:41 -080049 PersistenceType persistence{PersistenceType::TIMEOUT};
James Feistf8aa3d22020-04-08 18:32:33 -070050 bool cookieAuth = false;
Joseph Reynolds3bf4e632020-02-06 14:44:32 -060051 bool isConfigureSelfOnly = false;
52
53 // There are two sources of truth for isConfigureSelfOnly:
54 // 1. When pamAuthenticateUser() returns PAM_NEW_AUTHTOK_REQD.
55 // 2. D-Bus User.Manager.GetUserInfo property UserPasswordExpired.
56 // These should be in sync, but the underlying condition can change at any
57 // time. For example, a password can expire or be changed outside of
58 // bmcweb. The value stored here is updated at the start of each
59 // operation and used as the truth within bmcweb.
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010060
Ed Tanous1abe55e2018-09-05 08:30:59 -070061 /**
62 * @brief Fills object with data from UserSession's JSON representation
63 *
64 * This replaces nlohmann's from_json to ensure no-throw approach
65 *
66 * @param[in] j JSON object from which data should be loaded
67 *
68 * @return a shared pointer if data has been loaded properly, nullptr
69 * otherwise
70 */
71 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
72 {
73 std::shared_ptr<UserSession> userSession =
74 std::make_shared<UserSession>();
75 for (const auto& element : j.items())
76 {
77 const std::string* thisValue =
78 element.value().get_ptr<const std::string*>();
79 if (thisValue == nullptr)
80 {
81 BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
82 << element.key() << " was not of type string";
Ed Tanousdc511aa2020-10-21 12:33:42 -070083 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -070084 }
85 if (element.key() == "unique_id")
86 {
87 userSession->uniqueId = *thisValue;
88 }
89 else if (element.key() == "session_token")
90 {
91 userSession->sessionToken = *thisValue;
92 }
93 else if (element.key() == "csrf_token")
94 {
95 userSession->csrfToken = *thisValue;
96 }
97 else if (element.key() == "username")
98 {
99 userSession->username = *thisValue;
100 }
Ed Tanousdc511aa2020-10-21 12:33:42 -0700101#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
Sunitha Harish08bdcc72020-05-12 05:17:57 -0500102 else if (element.key() == "client_id")
103 {
104 userSession->clientId = *thisValue;
105 }
Ed Tanousdc511aa2020-10-21 12:33:42 -0700106#endif
Sunitha Harish92f68222020-05-28 05:09:09 -0500107 else if (element.key() == "client_ip")
108 {
109 userSession->clientIp = *thisValue;
110 }
111
Ed Tanous1abe55e2018-09-05 08:30:59 -0700112 else
113 {
114 BMCWEB_LOG_ERROR
115 << "Got unexpected property reading persistent file: "
116 << element.key();
Ed Tanousdc511aa2020-10-21 12:33:42 -0700117 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700118 }
119 }
Ed Tanousdc511aa2020-10-21 12:33:42 -0700120 // If any of these fields are missing, we can't restore the session, as
121 // we don't have enough information. These 4 fields have been present
122 // in every version of this file in bmcwebs history, so any file, even
123 // on upgrade, should have these present
124 if (userSession->uniqueId.empty() || userSession->username.empty() ||
125 userSession->sessionToken.empty() || userSession->csrfToken.empty())
126 {
127 BMCWEB_LOG_DEBUG << "Session missing required security "
128 "information, refusing to restore";
129 return nullptr;
130 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700131
132 // For now, sessions that were persisted through a reboot get their idle
133 // timer reset. This could probably be overcome with a better
134 // understanding of wall clock time and steady timer time, possibly
135 // persisting values with wall clock time instead of steady timer, but
136 // the tradeoffs of all the corner cases involved are non-trivial, so
137 // this is done temporarily
138 userSession->lastUpdated = std::chrono::steady_clock::now();
139 userSession->persistence = PersistenceType::TIMEOUT;
140
141 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100142 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100143};
144
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100145struct AuthConfigMethods
146{
Alan Kuof16f6262020-12-08 19:29:59 +0800147#ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100148 bool basic = true;
Alan Kuof16f6262020-12-08 19:29:59 +0800149#else
150 bool basic = false;
151#endif
152
153#ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
154 bool sessionToken = true;
155#else
156 bool sessionToken = false;
157#endif
158
159#ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
160 bool xtoken = true;
161#else
162 bool xtoken = false;
163#endif
164
165#ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
166 bool cookie = true;
167#else
168 bool cookie = false;
169#endif
170
171#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
172 bool tls = true;
173#else
Zbigniew Kurzynskicac94c52019-11-07 12:55:04 +0100174 bool tls = false;
Alan Kuof16f6262020-12-08 19:29:59 +0800175#endif
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100176
177 void fromJson(const nlohmann::json& j)
178 {
179 for (const auto& element : j.items())
180 {
181 const bool* value = element.value().get_ptr<const bool*>();
182 if (value == nullptr)
183 {
184 continue;
185 }
186
187 if (element.key() == "XToken")
188 {
189 xtoken = *value;
190 }
191 else if (element.key() == "Cookie")
192 {
193 cookie = *value;
194 }
195 else if (element.key() == "SessionToken")
196 {
197 sessionToken = *value;
198 }
199 else if (element.key() == "BasicAuth")
200 {
201 basic = *value;
202 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200203 else if (element.key() == "TLS")
204 {
205 tls = *value;
206 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100207 }
208 }
209};
210
Ed Tanous1abe55e2018-09-05 08:30:59 -0700211class SessionStore
212{
213 public:
214 std::shared_ptr<UserSession> generateUserSession(
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800215 const std::string_view username,
216 const boost::asio::ip::address& clientIp,
Sunitha Harishd3239222021-02-24 15:33:29 +0530217 const std::string_view clientId,
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600218 PersistenceType persistence = PersistenceType::TIMEOUT,
Sunitha Harishd3239222021-02-24 15:33:29 +0530219 bool isConfigureSelfOnly = false)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700220 {
221 // TODO(ed) find a secure way to not generate session identifiers if
222 // persistence is set to SINGLE_REQUEST
223 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500224 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
225 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
226 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700227 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
228 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100229
Ed Tanous1abe55e2018-09-05 08:30:59 -0700230 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700231 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700232 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
James Feista68a8042020-04-15 15:46:44 -0700233
Ed Tanousfc76b8a2020-09-28 17:21:52 -0700234 bmcweb::OpenSSLGenerator gen;
James Feista68a8042020-04-15 15:46:44 -0700235
Ed Tanous0dfeda62019-10-24 11:21:38 -0700236 for (char& sessionChar : sessionToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700237 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700238 sessionChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700239 if (gen.error())
240 {
241 return nullptr;
242 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100243 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700244 // Only need csrf tokens for cookie based auth, token doesn't matter
245 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700246 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700247 for (char& csrfChar : csrfToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700248 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700249 csrfChar = 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 }
255
256 std::string uniqueId;
257 uniqueId.resize(10, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700258 for (char& uidChar : uniqueId)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700259 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700260 uidChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700261 if (gen.error())
262 {
263 return nullptr;
264 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700265 }
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800266
267 auto session = std::make_shared<UserSession>(UserSession{
268 uniqueId, sessionToken, std::string(username), csrfToken,
269 std::string(clientId), redfish::ip_util::toString(clientIp),
270 std::chrono::steady_clock::now(), persistence, false,
271 isConfigureSelfOnly});
Ed Tanous1abe55e2018-09-05 08:30:59 -0700272 auto it = authTokens.emplace(std::make_pair(sessionToken, session));
273 // Only need to write to disk if session isn't about to be destroyed.
274 needWrite = persistence == PersistenceType::TIMEOUT;
275 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100276 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700277
278 std::shared_ptr<UserSession>
Ed Tanous39e77502019-03-04 17:35:53 -0800279 loginSessionByToken(const std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700280 {
281 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700282 if (token.size() != sessionTokenSize)
283 {
284 return nullptr;
285 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700286 auto sessionIt = authTokens.find(std::string(token));
287 if (sessionIt == authTokens.end())
288 {
289 return nullptr;
290 }
291 std::shared_ptr<UserSession> userSession = sessionIt->second;
292 userSession->lastUpdated = std::chrono::steady_clock::now();
293 return userSession;
294 }
295
Ed Tanous39e77502019-03-04 17:35:53 -0800296 std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700297 {
298 applySessionTimeouts();
299 // TODO(Ed) this is inefficient
300 auto sessionIt = authTokens.begin();
301 while (sessionIt != authTokens.end())
302 {
303 if (sessionIt->second->uniqueId == uid)
304 {
305 return sessionIt->second;
306 }
307 sessionIt++;
308 }
309 return nullptr;
310 }
311
Ed Tanousb5a76932020-09-29 16:16:58 -0700312 void removeSession(const std::shared_ptr<UserSession>& session)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700313 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530314#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
315 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
316#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700317 authTokens.erase(session->sessionToken);
318 needWrite = true;
319 }
320
321 std::vector<const std::string*> getUniqueIds(
322 bool getAll = true,
323 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
324 {
325 applySessionTimeouts();
326
327 std::vector<const std::string*> ret;
328 ret.reserve(authTokens.size());
329 for (auto& session : authTokens)
330 {
331 if (getAll || type == session.second->persistence)
332 {
333 ret.push_back(&session.second->uniqueId);
334 }
335 }
336 return ret;
337 }
338
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100339 void updateAuthMethodsConfig(const AuthConfigMethods& config)
340 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100341 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100342 authMethodsConfig = config;
343 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100344 if (isTLSchanged)
345 {
346 // recreate socket connections with new settings
347 std::raise(SIGHUP);
348 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100349 }
350
351 AuthConfigMethods& getAuthMethodsConfig()
352 {
353 return authMethodsConfig;
354 }
355
Ed Tanous9eb808c2022-01-25 10:19:23 -0800356 bool needsWrite() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700357 {
358 return needWrite;
359 }
Ed Tanous271584a2019-07-09 16:24:22 -0700360 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700361 {
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530362 return std::chrono::seconds(timeoutInSeconds).count();
363 }
364
365 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds)
366 {
367 timeoutInSeconds = newTimeoutInSeconds;
368 needWrite = true;
Ed Tanous23a21a12020-07-25 04:45:05 +0000369 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700370
Ed Tanous1abe55e2018-09-05 08:30:59 -0700371 static SessionStore& getInstance()
372 {
373 static SessionStore sessionStore;
374 return sessionStore;
375 }
376
Ed Tanous1abe55e2018-09-05 08:30:59 -0700377 void applySessionTimeouts()
378 {
379 auto timeNow = std::chrono::steady_clock::now();
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530380 if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1))
Ed Tanous1abe55e2018-09-05 08:30:59 -0700381 {
382 lastTimeoutUpdate = timeNow;
383 auto authTokensIt = authTokens.begin();
384 while (authTokensIt != authTokens.end())
385 {
386 if (timeNow - authTokensIt->second->lastUpdated >=
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530387 timeoutInSeconds)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700388 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530389#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
390 crow::ibm_mc_lock::Lock::getInstance().releaseLock(
391 authTokensIt->second->uniqueId);
392#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700393 authTokensIt = authTokens.erase(authTokensIt);
Ratan Gupta07386c62019-12-14 14:06:09 +0530394
Ed Tanous1abe55e2018-09-05 08:30:59 -0700395 needWrite = true;
396 }
397 else
398 {
399 authTokensIt++;
400 }
401 }
402 }
403 }
Gunnar Mills83cf8182020-11-11 15:37:34 -0600404
405 SessionStore(const SessionStore&) = delete;
406 SessionStore& operator=(const SessionStore&) = delete;
Ed Tanousecd6a3a2022-01-07 09:18:40 -0800407 SessionStore(SessionStore&&) = delete;
408 SessionStore& operator=(const SessionStore&&) = delete;
409 ~SessionStore() = default;
Gunnar Mills83cf8182020-11-11 15:37:34 -0600410
411 std::unordered_map<std::string, std::shared_ptr<UserSession>,
412 std::hash<std::string>,
413 crow::utility::ConstantTimeCompare>
414 authTokens;
415
416 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
417 bool needWrite{false};
418 std::chrono::seconds timeoutInSeconds;
419 AuthConfigMethods authMethodsConfig;
420
421 private:
Jason M. Billsdc414b52021-08-05 15:20:25 -0700422 SessionStore() : timeoutInSeconds(1800)
Gunnar Mills83cf8182020-11-11 15:37:34 -0600423 {}
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100424};
425
Ed Tanous1abe55e2018-09-05 08:30:59 -0700426} // namespace persistent_data