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