xref: /openbmc/bmcweb/include/authentication.hpp (revision b1d736fc6ba711ddbb71ebdbde0ebde7c7c9a893)
1d055a34aSNan Zhou #pragma once
2d055a34aSNan Zhou 
33ccb3adbSEd Tanous #include "app.hpp"
43ccb3adbSEd Tanous #include "common.hpp"
53ccb3adbSEd Tanous #include "forward_unauthorized.hpp"
63ccb3adbSEd Tanous #include "http_request.hpp"
73ccb3adbSEd Tanous #include "http_response.hpp"
83ccb3adbSEd Tanous #include "http_utility.hpp"
93ccb3adbSEd Tanous #include "pam_authenticate.hpp"
10d055a34aSNan Zhou #include "webroutes.hpp"
11d055a34aSNan Zhou 
12d055a34aSNan Zhou #include <boost/container/flat_set.hpp>
13d055a34aSNan Zhou 
14d055a34aSNan Zhou #include <random>
15d055a34aSNan Zhou #include <utility>
16d055a34aSNan Zhou 
17d055a34aSNan Zhou namespace crow
18d055a34aSNan Zhou {
19d055a34aSNan Zhou 
20d055a34aSNan Zhou namespace authentication
21d055a34aSNan Zhou {
22d055a34aSNan Zhou 
23*b1d736fcSEd Tanous inline void cleanupTempSession(const Request& req)
24d055a34aSNan Zhou {
25d055a34aSNan Zhou     // TODO(ed) THis should really be handled by the persistent data
26d055a34aSNan Zhou     // middleware, but because it is upstream, it doesn't have access to the
27d055a34aSNan Zhou     // session information.  Should the data middleware persist the current
28d055a34aSNan Zhou     // user session?
29d055a34aSNan Zhou     if (req.session != nullptr &&
30d055a34aSNan Zhou         req.session->persistence ==
31d055a34aSNan Zhou             persistent_data::PersistenceType::SINGLE_REQUEST)
32d055a34aSNan Zhou     {
33d055a34aSNan Zhou         persistent_data::SessionStore::getInstance().removeSession(req.session);
34d055a34aSNan Zhou     }
35d055a34aSNan Zhou }
36d055a34aSNan Zhou 
37d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
38d055a34aSNan Zhou static std::shared_ptr<persistent_data::UserSession>
39d055a34aSNan Zhou     performBasicAuth(const boost::asio::ip::address& clientIp,
40d055a34aSNan Zhou                      std::string_view authHeader)
41d055a34aSNan Zhou {
42d055a34aSNan Zhou     BMCWEB_LOG_DEBUG << "[AuthMiddleware] Basic authentication";
43d055a34aSNan Zhou 
4411ba3979SEd Tanous     if (!authHeader.starts_with("Basic "))
45d055a34aSNan Zhou     {
46d055a34aSNan Zhou         return nullptr;
47d055a34aSNan Zhou     }
48d055a34aSNan Zhou 
49d055a34aSNan Zhou     std::string_view param = authHeader.substr(strlen("Basic "));
50d055a34aSNan Zhou     std::string authData;
51d055a34aSNan Zhou 
52d055a34aSNan Zhou     if (!crow::utility::base64Decode(param, authData))
53d055a34aSNan Zhou     {
54d055a34aSNan Zhou         return nullptr;
55d055a34aSNan Zhou     }
56d055a34aSNan Zhou     std::size_t separator = authData.find(':');
57d055a34aSNan Zhou     if (separator == std::string::npos)
58d055a34aSNan Zhou     {
59d055a34aSNan Zhou         return nullptr;
60d055a34aSNan Zhou     }
61d055a34aSNan Zhou 
62d055a34aSNan Zhou     std::string user = authData.substr(0, separator);
63d055a34aSNan Zhou     separator += 1;
64d055a34aSNan Zhou     if (separator > authData.size())
65d055a34aSNan Zhou     {
66d055a34aSNan Zhou         return nullptr;
67d055a34aSNan Zhou     }
68d055a34aSNan Zhou     std::string pass = authData.substr(separator);
69d055a34aSNan Zhou 
70d055a34aSNan Zhou     BMCWEB_LOG_DEBUG << "[AuthMiddleware] Authenticating user: " << user;
71d055a34aSNan Zhou     BMCWEB_LOG_DEBUG << "[AuthMiddleware] User IPAddress: "
72d055a34aSNan Zhou                      << clientIp.to_string();
73d055a34aSNan Zhou 
74d055a34aSNan Zhou     int pamrc = pamAuthenticateUser(user, pass);
75d055a34aSNan Zhou     bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
76d055a34aSNan Zhou     if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
77d055a34aSNan Zhou     {
78d055a34aSNan Zhou         return nullptr;
79d055a34aSNan Zhou     }
80d055a34aSNan Zhou 
81d055a34aSNan Zhou     // TODO(ed) generateUserSession is a little expensive for basic
82d055a34aSNan Zhou     // auth, as it generates some random identifiers that will never be
83d055a34aSNan Zhou     // used.  This should have a "fast" path for when user tokens aren't
84d055a34aSNan Zhou     // needed.
85d055a34aSNan Zhou     // This whole flow needs to be revisited anyway, as we can't be
86d055a34aSNan Zhou     // calling directly into pam for every request
87d055a34aSNan Zhou     return persistent_data::SessionStore::getInstance().generateUserSession(
88bb759e3aSEd Tanous         user, clientIp, std::nullopt,
89d055a34aSNan Zhou         persistent_data::PersistenceType::SINGLE_REQUEST, isConfigureSelfOnly);
90d055a34aSNan Zhou }
91d055a34aSNan Zhou #endif
92d055a34aSNan Zhou 
93d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
94d055a34aSNan Zhou static std::shared_ptr<persistent_data::UserSession>
95d055a34aSNan Zhou     performTokenAuth(std::string_view authHeader)
96d055a34aSNan Zhou {
97d055a34aSNan Zhou     BMCWEB_LOG_DEBUG << "[AuthMiddleware] Token authentication";
9811ba3979SEd Tanous     if (!authHeader.starts_with("Token "))
99d055a34aSNan Zhou     {
100d055a34aSNan Zhou         return nullptr;
101d055a34aSNan Zhou     }
102d055a34aSNan Zhou     std::string_view token = authHeader.substr(strlen("Token "));
103d055a34aSNan Zhou     auto sessionOut =
104d055a34aSNan Zhou         persistent_data::SessionStore::getInstance().loginSessionByToken(token);
105d055a34aSNan Zhou     return sessionOut;
106d055a34aSNan Zhou }
107d055a34aSNan Zhou #endif
108d055a34aSNan Zhou 
109d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
110d055a34aSNan Zhou static std::shared_ptr<persistent_data::UserSession>
111d055a34aSNan Zhou     performXtokenAuth(const boost::beast::http::header<true>& reqHeader)
112d055a34aSNan Zhou {
113d055a34aSNan Zhou     BMCWEB_LOG_DEBUG << "[AuthMiddleware] X-Auth-Token authentication";
114d055a34aSNan Zhou 
115d055a34aSNan Zhou     std::string_view token = reqHeader["X-Auth-Token"];
116d055a34aSNan Zhou     if (token.empty())
117d055a34aSNan Zhou     {
118d055a34aSNan Zhou         return nullptr;
119d055a34aSNan Zhou     }
120d055a34aSNan Zhou     auto sessionOut =
121d055a34aSNan Zhou         persistent_data::SessionStore::getInstance().loginSessionByToken(token);
122d055a34aSNan Zhou     return sessionOut;
123d055a34aSNan Zhou }
124d055a34aSNan Zhou #endif
125d055a34aSNan Zhou 
126d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
127d055a34aSNan Zhou static std::shared_ptr<persistent_data::UserSession>
128d055a34aSNan Zhou     performCookieAuth(boost::beast::http::verb method,
129d055a34aSNan Zhou                       const boost::beast::http::header<true>& reqHeader)
130d055a34aSNan Zhou {
131d055a34aSNan Zhou     BMCWEB_LOG_DEBUG << "[AuthMiddleware] Cookie authentication";
132d055a34aSNan Zhou 
133d055a34aSNan Zhou     std::string_view cookieValue = reqHeader["Cookie"];
134d055a34aSNan Zhou     if (cookieValue.empty())
135d055a34aSNan Zhou     {
136d055a34aSNan Zhou         return nullptr;
137d055a34aSNan Zhou     }
138d055a34aSNan Zhou 
139d055a34aSNan Zhou     auto startIndex = cookieValue.find("SESSION=");
140d055a34aSNan Zhou     if (startIndex == std::string::npos)
141d055a34aSNan Zhou     {
142d055a34aSNan Zhou         return nullptr;
143d055a34aSNan Zhou     }
144d055a34aSNan Zhou     startIndex += sizeof("SESSION=") - 1;
145d055a34aSNan Zhou     auto endIndex = cookieValue.find(';', startIndex);
146d055a34aSNan Zhou     if (endIndex == std::string::npos)
147d055a34aSNan Zhou     {
148d055a34aSNan Zhou         endIndex = cookieValue.size();
149d055a34aSNan Zhou     }
150d055a34aSNan Zhou     std::string_view authKey =
151d055a34aSNan Zhou         cookieValue.substr(startIndex, endIndex - startIndex);
152d055a34aSNan Zhou 
153d055a34aSNan Zhou     std::shared_ptr<persistent_data::UserSession> sessionOut =
154d055a34aSNan Zhou         persistent_data::SessionStore::getInstance().loginSessionByToken(
155d055a34aSNan Zhou             authKey);
156d055a34aSNan Zhou     if (sessionOut == nullptr)
157d055a34aSNan Zhou     {
158d055a34aSNan Zhou         return nullptr;
159d055a34aSNan Zhou     }
160d055a34aSNan Zhou #ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION
161d055a34aSNan Zhou     // RFC7231 defines methods that need csrf protection
162d055a34aSNan Zhou     if (method != boost::beast::http::verb::get)
163d055a34aSNan Zhou     {
164d055a34aSNan Zhou         std::string_view csrf = reqHeader["X-XSRF-TOKEN"];
165d055a34aSNan Zhou         // Make sure both tokens are filled
166d055a34aSNan Zhou         if (csrf.empty() || sessionOut->csrfToken.empty())
167d055a34aSNan Zhou         {
168d055a34aSNan Zhou             return nullptr;
169d055a34aSNan Zhou         }
170d055a34aSNan Zhou 
171d055a34aSNan Zhou         if (csrf.size() != persistent_data::sessionTokenSize)
172d055a34aSNan Zhou         {
173d055a34aSNan Zhou             return nullptr;
174d055a34aSNan Zhou         }
175d055a34aSNan Zhou         // Reject if csrf token not available
176d055a34aSNan Zhou         if (!crow::utility::constantTimeStringCompare(csrf,
177d055a34aSNan Zhou                                                       sessionOut->csrfToken))
178d055a34aSNan Zhou         {
179d055a34aSNan Zhou             return nullptr;
180d055a34aSNan Zhou         }
181d055a34aSNan Zhou     }
182d055a34aSNan Zhou #endif
183d055a34aSNan Zhou     return sessionOut;
184d055a34aSNan Zhou }
185d055a34aSNan Zhou #endif
186d055a34aSNan Zhou 
187d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
188d055a34aSNan Zhou static std::shared_ptr<persistent_data::UserSession>
189d055a34aSNan Zhou     performTLSAuth(Response& res,
190d055a34aSNan Zhou                    const boost::beast::http::header<true>& reqHeader,
191d055a34aSNan Zhou                    const std::weak_ptr<persistent_data::UserSession>& session)
192d055a34aSNan Zhou {
193d055a34aSNan Zhou     if (auto sp = session.lock())
194d055a34aSNan Zhou     {
195d055a34aSNan Zhou         // set cookie only if this is req from the browser.
196d055a34aSNan Zhou         if (reqHeader["User-Agent"].empty())
197d055a34aSNan Zhou         {
198d055a34aSNan Zhou             BMCWEB_LOG_DEBUG << " TLS session: " << sp->uniqueId
199d055a34aSNan Zhou                              << " will be used for this request.";
200d055a34aSNan Zhou             return sp;
201d055a34aSNan Zhou         }
202d055a34aSNan Zhou         // TODO: change this to not switch to cookie auth
203ade2fe78SKarol Niczyj         res.addHeader("Set-Cookie",
204d055a34aSNan Zhou                       "XSRF-TOKEN=" + sp->csrfToken +
205d055a34aSNan Zhou                           "; SameSite=Strict; Secure\r\nSet-Cookie: SESSION=" +
206d055a34aSNan Zhou                           sp->sessionToken +
207d055a34aSNan Zhou                           "; SameSite=Strict; Secure; HttpOnly\r\nSet-Cookie: "
208d055a34aSNan Zhou                           "IsAuthenticated=true; Secure");
209d055a34aSNan Zhou         BMCWEB_LOG_DEBUG << " TLS session: " << sp->uniqueId
210d055a34aSNan Zhou                          << " with cookie will be used for this request.";
211d055a34aSNan Zhou         return sp;
212d055a34aSNan Zhou     }
213d055a34aSNan Zhou     return nullptr;
214d055a34aSNan Zhou }
215d055a34aSNan Zhou #endif
216d055a34aSNan Zhou 
217d055a34aSNan Zhou // checks if request can be forwarded without authentication
218d055a34aSNan Zhou [[maybe_unused]] static bool isOnAllowlist(std::string_view url,
219d055a34aSNan Zhou                                            boost::beast::http::verb method)
220d055a34aSNan Zhou {
221d055a34aSNan Zhou     if (boost::beast::http::verb::get == method)
222d055a34aSNan Zhou     {
223d055a34aSNan Zhou         if (url == "/redfish/v1" || url == "/redfish/v1/" ||
224d055a34aSNan Zhou             url == "/redfish" || url == "/redfish/" ||
225d055a34aSNan Zhou             url == "/redfish/v1/odata" || url == "/redfish/v1/odata/")
226d055a34aSNan Zhou         {
227d055a34aSNan Zhou             return true;
228d055a34aSNan Zhou         }
229d055a34aSNan Zhou         if (crow::webroutes::routes.find(std::string(url)) !=
230d055a34aSNan Zhou             crow::webroutes::routes.end())
231d055a34aSNan Zhou         {
232d055a34aSNan Zhou             return true;
233d055a34aSNan Zhou         }
234d055a34aSNan Zhou     }
235d055a34aSNan Zhou 
236d055a34aSNan Zhou     // it's allowed to POST on session collection & login without
237d055a34aSNan Zhou     // authentication
238d055a34aSNan Zhou     if (boost::beast::http::verb::post == method)
239d055a34aSNan Zhou     {
240d055a34aSNan Zhou         if ((url == "/redfish/v1/SessionService/Sessions") ||
241d055a34aSNan Zhou             (url == "/redfish/v1/SessionService/Sessions/") ||
242d055a34aSNan Zhou             (url == "/redfish/v1/SessionService/Sessions/Members") ||
243d055a34aSNan Zhou             (url == "/redfish/v1/SessionService/Sessions/Members/") ||
244d055a34aSNan Zhou             (url == "/login"))
245d055a34aSNan Zhou         {
246d055a34aSNan Zhou             return true;
247d055a34aSNan Zhou         }
248d055a34aSNan Zhou     }
249d055a34aSNan Zhou 
250d055a34aSNan Zhou     return false;
251d055a34aSNan Zhou }
252d055a34aSNan Zhou 
253d055a34aSNan Zhou [[maybe_unused]] static std::shared_ptr<persistent_data::UserSession>
254d055a34aSNan Zhou     authenticate(
25502cad96eSEd Tanous         const boost::asio::ip::address& ipAddress [[maybe_unused]],
2563acced2cSNan Zhou         Response& res [[maybe_unused]],
2573acced2cSNan Zhou         boost::beast::http::verb method [[maybe_unused]],
258d055a34aSNan Zhou         const boost::beast::http::header<true>& reqHeader,
259d055a34aSNan Zhou         [[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>&
260d055a34aSNan Zhou             session)
261d055a34aSNan Zhou {
262d055a34aSNan Zhou     const persistent_data::AuthConfigMethods& authMethodsConfig =
263d055a34aSNan Zhou         persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
264d055a34aSNan Zhou 
265d055a34aSNan Zhou     std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr;
266d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
267d055a34aSNan Zhou     if (authMethodsConfig.tls)
268d055a34aSNan Zhou     {
269d055a34aSNan Zhou         sessionOut = performTLSAuth(res, reqHeader, session);
270d055a34aSNan Zhou     }
271d055a34aSNan Zhou #endif
272d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
273d055a34aSNan Zhou     if (sessionOut == nullptr && authMethodsConfig.xtoken)
274d055a34aSNan Zhou     {
275d055a34aSNan Zhou         sessionOut = performXtokenAuth(reqHeader);
276d055a34aSNan Zhou     }
277d055a34aSNan Zhou #endif
278d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
279d055a34aSNan Zhou     if (sessionOut == nullptr && authMethodsConfig.cookie)
280d055a34aSNan Zhou     {
281d055a34aSNan Zhou         sessionOut = performCookieAuth(method, reqHeader);
282d055a34aSNan Zhou     }
283d055a34aSNan Zhou #endif
284d055a34aSNan Zhou     std::string_view authHeader = reqHeader["Authorization"];
285d055a34aSNan Zhou     BMCWEB_LOG_DEBUG << "authHeader=" << authHeader;
286d055a34aSNan Zhou 
287d055a34aSNan Zhou     if (sessionOut == nullptr && authMethodsConfig.sessionToken)
288d055a34aSNan Zhou     {
289d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
290d055a34aSNan Zhou         sessionOut = performTokenAuth(authHeader);
291d055a34aSNan Zhou #endif
292d055a34aSNan Zhou     }
293d055a34aSNan Zhou     if (sessionOut == nullptr && authMethodsConfig.basic)
294d055a34aSNan Zhou     {
295d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
296d055a34aSNan Zhou         sessionOut = performBasicAuth(ipAddress, authHeader);
297d055a34aSNan Zhou #endif
298d055a34aSNan Zhou     }
299d055a34aSNan Zhou     if (sessionOut != nullptr)
300d055a34aSNan Zhou     {
301d055a34aSNan Zhou         return sessionOut;
302d055a34aSNan Zhou     }
303d055a34aSNan Zhou 
304d055a34aSNan Zhou     return nullptr;
305d055a34aSNan Zhou }
306d055a34aSNan Zhou 
307d055a34aSNan Zhou } // namespace authentication
308d055a34aSNan Zhou } // namespace crow
309