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