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 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> 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> 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> 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 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 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 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