xref: /openbmc/bmcweb/include/authentication.hpp (revision 89cda63d6646074f4be2d49981a5609f761c015f)
1d055a34aSNan Zhou #pragma once
2d055a34aSNan Zhou 
329aab242SPaul Fertser #include "cookies.hpp"
43ccb3adbSEd Tanous #include "forward_unauthorized.hpp"
53ccb3adbSEd Tanous #include "http_request.hpp"
63ccb3adbSEd Tanous #include "http_response.hpp"
73ccb3adbSEd Tanous #include "http_utility.hpp"
83ccb3adbSEd Tanous #include "pam_authenticate.hpp"
9d055a34aSNan Zhou #include "webroutes.hpp"
10d055a34aSNan Zhou 
11d055a34aSNan Zhou #include <boost/container/flat_set.hpp>
12d055a34aSNan Zhou 
13d055a34aSNan Zhou #include <random>
14d055a34aSNan Zhou #include <utility>
15d055a34aSNan Zhou 
16d055a34aSNan Zhou namespace crow
17d055a34aSNan Zhou {
18d055a34aSNan Zhou 
19d055a34aSNan Zhou namespace authentication
20d055a34aSNan Zhou {
21d055a34aSNan Zhou 
2225b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession>
23d055a34aSNan Zhou     performBasicAuth(const boost::asio::ip::address& clientIp,
24d055a34aSNan Zhou                      std::string_view authHeader)
25d055a34aSNan Zhou {
2662598e31SEd Tanous     BMCWEB_LOG_DEBUG("[AuthMiddleware] Basic authentication");
27d055a34aSNan Zhou 
2811ba3979SEd Tanous     if (!authHeader.starts_with("Basic "))
29d055a34aSNan Zhou     {
30d055a34aSNan Zhou         return nullptr;
31d055a34aSNan Zhou     }
32d055a34aSNan Zhou 
33d055a34aSNan Zhou     std::string_view param = authHeader.substr(strlen("Basic "));
34d055a34aSNan Zhou     std::string authData;
35d055a34aSNan Zhou 
36d055a34aSNan Zhou     if (!crow::utility::base64Decode(param, authData))
37d055a34aSNan Zhou     {
38d055a34aSNan Zhou         return nullptr;
39d055a34aSNan Zhou     }
40d055a34aSNan Zhou     std::size_t separator = authData.find(':');
41d055a34aSNan Zhou     if (separator == std::string::npos)
42d055a34aSNan Zhou     {
43d055a34aSNan Zhou         return nullptr;
44d055a34aSNan Zhou     }
45d055a34aSNan Zhou 
46d055a34aSNan Zhou     std::string user = authData.substr(0, separator);
47d055a34aSNan Zhou     separator += 1;
48d055a34aSNan Zhou     if (separator > authData.size())
49d055a34aSNan Zhou     {
50d055a34aSNan Zhou         return nullptr;
51d055a34aSNan Zhou     }
52d055a34aSNan Zhou     std::string pass = authData.substr(separator);
53d055a34aSNan Zhou 
5462598e31SEd Tanous     BMCWEB_LOG_DEBUG("[AuthMiddleware] Authenticating user: {}", user);
5562598e31SEd Tanous     BMCWEB_LOG_DEBUG("[AuthMiddleware] User IPAddress: {}",
5662598e31SEd Tanous                      clientIp.to_string());
57d055a34aSNan Zhou 
58d055a34aSNan Zhou     int pamrc = pamAuthenticateUser(user, pass);
59d055a34aSNan Zhou     bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
60d055a34aSNan Zhou     if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
61d055a34aSNan Zhou     {
62d055a34aSNan Zhou         return nullptr;
63d055a34aSNan Zhou     }
64d055a34aSNan Zhou 
65*89cda63dSEd Tanous     // Attempt to locate an existing Basic Auth session from the same ip address
66*89cda63dSEd Tanous     // and user
67*89cda63dSEd Tanous     for (auto& session :
68*89cda63dSEd Tanous          persistent_data::SessionStore::getInstance().getSessions())
69*89cda63dSEd Tanous     {
70*89cda63dSEd Tanous         if (session->sessionType != persistent_data::SessionType::Basic)
71*89cda63dSEd Tanous         {
72*89cda63dSEd Tanous             continue;
73*89cda63dSEd Tanous         }
74*89cda63dSEd Tanous         if (session->clientIp != redfish::ip_util::toString(clientIp))
75*89cda63dSEd Tanous         {
76*89cda63dSEd Tanous             continue;
77*89cda63dSEd Tanous         }
78*89cda63dSEd Tanous         if (session->username != user)
79*89cda63dSEd Tanous         {
80*89cda63dSEd Tanous             continue;
81*89cda63dSEd Tanous         }
82*89cda63dSEd Tanous         return session;
83*89cda63dSEd Tanous     }
84*89cda63dSEd Tanous 
85d055a34aSNan Zhou     return persistent_data::SessionStore::getInstance().generateUserSession(
86*89cda63dSEd Tanous         user, clientIp, std::nullopt, persistent_data::SessionType::Basic,
87*89cda63dSEd Tanous         isConfigureSelfOnly);
88d055a34aSNan Zhou }
89d055a34aSNan Zhou 
9025b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession>
91d055a34aSNan Zhou     performTokenAuth(std::string_view authHeader)
92d055a34aSNan Zhou {
9362598e31SEd Tanous     BMCWEB_LOG_DEBUG("[AuthMiddleware] Token authentication");
9411ba3979SEd Tanous     if (!authHeader.starts_with("Token "))
95d055a34aSNan Zhou     {
96d055a34aSNan Zhou         return nullptr;
97d055a34aSNan Zhou     }
98d055a34aSNan Zhou     std::string_view token = authHeader.substr(strlen("Token "));
99d055a34aSNan Zhou     auto sessionOut =
100d055a34aSNan Zhou         persistent_data::SessionStore::getInstance().loginSessionByToken(token);
101d055a34aSNan Zhou     return sessionOut;
102d055a34aSNan Zhou }
103d055a34aSNan Zhou 
10425b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession>
105d055a34aSNan Zhou     performXtokenAuth(const boost::beast::http::header<true>& reqHeader)
106d055a34aSNan Zhou {
10762598e31SEd Tanous     BMCWEB_LOG_DEBUG("[AuthMiddleware] X-Auth-Token authentication");
108d055a34aSNan Zhou 
109d055a34aSNan Zhou     std::string_view token = reqHeader["X-Auth-Token"];
110d055a34aSNan Zhou     if (token.empty())
111d055a34aSNan Zhou     {
112d055a34aSNan Zhou         return nullptr;
113d055a34aSNan Zhou     }
114d055a34aSNan Zhou     auto sessionOut =
115d055a34aSNan Zhou         persistent_data::SessionStore::getInstance().loginSessionByToken(token);
116d055a34aSNan Zhou     return sessionOut;
117d055a34aSNan Zhou }
118d055a34aSNan Zhou 
11925b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession>
1201d869608SEd Tanous     performCookieAuth(boost::beast::http::verb method [[maybe_unused]],
121d055a34aSNan Zhou                       const boost::beast::http::header<true>& reqHeader)
122d055a34aSNan Zhou {
1239f217c26SEd Tanous     using headers = boost::beast::http::header<true>;
1249f217c26SEd Tanous     std::pair<headers::const_iterator, headers::const_iterator> cookies =
1259f217c26SEd Tanous         reqHeader.equal_range(boost::beast::http::field::cookie);
126d055a34aSNan Zhou 
1279f217c26SEd Tanous     for (auto it = cookies.first; it != cookies.second; it++)
128d055a34aSNan Zhou     {
1299f217c26SEd Tanous         std::string_view cookieValue = it->value();
1309f217c26SEd Tanous         BMCWEB_LOG_DEBUG("Checking cookie {}", cookieValue);
131d055a34aSNan Zhou         auto startIndex = cookieValue.find("SESSION=");
132d055a34aSNan Zhou         if (startIndex == std::string::npos)
133d055a34aSNan Zhou         {
1349f217c26SEd Tanous             BMCWEB_LOG_DEBUG(
1359f217c26SEd Tanous                 "Cookie was present, but didn't look like a session {}",
1369f217c26SEd Tanous                 cookieValue);
1379f217c26SEd Tanous             continue;
138d055a34aSNan Zhou         }
139d055a34aSNan Zhou         startIndex += sizeof("SESSION=") - 1;
140d055a34aSNan Zhou         auto endIndex = cookieValue.find(';', startIndex);
141d055a34aSNan Zhou         if (endIndex == std::string::npos)
142d055a34aSNan Zhou         {
143d055a34aSNan Zhou             endIndex = cookieValue.size();
144d055a34aSNan Zhou         }
14589492a15SPatrick Williams         std::string_view authKey = cookieValue.substr(startIndex,
14689492a15SPatrick Williams                                                       endIndex - startIndex);
147d055a34aSNan Zhou 
148d055a34aSNan Zhou         std::shared_ptr<persistent_data::UserSession> sessionOut =
149d055a34aSNan Zhou             persistent_data::SessionStore::getInstance().loginSessionByToken(
150d055a34aSNan Zhou                 authKey);
151d055a34aSNan Zhou         if (sessionOut == nullptr)
152d055a34aSNan Zhou         {
153d055a34aSNan Zhou             return nullptr;
154d055a34aSNan Zhou         }
155c3b3ad03SEd Tanous         sessionOut->cookieAuth = true;
15625b54dbaSEd Tanous 
157576db695SEd Tanous         if constexpr (!BMCWEB_INSECURE_DISABLE_CSRF)
15825b54dbaSEd Tanous         {
159d055a34aSNan Zhou             // RFC7231 defines methods that need csrf protection
160d055a34aSNan Zhou             if (method != boost::beast::http::verb::get)
161d055a34aSNan Zhou             {
162d055a34aSNan Zhou                 std::string_view csrf = reqHeader["X-XSRF-TOKEN"];
163d055a34aSNan Zhou                 // Make sure both tokens are filled
164d055a34aSNan Zhou                 if (csrf.empty() || sessionOut->csrfToken.empty())
165d055a34aSNan Zhou                 {
166d055a34aSNan Zhou                     return nullptr;
167d055a34aSNan Zhou                 }
168d055a34aSNan Zhou 
169d055a34aSNan Zhou                 if (csrf.size() != persistent_data::sessionTokenSize)
170d055a34aSNan Zhou                 {
171d055a34aSNan Zhou                     return nullptr;
172d055a34aSNan Zhou                 }
173d055a34aSNan Zhou                 // Reject if csrf token not available
1749f217c26SEd Tanous                 if (!crow::utility::constantTimeStringCompare(
1759f217c26SEd Tanous                         csrf, sessionOut->csrfToken))
176d055a34aSNan Zhou                 {
177d055a34aSNan Zhou                     return nullptr;
178d055a34aSNan Zhou                 }
179d055a34aSNan Zhou             }
18025b54dbaSEd Tanous         }
1813eaecac3SEd Tanous         return sessionOut;
182d055a34aSNan Zhou     }
1839f217c26SEd Tanous     return nullptr;
1849f217c26SEd Tanous }
185d055a34aSNan Zhou 
18625b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession>
187d055a34aSNan Zhou     performTLSAuth(Response& res,
188d055a34aSNan Zhou                    const boost::beast::http::header<true>& reqHeader,
189d055a34aSNan Zhou                    const std::weak_ptr<persistent_data::UserSession>& session)
190d055a34aSNan Zhou {
191d055a34aSNan Zhou     if (auto sp = session.lock())
192d055a34aSNan Zhou     {
193d055a34aSNan Zhou         // set cookie only if this is req from the browser.
194d055a34aSNan Zhou         if (reqHeader["User-Agent"].empty())
195d055a34aSNan Zhou         {
19662598e31SEd Tanous             BMCWEB_LOG_DEBUG(" TLS session: {} will be used for this request.",
19762598e31SEd Tanous                              sp->uniqueId);
198d055a34aSNan Zhou             return sp;
199d055a34aSNan Zhou         }
200d055a34aSNan Zhou         // TODO: change this to not switch to cookie auth
20129aab242SPaul Fertser         bmcweb::setSessionCookies(res, *sp);
202994fd86aSEd Tanous         res.addHeader(boost::beast::http::field::set_cookie,
203d055a34aSNan Zhou                       "IsAuthenticated=true; Secure");
20462598e31SEd Tanous         BMCWEB_LOG_DEBUG(
20562598e31SEd Tanous             " TLS session: {} with cookie will be used for this request.",
20662598e31SEd Tanous             sp->uniqueId);
207d055a34aSNan Zhou         return sp;
208d055a34aSNan Zhou     }
209d055a34aSNan Zhou     return nullptr;
210d055a34aSNan Zhou }
211d055a34aSNan Zhou 
212d055a34aSNan Zhou // checks if request can be forwarded without authentication
21325b54dbaSEd Tanous inline bool isOnAllowlist(std::string_view url, boost::beast::http::verb method)
214d055a34aSNan Zhou {
21538221509SEd Tanous     // Handle the case where the router registers routes as both ending with /
21638221509SEd Tanous     // and not.
2178f5df132SVinceChang6637     if (url.ends_with('/') && url != "/")
21838221509SEd Tanous     {
21938221509SEd Tanous         url.remove_suffix(1);
22038221509SEd Tanous     }
221d055a34aSNan Zhou     if (boost::beast::http::verb::get == method)
222d055a34aSNan Zhou     {
22338221509SEd Tanous         if ((url == "/redfish") ||          //
22438221509SEd Tanous             (url == "/redfish/v1") ||       //
22538221509SEd Tanous             (url == "/redfish/v1/odata") || //
22638221509SEd Tanous             (url == "/redfish/v1/$metadata"))
227d055a34aSNan Zhou         {
228d055a34aSNan Zhou             return true;
229d055a34aSNan Zhou         }
230d055a34aSNan Zhou         if (crow::webroutes::routes.find(std::string(url)) !=
231d055a34aSNan Zhou             crow::webroutes::routes.end())
232d055a34aSNan Zhou         {
233d055a34aSNan Zhou             return true;
234d055a34aSNan Zhou         }
235d055a34aSNan Zhou     }
236d055a34aSNan Zhou 
237d055a34aSNan Zhou     // it's allowed to POST on session collection & login without
238d055a34aSNan Zhou     // authentication
239d055a34aSNan Zhou     if (boost::beast::http::verb::post == method)
240d055a34aSNan Zhou     {
241d055a34aSNan Zhou         if ((url == "/redfish/v1/SessionService/Sessions") ||
242d055a34aSNan Zhou             (url == "/redfish/v1/SessionService/Sessions/Members") ||
243d055a34aSNan Zhou             (url == "/login"))
244d055a34aSNan Zhou         {
245d055a34aSNan Zhou             return true;
246d055a34aSNan Zhou         }
247d055a34aSNan Zhou     }
248d055a34aSNan Zhou 
249d055a34aSNan Zhou     return false;
250d055a34aSNan Zhou }
251d055a34aSNan Zhou 
25225b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession> authenticate(
25302cad96eSEd Tanous     const boost::asio::ip::address& ipAddress [[maybe_unused]],
2543acced2cSNan Zhou     Response& res [[maybe_unused]],
2553acced2cSNan Zhou     boost::beast::http::verb method [[maybe_unused]],
256d055a34aSNan Zhou     const boost::beast::http::header<true>& reqHeader,
257d055a34aSNan Zhou     [[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>&
258d055a34aSNan Zhou         session)
259d055a34aSNan Zhou {
260d055a34aSNan Zhou     const persistent_data::AuthConfigMethods& authMethodsConfig =
261d055a34aSNan Zhou         persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
262d055a34aSNan Zhou 
263d055a34aSNan Zhou     std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr;
26425b54dbaSEd Tanous     if constexpr (BMCWEB_MUTUAL_TLS_AUTH)
26525b54dbaSEd Tanous     {
266d055a34aSNan Zhou         if (authMethodsConfig.tls)
267d055a34aSNan Zhou         {
268d055a34aSNan Zhou             sessionOut = performTLSAuth(res, reqHeader, session);
269d055a34aSNan Zhou         }
27025b54dbaSEd Tanous     }
27125b54dbaSEd Tanous     if constexpr (BMCWEB_XTOKEN_AUTH)
27225b54dbaSEd Tanous     {
273d055a34aSNan Zhou         if (sessionOut == nullptr && authMethodsConfig.xtoken)
274d055a34aSNan Zhou         {
275d055a34aSNan Zhou             sessionOut = performXtokenAuth(reqHeader);
276d055a34aSNan Zhou         }
27725b54dbaSEd Tanous     }
27825b54dbaSEd Tanous     if constexpr (BMCWEB_COOKIE_AUTH)
27925b54dbaSEd Tanous     {
280d055a34aSNan Zhou         if (sessionOut == nullptr && authMethodsConfig.cookie)
281d055a34aSNan Zhou         {
282d055a34aSNan Zhou             sessionOut = performCookieAuth(method, reqHeader);
283d055a34aSNan Zhou         }
28425b54dbaSEd Tanous     }
285d055a34aSNan Zhou     std::string_view authHeader = reqHeader["Authorization"];
28662598e31SEd Tanous     BMCWEB_LOG_DEBUG("authHeader={}", authHeader);
28725b54dbaSEd Tanous     if constexpr (BMCWEB_SESSION_AUTH)
28825b54dbaSEd Tanous     {
289d055a34aSNan Zhou         if (sessionOut == nullptr && authMethodsConfig.sessionToken)
290d055a34aSNan Zhou         {
291d055a34aSNan Zhou             sessionOut = performTokenAuth(authHeader);
292d055a34aSNan Zhou         }
29325b54dbaSEd Tanous     }
29425b54dbaSEd Tanous     if constexpr (BMCWEB_BASIC_AUTH)
29525b54dbaSEd Tanous     {
296d055a34aSNan Zhou         if (sessionOut == nullptr && authMethodsConfig.basic)
297d055a34aSNan Zhou         {
298d055a34aSNan Zhou             sessionOut = performBasicAuth(ipAddress, authHeader);
29925b54dbaSEd Tanous         }
300d055a34aSNan Zhou     }
301d055a34aSNan Zhou     if (sessionOut != nullptr)
302d055a34aSNan Zhou     {
303d055a34aSNan Zhou         return sessionOut;
304d055a34aSNan Zhou     }
305d055a34aSNan Zhou 
306d055a34aSNan Zhou     return nullptr;
307d055a34aSNan Zhou }
308d055a34aSNan Zhou 
309d055a34aSNan Zhou } // namespace authentication
310d055a34aSNan Zhou } // namespace crow
311