xref: /openbmc/bmcweb/include/sessions.hpp (revision 844b4152)
1 #pragma once
2 
3 #include "logging.h"
4 #include "utility.h"
5 
6 #include <openssl/rand.h>
7 
8 #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>
12 #include <dbus_singleton.hpp>
13 #include <nlohmann/json.hpp>
14 #include <pam_authenticate.hpp>
15 #include <sdbusplus/bus/match.hpp>
16 
17 #include <csignal>
18 #include <random>
19 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
20 #include <ibm/locks.hpp>
21 #endif
22 
23 namespace crow
24 {
25 
26 namespace persistent_data
27 {
28 
29 // 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
32 constexpr std::size_t sessionTokenSize = 20;
33 
34 enum 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.
38 };
39 
40 struct UserSession
41 {
42     std::string uniqueId;
43     std::string sessionToken;
44     std::string username;
45     std::string csrfToken;
46     std::string clientId;
47     std::string clientIp;
48     std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
49     PersistenceType persistence;
50     bool cookieAuth = false;
51     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.
60 
61     /**
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";
83                 return nullptr;
84             }
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             }
101             else if (element.key() == "client_id")
102             {
103                 userSession->clientId = *thisValue;
104             }
105             else if (element.key() == "client_ip")
106             {
107                 userSession->clientIp = *thisValue;
108             }
109 
110             else
111             {
112                 BMCWEB_LOG_ERROR
113                     << "Got unexpected property reading persistent file: "
114                     << element.key();
115                 return nullptr;
116             }
117         }
118 
119         // For now, sessions that were persisted through a reboot get their idle
120         // timer reset.  This could probably be overcome with a better
121         // understanding of wall clock time and steady timer time, possibly
122         // persisting values with wall clock time instead of steady timer, but
123         // the tradeoffs of all the corner cases involved are non-trivial, so
124         // this is done temporarily
125         userSession->lastUpdated = std::chrono::steady_clock::now();
126         userSession->persistence = PersistenceType::TIMEOUT;
127 
128         return userSession;
129     }
130 };
131 
132 struct AuthConfigMethods
133 {
134     bool xtoken = true;
135     bool cookie = true;
136     bool sessionToken = true;
137     bool basic = true;
138     bool tls = false;
139 
140     void fromJson(const nlohmann::json& j)
141     {
142         for (const auto& element : j.items())
143         {
144             const bool* value = element.value().get_ptr<const bool*>();
145             if (value == nullptr)
146             {
147                 continue;
148             }
149 
150             if (element.key() == "XToken")
151             {
152                 xtoken = *value;
153             }
154             else if (element.key() == "Cookie")
155             {
156                 cookie = *value;
157             }
158             else if (element.key() == "SessionToken")
159             {
160                 sessionToken = *value;
161             }
162             else if (element.key() == "BasicAuth")
163             {
164                 basic = *value;
165             }
166             else if (element.key() == "TLS")
167             {
168                 tls = *value;
169             }
170         }
171     }
172 };
173 
174 class Middleware;
175 
176 struct OpenSSLGenerator
177 {
178 
179     uint8_t operator()(void)
180     {
181         uint8_t index = 0;
182         int rc = RAND_bytes(&index, sizeof(index));
183         if (rc != opensslSuccess)
184         {
185             std::cerr << "Cannot get random number\n";
186             err = true;
187         }
188 
189         return index;
190     };
191 
192     uint8_t max()
193     {
194         return std::numeric_limits<uint8_t>::max();
195     }
196     uint8_t min()
197     {
198         return std::numeric_limits<uint8_t>::min();
199     }
200 
201     bool error()
202     {
203         return err;
204     }
205 
206     // all generators require this variable
207     using result_type = uint8_t;
208 
209   private:
210     // RAND_bytes() returns 1 on success, 0 otherwise. -1 if bad function
211     static constexpr int opensslSuccess = 1;
212     bool err = false;
213 };
214 
215 class SessionStore
216 {
217   public:
218     std::shared_ptr<UserSession> generateUserSession(
219         const std::string_view username,
220         PersistenceType persistence = PersistenceType::TIMEOUT,
221         bool isConfigureSelfOnly = false, const std::string_view clientId = "",
222         const std::string_view clientIp = "")
223     {
224         // TODO(ed) find a secure way to not generate session identifiers if
225         // persistence is set to SINGLE_REQUEST
226         static constexpr std::array<char, 62> alphanum = {
227             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
228             'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
229             'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
230             'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
231             'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
232 
233         std::string sessionToken;
234         sessionToken.resize(sessionTokenSize, '0');
235         std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
236 
237         OpenSSLGenerator gen;
238 
239         for (size_t i = 0; i < sessionToken.size(); ++i)
240         {
241             sessionToken[i] = alphanum[dist(gen)];
242             if (gen.error())
243             {
244                 return nullptr;
245             }
246         }
247         // Only need csrf tokens for cookie based auth, token doesn't matter
248         std::string csrfToken;
249         csrfToken.resize(sessionTokenSize, '0');
250         for (size_t i = 0; i < csrfToken.size(); ++i)
251         {
252             csrfToken[i] = alphanum[dist(gen)];
253             if (gen.error())
254             {
255                 return nullptr;
256             }
257         }
258 
259         std::string uniqueId;
260         uniqueId.resize(10, '0');
261         for (size_t i = 0; i < uniqueId.size(); ++i)
262         {
263             uniqueId[i] = alphanum[dist(gen)];
264             if (gen.error())
265             {
266                 return nullptr;
267             }
268         }
269         auto session = std::make_shared<UserSession>(
270             UserSession{uniqueId, sessionToken, std::string(username),
271                         csrfToken, std::string(clientId), std::string(clientIp),
272                         std::chrono::steady_clock::now(), persistence, false,
273                         isConfigureSelfOnly});
274         auto it = authTokens.emplace(std::make_pair(sessionToken, session));
275         // Only need to write to disk if session isn't about to be destroyed.
276         needWrite = persistence == PersistenceType::TIMEOUT;
277         return it.first->second;
278     }
279 
280     std::shared_ptr<UserSession>
281         loginSessionByToken(const std::string_view token)
282     {
283         applySessionTimeouts();
284         if (token.size() != sessionTokenSize)
285         {
286             return nullptr;
287         }
288         auto sessionIt = authTokens.find(std::string(token));
289         if (sessionIt == authTokens.end())
290         {
291             return nullptr;
292         }
293         std::shared_ptr<UserSession> userSession = sessionIt->second;
294         userSession->lastUpdated = std::chrono::steady_clock::now();
295         return userSession;
296     }
297 
298     std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid)
299     {
300         applySessionTimeouts();
301         // TODO(Ed) this is inefficient
302         auto sessionIt = authTokens.begin();
303         while (sessionIt != authTokens.end())
304         {
305             if (sessionIt->second->uniqueId == uid)
306             {
307                 return sessionIt->second;
308             }
309             sessionIt++;
310         }
311         return nullptr;
312     }
313 
314     void removeSession(std::shared_ptr<UserSession> session)
315     {
316 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
317         crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
318 #endif
319         authTokens.erase(session->sessionToken);
320         needWrite = true;
321     }
322 
323     std::vector<const std::string*> getUniqueIds(
324         bool getAll = true,
325         const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
326     {
327         applySessionTimeouts();
328 
329         std::vector<const std::string*> ret;
330         ret.reserve(authTokens.size());
331         for (auto& session : authTokens)
332         {
333             if (getAll || type == session.second->persistence)
334             {
335                 ret.push_back(&session.second->uniqueId);
336             }
337         }
338         return ret;
339     }
340 
341     void updateAuthMethodsConfig(const AuthConfigMethods& config)
342     {
343         bool isTLSchanged = (authMethodsConfig.tls != config.tls);
344         authMethodsConfig = config;
345         needWrite = true;
346         if (isTLSchanged)
347         {
348             // recreate socket connections with new settings
349             std::raise(SIGHUP);
350         }
351     }
352 
353     AuthConfigMethods& getAuthMethodsConfig()
354     {
355         return authMethodsConfig;
356     }
357 
358     bool needsWrite()
359     {
360         return needWrite;
361     }
362     int64_t getTimeoutInSeconds() const
363     {
364         return std::chrono::seconds(timeoutInMinutes).count();
365     };
366 
367     // Persistent data middleware needs to be able to serialize our authTokens
368     // structure, which is private
369     friend Middleware;
370 
371     static SessionStore& getInstance()
372     {
373         static SessionStore sessionStore;
374         return sessionStore;
375     }
376 
377     SessionStore(const SessionStore&) = delete;
378     SessionStore& operator=(const SessionStore&) = delete;
379 
380   private:
381     SessionStore() : timeoutInMinutes(60)
382     {}
383 
384     void applySessionTimeouts()
385     {
386         auto timeNow = std::chrono::steady_clock::now();
387         if (timeNow - lastTimeoutUpdate > std::chrono::minutes(1))
388         {
389             lastTimeoutUpdate = timeNow;
390             auto authTokensIt = authTokens.begin();
391             while (authTokensIt != authTokens.end())
392             {
393                 if (timeNow - authTokensIt->second->lastUpdated >=
394                     timeoutInMinutes)
395                 {
396 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
397                     crow::ibm_mc_lock::Lock::getInstance().releaseLock(
398                         authTokensIt->second->uniqueId);
399 #endif
400                     authTokensIt = authTokens.erase(authTokensIt);
401 
402                     needWrite = true;
403                 }
404                 else
405                 {
406                     authTokensIt++;
407                 }
408             }
409         }
410     }
411 
412     std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
413     std::unordered_map<std::string, std::shared_ptr<UserSession>,
414                        std::hash<std::string>,
415                        crow::utility::ConstantTimeCompare>
416         authTokens;
417     bool needWrite{false};
418     std::chrono::minutes timeoutInMinutes;
419     AuthConfigMethods authMethodsConfig;
420 };
421 
422 } // namespace persistent_data
423 } // namespace crow
424 
425 // to_json(...) definition for objects of UserSession type
426 namespace nlohmann
427 {
428 template <>
429 struct adl_serializer<std::shared_ptr<crow::persistent_data::UserSession>>
430 {
431     static void
432         to_json(nlohmann::json& j,
433                 const std::shared_ptr<crow::persistent_data::UserSession>& p)
434     {
435         if (p->persistence !=
436             crow::persistent_data::PersistenceType::SINGLE_REQUEST)
437         {
438 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
439             j = nlohmann::json{
440                 {"unique_id", p->uniqueId}, {"session_token", p->sessionToken},
441                 {"username", p->username},  {"csrf_token", p->csrfToken},
442                 {"client_id", p->clientId}, { "client_ip", p->clientIp }};
443 #else
444             j = nlohmann::json{{"unique_id", p->uniqueId},
445                                {"session_token", p->sessionToken},
446                                {"username", p->username},
447                                {"csrf_token", p->csrfToken},
448                                {"client_ip", p->clientIp}};
449 #endif
450         }
451     }
452 };
453 
454 template <>
455 struct adl_serializer<crow::persistent_data::AuthConfigMethods>
456 {
457     static void to_json(nlohmann::json& j,
458                         const crow::persistent_data::AuthConfigMethods& c)
459     {
460         j = nlohmann::json{{"XToken", c.xtoken},
461                            {"Cookie", c.cookie},
462                            {"SessionToken", c.sessionToken},
463                            {"BasicAuth", c.basic},
464                            {"TLS", c.tls}};
465     }
466 };
467 } // namespace nlohmann
468