xref: /openbmc/bmcweb/include/sessions.hpp (revision 6c068982)
1 #pragma once
2 
3 #include "logging.hpp"
4 #include "random.hpp"
5 #include "utility.hpp"
6 #include "utils/ip_utils.hpp"
7 
8 #include <nlohmann/json.hpp>
9 
10 #include <algorithm>
11 #include <csignal>
12 #include <optional>
13 #include <random>
14 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
15 #include "ibm/locks.hpp"
16 #endif
17 
18 namespace persistent_data
19 {
20 
21 // entropy: 20 characters, 62 possibilities.  log2(62^20) = 119 bits of
22 // entropy.  OWASP recommends at least 64
23 // https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy
24 constexpr std::size_t sessionTokenSize = 20;
25 
26 enum class PersistenceType
27 {
28     TIMEOUT, // User session times out after a predetermined amount of time
29     SINGLE_REQUEST // User times out once this request is completed.
30 };
31 
32 struct UserSession
33 {
34     std::string uniqueId;
35     std::string sessionToken;
36     std::string username;
37     std::string csrfToken;
38     std::optional<std::string> clientId;
39     std::string clientIp;
40     std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
41     PersistenceType persistence{PersistenceType::TIMEOUT};
42     bool cookieAuth = false;
43     bool isConfigureSelfOnly = false;
44 
45     // There are two sources of truth for isConfigureSelfOnly:
46     //  1. When pamAuthenticateUser() returns PAM_NEW_AUTHTOK_REQD.
47     //  2. D-Bus User.Manager.GetUserInfo property UserPasswordExpired.
48     // These should be in sync, but the underlying condition can change at any
49     // time.  For example, a password can expire or be changed outside of
50     // bmcweb.  The value stored here is updated at the start of each
51     // operation and used as the truth within bmcweb.
52 
53     /**
54      * @brief Fills object with data from UserSession's JSON representation
55      *
56      * This replaces nlohmann's from_json to ensure no-throw approach
57      *
58      * @param[in] j   JSON object from which data should be loaded
59      *
60      * @return a shared pointer if data has been loaded properly, nullptr
61      * otherwise
62      */
63     static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
64     {
65         std::shared_ptr<UserSession> userSession =
66             std::make_shared<UserSession>();
67         for (const auto& element : j.items())
68         {
69             const std::string* thisValue =
70                 element.value().get_ptr<const std::string*>();
71             if (thisValue == nullptr)
72             {
73                 BMCWEB_LOG_ERROR << "Error reading persistent store.  Property "
74                                  << element.key() << " was not of type string";
75                 continue;
76             }
77             if (element.key() == "unique_id")
78             {
79                 userSession->uniqueId = *thisValue;
80             }
81             else if (element.key() == "session_token")
82             {
83                 userSession->sessionToken = *thisValue;
84             }
85             else if (element.key() == "csrf_token")
86             {
87                 userSession->csrfToken = *thisValue;
88             }
89             else if (element.key() == "username")
90             {
91                 userSession->username = *thisValue;
92             }
93             else if (element.key() == "client_id")
94             {
95                 userSession->clientId = *thisValue;
96             }
97             else if (element.key() == "client_ip")
98             {
99                 userSession->clientIp = *thisValue;
100             }
101 
102             else
103             {
104                 BMCWEB_LOG_ERROR
105                     << "Got unexpected property reading persistent file: "
106                     << element.key();
107                 continue;
108             }
109         }
110         // If any of these fields are missing, we can't restore the session, as
111         // we don't have enough information.  These 4 fields have been present
112         // in every version of this file in bmcwebs history, so any file, even
113         // on upgrade, should have these present
114         if (userSession->uniqueId.empty() || userSession->username.empty() ||
115             userSession->sessionToken.empty() || userSession->csrfToken.empty())
116         {
117             BMCWEB_LOG_DEBUG << "Session missing required security "
118                                 "information, refusing to restore";
119             return nullptr;
120         }
121 
122         // For now, sessions that were persisted through a reboot get their idle
123         // timer reset.  This could probably be overcome with a better
124         // understanding of wall clock time and steady timer time, possibly
125         // persisting values with wall clock time instead of steady timer, but
126         // the tradeoffs of all the corner cases involved are non-trivial, so
127         // this is done temporarily
128         userSession->lastUpdated = std::chrono::steady_clock::now();
129         userSession->persistence = PersistenceType::TIMEOUT;
130 
131         return userSession;
132     }
133 };
134 
135 struct AuthConfigMethods
136 {
137 #ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
138     bool basic = true;
139 #else
140     bool basic = false;
141 #endif
142 
143 #ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
144     bool sessionToken = true;
145 #else
146     bool sessionToken = false;
147 #endif
148 
149 #ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
150     bool xtoken = true;
151 #else
152     bool xtoken = false;
153 #endif
154 
155 #ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
156     bool cookie = true;
157 #else
158     bool cookie = false;
159 #endif
160 
161 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
162     bool tls = true;
163 #else
164     bool tls = false;
165 #endif
166 
167     void fromJson(const nlohmann::json& j)
168     {
169         for (const auto& element : j.items())
170         {
171             const bool* value = element.value().get_ptr<const bool*>();
172             if (value == nullptr)
173             {
174                 continue;
175             }
176 
177             if (element.key() == "XToken")
178             {
179                 xtoken = *value;
180             }
181             else if (element.key() == "Cookie")
182             {
183                 cookie = *value;
184             }
185             else if (element.key() == "SessionToken")
186             {
187                 sessionToken = *value;
188             }
189             else if (element.key() == "BasicAuth")
190             {
191                 basic = *value;
192             }
193             else if (element.key() == "TLS")
194             {
195                 tls = *value;
196             }
197         }
198     }
199 };
200 
201 class SessionStore
202 {
203   public:
204     std::shared_ptr<UserSession> generateUserSession(
205         std::string_view username, const boost::asio::ip::address& clientIp,
206         const std::optional<std::string>& clientId,
207         PersistenceType persistence = PersistenceType::TIMEOUT,
208         bool isConfigureSelfOnly = false)
209     {
210         // TODO(ed) find a secure way to not generate session identifiers if
211         // persistence is set to SINGLE_REQUEST
212         static constexpr std::array<char, 62> alphanum = {
213             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
214             'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
215             'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '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'};
218 
219         std::string sessionToken;
220         sessionToken.resize(sessionTokenSize, '0');
221         std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
222 
223         bmcweb::OpenSSLGenerator gen;
224 
225         for (char& sessionChar : sessionToken)
226         {
227             sessionChar = alphanum[dist(gen)];
228             if (gen.error())
229             {
230                 return nullptr;
231             }
232         }
233         // Only need csrf tokens for cookie based auth, token doesn't matter
234         std::string csrfToken;
235         csrfToken.resize(sessionTokenSize, '0');
236         for (char& csrfChar : csrfToken)
237         {
238             csrfChar = alphanum[dist(gen)];
239             if (gen.error())
240             {
241                 return nullptr;
242             }
243         }
244 
245         std::string uniqueId;
246         uniqueId.resize(10, '0');
247         for (char& uidChar : uniqueId)
248         {
249             uidChar = alphanum[dist(gen)];
250             if (gen.error())
251             {
252                 return nullptr;
253             }
254         }
255 
256         auto session = std::make_shared<UserSession>(UserSession{
257             uniqueId, sessionToken, std::string(username), csrfToken, clientId,
258             redfish::ip_util::toString(clientIp),
259             std::chrono::steady_clock::now(), persistence, false,
260             isConfigureSelfOnly});
261         auto it = authTokens.emplace(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> loginSessionByToken(std::string_view token)
268     {
269         applySessionTimeouts();
270         if (token.size() != sessionTokenSize)
271         {
272             return nullptr;
273         }
274         auto sessionIt = authTokens.find(std::string(token));
275         if (sessionIt == authTokens.end())
276         {
277             return nullptr;
278         }
279         std::shared_ptr<UserSession> userSession = sessionIt->second;
280         userSession->lastUpdated = std::chrono::steady_clock::now();
281         return userSession;
282     }
283 
284     std::shared_ptr<UserSession> getSessionByUid(std::string_view uid)
285     {
286         applySessionTimeouts();
287         // TODO(Ed) this is inefficient
288         auto sessionIt = authTokens.begin();
289         while (sessionIt != authTokens.end())
290         {
291             if (sessionIt->second->uniqueId == uid)
292             {
293                 return sessionIt->second;
294             }
295             sessionIt++;
296         }
297         return nullptr;
298     }
299 
300     void removeSession(const std::shared_ptr<UserSession>& session)
301     {
302 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
303         crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
304 #endif
305         authTokens.erase(session->sessionToken);
306         needWrite = true;
307     }
308 
309     std::vector<const std::string*> getUniqueIds(
310         bool getAll = true,
311         const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
312     {
313         applySessionTimeouts();
314 
315         std::vector<const std::string*> ret;
316         ret.reserve(authTokens.size());
317         for (auto& session : authTokens)
318         {
319             if (getAll || type == session.second->persistence)
320             {
321                 ret.push_back(&session.second->uniqueId);
322             }
323         }
324         return ret;
325     }
326 
327     void removeSessionsByUsername(std::string_view username)
328     {
329         std::erase_if(authTokens, [username](const auto& value) {
330             if (value.second == nullptr)
331             {
332                 return false;
333             }
334             return value.second->username == username;
335         });
336     }
337 
338     void updateAuthMethodsConfig(const AuthConfigMethods& config)
339     {
340         bool isTLSchanged = (authMethodsConfig.tls != config.tls);
341         authMethodsConfig = config;
342         needWrite = true;
343         if (isTLSchanged)
344         {
345             // recreate socket connections with new settings
346             std::raise(SIGHUP);
347         }
348     }
349 
350     AuthConfigMethods& getAuthMethodsConfig()
351     {
352         return authMethodsConfig;
353     }
354 
355     bool needsWrite() const
356     {
357         return needWrite;
358     }
359     int64_t getTimeoutInSeconds() const
360     {
361         return std::chrono::seconds(timeoutInSeconds).count();
362     }
363 
364     void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds)
365     {
366         timeoutInSeconds = newTimeoutInSeconds;
367         needWrite = true;
368     }
369 
370     static SessionStore& getInstance()
371     {
372         static SessionStore sessionStore;
373         return sessionStore;
374     }
375 
376     void applySessionTimeouts()
377     {
378         auto timeNow = std::chrono::steady_clock::now();
379         if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1))
380         {
381             lastTimeoutUpdate = timeNow;
382             auto authTokensIt = authTokens.begin();
383             while (authTokensIt != authTokens.end())
384             {
385                 if (timeNow - authTokensIt->second->lastUpdated >=
386                     timeoutInSeconds)
387                 {
388 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
389                     crow::ibm_mc_lock::Lock::getInstance().releaseLock(
390                         authTokensIt->second->uniqueId);
391 #endif
392                     authTokensIt = authTokens.erase(authTokensIt);
393 
394                     needWrite = true;
395                 }
396                 else
397                 {
398                     authTokensIt++;
399                 }
400             }
401         }
402     }
403 
404     SessionStore(const SessionStore&) = delete;
405     SessionStore& operator=(const SessionStore&) = delete;
406     SessionStore(SessionStore&&) = delete;
407     SessionStore& operator=(const SessionStore&&) = delete;
408     ~SessionStore() = default;
409 
410     std::unordered_map<std::string, std::shared_ptr<UserSession>,
411                        std::hash<std::string>,
412                        crow::utility::ConstantTimeCompare>
413         authTokens;
414 
415     std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
416     bool needWrite{false};
417     std::chrono::seconds timeoutInSeconds;
418     AuthConfigMethods authMethodsConfig;
419 
420   private:
421     SessionStore() : timeoutInSeconds(1800)
422     {}
423 };
424 
425 } // namespace persistent_data
426