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