xref: /openbmc/bmcweb/include/authentication.hpp (revision 25b54dba775b31021a3a4677eb79e9771bcb97f7)
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 inline std::shared_ptr<persistent_data::UserSession>
36     performBasicAuth(const boost::asio::ip::address& clientIp,
37                      std::string_view authHeader)
38 {
39     BMCWEB_LOG_DEBUG("[AuthMiddleware] Basic authentication");
40 
41     if (!authHeader.starts_with("Basic "))
42     {
43         return nullptr;
44     }
45 
46     std::string_view param = authHeader.substr(strlen("Basic "));
47     std::string authData;
48 
49     if (!crow::utility::base64Decode(param, authData))
50     {
51         return nullptr;
52     }
53     std::size_t separator = authData.find(':');
54     if (separator == std::string::npos)
55     {
56         return nullptr;
57     }
58 
59     std::string user = authData.substr(0, separator);
60     separator += 1;
61     if (separator > authData.size())
62     {
63         return nullptr;
64     }
65     std::string pass = authData.substr(separator);
66 
67     BMCWEB_LOG_DEBUG("[AuthMiddleware] Authenticating user: {}", user);
68     BMCWEB_LOG_DEBUG("[AuthMiddleware] User IPAddress: {}",
69                      clientIp.to_string());
70 
71     int pamrc = pamAuthenticateUser(user, pass);
72     bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
73     if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
74     {
75         return nullptr;
76     }
77 
78     // TODO(ed) generateUserSession is a little expensive for basic
79     // auth, as it generates some random identifiers that will never be
80     // used.  This should have a "fast" path for when user tokens aren't
81     // needed.
82     // This whole flow needs to be revisited anyway, as we can't be
83     // calling directly into pam for every request
84     return persistent_data::SessionStore::getInstance().generateUserSession(
85         user, clientIp, std::nullopt,
86         persistent_data::PersistenceType::SINGLE_REQUEST, isConfigureSelfOnly);
87 }
88 
89 inline std::shared_ptr<persistent_data::UserSession>
90     performTokenAuth(std::string_view authHeader)
91 {
92     BMCWEB_LOG_DEBUG("[AuthMiddleware] Token authentication");
93     if (!authHeader.starts_with("Token "))
94     {
95         return nullptr;
96     }
97     std::string_view token = authHeader.substr(strlen("Token "));
98     auto sessionOut =
99         persistent_data::SessionStore::getInstance().loginSessionByToken(token);
100     return sessionOut;
101 }
102 
103 inline std::shared_ptr<persistent_data::UserSession>
104     performXtokenAuth(const boost::beast::http::header<true>& reqHeader)
105 {
106     BMCWEB_LOG_DEBUG("[AuthMiddleware] X-Auth-Token authentication");
107 
108     std::string_view token = reqHeader["X-Auth-Token"];
109     if (token.empty())
110     {
111         return nullptr;
112     }
113     auto sessionOut =
114         persistent_data::SessionStore::getInstance().loginSessionByToken(token);
115     return sessionOut;
116 }
117 
118 inline std::shared_ptr<persistent_data::UserSession>
119     performCookieAuth(boost::beast::http::verb method [[maybe_unused]],
120                       const boost::beast::http::header<true>& reqHeader)
121 {
122     using headers = boost::beast::http::header<true>;
123     std::pair<headers::const_iterator, headers::const_iterator> cookies =
124         reqHeader.equal_range(boost::beast::http::field::cookie);
125 
126     for (auto it = cookies.first; it != cookies.second; it++)
127     {
128         std::string_view cookieValue = it->value();
129         BMCWEB_LOG_DEBUG("Checking cookie {}", cookieValue);
130         auto startIndex = cookieValue.find("SESSION=");
131         if (startIndex == std::string::npos)
132         {
133             BMCWEB_LOG_DEBUG(
134                 "Cookie was present, but didn't look like a session {}",
135                 cookieValue);
136             continue;
137         }
138         startIndex += sizeof("SESSION=") - 1;
139         auto endIndex = cookieValue.find(';', startIndex);
140         if (endIndex == std::string::npos)
141         {
142             endIndex = cookieValue.size();
143         }
144         std::string_view authKey = cookieValue.substr(startIndex,
145                                                       endIndex - startIndex);
146 
147         std::shared_ptr<persistent_data::UserSession> sessionOut =
148             persistent_data::SessionStore::getInstance().loginSessionByToken(
149                 authKey);
150         if (sessionOut == nullptr)
151         {
152             return nullptr;
153         }
154         sessionOut->cookieAuth = true;
155 
156         if constexpr (BMCWEB_INSECURE_DISABLE_CSRF)
157         {
158             // RFC7231 defines methods that need csrf protection
159             if (method != boost::beast::http::verb::get)
160             {
161                 std::string_view csrf = reqHeader["X-XSRF-TOKEN"];
162                 // Make sure both tokens are filled
163                 if (csrf.empty() || sessionOut->csrfToken.empty())
164                 {
165                     return nullptr;
166                 }
167 
168                 if (csrf.size() != persistent_data::sessionTokenSize)
169                 {
170                     return nullptr;
171                 }
172                 // Reject if csrf token not available
173                 if (!crow::utility::constantTimeStringCompare(
174                         csrf, sessionOut->csrfToken))
175                 {
176                     return nullptr;
177                 }
178             }
179         }
180     }
181     return nullptr;
182 }
183 
184 inline std::shared_ptr<persistent_data::UserSession>
185     performTLSAuth(Response& res,
186                    const boost::beast::http::header<true>& reqHeader,
187                    const std::weak_ptr<persistent_data::UserSession>& session)
188 {
189     if (auto sp = session.lock())
190     {
191         // set cookie only if this is req from the browser.
192         if (reqHeader["User-Agent"].empty())
193         {
194             BMCWEB_LOG_DEBUG(" TLS session: {} will be used for this request.",
195                              sp->uniqueId);
196             return sp;
197         }
198         // TODO: change this to not switch to cookie auth
199         res.addHeader(boost::beast::http::field::set_cookie,
200                       "XSRF-TOKEN=" + sp->csrfToken +
201                           "; SameSite=Strict; Secure");
202         res.addHeader(boost::beast::http::field::set_cookie,
203                       "SESSION=" + sp->sessionToken +
204                           "; SameSite=Strict; Secure; HttpOnly");
205         res.addHeader(boost::beast::http::field::set_cookie,
206                       "IsAuthenticated=true; Secure");
207         BMCWEB_LOG_DEBUG(
208             " TLS session: {} with cookie will be used for this request.",
209             sp->uniqueId);
210         return sp;
211     }
212     return nullptr;
213 }
214 
215 // checks if request can be forwarded without authentication
216 inline bool isOnAllowlist(std::string_view url, boost::beast::http::verb method)
217 {
218     if (boost::beast::http::verb::get == method)
219     {
220         if (url == "/redfish/v1" || url == "/redfish/v1/" ||
221             url == "/redfish" || url == "/redfish/" ||
222             url == "/redfish/v1/odata" || url == "/redfish/v1/odata/")
223         {
224             return true;
225         }
226         if (crow::webroutes::routes.find(std::string(url)) !=
227             crow::webroutes::routes.end())
228         {
229             return true;
230         }
231     }
232 
233     // it's allowed to POST on session collection & login without
234     // authentication
235     if (boost::beast::http::verb::post == method)
236     {
237         if ((url == "/redfish/v1/SessionService/Sessions") ||
238             (url == "/redfish/v1/SessionService/Sessions/") ||
239             (url == "/redfish/v1/SessionService/Sessions/Members") ||
240             (url == "/redfish/v1/SessionService/Sessions/Members/") ||
241             (url == "/login"))
242         {
243             return true;
244         }
245     }
246 
247     return false;
248 }
249 
250 inline std::shared_ptr<persistent_data::UserSession> authenticate(
251     const boost::asio::ip::address& ipAddress [[maybe_unused]],
252     Response& res [[maybe_unused]],
253     boost::beast::http::verb method [[maybe_unused]],
254     const boost::beast::http::header<true>& reqHeader,
255     [[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>&
256         session)
257 {
258     const persistent_data::AuthConfigMethods& authMethodsConfig =
259         persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
260 
261     std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr;
262     if constexpr (BMCWEB_MUTUAL_TLS_AUTH)
263     {
264         if (authMethodsConfig.tls)
265         {
266             sessionOut = performTLSAuth(res, reqHeader, session);
267         }
268     }
269     if constexpr (BMCWEB_XTOKEN_AUTH)
270     {
271         if (sessionOut == nullptr && authMethodsConfig.xtoken)
272         {
273             sessionOut = performXtokenAuth(reqHeader);
274         }
275     }
276     if constexpr (BMCWEB_COOKIE_AUTH)
277     {
278         if (sessionOut == nullptr && authMethodsConfig.cookie)
279         {
280             sessionOut = performCookieAuth(method, reqHeader);
281         }
282     }
283     std::string_view authHeader = reqHeader["Authorization"];
284     BMCWEB_LOG_DEBUG("authHeader={}", authHeader);
285     if constexpr (BMCWEB_SESSION_AUTH)
286     {
287         if (sessionOut == nullptr && authMethodsConfig.sessionToken)
288         {
289             sessionOut = performTokenAuth(authHeader);
290         }
291     }
292     if constexpr (BMCWEB_BASIC_AUTH)
293     {
294         if (sessionOut == nullptr && authMethodsConfig.basic)
295         {
296             sessionOut = performBasicAuth(ipAddress, authHeader);
297         }
298     }
299     if (sessionOut != nullptr)
300     {
301         return sessionOut;
302     }
303 
304     return nullptr;
305 }
306 
307 } // namespace authentication
308 } // namespace crow
309