blob: dc6ac1f4fea793751287e9f5bcd3397367b606e3 [file] [log] [blame]
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +01001#pragma once
2
Gunnar Mills1214b7e2020-06-04 10:11:30 -05003#include "logging.h"
4#include "utility.h"
5
Ed Tanousfc76b8a2020-09-28 17:21:52 -07006#include "random.hpp"
7
James Feista68a8042020-04-15 15:46:44 -07008#include <openssl/rand.h>
9
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010010#include <boost/container/flat_map.hpp>
11#include <boost/uuid/uuid.hpp>
12#include <boost/uuid/uuid_generators.hpp>
13#include <boost/uuid/uuid_io.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +053014#include <dbus_singleton.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -070015#include <nlohmann/json.hpp>
16#include <pam_authenticate.hpp>
RAJESWARAN THILLAIGOVINDAN70525172019-07-09 13:15:05 -050017#include <sdbusplus/bus/match.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;
48 PersistenceType persistence;
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{
146 bool xtoken = true;
147 bool cookie = true;
148 bool sessionToken = true;
149 bool basic = true;
Zbigniew Kurzynskicac94c52019-11-07 12:55:04 +0100150 bool tls = false;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100151
152 void fromJson(const nlohmann::json& j)
153 {
154 for (const auto& element : j.items())
155 {
156 const bool* value = element.value().get_ptr<const bool*>();
157 if (value == nullptr)
158 {
159 continue;
160 }
161
162 if (element.key() == "XToken")
163 {
164 xtoken = *value;
165 }
166 else if (element.key() == "Cookie")
167 {
168 cookie = *value;
169 }
170 else if (element.key() == "SessionToken")
171 {
172 sessionToken = *value;
173 }
174 else if (element.key() == "BasicAuth")
175 {
176 basic = *value;
177 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200178 else if (element.key() == "TLS")
179 {
180 tls = *value;
181 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100182 }
183 }
184};
185
Ed Tanous1abe55e2018-09-05 08:30:59 -0700186class SessionStore
187{
188 public:
189 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous39e77502019-03-04 17:35:53 -0800190 const std::string_view username,
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600191 PersistenceType persistence = PersistenceType::TIMEOUT,
Sunitha Harish92f68222020-05-28 05:09:09 -0500192 bool isConfigureSelfOnly = false, const std::string_view clientId = "",
193 const std::string_view clientIp = "")
Ed Tanous1abe55e2018-09-05 08:30:59 -0700194 {
195 // TODO(ed) find a secure way to not generate session identifiers if
196 // persistence is set to SINGLE_REQUEST
197 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500198 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
199 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
200 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700201 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
202 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100203
Ed Tanous1abe55e2018-09-05 08:30:59 -0700204 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700205 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700206 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
James Feista68a8042020-04-15 15:46:44 -0700207
Ed Tanousfc76b8a2020-09-28 17:21:52 -0700208 bmcweb::OpenSSLGenerator gen;
James Feista68a8042020-04-15 15:46:44 -0700209
Ed Tanous0dfeda62019-10-24 11:21:38 -0700210 for (char& sessionChar : sessionToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700211 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700212 sessionChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700213 if (gen.error())
214 {
215 return nullptr;
216 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100217 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700218 // Only need csrf tokens for cookie based auth, token doesn't matter
219 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700220 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700221 for (char& csrfChar : csrfToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700222 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700223 csrfChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700224 if (gen.error())
225 {
226 return nullptr;
227 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700228 }
229
230 std::string uniqueId;
231 uniqueId.resize(10, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700232 for (char& uidChar : uniqueId)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700233 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700234 uidChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700235 if (gen.error())
236 {
237 return nullptr;
238 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700239 }
Sunitha Harish92f68222020-05-28 05:09:09 -0500240 auto session = std::make_shared<UserSession>(
241 UserSession{uniqueId, sessionToken, std::string(username),
242 csrfToken, std::string(clientId), std::string(clientIp),
243 std::chrono::steady_clock::now(), persistence, false,
244 isConfigureSelfOnly});
Ed Tanous1abe55e2018-09-05 08:30:59 -0700245 auto it = authTokens.emplace(std::make_pair(sessionToken, session));
246 // Only need to write to disk if session isn't about to be destroyed.
247 needWrite = persistence == PersistenceType::TIMEOUT;
248 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100249 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700250
251 std::shared_ptr<UserSession>
Ed Tanous39e77502019-03-04 17:35:53 -0800252 loginSessionByToken(const std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700253 {
254 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700255 if (token.size() != sessionTokenSize)
256 {
257 return nullptr;
258 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700259 auto sessionIt = authTokens.find(std::string(token));
260 if (sessionIt == authTokens.end())
261 {
262 return nullptr;
263 }
264 std::shared_ptr<UserSession> userSession = sessionIt->second;
265 userSession->lastUpdated = std::chrono::steady_clock::now();
266 return userSession;
267 }
268
Ed Tanous39e77502019-03-04 17:35:53 -0800269 std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700270 {
271 applySessionTimeouts();
272 // TODO(Ed) this is inefficient
273 auto sessionIt = authTokens.begin();
274 while (sessionIt != authTokens.end())
275 {
276 if (sessionIt->second->uniqueId == uid)
277 {
278 return sessionIt->second;
279 }
280 sessionIt++;
281 }
282 return nullptr;
283 }
284
Ed Tanousb5a76932020-09-29 16:16:58 -0700285 void removeSession(const std::shared_ptr<UserSession>& session)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700286 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530287#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
288 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
289#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700290 authTokens.erase(session->sessionToken);
291 needWrite = true;
292 }
293
294 std::vector<const std::string*> getUniqueIds(
295 bool getAll = true,
296 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
297 {
298 applySessionTimeouts();
299
300 std::vector<const std::string*> ret;
301 ret.reserve(authTokens.size());
302 for (auto& session : authTokens)
303 {
304 if (getAll || type == session.second->persistence)
305 {
306 ret.push_back(&session.second->uniqueId);
307 }
308 }
309 return ret;
310 }
311
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100312 void updateAuthMethodsConfig(const AuthConfigMethods& config)
313 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100314 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100315 authMethodsConfig = config;
316 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100317 if (isTLSchanged)
318 {
319 // recreate socket connections with new settings
320 std::raise(SIGHUP);
321 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100322 }
323
324 AuthConfigMethods& getAuthMethodsConfig()
325 {
326 return authMethodsConfig;
327 }
328
Ed Tanous1abe55e2018-09-05 08:30:59 -0700329 bool needsWrite()
330 {
331 return needWrite;
332 }
Ed Tanous271584a2019-07-09 16:24:22 -0700333 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700334 {
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530335 return std::chrono::seconds(timeoutInSeconds).count();
336 }
337
338 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds)
339 {
340 timeoutInSeconds = newTimeoutInSeconds;
341 needWrite = true;
Ed Tanous23a21a12020-07-25 04:45:05 +0000342 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700343
Ed Tanous1abe55e2018-09-05 08:30:59 -0700344 static SessionStore& getInstance()
345 {
346 static SessionStore sessionStore;
347 return sessionStore;
348 }
349
350 SessionStore(const SessionStore&) = delete;
351 SessionStore& operator=(const SessionStore&) = delete;
352
Ed Tanous52cc1122020-07-18 13:51:21 -0700353 std::unordered_map<std::string, std::shared_ptr<UserSession>,
354 std::hash<std::string>,
355 crow::utility::ConstantTimeCompare>
356 authTokens;
357
358 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
359 bool needWrite{false};
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530360 std::chrono::seconds timeoutInSeconds;
Ed Tanous52cc1122020-07-18 13:51:21 -0700361 AuthConfigMethods authMethodsConfig;
362
Ed Tanous1abe55e2018-09-05 08:30:59 -0700363 private:
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530364 SessionStore() : timeoutInSeconds(3600)
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500365 {}
Ed Tanous1abe55e2018-09-05 08:30:59 -0700366
367 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 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100394};
395
Ed Tanous1abe55e2018-09-05 08:30:59 -0700396} // namespace persistent_data