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