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