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