Send cookies to webui-vue from Sessions POST
Using Redfish-standard X-Auth-Token authentication is less secure
(against injected JS code) compared to an HttpOnly (not available to the
JS VM) SESSION cookie. Currently webui-vue authenticates connections to
WebSocket URIs not only by a JS-accessible token (passed as subprotocol
when upgrading to WS) but also via a SESSION cookie (even though it is
not subject to CORS policy).
To allow WebSocket-based functionality (IP KVM, SOL, VM) after creating
a Session object send a set of cookies instead of the X-Auth-Token
header if the request was made by webui-vue (detected by presence of
"X-Requested-With" header).
Factor out cookie setting and clearing functions and use explicit Path=/
attribute as the cookies are valid for the whole server, not just the
path of the endpoint they were created by.
Not specifying Path was functional for /login endpoint because
https://www.rfc-editor.org/rfc/rfc6265#section-5.3 point 7 for this case
says "set the cookie's path to the default-path of the request-uri" and
https://www.rfc-editor.org/rfc/rfc6265#section-5.1.4 tells how to
compute the default path. Basically, it was a "happy coincidence" that
/login defaults to / for the Path, if it was /openbmc/login then the
cookies would have been set to Path=/openbmc and not work at all for
/redfish/v1 endpoints.
Tested: Redfish-Service-Validator doesn't see a difference. Runtime
testing logging in via Sessions endpoint, getting data, using websockets
and logging out against webui-vue with a corresponding change while
carefully observing Request and Response headers. Creating a session
with curl without the special header shows just X-Auth-Token and no
cookies in the response.
Change-Id: I0b1774e586671874bb79f115e9cddf194f9ea653
Signed-off-by: Paul Fertser <fercerpav@gmail.com>
diff --git a/include/authentication.hpp b/include/authentication.hpp
index e6e699c..2c3a08a 100644
--- a/include/authentication.hpp
+++ b/include/authentication.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include "cookies.hpp"
#include "forward_unauthorized.hpp"
#include "http_request.hpp"
#include "http_response.hpp"
@@ -197,12 +198,7 @@
return sp;
}
// TODO: change this to not switch to cookie auth
- res.addHeader(boost::beast::http::field::set_cookie,
- "XSRF-TOKEN=" + sp->csrfToken +
- "; SameSite=Strict; Secure");
- res.addHeader(boost::beast::http::field::set_cookie,
- "SESSION=" + sp->sessionToken +
- "; SameSite=Strict; Secure; HttpOnly");
+ bmcweb::setSessionCookies(res, *sp);
res.addHeader(boost::beast::http::field::set_cookie,
"IsAuthenticated=true; Secure");
BMCWEB_LOG_DEBUG(
diff --git a/include/cookies.hpp b/include/cookies.hpp
new file mode 100644
index 0000000..e66eec8
--- /dev/null
+++ b/include/cookies.hpp
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "http_response.hpp"
+#include "sessions.hpp"
+
+namespace bmcweb
+{
+
+inline void setSessionCookies(crow::Response& res,
+ const persistent_data::UserSession& session)
+{
+ res.addHeader(boost::beast::http::field::set_cookie,
+ "XSRF-TOKEN=" + session.csrfToken +
+ "; Path=/; SameSite=Strict; Secure");
+ res.addHeader(boost::beast::http::field::set_cookie,
+ "SESSION=" + session.sessionToken +
+ "; Path=/; SameSite=Strict; Secure; HttpOnly");
+}
+
+inline void clearSessionCookies(crow::Response& res)
+{
+ res.addHeader(boost::beast::http::field::set_cookie,
+ "SESSION="
+ "; Path=/; SameSite=Strict; Secure; HttpOnly; "
+ "expires=Thu, 01 Jan 1970 00:00:00 GMT");
+ res.addHeader("Clear-Site-Data", R"("cache","cookies","storage")");
+}
+
+} // namespace bmcweb
diff --git a/include/login_routes.hpp b/include/login_routes.hpp
index f78c159..0adb9d4 100644
--- a/include/login_routes.hpp
+++ b/include/login_routes.hpp
@@ -1,6 +1,7 @@
#pragma once
#include "app.hpp"
+#include "cookies.hpp"
#include "http_request.hpp"
#include "http_response.hpp"
#include "multipart_parser.hpp"
@@ -166,12 +167,7 @@
persistent_data::PersistenceType::TIMEOUT,
isConfigureSelfOnly);
- asyncResp->res.addHeader(boost::beast::http::field::set_cookie,
- "XSRF-TOKEN=" + session->csrfToken +
- "; SameSite=Strict; Secure");
- asyncResp->res.addHeader(boost::beast::http::field::set_cookie,
- "SESSION=" + session->sessionToken +
- "; SameSite=Strict; Secure; HttpOnly");
+ bmcweb::setSessionCookies(asyncResp->res, *session);
// if content type is json, assume json token
asyncResp->res.jsonValue["token"] = session->sessionToken;
@@ -195,12 +191,7 @@
asyncResp->res.jsonValue["message"] = "200 OK";
asyncResp->res.jsonValue["status"] = "ok";
- asyncResp->res.addHeader("Set-Cookie",
- "SESSION="
- "; SameSite=Strict; Secure; HttpOnly; "
- "expires=Thu, 01 Jan 1970 00:00:00 GMT");
- asyncResp->res.addHeader("Clear-Site-Data",
- R"("cache","cookies","storage")");
+ bmcweb::clearSessionCookies(asyncResp->res);
persistent_data::SessionStore::getInstance().removeSession(session);
}
}
diff --git a/redfish-core/lib/redfish_sessions.hpp b/redfish-core/lib/redfish_sessions.hpp
index 555e7f3..dba1aac 100644
--- a/redfish-core/lib/redfish_sessions.hpp
+++ b/redfish-core/lib/redfish_sessions.hpp
@@ -17,6 +17,7 @@
#include "account_service.hpp"
#include "app.hpp"
+#include "cookies.hpp"
#include "error_messages.hpp"
#include "http/utility.hpp"
#include "persistent_data.hpp"
@@ -125,6 +126,11 @@
}
}
+ if (session->cookieAuth)
+ {
+ bmcweb::clearSessionCookies(asyncResp->res);
+ }
+
persistent_data::SessionStore::getInstance().removeSession(session);
messages::success(asyncResp->res);
}
@@ -245,7 +251,18 @@
return;
}
- asyncResp->res.addHeader("X-Auth-Token", session->sessionToken);
+ // When session is created by webui-vue give it session cookies as a
+ // non-standard Redfish extension. This is needed for authentication for
+ // WebSockets-based functionality.
+ if (!req.getHeaderValue("X-Requested-With").empty())
+ {
+ bmcweb::setSessionCookies(asyncResp->res, *session);
+ }
+ else
+ {
+ asyncResp->res.addHeader("X-Auth-Token", session->sessionToken);
+ }
+
asyncResp->res.addHeader(
"Location", "/redfish/v1/SessionService/Sessions/" + session->uniqueId);
asyncResp->res.result(boost::beast::http::status::created);