blob: 1176cfca471d60a78724a0783f1a65c198513716 [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
James Feista68a8042020-04-15 15:46:44 -07006#include <openssl/rand.h>
7
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +01008#include <boost/container/flat_map.hpp>
9#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>
RAJESWARAN THILLAIGOVINDAN70525172019-07-09 13:15:05 -050015#include <sdbusplus/bus/match.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +053016
Gunnar Mills1214b7e2020-06-04 10:11:30 -050017#include <csignal>
18#include <random>
Ratan Gupta07386c62019-12-14 14:06:09 +053019#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
20#include <ibm/locks.hpp>
21#endif
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010022
Ed Tanous1abe55e2018-09-05 08:30:59 -070023namespace crow
24{
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;
46 std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
47 PersistenceType persistence;
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";
81 return nullptr;
82 }
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 }
99 else
100 {
101 BMCWEB_LOG_ERROR
102 << "Got unexpected property reading persistent file: "
103 << element.key();
104 return nullptr;
105 }
106 }
107
108 // For now, sessions that were persisted through a reboot get their idle
109 // timer reset. This could probably be overcome with a better
110 // understanding of wall clock time and steady timer time, possibly
111 // persisting values with wall clock time instead of steady timer, but
112 // the tradeoffs of all the corner cases involved are non-trivial, so
113 // this is done temporarily
114 userSession->lastUpdated = std::chrono::steady_clock::now();
115 userSession->persistence = PersistenceType::TIMEOUT;
116
117 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100118 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100119};
120
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100121struct AuthConfigMethods
122{
123 bool xtoken = true;
124 bool cookie = true;
125 bool sessionToken = true;
126 bool basic = true;
Zbigniew Kurzynskicac94c52019-11-07 12:55:04 +0100127 bool tls = false;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100128
129 void fromJson(const nlohmann::json& j)
130 {
131 for (const auto& element : j.items())
132 {
133 const bool* value = element.value().get_ptr<const bool*>();
134 if (value == nullptr)
135 {
136 continue;
137 }
138
139 if (element.key() == "XToken")
140 {
141 xtoken = *value;
142 }
143 else if (element.key() == "Cookie")
144 {
145 cookie = *value;
146 }
147 else if (element.key() == "SessionToken")
148 {
149 sessionToken = *value;
150 }
151 else if (element.key() == "BasicAuth")
152 {
153 basic = *value;
154 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200155 else if (element.key() == "TLS")
156 {
157 tls = *value;
158 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100159 }
160 }
161};
162
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100163class Middleware;
164
James Feista68a8042020-04-15 15:46:44 -0700165struct OpenSSLGenerator
166{
167
168 uint8_t operator()(void)
169 {
170 uint8_t index = 0;
171 int rc = RAND_bytes(&index, sizeof(index));
172 if (rc != opensslSuccess)
173 {
174 std::cerr << "Cannot get random number\n";
175 err = true;
176 }
177
178 return index;
179 };
180
181 uint8_t max()
182 {
183 return std::numeric_limits<uint8_t>::max();
184 }
185 uint8_t min()
186 {
187 return std::numeric_limits<uint8_t>::min();
188 }
189
190 bool error()
191 {
192 return err;
193 }
194
195 // all generators require this variable
196 using result_type = uint8_t;
197
198 private:
199 // RAND_bytes() returns 1 on success, 0 otherwise. -1 if bad function
200 static constexpr int opensslSuccess = 1;
201 bool err = false;
202};
203
Ed Tanous1abe55e2018-09-05 08:30:59 -0700204class SessionStore
205{
206 public:
207 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous39e77502019-03-04 17:35:53 -0800208 const std::string_view username,
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600209 PersistenceType persistence = PersistenceType::TIMEOUT,
210 bool isConfigureSelfOnly = false)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700211 {
212 // TODO(ed) find a secure way to not generate session identifiers if
213 // persistence is set to SINGLE_REQUEST
214 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500215 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
216 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
217 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700218 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
219 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100220
Ed Tanous1abe55e2018-09-05 08:30:59 -0700221 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700222 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700223 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
James Feista68a8042020-04-15 15:46:44 -0700224
225 OpenSSLGenerator gen;
226
Ed Tanous271584a2019-07-09 16:24:22 -0700227 for (size_t i = 0; i < sessionToken.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700228 {
James Feista68a8042020-04-15 15:46:44 -0700229 sessionToken[i] = alphanum[dist(gen)];
230 if (gen.error())
231 {
232 return nullptr;
233 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100234 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700235 // Only need csrf tokens for cookie based auth, token doesn't matter
236 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700237 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700238 for (size_t i = 0; i < csrfToken.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700239 {
James Feista68a8042020-04-15 15:46:44 -0700240 csrfToken[i] = alphanum[dist(gen)];
241 if (gen.error())
242 {
243 return nullptr;
244 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700245 }
246
247 std::string uniqueId;
248 uniqueId.resize(10, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700249 for (size_t i = 0; i < uniqueId.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700250 {
James Feista68a8042020-04-15 15:46:44 -0700251 uniqueId[i] = alphanum[dist(gen)];
252 if (gen.error())
253 {
254 return nullptr;
255 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700256 }
Ratan Gupta12c04ef2019-04-03 10:08:11 +0530257
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600258 auto session = std::make_shared<UserSession>(
259 UserSession{uniqueId, sessionToken, std::string(username),
260 csrfToken, std::chrono::steady_clock::now(),
261 persistence, false, 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
302 void removeSession(std::shared_ptr<UserSession> session)
303 {
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 Tanous1abe55e2018-09-05 08:30:59 -0700346 bool needsWrite()
347 {
348 return needWrite;
349 }
Ed Tanous271584a2019-07-09 16:24:22 -0700350 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700351 {
352 return std::chrono::seconds(timeoutInMinutes).count();
353 };
354
355 // Persistent data middleware needs to be able to serialize our authTokens
356 // structure, which is private
357 friend Middleware;
358
359 static SessionStore& getInstance()
360 {
361 static SessionStore sessionStore;
362 return sessionStore;
363 }
364
365 SessionStore(const SessionStore&) = delete;
366 SessionStore& operator=(const SessionStore&) = delete;
367
368 private:
369 SessionStore() : timeoutInMinutes(60)
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500370 {}
Ed Tanous1abe55e2018-09-05 08:30:59 -0700371
372 void applySessionTimeouts()
373 {
374 auto timeNow = std::chrono::steady_clock::now();
375 if (timeNow - lastTimeoutUpdate > std::chrono::minutes(1))
376 {
377 lastTimeoutUpdate = timeNow;
378 auto authTokensIt = authTokens.begin();
379 while (authTokensIt != authTokens.end())
380 {
381 if (timeNow - authTokensIt->second->lastUpdated >=
382 timeoutInMinutes)
383 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530384#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
385 crow::ibm_mc_lock::Lock::getInstance().releaseLock(
386 authTokensIt->second->uniqueId);
387#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700388 authTokensIt = authTokens.erase(authTokensIt);
Ratan Gupta07386c62019-12-14 14:06:09 +0530389
Ed Tanous1abe55e2018-09-05 08:30:59 -0700390 needWrite = true;
391 }
392 else
393 {
394 authTokensIt++;
395 }
396 }
397 }
398 }
Ratan Gupta12c04ef2019-04-03 10:08:11 +0530399
Ed Tanous1abe55e2018-09-05 08:30:59 -0700400 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
Ed Tanous51dae672018-09-05 16:07:32 -0700401 std::unordered_map<std::string, std::shared_ptr<UserSession>,
402 std::hash<std::string>,
403 crow::utility::ConstantTimeCompare>
Ed Tanous1abe55e2018-09-05 08:30:59 -0700404 authTokens;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700405 bool needWrite{false};
406 std::chrono::minutes timeoutInMinutes;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100407 AuthConfigMethods authMethodsConfig;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100408};
409
Ed Tanous1abe55e2018-09-05 08:30:59 -0700410} // namespace persistent_data
411} // namespace crow
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200412
413// to_json(...) definition for objects of UserSession type
Ed Tanous1abe55e2018-09-05 08:30:59 -0700414namespace nlohmann
415{
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200416template <>
Ed Tanous1abe55e2018-09-05 08:30:59 -0700417struct adl_serializer<std::shared_ptr<crow::persistent_data::UserSession>>
418{
419 static void
420 to_json(nlohmann::json& j,
421 const std::shared_ptr<crow::persistent_data::UserSession>& p)
422 {
423 if (p->persistence !=
424 crow::persistent_data::PersistenceType::SINGLE_REQUEST)
425 {
426 j = nlohmann::json{{"unique_id", p->uniqueId},
427 {"session_token", p->sessionToken},
428 {"username", p->username},
429 {"csrf_token", p->csrfToken}};
430 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200431 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200432};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100433
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500434template <>
435struct adl_serializer<crow::persistent_data::AuthConfigMethods>
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100436{
437 static void to_json(nlohmann::json& j,
438 const crow::persistent_data::AuthConfigMethods& c)
439 {
440 j = nlohmann::json{{"XToken", c.xtoken},
441 {"Cookie", c.cookie},
442 {"SessionToken", c.sessionToken},
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200443 {"BasicAuth", c.basic},
444 {"TLS", c.tls}};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100445 }
446};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700447} // namespace nlohmann