xref: /openbmc/bmcweb/include/sessions.hpp (revision 262d7d4b)
1 #pragma once
2 
3 #include "logging.hpp"
4 #include "random.hpp"
5 #include "utility.hpp"
6 
7 #include <openssl/rand.h>
8 
9 #include <boost/container/flat_map.hpp>
10 #include <boost/uuid/uuid.hpp>
11 #include <boost/uuid/uuid_generators.hpp>
12 #include <boost/uuid/uuid_io.hpp>
13 #include <dbus_singleton.hpp>
14 #include <nlohmann/json.hpp>
15 #include <pam_authenticate.hpp>
16 #include <sdbusplus/bus/match.hpp>
17 
18 #include <csignal>
19 #include <random>
20 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
21 #include <ibm/locks.hpp>
22 #endif
23 
24 namespace persistent_data
25 {
26 
27 // 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
30 constexpr std::size_t sessionTokenSize = 20;
31 
32 enum 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.
36 };
37 
38 struct UserSession
39 {
40     std::string uniqueId;
41     std::string sessionToken;
42     std::string username;
43     std::string csrfToken;
44     std::string clientId;
45     std::string clientIp;
46     std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
47     PersistenceType persistence;
48     bool cookieAuth = false;
49     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.
58 
59     /**
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                 continue;
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 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
100             else if (element.key() == "client_id")
101             {
102                 userSession->clientId = *thisValue;
103             }
104 #endif
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                 continue;
116             }
117         }
118         // 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         }
129 
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;
140     }
141 };
142 
143 struct AuthConfigMethods
144 {
145 #ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
146     bool basic = true;
147 #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
172     bool tls = false;
173 #endif
174 
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             }
201             else if (element.key() == "TLS")
202             {
203                 tls = *value;
204             }
205         }
206     }
207 };
208 
209 class SessionStore
210 {
211   public:
212     std::shared_ptr<UserSession> generateUserSession(
213         const std::string_view username,
214         PersistenceType persistence = PersistenceType::TIMEOUT,
215         bool isConfigureSelfOnly = false, const std::string_view clientId = "",
216         const std::string_view clientIp = "")
217     {
218         // TODO(ed) find a secure way to not generate session identifiers if
219         // persistence is set to SINGLE_REQUEST
220         static constexpr std::array<char, 62> alphanum = {
221             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
222             'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
223             'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
224             'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
225             'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
226 
227         std::string sessionToken;
228         sessionToken.resize(sessionTokenSize, '0');
229         std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
230 
231         bmcweb::OpenSSLGenerator gen;
232 
233         for (char& sessionChar : sessionToken)
234         {
235             sessionChar = alphanum[dist(gen)];
236             if (gen.error())
237             {
238                 return nullptr;
239             }
240         }
241         // Only need csrf tokens for cookie based auth, token doesn't matter
242         std::string csrfToken;
243         csrfToken.resize(sessionTokenSize, '0');
244         for (char& csrfChar : csrfToken)
245         {
246             csrfChar = alphanum[dist(gen)];
247             if (gen.error())
248             {
249                 return nullptr;
250             }
251         }
252 
253         std::string uniqueId;
254         uniqueId.resize(10, '0');
255         for (char& uidChar : uniqueId)
256         {
257             uidChar = alphanum[dist(gen)];
258             if (gen.error())
259             {
260                 return nullptr;
261             }
262         }
263         auto session = std::make_shared<UserSession>(
264             UserSession{uniqueId, sessionToken, std::string(username),
265                         csrfToken, std::string(clientId), std::string(clientIp),
266                         std::chrono::steady_clock::now(), persistence, false,
267                         isConfigureSelfOnly});
268         auto it = authTokens.emplace(std::make_pair(sessionToken, session));
269         // Only need to write to disk if session isn't about to be destroyed.
270         needWrite = persistence == PersistenceType::TIMEOUT;
271         return it.first->second;
272     }
273 
274     std::shared_ptr<UserSession>
275         loginSessionByToken(const std::string_view token)
276     {
277         applySessionTimeouts();
278         if (token.size() != sessionTokenSize)
279         {
280             return nullptr;
281         }
282         auto sessionIt = authTokens.find(std::string(token));
283         if (sessionIt == authTokens.end())
284         {
285             return nullptr;
286         }
287         std::shared_ptr<UserSession> userSession = sessionIt->second;
288         userSession->lastUpdated = std::chrono::steady_clock::now();
289         return userSession;
290     }
291 
292     std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid)
293     {
294         applySessionTimeouts();
295         // TODO(Ed) this is inefficient
296         auto sessionIt = authTokens.begin();
297         while (sessionIt != authTokens.end())
298         {
299             if (sessionIt->second->uniqueId == uid)
300             {
301                 return sessionIt->second;
302             }
303             sessionIt++;
304         }
305         return nullptr;
306     }
307 
308     void removeSession(const std::shared_ptr<UserSession>& session)
309     {
310 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
311         crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
312 #endif
313         authTokens.erase(session->sessionToken);
314         needWrite = true;
315     }
316 
317     std::vector<const std::string*> getUniqueIds(
318         bool getAll = true,
319         const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
320     {
321         applySessionTimeouts();
322 
323         std::vector<const std::string*> ret;
324         ret.reserve(authTokens.size());
325         for (auto& session : authTokens)
326         {
327             if (getAll || type == session.second->persistence)
328             {
329                 ret.push_back(&session.second->uniqueId);
330             }
331         }
332         return ret;
333     }
334 
335     void updateAuthMethodsConfig(const AuthConfigMethods& config)
336     {
337         bool isTLSchanged = (authMethodsConfig.tls != config.tls);
338         authMethodsConfig = config;
339         needWrite = true;
340         if (isTLSchanged)
341         {
342             // recreate socket connections with new settings
343             std::raise(SIGHUP);
344         }
345     }
346 
347     AuthConfigMethods& getAuthMethodsConfig()
348     {
349         return authMethodsConfig;
350     }
351 
352     bool needsWrite()
353     {
354         return needWrite;
355     }
356     int64_t getTimeoutInSeconds() const
357     {
358         return std::chrono::seconds(timeoutInSeconds).count();
359     }
360 
361     void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds)
362     {
363         timeoutInSeconds = newTimeoutInSeconds;
364         needWrite = true;
365     }
366 
367     static SessionStore& getInstance()
368     {
369         static SessionStore sessionStore;
370         return sessionStore;
371     }
372 
373     void applySessionTimeouts()
374     {
375         auto timeNow = std::chrono::steady_clock::now();
376         if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1))
377         {
378             lastTimeoutUpdate = timeNow;
379             auto authTokensIt = authTokens.begin();
380             while (authTokensIt != authTokens.end())
381             {
382                 if (timeNow - authTokensIt->second->lastUpdated >=
383                     timeoutInSeconds)
384                 {
385 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
386                     crow::ibm_mc_lock::Lock::getInstance().releaseLock(
387                         authTokensIt->second->uniqueId);
388 #endif
389                     authTokensIt = authTokens.erase(authTokensIt);
390 
391                     needWrite = true;
392                 }
393                 else
394                 {
395                     authTokensIt++;
396                 }
397             }
398         }
399     }
400 
401     SessionStore(const SessionStore&) = delete;
402     SessionStore& operator=(const SessionStore&) = delete;
403 
404     std::unordered_map<std::string, std::shared_ptr<UserSession>,
405                        std::hash<std::string>,
406                        crow::utility::ConstantTimeCompare>
407         authTokens;
408 
409     std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
410     bool needWrite{false};
411     std::chrono::seconds timeoutInSeconds;
412     AuthConfigMethods authMethodsConfig;
413 
414   private:
415     SessionStore() : timeoutInSeconds(3600)
416     {}
417 };
418 
419 } // namespace persistent_data
420