blob: 52e7450f4a6bd285908fce3eefd0586c38536ecf [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>
Nan Zhou97128e92021-08-17 12:01:55 -070014#include <random.hpp>
RAJESWARAN THILLAIGOVINDAN70525172019-07-09 13:15:05 -050015#include <sdbusplus/bus/match.hpp>
Jiaqing Zhao41d61c82021-12-07 13:21:47 +080016#include <utils/ip_utils.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +053017
Gunnar Mills1214b7e2020-06-04 10:11:30 -050018#include <csignal>
19#include <random>
Ratan Gupta07386c62019-12-14 14:06:09 +053020#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
21#include <ibm/locks.hpp>
22#endif
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010023
Ed Tanous1abe55e2018-09-05 08:30:59 -070024namespace persistent_data
25{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010026
Ed Tanous51dae672018-09-05 16:07:32 -070027// entropy: 20 characters, 62 possibilities. log2(62^20) = 119 bits of
28// entropy. OWASP recommends at least 64
29// https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy
30constexpr std::size_t sessionTokenSize = 20;
31
Ed Tanous1abe55e2018-09-05 08:30:59 -070032enum class PersistenceType
33{
34 TIMEOUT, // User session times out after a predetermined amount of time
35 SINGLE_REQUEST // User times out once this request is completed.
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010036};
37
Ed Tanous1abe55e2018-09-05 08:30:59 -070038struct UserSession
39{
40 std::string uniqueId;
41 std::string sessionToken;
42 std::string username;
43 std::string csrfToken;
Sunitha Harish08bdcc72020-05-12 05:17:57 -050044 std::string clientId;
Sunitha Harish92f68222020-05-28 05:09:09 -050045 std::string clientIp;
Ed Tanous1abe55e2018-09-05 08:30:59 -070046 std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
Ed Tanousd3a9e082022-01-07 09:30:41 -080047 PersistenceType persistence{PersistenceType::TIMEOUT};
James Feistf8aa3d22020-04-08 18:32:33 -070048 bool cookieAuth = false;
Joseph Reynolds3bf4e632020-02-06 14:44:32 -060049 bool isConfigureSelfOnly = false;
50
51 // There are two sources of truth for isConfigureSelfOnly:
52 // 1. When pamAuthenticateUser() returns PAM_NEW_AUTHTOK_REQD.
53 // 2. D-Bus User.Manager.GetUserInfo property UserPasswordExpired.
54 // These should be in sync, but the underlying condition can change at any
55 // time. For example, a password can expire or be changed outside of
56 // bmcweb. The value stored here is updated at the start of each
57 // operation and used as the truth within bmcweb.
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010058
Ed Tanous1abe55e2018-09-05 08:30:59 -070059 /**
60 * @brief Fills object with data from UserSession's JSON representation
61 *
62 * This replaces nlohmann's from_json to ensure no-throw approach
63 *
64 * @param[in] j JSON object from which data should be loaded
65 *
66 * @return a shared pointer if data has been loaded properly, nullptr
67 * otherwise
68 */
69 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
70 {
71 std::shared_ptr<UserSession> userSession =
72 std::make_shared<UserSession>();
73 for (const auto& element : j.items())
74 {
75 const std::string* thisValue =
76 element.value().get_ptr<const std::string*>();
77 if (thisValue == nullptr)
78 {
79 BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
80 << element.key() << " was not of type string";
Ed Tanousdc511aa2020-10-21 12:33:42 -070081 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -070082 }
83 if (element.key() == "unique_id")
84 {
85 userSession->uniqueId = *thisValue;
86 }
87 else if (element.key() == "session_token")
88 {
89 userSession->sessionToken = *thisValue;
90 }
91 else if (element.key() == "csrf_token")
92 {
93 userSession->csrfToken = *thisValue;
94 }
95 else if (element.key() == "username")
96 {
97 userSession->username = *thisValue;
98 }
Ed Tanousdc511aa2020-10-21 12:33:42 -070099#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
Sunitha Harish08bdcc72020-05-12 05:17:57 -0500100 else if (element.key() == "client_id")
101 {
102 userSession->clientId = *thisValue;
103 }
Ed Tanousdc511aa2020-10-21 12:33:42 -0700104#endif
Sunitha Harish92f68222020-05-28 05:09:09 -0500105 else if (element.key() == "client_ip")
106 {
107 userSession->clientIp = *thisValue;
108 }
109
Ed Tanous1abe55e2018-09-05 08:30:59 -0700110 else
111 {
112 BMCWEB_LOG_ERROR
113 << "Got unexpected property reading persistent file: "
114 << element.key();
Ed Tanousdc511aa2020-10-21 12:33:42 -0700115 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700116 }
117 }
Ed Tanousdc511aa2020-10-21 12:33:42 -0700118 // If any of these fields are missing, we can't restore the session, as
119 // we don't have enough information. These 4 fields have been present
120 // in every version of this file in bmcwebs history, so any file, even
121 // on upgrade, should have these present
122 if (userSession->uniqueId.empty() || userSession->username.empty() ||
123 userSession->sessionToken.empty() || userSession->csrfToken.empty())
124 {
125 BMCWEB_LOG_DEBUG << "Session missing required security "
126 "information, refusing to restore";
127 return nullptr;
128 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700129
130 // For now, sessions that were persisted through a reboot get their idle
131 // timer reset. This could probably be overcome with a better
132 // understanding of wall clock time and steady timer time, possibly
133 // persisting values with wall clock time instead of steady timer, but
134 // the tradeoffs of all the corner cases involved are non-trivial, so
135 // this is done temporarily
136 userSession->lastUpdated = std::chrono::steady_clock::now();
137 userSession->persistence = PersistenceType::TIMEOUT;
138
139 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100140 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100141};
142
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100143struct AuthConfigMethods
144{
Alan Kuof16f6262020-12-08 19:29:59 +0800145#ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100146 bool basic = true;
Alan Kuof16f6262020-12-08 19:29:59 +0800147#else
148 bool basic = false;
149#endif
150
151#ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
152 bool sessionToken = true;
153#else
154 bool sessionToken = false;
155#endif
156
157#ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
158 bool xtoken = true;
159#else
160 bool xtoken = false;
161#endif
162
163#ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
164 bool cookie = true;
165#else
166 bool cookie = false;
167#endif
168
169#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
170 bool tls = true;
171#else
Zbigniew Kurzynskicac94c52019-11-07 12:55:04 +0100172 bool tls = false;
Alan Kuof16f6262020-12-08 19:29:59 +0800173#endif
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100174
175 void fromJson(const nlohmann::json& j)
176 {
177 for (const auto& element : j.items())
178 {
179 const bool* value = element.value().get_ptr<const bool*>();
180 if (value == nullptr)
181 {
182 continue;
183 }
184
185 if (element.key() == "XToken")
186 {
187 xtoken = *value;
188 }
189 else if (element.key() == "Cookie")
190 {
191 cookie = *value;
192 }
193 else if (element.key() == "SessionToken")
194 {
195 sessionToken = *value;
196 }
197 else if (element.key() == "BasicAuth")
198 {
199 basic = *value;
200 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200201 else if (element.key() == "TLS")
202 {
203 tls = *value;
204 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100205 }
206 }
207};
208
Ed Tanous1abe55e2018-09-05 08:30:59 -0700209class SessionStore
210{
211 public:
212 std::shared_ptr<UserSession> generateUserSession(
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800213 const std::string_view username,
214 const boost::asio::ip::address& clientIp,
Sunitha Harishd3239222021-02-24 15:33:29 +0530215 const std::string_view clientId,
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600216 PersistenceType persistence = PersistenceType::TIMEOUT,
Sunitha Harishd3239222021-02-24 15:33:29 +0530217 bool isConfigureSelfOnly = false)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700218 {
219 // TODO(ed) find a secure way to not generate session identifiers if
220 // persistence is set to SINGLE_REQUEST
221 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500222 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
223 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
224 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700225 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
226 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100227
Ed Tanous1abe55e2018-09-05 08:30:59 -0700228 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700229 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700230 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
James Feista68a8042020-04-15 15:46:44 -0700231
Ed Tanousfc76b8a2020-09-28 17:21:52 -0700232 bmcweb::OpenSSLGenerator gen;
James Feista68a8042020-04-15 15:46:44 -0700233
Ed Tanous0dfeda62019-10-24 11:21:38 -0700234 for (char& sessionChar : sessionToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700235 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700236 sessionChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700237 if (gen.error())
238 {
239 return nullptr;
240 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100241 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700242 // Only need csrf tokens for cookie based auth, token doesn't matter
243 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700244 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700245 for (char& csrfChar : csrfToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700246 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700247 csrfChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700248 if (gen.error())
249 {
250 return nullptr;
251 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700252 }
253
254 std::string uniqueId;
255 uniqueId.resize(10, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700256 for (char& uidChar : uniqueId)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700257 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700258 uidChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700259 if (gen.error())
260 {
261 return nullptr;
262 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700263 }
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800264
265 auto session = std::make_shared<UserSession>(UserSession{
266 uniqueId, sessionToken, std::string(username), csrfToken,
267 std::string(clientId), redfish::ip_util::toString(clientIp),
268 std::chrono::steady_clock::now(), persistence, false,
269 isConfigureSelfOnly});
Ed Tanous1abe55e2018-09-05 08:30:59 -0700270 auto it = authTokens.emplace(std::make_pair(sessionToken, session));
271 // 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
276 std::shared_ptr<UserSession>
Ed Tanous39e77502019-03-04 17:35:53 -0800277 loginSessionByToken(const std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700278 {
279 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700280 if (token.size() != sessionTokenSize)
281 {
282 return nullptr;
283 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700284 auto sessionIt = authTokens.find(std::string(token));
285 if (sessionIt == authTokens.end())
286 {
287 return nullptr;
288 }
289 std::shared_ptr<UserSession> userSession = sessionIt->second;
290 userSession->lastUpdated = std::chrono::steady_clock::now();
291 return userSession;
292 }
293
Ed Tanous39e77502019-03-04 17:35:53 -0800294 std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700295 {
296 applySessionTimeouts();
297 // TODO(Ed) this is inefficient
298 auto sessionIt = authTokens.begin();
299 while (sessionIt != authTokens.end())
300 {
301 if (sessionIt->second->uniqueId == uid)
302 {
303 return sessionIt->second;
304 }
305 sessionIt++;
306 }
307 return nullptr;
308 }
309
Ed Tanousb5a76932020-09-29 16:16:58 -0700310 void removeSession(const std::shared_ptr<UserSession>& session)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700311 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530312#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
313 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
314#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700315 authTokens.erase(session->sessionToken);
316 needWrite = true;
317 }
318
319 std::vector<const std::string*> getUniqueIds(
320 bool getAll = true,
321 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
322 {
323 applySessionTimeouts();
324
325 std::vector<const std::string*> ret;
326 ret.reserve(authTokens.size());
327 for (auto& session : authTokens)
328 {
329 if (getAll || type == session.second->persistence)
330 {
331 ret.push_back(&session.second->uniqueId);
332 }
333 }
334 return ret;
335 }
336
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100337 void updateAuthMethodsConfig(const AuthConfigMethods& config)
338 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100339 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100340 authMethodsConfig = config;
341 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100342 if (isTLSchanged)
343 {
344 // recreate socket connections with new settings
345 std::raise(SIGHUP);
346 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100347 }
348
349 AuthConfigMethods& getAuthMethodsConfig()
350 {
351 return authMethodsConfig;
352 }
353
Ed Tanous9eb808c2022-01-25 10:19:23 -0800354 bool needsWrite() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700355 {
356 return needWrite;
357 }
Ed Tanous271584a2019-07-09 16:24:22 -0700358 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700359 {
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530360 return std::chrono::seconds(timeoutInSeconds).count();
361 }
362
363 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds)
364 {
365 timeoutInSeconds = newTimeoutInSeconds;
366 needWrite = true;
Ed Tanous23a21a12020-07-25 04:45:05 +0000367 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700368
Ed Tanous1abe55e2018-09-05 08:30:59 -0700369 static SessionStore& getInstance()
370 {
371 static SessionStore sessionStore;
372 return sessionStore;
373 }
374
Ed Tanous1abe55e2018-09-05 08:30:59 -0700375 void applySessionTimeouts()
376 {
377 auto timeNow = std::chrono::steady_clock::now();
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530378 if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1))
Ed Tanous1abe55e2018-09-05 08:30:59 -0700379 {
380 lastTimeoutUpdate = timeNow;
381 auto authTokensIt = authTokens.begin();
382 while (authTokensIt != authTokens.end())
383 {
384 if (timeNow - authTokensIt->second->lastUpdated >=
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530385 timeoutInSeconds)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700386 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530387#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
388 crow::ibm_mc_lock::Lock::getInstance().releaseLock(
389 authTokensIt->second->uniqueId);
390#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700391 authTokensIt = authTokens.erase(authTokensIt);
Ratan Gupta07386c62019-12-14 14:06:09 +0530392
Ed Tanous1abe55e2018-09-05 08:30:59 -0700393 needWrite = true;
394 }
395 else
396 {
397 authTokensIt++;
398 }
399 }
400 }
401 }
Gunnar Mills83cf8182020-11-11 15:37:34 -0600402
403 SessionStore(const SessionStore&) = delete;
404 SessionStore& operator=(const SessionStore&) = delete;
Ed Tanousecd6a3a2022-01-07 09:18:40 -0800405 SessionStore(SessionStore&&) = delete;
406 SessionStore& operator=(const SessionStore&&) = delete;
407 ~SessionStore() = default;
Gunnar Mills83cf8182020-11-11 15:37:34 -0600408
409 std::unordered_map<std::string, std::shared_ptr<UserSession>,
410 std::hash<std::string>,
411 crow::utility::ConstantTimeCompare>
412 authTokens;
413
414 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
415 bool needWrite{false};
416 std::chrono::seconds timeoutInSeconds;
417 AuthConfigMethods authMethodsConfig;
418
419 private:
Jason M. Billsdc414b52021-08-05 15:20:25 -0700420 SessionStore() : timeoutInSeconds(1800)
Gunnar Mills83cf8182020-11-11 15:37:34 -0600421 {}
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100422};
423
Ed Tanous1abe55e2018-09-05 08:30:59 -0700424} // namespace persistent_data