xref: /openbmc/bmcweb/include/sessions.hpp (revision 81d523a7)
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/uuid/uuid.hpp>
10 #include <boost/uuid/uuid_generators.hpp>
11 #include <boost/uuid/uuid_io.hpp>
12 #include <dbus_singleton.hpp>
13 #include <nlohmann/json.hpp>
14 #include <pam_authenticate.hpp>
15 #include <random.hpp>
16 #include <sdbusplus/bus/match.hpp>
17 #include <utils/ip_utils.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{PersistenceType::TIMEOUT};
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,
215         const boost::asio::ip::address& clientIp,
216         const std::string_view clientId,
217         PersistenceType persistence = PersistenceType::TIMEOUT,
218         bool isConfigureSelfOnly = false)
219     {
220         // TODO(ed) find a secure way to not generate session identifiers if
221         // persistence is set to SINGLE_REQUEST
222         static constexpr std::array<char, 62> alphanum = {
223             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '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', 'a', 'b', 'c',
226             'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
227             'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
228 
229         std::string sessionToken;
230         sessionToken.resize(sessionTokenSize, '0');
231         std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
232 
233         bmcweb::OpenSSLGenerator gen;
234 
235         for (char& sessionChar : sessionToken)
236         {
237             sessionChar = alphanum[dist(gen)];
238             if (gen.error())
239             {
240                 return nullptr;
241             }
242         }
243         // Only need csrf tokens for cookie based auth, token doesn't matter
244         std::string csrfToken;
245         csrfToken.resize(sessionTokenSize, '0');
246         for (char& csrfChar : csrfToken)
247         {
248             csrfChar = alphanum[dist(gen)];
249             if (gen.error())
250             {
251                 return nullptr;
252             }
253         }
254 
255         std::string uniqueId;
256         uniqueId.resize(10, '0');
257         for (char& uidChar : uniqueId)
258         {
259             uidChar = alphanum[dist(gen)];
260             if (gen.error())
261             {
262                 return nullptr;
263             }
264         }
265 
266         auto session = std::make_shared<UserSession>(UserSession{
267             uniqueId, sessionToken, std::string(username), csrfToken,
268             std::string(clientId), redfish::ip_util::toString(clientIp),
269             std::chrono::steady_clock::now(), persistence, false,
270             isConfigureSelfOnly});
271         auto it = authTokens.emplace(std::make_pair(sessionToken, session));
272         // Only need to write to disk if session isn't about to be destroyed.
273         needWrite = persistence == PersistenceType::TIMEOUT;
274         return it.first->second;
275     }
276 
277     std::shared_ptr<UserSession>
278         loginSessionByToken(const std::string_view token)
279     {
280         applySessionTimeouts();
281         if (token.size() != sessionTokenSize)
282         {
283             return nullptr;
284         }
285         auto sessionIt = authTokens.find(std::string(token));
286         if (sessionIt == authTokens.end())
287         {
288             return nullptr;
289         }
290         std::shared_ptr<UserSession> userSession = sessionIt->second;
291         userSession->lastUpdated = std::chrono::steady_clock::now();
292         return userSession;
293     }
294 
295     std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid)
296     {
297         applySessionTimeouts();
298         // TODO(Ed) this is inefficient
299         auto sessionIt = authTokens.begin();
300         while (sessionIt != authTokens.end())
301         {
302             if (sessionIt->second->uniqueId == uid)
303             {
304                 return sessionIt->second;
305             }
306             sessionIt++;
307         }
308         return nullptr;
309     }
310 
311     void removeSession(const std::shared_ptr<UserSession>& session)
312     {
313 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
314         crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
315 #endif
316         authTokens.erase(session->sessionToken);
317         needWrite = true;
318     }
319 
320     std::vector<const std::string*> getUniqueIds(
321         bool getAll = true,
322         const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
323     {
324         applySessionTimeouts();
325 
326         std::vector<const std::string*> ret;
327         ret.reserve(authTokens.size());
328         for (auto& session : authTokens)
329         {
330             if (getAll || type == session.second->persistence)
331             {
332                 ret.push_back(&session.second->uniqueId);
333             }
334         }
335         return ret;
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