xref: /openbmc/bmcweb/include/authentication.hpp (revision 38221509e6cc06a6897e8b9b2aa049a70c033840)
1d055a34aSNan Zhou #pragma once
2d055a34aSNan Zhou 
33ccb3adbSEd Tanous #include "forward_unauthorized.hpp"
43ccb3adbSEd Tanous #include "http_request.hpp"
53ccb3adbSEd Tanous #include "http_response.hpp"
63ccb3adbSEd Tanous #include "http_utility.hpp"
73ccb3adbSEd Tanous #include "pam_authenticate.hpp"
8d055a34aSNan Zhou #include "webroutes.hpp"
9d055a34aSNan Zhou 
10d055a34aSNan Zhou #include <boost/container/flat_set.hpp>
11d055a34aSNan Zhou 
12d055a34aSNan Zhou #include <random>
13d055a34aSNan Zhou #include <utility>
14d055a34aSNan Zhou 
15d055a34aSNan Zhou namespace crow
16d055a34aSNan Zhou {
17d055a34aSNan Zhou 
18d055a34aSNan Zhou namespace authentication
19d055a34aSNan Zhou {
20d055a34aSNan Zhou 
21b1d736fcSEd Tanous inline void cleanupTempSession(const Request& req)
22d055a34aSNan Zhou {
23d055a34aSNan Zhou     // TODO(ed) THis should really be handled by the persistent data
24d055a34aSNan Zhou     // middleware, but because it is upstream, it doesn't have access to the
25d055a34aSNan Zhou     // session information.  Should the data middleware persist the current
26d055a34aSNan Zhou     // user session?
27d055a34aSNan Zhou     if (req.session != nullptr &&
28d055a34aSNan Zhou         req.session->persistence ==
29d055a34aSNan Zhou             persistent_data::PersistenceType::SINGLE_REQUEST)
30d055a34aSNan Zhou     {
31d055a34aSNan Zhou         persistent_data::SessionStore::getInstance().removeSession(req.session);
32d055a34aSNan Zhou     }
33d055a34aSNan Zhou }
34d055a34aSNan Zhou 
3525b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession>
36d055a34aSNan Zhou     performBasicAuth(const boost::asio::ip::address& clientIp,
37d055a34aSNan Zhou                      std::string_view authHeader)
38d055a34aSNan Zhou {
3962598e31SEd Tanous     BMCWEB_LOG_DEBUG("[AuthMiddleware] Basic authentication");
40d055a34aSNan Zhou 
4111ba3979SEd Tanous     if (!authHeader.starts_with("Basic "))
42d055a34aSNan Zhou     {
43d055a34aSNan Zhou         return nullptr;
44d055a34aSNan Zhou     }
45d055a34aSNan Zhou 
46d055a34aSNan Zhou     std::string_view param = authHeader.substr(strlen("Basic "));
47d055a34aSNan Zhou     std::string authData;
48d055a34aSNan Zhou 
49d055a34aSNan Zhou     if (!crow::utility::base64Decode(param, authData))
50d055a34aSNan Zhou     {
51d055a34aSNan Zhou         return nullptr;
52d055a34aSNan Zhou     }
53d055a34aSNan Zhou     std::size_t separator = authData.find(':');
54d055a34aSNan Zhou     if (separator == std::string::npos)
55d055a34aSNan Zhou     {
56d055a34aSNan Zhou         return nullptr;
57d055a34aSNan Zhou     }
58d055a34aSNan Zhou 
59d055a34aSNan Zhou     std::string user = authData.substr(0, separator);
60d055a34aSNan Zhou     separator += 1;
61d055a34aSNan Zhou     if (separator > authData.size())
62d055a34aSNan Zhou     {
63d055a34aSNan Zhou         return nullptr;
64d055a34aSNan Zhou     }
65d055a34aSNan Zhou     std::string pass = authData.substr(separator);
66d055a34aSNan Zhou 
6762598e31SEd Tanous     BMCWEB_LOG_DEBUG("[AuthMiddleware] Authenticating user: {}", user);
6862598e31SEd Tanous     BMCWEB_LOG_DEBUG("[AuthMiddleware] User IPAddress: {}",
6962598e31SEd Tanous                      clientIp.to_string());
70d055a34aSNan Zhou 
71d055a34aSNan Zhou     int pamrc = pamAuthenticateUser(user, pass);
72d055a34aSNan Zhou     bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
73d055a34aSNan Zhou     if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
74d055a34aSNan Zhou     {
75d055a34aSNan Zhou         return nullptr;
76d055a34aSNan Zhou     }
77d055a34aSNan Zhou 
78d055a34aSNan Zhou     // TODO(ed) generateUserSession is a little expensive for basic
79d055a34aSNan Zhou     // auth, as it generates some random identifiers that will never be
80d055a34aSNan Zhou     // used.  This should have a "fast" path for when user tokens aren't
81d055a34aSNan Zhou     // needed.
82d055a34aSNan Zhou     // This whole flow needs to be revisited anyway, as we can't be
83d055a34aSNan Zhou     // calling directly into pam for every request
84d055a34aSNan Zhou     return persistent_data::SessionStore::getInstance().generateUserSession(
85bb759e3aSEd Tanous         user, clientIp, std::nullopt,
86d055a34aSNan Zhou         persistent_data::PersistenceType::SINGLE_REQUEST, isConfigureSelfOnly);
87d055a34aSNan Zhou }
88d055a34aSNan Zhou 
8925b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession>
90d055a34aSNan Zhou     performTokenAuth(std::string_view authHeader)
91d055a34aSNan Zhou {
9262598e31SEd Tanous     BMCWEB_LOG_DEBUG("[AuthMiddleware] Token authentication");
9311ba3979SEd Tanous     if (!authHeader.starts_with("Token "))
94d055a34aSNan Zhou     {
95d055a34aSNan Zhou         return nullptr;
96d055a34aSNan Zhou     }
97d055a34aSNan Zhou     std::string_view token = authHeader.substr(strlen("Token "));
98d055a34aSNan Zhou     auto sessionOut =
99d055a34aSNan Zhou         persistent_data::SessionStore::getInstance().loginSessionByToken(token);
100d055a34aSNan Zhou     return sessionOut;
101d055a34aSNan Zhou }
102d055a34aSNan Zhou 
10325b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession>
104d055a34aSNan Zhou     performXtokenAuth(const boost::beast::http::header<true>& reqHeader)
105d055a34aSNan Zhou {
10662598e31SEd Tanous     BMCWEB_LOG_DEBUG("[AuthMiddleware] X-Auth-Token authentication");
107d055a34aSNan Zhou 
108d055a34aSNan Zhou     std::string_view token = reqHeader["X-Auth-Token"];
109d055a34aSNan Zhou     if (token.empty())
110d055a34aSNan Zhou     {
111d055a34aSNan Zhou         return nullptr;
112d055a34aSNan Zhou     }
113d055a34aSNan Zhou     auto sessionOut =
114d055a34aSNan Zhou         persistent_data::SessionStore::getInstance().loginSessionByToken(token);
115d055a34aSNan Zhou     return sessionOut;
116d055a34aSNan Zhou }
117d055a34aSNan Zhou 
11825b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession>
1191d869608SEd Tanous     performCookieAuth(boost::beast::http::verb method [[maybe_unused]],
120d055a34aSNan Zhou                       const boost::beast::http::header<true>& reqHeader)
121d055a34aSNan Zhou {
1229f217c26SEd Tanous     using headers = boost::beast::http::header<true>;
1239f217c26SEd Tanous     std::pair<headers::const_iterator, headers::const_iterator> cookies =
1249f217c26SEd Tanous         reqHeader.equal_range(boost::beast::http::field::cookie);
125d055a34aSNan Zhou 
1269f217c26SEd Tanous     for (auto it = cookies.first; it != cookies.second; it++)
127d055a34aSNan Zhou     {
1289f217c26SEd Tanous         std::string_view cookieValue = it->value();
1299f217c26SEd Tanous         BMCWEB_LOG_DEBUG("Checking cookie {}", cookieValue);
130d055a34aSNan Zhou         auto startIndex = cookieValue.find("SESSION=");
131d055a34aSNan Zhou         if (startIndex == std::string::npos)
132d055a34aSNan Zhou         {
1339f217c26SEd Tanous             BMCWEB_LOG_DEBUG(
1349f217c26SEd Tanous                 "Cookie was present, but didn't look like a session {}",
1359f217c26SEd Tanous                 cookieValue);
1369f217c26SEd Tanous             continue;
137d055a34aSNan Zhou         }
138d055a34aSNan Zhou         startIndex += sizeof("SESSION=") - 1;
139d055a34aSNan Zhou         auto endIndex = cookieValue.find(';', startIndex);
140d055a34aSNan Zhou         if (endIndex == std::string::npos)
141d055a34aSNan Zhou         {
142d055a34aSNan Zhou             endIndex = cookieValue.size();
143d055a34aSNan Zhou         }
14489492a15SPatrick Williams         std::string_view authKey = cookieValue.substr(startIndex,
14589492a15SPatrick Williams                                                       endIndex - startIndex);
146d055a34aSNan Zhou 
147d055a34aSNan Zhou         std::shared_ptr<persistent_data::UserSession> sessionOut =
148d055a34aSNan Zhou             persistent_data::SessionStore::getInstance().loginSessionByToken(
149d055a34aSNan Zhou                 authKey);
150d055a34aSNan Zhou         if (sessionOut == nullptr)
151d055a34aSNan Zhou         {
152d055a34aSNan Zhou             return nullptr;
153d055a34aSNan Zhou         }
154c3b3ad03SEd Tanous         sessionOut->cookieAuth = true;
15525b54dbaSEd Tanous 
156576db695SEd Tanous         if constexpr (!BMCWEB_INSECURE_DISABLE_CSRF)
15725b54dbaSEd Tanous         {
158d055a34aSNan Zhou             // RFC7231 defines methods that need csrf protection
159d055a34aSNan Zhou             if (method != boost::beast::http::verb::get)
160d055a34aSNan Zhou             {
161d055a34aSNan Zhou                 std::string_view csrf = reqHeader["X-XSRF-TOKEN"];
162d055a34aSNan Zhou                 // Make sure both tokens are filled
163d055a34aSNan Zhou                 if (csrf.empty() || sessionOut->csrfToken.empty())
164d055a34aSNan Zhou                 {
165d055a34aSNan Zhou                     return nullptr;
166d055a34aSNan Zhou                 }
167d055a34aSNan Zhou 
168d055a34aSNan Zhou                 if (csrf.size() != persistent_data::sessionTokenSize)
169d055a34aSNan Zhou                 {
170d055a34aSNan Zhou                     return nullptr;
171d055a34aSNan Zhou                 }
172d055a34aSNan Zhou                 // Reject if csrf token not available
1739f217c26SEd Tanous                 if (!crow::utility::constantTimeStringCompare(
1749f217c26SEd Tanous                         csrf, sessionOut->csrfToken))
175d055a34aSNan Zhou                 {
176d055a34aSNan Zhou                     return nullptr;
177d055a34aSNan Zhou                 }
178d055a34aSNan Zhou             }
17925b54dbaSEd Tanous         }
1803eaecac3SEd Tanous         return sessionOut;
181d055a34aSNan Zhou     }
1829f217c26SEd Tanous     return nullptr;
1839f217c26SEd Tanous }
184d055a34aSNan Zhou 
18525b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession>
186d055a34aSNan Zhou     performTLSAuth(Response& res,
187d055a34aSNan Zhou                    const boost::beast::http::header<true>& reqHeader,
188d055a34aSNan Zhou                    const std::weak_ptr<persistent_data::UserSession>& session)
189d055a34aSNan Zhou {
190d055a34aSNan Zhou     if (auto sp = session.lock())
191d055a34aSNan Zhou     {
192d055a34aSNan Zhou         // set cookie only if this is req from the browser.
193d055a34aSNan Zhou         if (reqHeader["User-Agent"].empty())
194d055a34aSNan Zhou         {
19562598e31SEd Tanous             BMCWEB_LOG_DEBUG(" TLS session: {} will be used for this request.",
19662598e31SEd Tanous                              sp->uniqueId);
197d055a34aSNan Zhou             return sp;
198d055a34aSNan Zhou         }
199d055a34aSNan Zhou         // TODO: change this to not switch to cookie auth
200994fd86aSEd Tanous         res.addHeader(boost::beast::http::field::set_cookie,
201d055a34aSNan Zhou                       "XSRF-TOKEN=" + sp->csrfToken +
202994fd86aSEd Tanous                           "; SameSite=Strict; Secure");
203994fd86aSEd Tanous         res.addHeader(boost::beast::http::field::set_cookie,
204994fd86aSEd Tanous                       "SESSION=" + sp->sessionToken +
205994fd86aSEd Tanous                           "; SameSite=Strict; Secure; HttpOnly");
206994fd86aSEd Tanous         res.addHeader(boost::beast::http::field::set_cookie,
207d055a34aSNan Zhou                       "IsAuthenticated=true; Secure");
20862598e31SEd Tanous         BMCWEB_LOG_DEBUG(
20962598e31SEd Tanous             " TLS session: {} with cookie will be used for this request.",
21062598e31SEd Tanous             sp->uniqueId);
211d055a34aSNan Zhou         return sp;
212d055a34aSNan Zhou     }
213d055a34aSNan Zhou     return nullptr;
214d055a34aSNan Zhou }
215d055a34aSNan Zhou 
216d055a34aSNan Zhou // checks if request can be forwarded without authentication
21725b54dbaSEd Tanous inline bool isOnAllowlist(std::string_view url, boost::beast::http::verb method)
218d055a34aSNan Zhou {
219*38221509SEd Tanous     // Handle the case where the router registers routes as both ending with /
220*38221509SEd Tanous     // and not.
221*38221509SEd Tanous     if (url.ends_with('/'))
222*38221509SEd Tanous     {
223*38221509SEd Tanous         url.remove_suffix(1);
224*38221509SEd Tanous     }
225d055a34aSNan Zhou     if (boost::beast::http::verb::get == method)
226d055a34aSNan Zhou     {
227*38221509SEd Tanous         if ((url == "/redfish") ||          //
228*38221509SEd Tanous             (url == "/redfish/v1") ||       //
229*38221509SEd Tanous             (url == "/redfish/v1/odata") || //
230*38221509SEd Tanous             (url == "/redfish/v1/$metadata"))
231d055a34aSNan Zhou         {
232d055a34aSNan Zhou             return true;
233d055a34aSNan Zhou         }
234d055a34aSNan Zhou         if (crow::webroutes::routes.find(std::string(url)) !=
235d055a34aSNan Zhou             crow::webroutes::routes.end())
236d055a34aSNan Zhou         {
237d055a34aSNan Zhou             return true;
238d055a34aSNan Zhou         }
239d055a34aSNan Zhou     }
240d055a34aSNan Zhou 
241d055a34aSNan Zhou     // it's allowed to POST on session collection & login without
242d055a34aSNan Zhou     // authentication
243d055a34aSNan Zhou     if (boost::beast::http::verb::post == method)
244d055a34aSNan Zhou     {
245d055a34aSNan Zhou         if ((url == "/redfish/v1/SessionService/Sessions") ||
246d055a34aSNan Zhou             (url == "/redfish/v1/SessionService/Sessions/Members") ||
247d055a34aSNan Zhou             (url == "/login"))
248d055a34aSNan Zhou         {
249d055a34aSNan Zhou             return true;
250d055a34aSNan Zhou         }
251d055a34aSNan Zhou     }
252d055a34aSNan Zhou 
253d055a34aSNan Zhou     return false;
254d055a34aSNan Zhou }
255d055a34aSNan Zhou 
25625b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession> authenticate(
25702cad96eSEd Tanous     const boost::asio::ip::address& ipAddress [[maybe_unused]],
2583acced2cSNan Zhou     Response& res [[maybe_unused]],
2593acced2cSNan Zhou     boost::beast::http::verb method [[maybe_unused]],
260d055a34aSNan Zhou     const boost::beast::http::header<true>& reqHeader,
261d055a34aSNan Zhou     [[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>&
262d055a34aSNan Zhou         session)
263d055a34aSNan Zhou {
264d055a34aSNan Zhou     const persistent_data::AuthConfigMethods& authMethodsConfig =
265d055a34aSNan Zhou         persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
266d055a34aSNan Zhou 
267d055a34aSNan Zhou     std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr;
26825b54dbaSEd Tanous     if constexpr (BMCWEB_MUTUAL_TLS_AUTH)
26925b54dbaSEd Tanous     {
270d055a34aSNan Zhou         if (authMethodsConfig.tls)
271d055a34aSNan Zhou         {
272d055a34aSNan Zhou             sessionOut = performTLSAuth(res, reqHeader, session);
273d055a34aSNan Zhou         }
27425b54dbaSEd Tanous     }
27525b54dbaSEd Tanous     if constexpr (BMCWEB_XTOKEN_AUTH)
27625b54dbaSEd Tanous     {
277d055a34aSNan Zhou         if (sessionOut == nullptr && authMethodsConfig.xtoken)
278d055a34aSNan Zhou         {
279d055a34aSNan Zhou             sessionOut = performXtokenAuth(reqHeader);
280d055a34aSNan Zhou         }
28125b54dbaSEd Tanous     }
28225b54dbaSEd Tanous     if constexpr (BMCWEB_COOKIE_AUTH)
28325b54dbaSEd Tanous     {
284d055a34aSNan Zhou         if (sessionOut == nullptr && authMethodsConfig.cookie)
285d055a34aSNan Zhou         {
286d055a34aSNan Zhou             sessionOut = performCookieAuth(method, reqHeader);
287d055a34aSNan Zhou         }
28825b54dbaSEd Tanous     }
289d055a34aSNan Zhou     std::string_view authHeader = reqHeader["Authorization"];
29062598e31SEd Tanous     BMCWEB_LOG_DEBUG("authHeader={}", authHeader);
29125b54dbaSEd Tanous     if constexpr (BMCWEB_SESSION_AUTH)
29225b54dbaSEd Tanous     {
293d055a34aSNan Zhou         if (sessionOut == nullptr && authMethodsConfig.sessionToken)
294d055a34aSNan Zhou         {
295d055a34aSNan Zhou             sessionOut = performTokenAuth(authHeader);
296d055a34aSNan Zhou         }
29725b54dbaSEd Tanous     }
29825b54dbaSEd Tanous     if constexpr (BMCWEB_BASIC_AUTH)
29925b54dbaSEd Tanous     {
300d055a34aSNan Zhou         if (sessionOut == nullptr && authMethodsConfig.basic)
301d055a34aSNan Zhou         {
302d055a34aSNan Zhou             sessionOut = performBasicAuth(ipAddress, authHeader);
30325b54dbaSEd Tanous         }
304d055a34aSNan Zhou     }
305d055a34aSNan Zhou     if (sessionOut != nullptr)
306d055a34aSNan Zhou     {
307d055a34aSNan Zhou         return sessionOut;
308d055a34aSNan Zhou     }
309d055a34aSNan Zhou 
310d055a34aSNan Zhou     return nullptr;
311d055a34aSNan Zhou }
312d055a34aSNan Zhou 
313d055a34aSNan Zhou } // namespace authentication
314d055a34aSNan Zhou } // namespace crow
315