xref: /openbmc/bmcweb/include/authentication.hpp (revision 40e9b92ec19acffb46f83a6e55b18974da5d708e)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #pragma once
4 
5 #include "cookies.hpp"
6 #include "forward_unauthorized.hpp"
7 #include "http_request.hpp"
8 #include "http_response.hpp"
9 #include "http_utility.hpp"
10 #include "pam_authenticate.hpp"
11 #include "webroutes.hpp"
12 
13 #include <boost/container/flat_set.hpp>
14 
15 #include <random>
16 #include <utility>
17 
18 namespace crow
19 {
20 
21 namespace authentication
22 {
23 
performBasicAuth(const boost::asio::ip::address & clientIp,std::string_view authHeader)24 inline std::shared_ptr<persistent_data::UserSession> performBasicAuth(
25     const boost::asio::ip::address& clientIp, std::string_view authHeader)
26 {
27     BMCWEB_LOG_DEBUG("[AuthMiddleware] Basic authentication");
28 
29     if (!authHeader.starts_with("Basic "))
30     {
31         return nullptr;
32     }
33 
34     std::string_view param = authHeader.substr(strlen("Basic "));
35     std::string authData;
36 
37     if (!crow::utility::base64Decode(param, authData))
38     {
39         return nullptr;
40     }
41     std::size_t separator = authData.find(':');
42     if (separator == std::string::npos)
43     {
44         return nullptr;
45     }
46 
47     std::string user = authData.substr(0, separator);
48     separator += 1;
49     if (separator > authData.size())
50     {
51         return nullptr;
52     }
53     std::string pass = authData.substr(separator);
54 
55     BMCWEB_LOG_DEBUG("[AuthMiddleware] Authenticating user: {}", user);
56     BMCWEB_LOG_DEBUG("[AuthMiddleware] User IPAddress: {}",
57                      clientIp.to_string());
58 
59     int pamrc = pamAuthenticateUser(user, pass, std::nullopt);
60     bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
61     if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
62     {
63         return nullptr;
64     }
65 
66     // Attempt to locate an existing Basic Auth session from the same ip address
67     // and user
68     for (auto& session :
69          persistent_data::SessionStore::getInstance().getSessions())
70     {
71         if (session->sessionType != persistent_data::SessionType::Basic)
72         {
73             continue;
74         }
75         if (session->clientIp != redfish::ip_util::toString(clientIp))
76         {
77             continue;
78         }
79         if (session->username != user)
80         {
81             continue;
82         }
83         return session;
84     }
85 
86     return persistent_data::SessionStore::getInstance().generateUserSession(
87         user, clientIp, std::nullopt, persistent_data::SessionType::Basic,
88         isConfigureSelfOnly);
89 }
90 
91 inline std::shared_ptr<persistent_data::UserSession>
performTokenAuth(std::string_view authHeader)92     performTokenAuth(std::string_view authHeader)
93 {
94     BMCWEB_LOG_DEBUG("[AuthMiddleware] Token authentication");
95     if (!authHeader.starts_with("Token "))
96     {
97         return nullptr;
98     }
99     std::string_view token = authHeader.substr(strlen("Token "));
100     auto sessionOut =
101         persistent_data::SessionStore::getInstance().loginSessionByToken(token);
102     return sessionOut;
103 }
104 
105 inline std::shared_ptr<persistent_data::UserSession>
performXtokenAuth(const boost::beast::http::header<true> & reqHeader)106     performXtokenAuth(const boost::beast::http::header<true>& reqHeader)
107 {
108     BMCWEB_LOG_DEBUG("[AuthMiddleware] X-Auth-Token authentication");
109 
110     std::string_view token = reqHeader["X-Auth-Token"];
111     if (token.empty())
112     {
113         return nullptr;
114     }
115     auto sessionOut =
116         persistent_data::SessionStore::getInstance().loginSessionByToken(token);
117     return sessionOut;
118 }
119 
120 inline std::shared_ptr<persistent_data::UserSession>
performCookieAuth(boost::beast::http::verb method,const boost::beast::http::header<true> & reqHeader)121     performCookieAuth(boost::beast::http::verb method [[maybe_unused]],
122                       const boost::beast::http::header<true>& reqHeader)
123 {
124     using headers = boost::beast::http::header<true>;
125     std::pair<headers::const_iterator, headers::const_iterator> cookies =
126         reqHeader.equal_range(boost::beast::http::field::cookie);
127 
128     for (auto it = cookies.first; it != cookies.second; it++)
129     {
130         std::string_view cookieValue = it->value();
131         BMCWEB_LOG_DEBUG("Checking cookie {}", cookieValue);
132         auto startIndex = cookieValue.find("SESSION=");
133         if (startIndex == std::string::npos)
134         {
135             BMCWEB_LOG_DEBUG(
136                 "Cookie was present, but didn't look like a session {}",
137                 cookieValue);
138             continue;
139         }
140         startIndex += sizeof("SESSION=") - 1;
141         auto endIndex = cookieValue.find(';', startIndex);
142         if (endIndex == std::string::npos)
143         {
144             endIndex = cookieValue.size();
145         }
146         std::string_view authKey =
147             cookieValue.substr(startIndex, endIndex - startIndex);
148 
149         std::shared_ptr<persistent_data::UserSession> sessionOut =
150             persistent_data::SessionStore::getInstance().loginSessionByToken(
151                 authKey);
152         if (sessionOut == nullptr)
153         {
154             return nullptr;
155         }
156         sessionOut->cookieAuth = true;
157 
158         if constexpr (!BMCWEB_INSECURE_DISABLE_CSRF)
159         {
160             // RFC7231 defines methods that need csrf protection
161             if (method != boost::beast::http::verb::get)
162             {
163                 std::string_view csrf = reqHeader["X-XSRF-TOKEN"];
164                 // Make sure both tokens are filled
165                 if (csrf.empty() || sessionOut->csrfToken.empty())
166                 {
167                     return nullptr;
168                 }
169 
170                 if (csrf.size() != persistent_data::sessionTokenSize)
171                 {
172                     return nullptr;
173                 }
174                 // Reject if csrf token not available
175                 if (!bmcweb::constantTimeStringCompare(csrf,
176                                                        sessionOut->csrfToken))
177                 {
178                     return nullptr;
179                 }
180             }
181         }
182         return sessionOut;
183     }
184     return nullptr;
185 }
186 
performTLSAuth(Response & res,const std::shared_ptr<persistent_data::UserSession> & session)187 inline std::shared_ptr<persistent_data::UserSession> performTLSAuth(
188     Response& res, const std::shared_ptr<persistent_data::UserSession>& session)
189 {
190     if (session != nullptr)
191     {
192         res.addHeader(boost::beast::http::field::set_cookie,
193                       "IsAuthenticated=true; Secure");
194         BMCWEB_LOG_DEBUG(
195             " TLS session: {} with cookie will be used for this request.",
196             session->uniqueId);
197     }
198 
199     return session;
200 }
201 
202 // checks if request can be forwarded without authentication
isOnAllowlist(std::string_view url,boost::beast::http::verb method)203 inline bool isOnAllowlist(std::string_view url, boost::beast::http::verb method)
204 {
205     // Handle the case where the router registers routes as both ending with /
206     // and not.
207     if (url.ends_with('/') && url != "/")
208     {
209         url.remove_suffix(1);
210     }
211     if (boost::beast::http::verb::get == method)
212     {
213         if ((url == "/redfish") || //
214             (url == "/redfish/v1") || //
215             (url == "/redfish/v1/odata") || //
216             (url == "/redfish/v1/$metadata"))
217         {
218             return true;
219         }
220         if (crow::webroutes::routes.find(std::string(url)) !=
221             crow::webroutes::routes.end())
222         {
223             return true;
224         }
225     }
226 
227     // it's allowed to POST on session collection & login without
228     // authentication
229     if (boost::beast::http::verb::post == method)
230     {
231         if ((url == "/redfish/v1/SessionService/Sessions") ||
232             (url == "/redfish/v1/SessionService/Sessions/Members") ||
233             (url == "/login"))
234         {
235             return true;
236         }
237     }
238 
239     return false;
240 }
241 
authenticate(const boost::asio::ip::address & ipAddress,Response & res,boost::beast::http::verb method,const boost::beast::http::header<true> & reqHeader,const std::shared_ptr<persistent_data::UserSession> & session)242 inline std::shared_ptr<persistent_data::UserSession> authenticate(
243     const boost::asio::ip::address& ipAddress [[maybe_unused]],
244     Response& res [[maybe_unused]],
245     boost::beast::http::verb method [[maybe_unused]],
246     const boost::beast::http::header<true>& reqHeader,
247     [[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>&
248         session)
249 {
250     const persistent_data::AuthConfigMethods& authMethodsConfig =
251         persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
252 
253     std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr;
254     if constexpr (BMCWEB_MUTUAL_TLS_AUTH)
255     {
256         if (authMethodsConfig.tls)
257         {
258             sessionOut = performTLSAuth(res, session);
259         }
260     }
261     if constexpr (BMCWEB_XTOKEN_AUTH)
262     {
263         if (sessionOut == nullptr && authMethodsConfig.xtoken)
264         {
265             sessionOut = performXtokenAuth(reqHeader);
266         }
267     }
268     if constexpr (BMCWEB_COOKIE_AUTH)
269     {
270         if (sessionOut == nullptr && authMethodsConfig.cookie)
271         {
272             sessionOut = performCookieAuth(method, reqHeader);
273         }
274     }
275     std::string_view authHeader = reqHeader["Authorization"];
276     BMCWEB_LOG_DEBUG("authHeader={}", authHeader);
277     if constexpr (BMCWEB_SESSION_AUTH)
278     {
279         if (sessionOut == nullptr && authMethodsConfig.sessionToken)
280         {
281             sessionOut = performTokenAuth(authHeader);
282         }
283     }
284     if constexpr (BMCWEB_BASIC_AUTH)
285     {
286         if (sessionOut == nullptr && authMethodsConfig.basic)
287         {
288             sessionOut = performBasicAuth(ipAddress, authHeader);
289         }
290     }
291     if (sessionOut != nullptr)
292     {
293         return sessionOut;
294     }
295 
296     return nullptr;
297 }
298 
299 } // namespace authentication
300 } // namespace crow
301