xref: /openbmc/bmcweb/include/sessions.hpp (revision 9d424669)
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 <random.hpp>
17 #include <sdbusplus/bus/match.hpp>
18 
19 #include <csignal>
20 #include <random>
21 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
22 #include <ibm/locks.hpp>
23 #endif
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::string clientId;
46     std::string clientIp;
47     std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
48     PersistenceType persistence;
49     bool cookieAuth = false;
50     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.
59 
60     /**
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";
82                 continue;
83             }
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             }
100 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
101             else if (element.key() == "client_id")
102             {
103                 userSession->clientId = *thisValue;
104             }
105 #endif
106             else if (element.key() == "client_ip")
107             {
108                 userSession->clientIp = *thisValue;
109             }
110 
111             else
112             {
113                 BMCWEB_LOG_ERROR
114                     << "Got unexpected property reading persistent file: "
115                     << element.key();
116                 continue;
117             }
118         }
119         // 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         }
130 
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;
141     }
142 };
143 
144 struct AuthConfigMethods
145 {
146 #ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
147     bool basic = true;
148 #else
149     bool basic = false;
150 #endif
151 
152 #ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
153     bool sessionToken = true;
154 #else
155     bool sessionToken = false;
156 #endif
157 
158 #ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
159     bool xtoken = true;
160 #else
161     bool xtoken = false;
162 #endif
163 
164 #ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
165     bool cookie = true;
166 #else
167     bool cookie = false;
168 #endif
169 
170 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
171     bool tls = true;
172 #else
173     bool tls = false;
174 #endif
175 
176     void fromJson(const nlohmann::json& j)
177     {
178         for (const auto& element : j.items())
179         {
180             const bool* value = element.value().get_ptr<const bool*>();
181             if (value == nullptr)
182             {
183                 continue;
184             }
185 
186             if (element.key() == "XToken")
187             {
188                 xtoken = *value;
189             }
190             else if (element.key() == "Cookie")
191             {
192                 cookie = *value;
193             }
194             else if (element.key() == "SessionToken")
195             {
196                 sessionToken = *value;
197             }
198             else if (element.key() == "BasicAuth")
199             {
200                 basic = *value;
201             }
202             else if (element.key() == "TLS")
203             {
204                 tls = *value;
205             }
206         }
207     }
208 };
209 
210 class SessionStore
211 {
212   public:
213     std::shared_ptr<UserSession> generateUserSession(
214         const std::string_view username, const std::string_view clientIp,
215         const std::string_view clientId,
216         PersistenceType persistence = PersistenceType::TIMEOUT,
217         bool isConfigureSelfOnly = false)
218     {
219         // TODO(ed) find a secure way to not generate session identifiers if
220         // persistence is set to SINGLE_REQUEST
221         static constexpr std::array<char, 62> alphanum = {
222             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
223             'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
224             'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
225             'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
226             'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
227 
228         std::string sessionToken;
229         sessionToken.resize(sessionTokenSize, '0');
230         std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
231 
232         bmcweb::OpenSSLGenerator gen;
233 
234         for (char& sessionChar : sessionToken)
235         {
236             sessionChar = alphanum[dist(gen)];
237             if (gen.error())
238             {
239                 return nullptr;
240             }
241         }
242         // Only need csrf tokens for cookie based auth, token doesn't matter
243         std::string csrfToken;
244         csrfToken.resize(sessionTokenSize, '0');
245         for (char& csrfChar : csrfToken)
246         {
247             csrfChar = alphanum[dist(gen)];
248             if (gen.error())
249             {
250                 return nullptr;
251             }
252         }
253 
254         std::string uniqueId;
255         uniqueId.resize(10, '0');
256         for (char& uidChar : uniqueId)
257         {
258             uidChar = alphanum[dist(gen)];
259             if (gen.error())
260             {
261                 return nullptr;
262             }
263         }
264         auto session = std::make_shared<UserSession>(
265             UserSession{uniqueId, sessionToken, std::string(username),
266                         csrfToken, std::string(clientId), std::string(clientIp),
267                         std::chrono::steady_clock::now(), persistence, false,
268                         isConfigureSelfOnly});
269         auto it = authTokens.emplace(std::make_pair(sessionToken, session));
270         // Only need to write to disk if session isn't about to be destroyed.
271         needWrite = persistence == PersistenceType::TIMEOUT;
272         return it.first->second;
273     }
274 
275     std::shared_ptr<UserSession>
276         loginSessionByToken(const std::string_view token)
277     {
278         applySessionTimeouts();
279         if (token.size() != sessionTokenSize)
280         {
281             return nullptr;
282         }
283         auto sessionIt = authTokens.find(std::string(token));
284         if (sessionIt == authTokens.end())
285         {
286             return nullptr;
287         }
288         std::shared_ptr<UserSession> userSession = sessionIt->second;
289         userSession->lastUpdated = std::chrono::steady_clock::now();
290         return userSession;
291     }
292 
293     std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid)
294     {
295         applySessionTimeouts();
296         // TODO(Ed) this is inefficient
297         auto sessionIt = authTokens.begin();
298         while (sessionIt != authTokens.end())
299         {
300             if (sessionIt->second->uniqueId == uid)
301             {
302                 return sessionIt->second;
303             }
304             sessionIt++;
305         }
306         return nullptr;
307     }
308 
309     void removeSession(const std::shared_ptr<UserSession>& session)
310     {
311 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
312         crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
313 #endif
314         authTokens.erase(session->sessionToken);
315         needWrite = true;
316     }
317 
318     std::vector<const std::string*> getUniqueIds(
319         bool getAll = true,
320         const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
321     {
322         applySessionTimeouts();
323 
324         std::vector<const std::string*> ret;
325         ret.reserve(authTokens.size());
326         for (auto& session : authTokens)
327         {
328             if (getAll || type == session.second->persistence)
329             {
330                 ret.push_back(&session.second->uniqueId);
331             }
332         }
333         return ret;
334     }
335 
336     void updateAuthMethodsConfig(const AuthConfigMethods& config)
337     {
338         bool isTLSchanged = (authMethodsConfig.tls != config.tls);
339         authMethodsConfig = config;
340         needWrite = true;
341         if (isTLSchanged)
342         {
343             // recreate socket connections with new settings
344             std::raise(SIGHUP);
345         }
346     }
347 
348     AuthConfigMethods& getAuthMethodsConfig()
349     {
350         return authMethodsConfig;
351     }
352 
353     bool needsWrite()
354     {
355         return needWrite;
356     }
357     int64_t getTimeoutInSeconds() const
358     {
359         return std::chrono::seconds(timeoutInSeconds).count();
360     }
361 
362     void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds)
363     {
364         timeoutInSeconds = newTimeoutInSeconds;
365         needWrite = true;
366     }
367 
368     static SessionStore& getInstance()
369     {
370         static SessionStore sessionStore;
371         return sessionStore;
372     }
373 
374     void applySessionTimeouts()
375     {
376         auto timeNow = std::chrono::steady_clock::now();
377         if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1))
378         {
379             lastTimeoutUpdate = timeNow;
380             auto authTokensIt = authTokens.begin();
381             while (authTokensIt != authTokens.end())
382             {
383                 if (timeNow - authTokensIt->second->lastUpdated >=
384                     timeoutInSeconds)
385                 {
386 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
387                     crow::ibm_mc_lock::Lock::getInstance().releaseLock(
388                         authTokensIt->second->uniqueId);
389 #endif
390                     authTokensIt = authTokens.erase(authTokensIt);
391 
392                     needWrite = true;
393                 }
394                 else
395                 {
396                     authTokensIt++;
397                 }
398             }
399         }
400     }
401 
402     SessionStore(const SessionStore&) = delete;
403     SessionStore& operator=(const SessionStore&) = delete;
404 
405     std::unordered_map<std::string, std::shared_ptr<UserSession>,
406                        std::hash<std::string>,
407                        crow::utility::ConstantTimeCompare>
408         authTokens;
409 
410     std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
411     bool needWrite{false};
412     std::chrono::seconds timeoutInSeconds;
413     AuthConfigMethods authMethodsConfig;
414 
415   private:
416     SessionStore() : timeoutInSeconds(1800)
417     {}
418 };
419 
420 } // namespace persistent_data
421