blob: 748bb089993010729829ef0fe99f1aeb956820a7 [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/uuid/uuid.hpp>
10#include <boost/uuid/uuid_generators.hpp>
11#include <boost/uuid/uuid_io.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +053012#include <dbus_singleton.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -070013#include <nlohmann/json.hpp>
14#include <pam_authenticate.hpp>
Nan Zhou97128e92021-08-17 12:01:55 -070015#include <random.hpp>
RAJESWARAN THILLAIGOVINDAN70525172019-07-09 13:15:05 -050016#include <sdbusplus/bus/match.hpp>
Jiaqing Zhao41d61c82021-12-07 13:21:47 +080017#include <utils/ip_utils.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +053018
Gunnar Mills1214b7e2020-06-04 10:11:30 -050019#include <csignal>
20#include <random>
Ratan Gupta07386c62019-12-14 14:06:09 +053021#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
22#include <ibm/locks.hpp>
23#endif
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010024
Ed Tanous1abe55e2018-09-05 08:30:59 -070025namespace persistent_data
26{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010027
Ed Tanous51dae672018-09-05 16:07:32 -070028// entropy: 20 characters, 62 possibilities. log2(62^20) = 119 bits of
29// entropy. OWASP recommends at least 64
30// https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy
31constexpr std::size_t sessionTokenSize = 20;
32
Ed Tanous1abe55e2018-09-05 08:30:59 -070033enum class PersistenceType
34{
35 TIMEOUT, // User session times out after a predetermined amount of time
36 SINGLE_REQUEST // User times out once this request is completed.
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010037};
38
Ed Tanous1abe55e2018-09-05 08:30:59 -070039struct UserSession
40{
41 std::string uniqueId;
42 std::string sessionToken;
43 std::string username;
44 std::string csrfToken;
Sunitha Harish08bdcc72020-05-12 05:17:57 -050045 std::string clientId;
Sunitha Harish92f68222020-05-28 05:09:09 -050046 std::string clientIp;
Ed Tanous1abe55e2018-09-05 08:30:59 -070047 std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
Ed Tanousd3a9e082022-01-07 09:30:41 -080048 PersistenceType persistence{PersistenceType::TIMEOUT};
James Feistf8aa3d22020-04-08 18:32:33 -070049 bool cookieAuth = false;
Joseph Reynolds3bf4e632020-02-06 14:44:32 -060050 bool isConfigureSelfOnly = false;
51
52 // There are two sources of truth for isConfigureSelfOnly:
53 // 1. When pamAuthenticateUser() returns PAM_NEW_AUTHTOK_REQD.
54 // 2. D-Bus User.Manager.GetUserInfo property UserPasswordExpired.
55 // These should be in sync, but the underlying condition can change at any
56 // time. For example, a password can expire or be changed outside of
57 // bmcweb. The value stored here is updated at the start of each
58 // operation and used as the truth within bmcweb.
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010059
Ed Tanous1abe55e2018-09-05 08:30:59 -070060 /**
61 * @brief Fills object with data from UserSession's JSON representation
62 *
63 * This replaces nlohmann's from_json to ensure no-throw approach
64 *
65 * @param[in] j JSON object from which data should be loaded
66 *
67 * @return a shared pointer if data has been loaded properly, nullptr
68 * otherwise
69 */
70 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
71 {
72 std::shared_ptr<UserSession> userSession =
73 std::make_shared<UserSession>();
74 for (const auto& element : j.items())
75 {
76 const std::string* thisValue =
77 element.value().get_ptr<const std::string*>();
78 if (thisValue == nullptr)
79 {
80 BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
81 << element.key() << " was not of type string";
Ed Tanousdc511aa2020-10-21 12:33:42 -070082 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -070083 }
84 if (element.key() == "unique_id")
85 {
86 userSession->uniqueId = *thisValue;
87 }
88 else if (element.key() == "session_token")
89 {
90 userSession->sessionToken = *thisValue;
91 }
92 else if (element.key() == "csrf_token")
93 {
94 userSession->csrfToken = *thisValue;
95 }
96 else if (element.key() == "username")
97 {
98 userSession->username = *thisValue;
99 }
Ed Tanousdc511aa2020-10-21 12:33:42 -0700100#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
Sunitha Harish08bdcc72020-05-12 05:17:57 -0500101 else if (element.key() == "client_id")
102 {
103 userSession->clientId = *thisValue;
104 }
Ed Tanousdc511aa2020-10-21 12:33:42 -0700105#endif
Sunitha Harish92f68222020-05-28 05:09:09 -0500106 else if (element.key() == "client_ip")
107 {
108 userSession->clientIp = *thisValue;
109 }
110
Ed Tanous1abe55e2018-09-05 08:30:59 -0700111 else
112 {
113 BMCWEB_LOG_ERROR
114 << "Got unexpected property reading persistent file: "
115 << element.key();
Ed Tanousdc511aa2020-10-21 12:33:42 -0700116 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700117 }
118 }
Ed Tanousdc511aa2020-10-21 12:33:42 -0700119 // If any of these fields are missing, we can't restore the session, as
120 // we don't have enough information. These 4 fields have been present
121 // in every version of this file in bmcwebs history, so any file, even
122 // on upgrade, should have these present
123 if (userSession->uniqueId.empty() || userSession->username.empty() ||
124 userSession->sessionToken.empty() || userSession->csrfToken.empty())
125 {
126 BMCWEB_LOG_DEBUG << "Session missing required security "
127 "information, refusing to restore";
128 return nullptr;
129 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700130
131 // For now, sessions that were persisted through a reboot get their idle
132 // timer reset. This could probably be overcome with a better
133 // understanding of wall clock time and steady timer time, possibly
134 // persisting values with wall clock time instead of steady timer, but
135 // the tradeoffs of all the corner cases involved are non-trivial, so
136 // this is done temporarily
137 userSession->lastUpdated = std::chrono::steady_clock::now();
138 userSession->persistence = PersistenceType::TIMEOUT;
139
140 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100141 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100142};
143
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100144struct AuthConfigMethods
145{
Alan Kuof16f6262020-12-08 19:29:59 +0800146#ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100147 bool basic = true;
Alan Kuof16f6262020-12-08 19:29:59 +0800148#else
149 bool basic = false;
150#endif
151
152#ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
153 bool sessionToken = true;
154#else
155 bool sessionToken = false;
156#endif
157
158#ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
159 bool xtoken = true;
160#else
161 bool xtoken = false;
162#endif
163
164#ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
165 bool cookie = true;
166#else
167 bool cookie = false;
168#endif
169
170#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
171 bool tls = true;
172#else
Zbigniew Kurzynskicac94c52019-11-07 12:55:04 +0100173 bool tls = false;
Alan Kuof16f6262020-12-08 19:29:59 +0800174#endif
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100175
176 void fromJson(const nlohmann::json& j)
177 {
178 for (const auto& element : j.items())
179 {
180 const bool* value = element.value().get_ptr<const bool*>();
181 if (value == nullptr)
182 {
183 continue;
184 }
185
186 if (element.key() == "XToken")
187 {
188 xtoken = *value;
189 }
190 else if (element.key() == "Cookie")
191 {
192 cookie = *value;
193 }
194 else if (element.key() == "SessionToken")
195 {
196 sessionToken = *value;
197 }
198 else if (element.key() == "BasicAuth")
199 {
200 basic = *value;
201 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200202 else if (element.key() == "TLS")
203 {
204 tls = *value;
205 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100206 }
207 }
208};
209
Ed Tanous1abe55e2018-09-05 08:30:59 -0700210class SessionStore
211{
212 public:
213 std::shared_ptr<UserSession> generateUserSession(
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800214 const std::string_view username,
215 const boost::asio::ip::address& clientIp,
Sunitha Harishd3239222021-02-24 15:33:29 +0530216 const std::string_view clientId,
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600217 PersistenceType persistence = PersistenceType::TIMEOUT,
Sunitha Harishd3239222021-02-24 15:33:29 +0530218 bool isConfigureSelfOnly = false)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700219 {
220 // TODO(ed) find a secure way to not generate session identifiers if
221 // persistence is set to SINGLE_REQUEST
222 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500223 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
224 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
225 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700226 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
227 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100228
Ed Tanous1abe55e2018-09-05 08:30:59 -0700229 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700230 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700231 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
James Feista68a8042020-04-15 15:46:44 -0700232
Ed Tanousfc76b8a2020-09-28 17:21:52 -0700233 bmcweb::OpenSSLGenerator gen;
James Feista68a8042020-04-15 15:46:44 -0700234
Ed Tanous0dfeda62019-10-24 11:21:38 -0700235 for (char& sessionChar : sessionToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700236 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700237 sessionChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700238 if (gen.error())
239 {
240 return nullptr;
241 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100242 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700243 // Only need csrf tokens for cookie based auth, token doesn't matter
244 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700245 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700246 for (char& csrfChar : csrfToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700247 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700248 csrfChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700249 if (gen.error())
250 {
251 return nullptr;
252 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700253 }
254
255 std::string uniqueId;
256 uniqueId.resize(10, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700257 for (char& uidChar : uniqueId)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700258 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700259 uidChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700260 if (gen.error())
261 {
262 return nullptr;
263 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700264 }
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800265
266 auto session = std::make_shared<UserSession>(UserSession{
267 uniqueId, sessionToken, std::string(username), csrfToken,
268 std::string(clientId), redfish::ip_util::toString(clientIp),
269 std::chrono::steady_clock::now(), persistence, false,
270 isConfigureSelfOnly});
Ed Tanous1abe55e2018-09-05 08:30:59 -0700271 auto it = authTokens.emplace(std::make_pair(sessionToken, session));
272 // Only need to write to disk if session isn't about to be destroyed.
273 needWrite = persistence == PersistenceType::TIMEOUT;
274 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100275 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700276
277 std::shared_ptr<UserSession>
Ed Tanous39e77502019-03-04 17:35:53 -0800278 loginSessionByToken(const std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700279 {
280 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700281 if (token.size() != sessionTokenSize)
282 {
283 return nullptr;
284 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700285 auto sessionIt = authTokens.find(std::string(token));
286 if (sessionIt == authTokens.end())
287 {
288 return nullptr;
289 }
290 std::shared_ptr<UserSession> userSession = sessionIt->second;
291 userSession->lastUpdated = std::chrono::steady_clock::now();
292 return userSession;
293 }
294
Ed Tanous39e77502019-03-04 17:35:53 -0800295 std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700296 {
297 applySessionTimeouts();
298 // TODO(Ed) this is inefficient
299 auto sessionIt = authTokens.begin();
300 while (sessionIt != authTokens.end())
301 {
302 if (sessionIt->second->uniqueId == uid)
303 {
304 return sessionIt->second;
305 }
306 sessionIt++;
307 }
308 return nullptr;
309 }
310
Ed Tanousb5a76932020-09-29 16:16:58 -0700311 void removeSession(const std::shared_ptr<UserSession>& session)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700312 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530313#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
314 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
315#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700316 authTokens.erase(session->sessionToken);
317 needWrite = true;
318 }
319
320 std::vector<const std::string*> getUniqueIds(
321 bool getAll = true,
322 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
323 {
324 applySessionTimeouts();
325
326 std::vector<const std::string*> ret;
327 ret.reserve(authTokens.size());
328 for (auto& session : authTokens)
329 {
330 if (getAll || type == session.second->persistence)
331 {
332 ret.push_back(&session.second->uniqueId);
333 }
334 }
335 return ret;
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