xref: /openbmc/bmcweb/include/authentication.hpp (revision ae9031f0)
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     return persistent_data::SessionStore::getInstance().generateUserSession(
88         user, clientIp, std::nullopt,
89         persistent_data::PersistenceType::SINGLE_REQUEST, isConfigureSelfOnly);
90 }
91 #endif
92 
93 #ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
94 static std::shared_ptr<persistent_data::UserSession>
95     performTokenAuth(std::string_view authHeader)
96 {
97     BMCWEB_LOG_DEBUG << "[AuthMiddleware] Token authentication";
98     if (!authHeader.starts_with("Token "))
99     {
100         return nullptr;
101     }
102     std::string_view token = authHeader.substr(strlen("Token "));
103     auto sessionOut =
104         persistent_data::SessionStore::getInstance().loginSessionByToken(token);
105     return sessionOut;
106 }
107 #endif
108 
109 #ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
110 static std::shared_ptr<persistent_data::UserSession>
111     performXtokenAuth(const boost::beast::http::header<true>& reqHeader)
112 {
113     BMCWEB_LOG_DEBUG << "[AuthMiddleware] X-Auth-Token authentication";
114 
115     std::string_view token = reqHeader["X-Auth-Token"];
116     if (token.empty())
117     {
118         return nullptr;
119     }
120     auto sessionOut =
121         persistent_data::SessionStore::getInstance().loginSessionByToken(token);
122     return sessionOut;
123 }
124 #endif
125 
126 #ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
127 static std::shared_ptr<persistent_data::UserSession>
128     performCookieAuth(boost::beast::http::verb method,
129                       const boost::beast::http::header<true>& reqHeader)
130 {
131     BMCWEB_LOG_DEBUG << "[AuthMiddleware] Cookie authentication";
132 
133     std::string_view cookieValue = reqHeader["Cookie"];
134     if (cookieValue.empty())
135     {
136         return nullptr;
137     }
138 
139     auto startIndex = cookieValue.find("SESSION=");
140     if (startIndex == std::string::npos)
141     {
142         return nullptr;
143     }
144     startIndex += sizeof("SESSION=") - 1;
145     auto endIndex = cookieValue.find(';', startIndex);
146     if (endIndex == std::string::npos)
147     {
148         endIndex = cookieValue.size();
149     }
150     std::string_view authKey =
151         cookieValue.substr(startIndex, endIndex - startIndex);
152 
153     std::shared_ptr<persistent_data::UserSession> sessionOut =
154         persistent_data::SessionStore::getInstance().loginSessionByToken(
155             authKey);
156     if (sessionOut == nullptr)
157     {
158         return nullptr;
159     }
160 #ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION
161     // RFC7231 defines methods that need csrf protection
162     if (method != boost::beast::http::verb::get)
163     {
164         std::string_view csrf = reqHeader["X-XSRF-TOKEN"];
165         // Make sure both tokens are filled
166         if (csrf.empty() || sessionOut->csrfToken.empty())
167         {
168             return nullptr;
169         }
170 
171         if (csrf.size() != persistent_data::sessionTokenSize)
172         {
173             return nullptr;
174         }
175         // Reject if csrf token not available
176         if (!crow::utility::constantTimeStringCompare(csrf,
177                                                       sessionOut->csrfToken))
178         {
179             return nullptr;
180         }
181     }
182 #endif
183     return sessionOut;
184 }
185 #endif
186 
187 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
188 static std::shared_ptr<persistent_data::UserSession>
189     performTLSAuth(Response& res,
190                    const boost::beast::http::header<true>& reqHeader,
191                    const std::weak_ptr<persistent_data::UserSession>& session)
192 {
193     if (auto sp = session.lock())
194     {
195         // set cookie only if this is req from the browser.
196         if (reqHeader["User-Agent"].empty())
197         {
198             BMCWEB_LOG_DEBUG << " TLS session: " << sp->uniqueId
199                              << " will be used for this request.";
200             return sp;
201         }
202         std::string_view cookieValue = reqHeader["Cookie"];
203         if (cookieValue.empty() ||
204             cookieValue.find("SESSION=") == std::string::npos)
205         {
206             // TODO: change this to not switch to cookie auth
207             res.addHeader(
208                 "Set-Cookie",
209                 "XSRF-TOKEN=" + sp->csrfToken +
210                     "; SameSite=Strict; Secure\r\nSet-Cookie: SESSION=" +
211                     sp->sessionToken +
212                     "; SameSite=Strict; Secure; HttpOnly\r\nSet-Cookie: "
213                     "IsAuthenticated=true; Secure");
214             BMCWEB_LOG_DEBUG << " TLS session: " << sp->uniqueId
215                              << " with cookie will be used for this request.";
216             return sp;
217         }
218     }
219     return nullptr;
220 }
221 #endif
222 
223 // checks if request can be forwarded without authentication
224 [[maybe_unused]] static bool isOnAllowlist(std::string_view url,
225                                            boost::beast::http::verb method)
226 {
227     if (boost::beast::http::verb::get == method)
228     {
229         if (url == "/redfish/v1" || url == "/redfish/v1/" ||
230             url == "/redfish" || url == "/redfish/" ||
231             url == "/redfish/v1/odata" || url == "/redfish/v1/odata/")
232         {
233             return true;
234         }
235         if (crow::webroutes::routes.find(std::string(url)) !=
236             crow::webroutes::routes.end())
237         {
238             return true;
239         }
240     }
241 
242     // it's allowed to POST on session collection & login without
243     // authentication
244     if (boost::beast::http::verb::post == method)
245     {
246         if ((url == "/redfish/v1/SessionService/Sessions") ||
247             (url == "/redfish/v1/SessionService/Sessions/") ||
248             (url == "/redfish/v1/SessionService/Sessions/Members") ||
249             (url == "/redfish/v1/SessionService/Sessions/Members/") ||
250             (url == "/login"))
251         {
252             return true;
253         }
254     }
255 
256     return false;
257 }
258 
259 [[maybe_unused]] static std::shared_ptr<persistent_data::UserSession>
260     authenticate(
261         const boost::asio::ip::address& ipAddress [[maybe_unused]],
262         Response& res [[maybe_unused]],
263         boost::beast::http::verb method [[maybe_unused]],
264         const boost::beast::http::header<true>& reqHeader,
265         [[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>&
266             session)
267 {
268     const persistent_data::AuthConfigMethods& authMethodsConfig =
269         persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
270 
271     std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr;
272 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
273     if (authMethodsConfig.tls)
274     {
275         sessionOut = performTLSAuth(res, reqHeader, session);
276     }
277 #endif
278 #ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
279     if (sessionOut == nullptr && authMethodsConfig.xtoken)
280     {
281         sessionOut = performXtokenAuth(reqHeader);
282     }
283 #endif
284 #ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
285     if (sessionOut == nullptr && authMethodsConfig.cookie)
286     {
287         sessionOut = performCookieAuth(method, reqHeader);
288     }
289 #endif
290     std::string_view authHeader = reqHeader["Authorization"];
291     BMCWEB_LOG_DEBUG << "authHeader=" << authHeader;
292 
293     if (sessionOut == nullptr && authMethodsConfig.sessionToken)
294     {
295 #ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
296         sessionOut = performTokenAuth(authHeader);
297 #endif
298     }
299     if (sessionOut == nullptr && authMethodsConfig.basic)
300     {
301 #ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
302         sessionOut = performBasicAuth(ipAddress, authHeader);
303 #endif
304     }
305     if (sessionOut != nullptr)
306     {
307         return sessionOut;
308     }
309 
310     return nullptr;
311 }
312 
313 } // namespace authentication
314 } // namespace crow
315