xref: /openbmc/bmcweb/include/sessions.hpp (revision 141d9431)
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 #include <utils/ip_utils.hpp>
19 
20 #include <csignal>
21 #include <random>
22 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
23 #include <ibm/locks.hpp>
24 #endif
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{PersistenceType::TIMEOUT};
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                 continue;
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 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
102             else if (element.key() == "client_id")
103             {
104                 userSession->clientId = *thisValue;
105             }
106 #endif
107             else if (element.key() == "client_ip")
108             {
109                 userSession->clientIp = *thisValue;
110             }
111 
112             else
113             {
114                 BMCWEB_LOG_ERROR
115                     << "Got unexpected property reading persistent file: "
116                     << element.key();
117                 continue;
118             }
119         }
120         // If any of these fields are missing, we can't restore the session, as
121         // we don't have enough information.  These 4 fields have been present
122         // in every version of this file in bmcwebs history, so any file, even
123         // on upgrade, should have these present
124         if (userSession->uniqueId.empty() || userSession->username.empty() ||
125             userSession->sessionToken.empty() || userSession->csrfToken.empty())
126         {
127             BMCWEB_LOG_DEBUG << "Session missing required security "
128                                 "information, refusing to restore";
129             return nullptr;
130         }
131 
132         // For now, sessions that were persisted through a reboot get their idle
133         // timer reset.  This could probably be overcome with a better
134         // understanding of wall clock time and steady timer time, possibly
135         // persisting values with wall clock time instead of steady timer, but
136         // the tradeoffs of all the corner cases involved are non-trivial, so
137         // this is done temporarily
138         userSession->lastUpdated = std::chrono::steady_clock::now();
139         userSession->persistence = PersistenceType::TIMEOUT;
140 
141         return userSession;
142     }
143 };
144 
145 struct AuthConfigMethods
146 {
147 #ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
148     bool basic = true;
149 #else
150     bool basic = false;
151 #endif
152 
153 #ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
154     bool sessionToken = true;
155 #else
156     bool sessionToken = false;
157 #endif
158 
159 #ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
160     bool xtoken = true;
161 #else
162     bool xtoken = false;
163 #endif
164 
165 #ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
166     bool cookie = true;
167 #else
168     bool cookie = false;
169 #endif
170 
171 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
172     bool tls = true;
173 #else
174     bool tls = false;
175 #endif
176 
177     void fromJson(const nlohmann::json& j)
178     {
179         for (const auto& element : j.items())
180         {
181             const bool* value = element.value().get_ptr<const bool*>();
182             if (value == nullptr)
183             {
184                 continue;
185             }
186 
187             if (element.key() == "XToken")
188             {
189                 xtoken = *value;
190             }
191             else if (element.key() == "Cookie")
192             {
193                 cookie = *value;
194             }
195             else if (element.key() == "SessionToken")
196             {
197                 sessionToken = *value;
198             }
199             else if (element.key() == "BasicAuth")
200             {
201                 basic = *value;
202             }
203             else if (element.key() == "TLS")
204             {
205                 tls = *value;
206             }
207         }
208     }
209 };
210 
211 class SessionStore
212 {
213   public:
214     std::shared_ptr<UserSession> generateUserSession(
215         const std::string_view username,
216         const boost::asio::ip::address& clientIp,
217         const std::string_view clientId,
218         PersistenceType persistence = PersistenceType::TIMEOUT,
219         bool isConfigureSelfOnly = false)
220     {
221         // TODO(ed) find a secure way to not generate session identifiers if
222         // persistence is set to SINGLE_REQUEST
223         static constexpr std::array<char, 62> alphanum = {
224             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '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', 'a', 'b', 'c',
227             'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
228             'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
229 
230         std::string sessionToken;
231         sessionToken.resize(sessionTokenSize, '0');
232         std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
233 
234         bmcweb::OpenSSLGenerator gen;
235 
236         for (char& sessionChar : sessionToken)
237         {
238             sessionChar = alphanum[dist(gen)];
239             if (gen.error())
240             {
241                 return nullptr;
242             }
243         }
244         // Only need csrf tokens for cookie based auth, token doesn't matter
245         std::string csrfToken;
246         csrfToken.resize(sessionTokenSize, '0');
247         for (char& csrfChar : csrfToken)
248         {
249             csrfChar = alphanum[dist(gen)];
250             if (gen.error())
251             {
252                 return nullptr;
253             }
254         }
255 
256         std::string uniqueId;
257         uniqueId.resize(10, '0');
258         for (char& uidChar : uniqueId)
259         {
260             uidChar = alphanum[dist(gen)];
261             if (gen.error())
262             {
263                 return nullptr;
264             }
265         }
266 
267         auto session = std::make_shared<UserSession>(UserSession{
268             uniqueId, sessionToken, std::string(username), csrfToken,
269             std::string(clientId), redfish::ip_util::toString(clientIp),
270             std::chrono::steady_clock::now(), persistence, false,
271             isConfigureSelfOnly});
272         auto it = authTokens.emplace(std::make_pair(sessionToken, session));
273         // Only need to write to disk if session isn't about to be destroyed.
274         needWrite = persistence == PersistenceType::TIMEOUT;
275         return it.first->second;
276     }
277 
278     std::shared_ptr<UserSession>
279         loginSessionByToken(const std::string_view token)
280     {
281         applySessionTimeouts();
282         if (token.size() != sessionTokenSize)
283         {
284             return nullptr;
285         }
286         auto sessionIt = authTokens.find(std::string(token));
287         if (sessionIt == authTokens.end())
288         {
289             return nullptr;
290         }
291         std::shared_ptr<UserSession> userSession = sessionIt->second;
292         userSession->lastUpdated = std::chrono::steady_clock::now();
293         return userSession;
294     }
295 
296     std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid)
297     {
298         applySessionTimeouts();
299         // TODO(Ed) this is inefficient
300         auto sessionIt = authTokens.begin();
301         while (sessionIt != authTokens.end())
302         {
303             if (sessionIt->second->uniqueId == uid)
304             {
305                 return sessionIt->second;
306             }
307             sessionIt++;
308         }
309         return nullptr;
310     }
311 
312     void removeSession(const std::shared_ptr<UserSession>& session)
313     {
314 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
315         crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
316 #endif
317         authTokens.erase(session->sessionToken);
318         needWrite = true;
319     }
320 
321     std::vector<const std::string*> getUniqueIds(
322         bool getAll = true,
323         const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
324     {
325         applySessionTimeouts();
326 
327         std::vector<const std::string*> ret;
328         ret.reserve(authTokens.size());
329         for (auto& session : authTokens)
330         {
331             if (getAll || type == session.second->persistence)
332             {
333                 ret.push_back(&session.second->uniqueId);
334             }
335         }
336         return ret;
337     }
338 
339     void updateAuthMethodsConfig(const AuthConfigMethods& config)
340     {
341         bool isTLSchanged = (authMethodsConfig.tls != config.tls);
342         authMethodsConfig = config;
343         needWrite = true;
344         if (isTLSchanged)
345         {
346             // recreate socket connections with new settings
347             std::raise(SIGHUP);
348         }
349     }
350 
351     AuthConfigMethods& getAuthMethodsConfig()
352     {
353         return authMethodsConfig;
354     }
355 
356     bool needsWrite() const
357     {
358         return needWrite;
359     }
360     int64_t getTimeoutInSeconds() const
361     {
362         return std::chrono::seconds(timeoutInSeconds).count();
363     }
364 
365     void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds)
366     {
367         timeoutInSeconds = newTimeoutInSeconds;
368         needWrite = true;
369     }
370 
371     static SessionStore& getInstance()
372     {
373         static SessionStore sessionStore;
374         return sessionStore;
375     }
376 
377     void applySessionTimeouts()
378     {
379         auto timeNow = std::chrono::steady_clock::now();
380         if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1))
381         {
382             lastTimeoutUpdate = timeNow;
383             auto authTokensIt = authTokens.begin();
384             while (authTokensIt != authTokens.end())
385             {
386                 if (timeNow - authTokensIt->second->lastUpdated >=
387                     timeoutInSeconds)
388                 {
389 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
390                     crow::ibm_mc_lock::Lock::getInstance().releaseLock(
391                         authTokensIt->second->uniqueId);
392 #endif
393                     authTokensIt = authTokens.erase(authTokensIt);
394 
395                     needWrite = true;
396                 }
397                 else
398                 {
399                     authTokensIt++;
400                 }
401             }
402         }
403     }
404 
405     SessionStore(const SessionStore&) = delete;
406     SessionStore& operator=(const SessionStore&) = delete;
407     SessionStore(SessionStore&&) = delete;
408     SessionStore& operator=(const SessionStore&&) = delete;
409     ~SessionStore() = default;
410 
411     std::unordered_map<std::string, std::shared_ptr<UserSession>,
412                        std::hash<std::string>,
413                        crow::utility::ConstantTimeCompare>
414         authTokens;
415 
416     std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
417     bool needWrite{false};
418     std::chrono::seconds timeoutInSeconds;
419     AuthConfigMethods authMethodsConfig;
420 
421   private:
422     SessionStore() : timeoutInSeconds(1800)
423     {}
424 };
425 
426 } // namespace persistent_data
427