xref: /openbmc/bmcweb/include/authentication.hpp (revision c76f964a8f793e295a149064df0fbc263ef97e93)
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("BMCWEB-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("BMCWEB-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