xref: /openbmc/bmcweb/include/authentication.hpp (revision e9cc1bc9)
1 #pragma once
2 
3 #include "common.hpp"
4 #include "forward_unauthorized.hpp"
5 #include "http_request.hpp"
6 #include "http_response.hpp"
7 #include "http_utility.hpp"
8 #include "pam_authenticate.hpp"
9 #include "webroutes.hpp"
10 
11 #include <boost/container/flat_set.hpp>
12 
13 #include <random>
14 #include <utility>
15 
16 namespace crow
17 {
18 
19 namespace authentication
20 {
21 
22 inline void cleanupTempSession(const Request& req)
23 {
24     // TODO(ed) THis should really be handled by the persistent data
25     // middleware, but because it is upstream, it doesn't have access to the
26     // session information.  Should the data middleware persist the current
27     // user session?
28     if (req.session != nullptr &&
29         req.session->persistence ==
30             persistent_data::PersistenceType::SINGLE_REQUEST)
31     {
32         persistent_data::SessionStore::getInstance().removeSession(req.session);
33     }
34 }
35 
36 #ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
37 static std::shared_ptr<persistent_data::UserSession>
38     performBasicAuth(const boost::asio::ip::address& clientIp,
39                      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);
74     bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
75     if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
76     {
77         return nullptr;
78     }
79 
80     // TODO(ed) generateUserSession is a little expensive for basic
81     // auth, as it generates some random identifiers that will never be
82     // used.  This should have a "fast" path for when user tokens aren't
83     // needed.
84     // This whole flow needs to be revisited anyway, as we can't be
85     // calling directly into pam for every request
86     return persistent_data::SessionStore::getInstance().generateUserSession(
87         user, clientIp, std::nullopt,
88         persistent_data::PersistenceType::SINGLE_REQUEST, isConfigureSelfOnly);
89 }
90 #endif
91 
92 #ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
93 static std::shared_ptr<persistent_data::UserSession>
94     performTokenAuth(std::string_view authHeader)
95 {
96     BMCWEB_LOG_DEBUG("[AuthMiddleware] Token authentication");
97     if (!authHeader.starts_with("Token "))
98     {
99         return nullptr;
100     }
101     std::string_view token = authHeader.substr(strlen("Token "));
102     auto sessionOut =
103         persistent_data::SessionStore::getInstance().loginSessionByToken(token);
104     return sessionOut;
105 }
106 #endif
107 
108 #ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
109 static std::shared_ptr<persistent_data::UserSession>
110     performXtokenAuth(const boost::beast::http::header<true>& reqHeader)
111 {
112     BMCWEB_LOG_DEBUG("[AuthMiddleware] X-Auth-Token authentication");
113 
114     std::string_view token = reqHeader["X-Auth-Token"];
115     if (token.empty())
116     {
117         return nullptr;
118     }
119     auto sessionOut =
120         persistent_data::SessionStore::getInstance().loginSessionByToken(token);
121     return sessionOut;
122 }
123 #endif
124 
125 #ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
126 static std::shared_ptr<persistent_data::UserSession>
127     performCookieAuth(boost::beast::http::verb method [[maybe_unused]],
128                       const boost::beast::http::header<true>& reqHeader)
129 {
130     BMCWEB_LOG_DEBUG("[AuthMiddleware] Cookie authentication");
131 
132     std::string_view cookieValue = reqHeader["Cookie"];
133     if (cookieValue.empty())
134     {
135         return nullptr;
136     }
137 
138     auto startIndex = cookieValue.find("SESSION=");
139     if (startIndex == std::string::npos)
140     {
141         return nullptr;
142     }
143     startIndex += sizeof("SESSION=") - 1;
144     auto endIndex = cookieValue.find(';', startIndex);
145     if (endIndex == std::string::npos)
146     {
147         endIndex = cookieValue.size();
148     }
149     std::string_view authKey = cookieValue.substr(startIndex,
150                                                   endIndex - startIndex);
151 
152     std::shared_ptr<persistent_data::UserSession> sessionOut =
153         persistent_data::SessionStore::getInstance().loginSessionByToken(
154             authKey);
155     if (sessionOut == nullptr)
156     {
157         return nullptr;
158     }
159     sessionOut->cookieAuth = true;
160 #ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION
161     // RFC7231 defines methods that need csrf protection
162     if (method != boost::beast::http::verb::get)
163     {
164         std::string_view csrf = reqHeader["X-XSRF-TOKEN"];
165         // Make sure both tokens are filled
166         if (csrf.empty() || sessionOut->csrfToken.empty())
167         {
168             return nullptr;
169         }
170 
171         if (csrf.size() != persistent_data::sessionTokenSize)
172         {
173             return nullptr;
174         }
175         // Reject if csrf token not available
176         if (!crow::utility::constantTimeStringCompare(csrf,
177                                                       sessionOut->csrfToken))
178         {
179             return nullptr;
180         }
181     }
182 #endif
183     return sessionOut;
184 }
185 #endif
186 
187 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
188 static std::shared_ptr<persistent_data::UserSession>
189     performTLSAuth(Response& res,
190                    const boost::beast::http::header<true>& reqHeader,
191                    const std::weak_ptr<persistent_data::UserSession>& session)
192 {
193     if (auto sp = session.lock())
194     {
195         // set cookie only if this is req from the browser.
196         if (reqHeader["User-Agent"].empty())
197         {
198             BMCWEB_LOG_DEBUG(" TLS session: {} will be used for this request.",
199                              sp->uniqueId);
200             return sp;
201         }
202         // TODO: change this to not switch to cookie auth
203         res.addHeader(boost::beast::http::field::set_cookie,
204                       "XSRF-TOKEN=" + sp->csrfToken +
205                           "; SameSite=Strict; Secure");
206         res.addHeader(boost::beast::http::field::set_cookie,
207                       "SESSION=" + sp->sessionToken +
208                           "; SameSite=Strict; Secure; HttpOnly");
209         res.addHeader(boost::beast::http::field::set_cookie,
210                       "IsAuthenticated=true; Secure");
211         BMCWEB_LOG_DEBUG(
212             " TLS session: {} with cookie will be used for this request.",
213             sp->uniqueId);
214         return sp;
215     }
216     return nullptr;
217 }
218 #endif
219 
220 // checks if request can be forwarded without authentication
221 [[maybe_unused]] static bool isOnAllowlist(std::string_view url,
222                                            boost::beast::http::verb method)
223 {
224     if (boost::beast::http::verb::get == method)
225     {
226         if (url == "/redfish/v1" || url == "/redfish/v1/" ||
227             url == "/redfish" || url == "/redfish/" ||
228             url == "/redfish/v1/odata" || url == "/redfish/v1/odata/")
229         {
230             return true;
231         }
232         if (crow::webroutes::routes.find(std::string(url)) !=
233             crow::webroutes::routes.end())
234         {
235             return true;
236         }
237     }
238 
239     // it's allowed to POST on session collection & login without
240     // authentication
241     if (boost::beast::http::verb::post == method)
242     {
243         if ((url == "/redfish/v1/SessionService/Sessions") ||
244             (url == "/redfish/v1/SessionService/Sessions/") ||
245             (url == "/redfish/v1/SessionService/Sessions/Members") ||
246             (url == "/redfish/v1/SessionService/Sessions/Members/") ||
247             (url == "/login"))
248         {
249             return true;
250         }
251     }
252 
253     return false;
254 }
255 
256 [[maybe_unused]] static std::shared_ptr<persistent_data::UserSession>
257     authenticate(
258         const boost::asio::ip::address& ipAddress [[maybe_unused]],
259         Response& res [[maybe_unused]],
260         boost::beast::http::verb method [[maybe_unused]],
261         const boost::beast::http::header<true>& reqHeader,
262         [[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>&
263             session)
264 {
265     const persistent_data::AuthConfigMethods& authMethodsConfig =
266         persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
267 
268     std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr;
269 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
270     if (authMethodsConfig.tls)
271     {
272         sessionOut = performTLSAuth(res, reqHeader, session);
273     }
274 #endif
275 #ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
276     if (sessionOut == nullptr && authMethodsConfig.xtoken)
277     {
278         sessionOut = performXtokenAuth(reqHeader);
279     }
280 #endif
281 #ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
282     if (sessionOut == nullptr && authMethodsConfig.cookie)
283     {
284         sessionOut = performCookieAuth(method, reqHeader);
285     }
286 #endif
287     std::string_view authHeader = reqHeader["Authorization"];
288     BMCWEB_LOG_DEBUG("authHeader={}", authHeader);
289 
290     if (sessionOut == nullptr && authMethodsConfig.sessionToken)
291     {
292 #ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
293         sessionOut = performTokenAuth(authHeader);
294 #endif
295     }
296     if (sessionOut == nullptr && authMethodsConfig.basic)
297     {
298 #ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
299         sessionOut = performBasicAuth(ipAddress, authHeader);
300 #endif
301     }
302     if (sessionOut != nullptr)
303     {
304         return sessionOut;
305     }
306 
307     return nullptr;
308 }
309 
310 } // namespace authentication
311 } // namespace crow
312