xref: /openbmc/bmcweb/include/authentication.hpp (revision 3bfa3b29c0515a9e77c7c69fe072b7ff2e0fc302)
1 #pragma once
2 
3 #include "forward_unauthorized.hpp"
4 #include "http_request.hpp"
5 #include "http_response.hpp"
6 #include "http_utility.hpp"
7 #include "pam_authenticate.hpp"
8 #include "webroutes.hpp"
9 
10 #include <boost/container/flat_set.hpp>
11 
12 #include <random>
13 #include <utility>
14 
15 namespace crow
16 {
17 
18 namespace authentication
19 {
20 
21 inline void cleanupTempSession(const Request& req)
22 {
23     // TODO(ed) THis should really be handled by the persistent data
24     // middleware, but because it is upstream, it doesn't have access to the
25     // session information.  Should the data middleware persist the current
26     // user session?
27     if (req.session != nullptr &&
28         req.session->persistence ==
29             persistent_data::PersistenceType::SINGLE_REQUEST)
30     {
31         persistent_data::SessionStore::getInstance().removeSession(req.session);
32     }
33 }
34 
35 #ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
36 static std::shared_ptr<persistent_data::UserSession>
37     performBasicAuth(const boost::asio::ip::address& clientIp,
38                      std::string_view authHeader)
39 {
40     BMCWEB_LOG_DEBUG("[AuthMiddleware] Basic authentication");
41 
42     if (!authHeader.starts_with("Basic "))
43     {
44         return nullptr;
45     }
46 
47     std::string_view param = authHeader.substr(strlen("Basic "));
48     std::string authData;
49 
50     if (!crow::utility::base64Decode(param, authData))
51     {
52         return nullptr;
53     }
54     std::size_t separator = authData.find(':');
55     if (separator == std::string::npos)
56     {
57         return nullptr;
58     }
59 
60     std::string user = authData.substr(0, separator);
61     separator += 1;
62     if (separator > authData.size())
63     {
64         return nullptr;
65     }
66     std::string pass = authData.substr(separator);
67 
68     BMCWEB_LOG_DEBUG("[AuthMiddleware] Authenticating user: {}", user);
69     BMCWEB_LOG_DEBUG("[AuthMiddleware] User IPAddress: {}",
70                      clientIp.to_string());
71 
72     int pamrc = pamAuthenticateUser(user, pass);
73     bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
74     if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
75     {
76         return nullptr;
77     }
78 
79     // TODO(ed) generateUserSession is a little expensive for basic
80     // auth, as it generates some random identifiers that will never be
81     // used.  This should have a "fast" path for when user tokens aren't
82     // needed.
83     // This whole flow needs to be revisited anyway, as we can't be
84     // calling directly into pam for every request
85     return persistent_data::SessionStore::getInstance().generateUserSession(
86         user, clientIp, std::nullopt,
87         persistent_data::PersistenceType::SINGLE_REQUEST, isConfigureSelfOnly);
88 }
89 #endif
90 
91 #ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
92 static std::shared_ptr<persistent_data::UserSession>
93     performTokenAuth(std::string_view authHeader)
94 {
95     BMCWEB_LOG_DEBUG("[AuthMiddleware] Token authentication");
96     if (!authHeader.starts_with("Token "))
97     {
98         return nullptr;
99     }
100     std::string_view token = authHeader.substr(strlen("Token "));
101     auto sessionOut =
102         persistent_data::SessionStore::getInstance().loginSessionByToken(token);
103     return sessionOut;
104 }
105 #endif
106 
107 #ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
108 static std::shared_ptr<persistent_data::UserSession>
109     performXtokenAuth(const boost::beast::http::header<true>& reqHeader)
110 {
111     BMCWEB_LOG_DEBUG("[AuthMiddleware] X-Auth-Token authentication");
112 
113     std::string_view token = reqHeader["X-Auth-Token"];
114     if (token.empty())
115     {
116         return nullptr;
117     }
118     auto sessionOut =
119         persistent_data::SessionStore::getInstance().loginSessionByToken(token);
120     return sessionOut;
121 }
122 #endif
123 
124 #ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
125 static std::shared_ptr<persistent_data::UserSession>
126     performCookieAuth(boost::beast::http::verb method [[maybe_unused]],
127                       const boost::beast::http::header<true>& reqHeader)
128 {
129     using headers = boost::beast::http::header<true>;
130     std::pair<headers::const_iterator, headers::const_iterator> cookies =
131         reqHeader.equal_range(boost::beast::http::field::cookie);
132 
133     for (auto it = cookies.first; it != cookies.second; it++)
134     {
135         std::string_view cookieValue = it->value();
136         BMCWEB_LOG_DEBUG("Checking cookie {}", cookieValue);
137         auto startIndex = cookieValue.find("SESSION=");
138         if (startIndex == std::string::npos)
139         {
140             BMCWEB_LOG_DEBUG(
141                 "Cookie was present, but didn't look like a session {}",
142                 cookieValue);
143             continue;
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 = cookieValue.substr(startIndex,
152                                                       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         sessionOut->cookieAuth = true;
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(
179                     csrf, sessionOut->csrfToken))
180             {
181                 return nullptr;
182             }
183         }
184 #endif
185         return sessionOut;
186     }
187     return nullptr;
188 }
189 #endif
190 
191 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
192 static std::shared_ptr<persistent_data::UserSession>
193     performTLSAuth(Response& res,
194                    const boost::beast::http::header<true>& reqHeader,
195                    const std::weak_ptr<persistent_data::UserSession>& session)
196 {
197     if (auto sp = session.lock())
198     {
199         // set cookie only if this is req from the browser.
200         if (reqHeader["User-Agent"].empty())
201         {
202             BMCWEB_LOG_DEBUG(" TLS session: {} will be used for this request.",
203                              sp->uniqueId);
204             return sp;
205         }
206         // TODO: change this to not switch to cookie auth
207         res.addHeader(boost::beast::http::field::set_cookie,
208                       "XSRF-TOKEN=" + sp->csrfToken +
209                           "; SameSite=Strict; Secure");
210         res.addHeader(boost::beast::http::field::set_cookie,
211                       "SESSION=" + sp->sessionToken +
212                           "; SameSite=Strict; Secure; HttpOnly");
213         res.addHeader(boost::beast::http::field::set_cookie,
214                       "IsAuthenticated=true; Secure");
215         BMCWEB_LOG_DEBUG(
216             " TLS session: {} with cookie will be used for this request.",
217             sp->uniqueId);
218         return sp;
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