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