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