From 6e0d5090baed9c7dbb9038332a6154d3586ecd7a Mon Sep 17 00:00:00 2001 From: Marius Vollmer Date: Wed, 7 Aug 2024 12:37:17 +0300 Subject: [PATCH] login: Prevent multiple logins in a single browser session unless allowed by cockpit.conf. If the login page is loaded and a valid session cookie is already available, then we are about to log into a second host from the same browser session, and both logins will have access to each others cookies. This should only be allowed when AllowMultiHost is true. If it is not true, the login page immediately redirects to the session for the existing cookie. Information about session cookies is not available to login.js, so cockpit-ws helps out by exposing which ones are present, without exposing the cookies themselves. --- pkg/static/login.js | 22 ++++++++++++++++ src/ws/cockpitauth.c | 57 +++++++++++++++++++++++++++------------- src/ws/cockpitauth.h | 5 +++- src/ws/cockpithandlers.c | 45 +++++++++++++++++++++++++++++-- 4 files changed, 108 insertions(+), 21 deletions(-) diff --git a/pkg/static/login.js b/pkg/static/login.js index 37481144381e..1966be890e7a 100644 --- a/pkg/static/login.js +++ b/pkg/static/login.js @@ -338,6 +338,26 @@ import "./login.scss"; event.stopPropagation(); } + function deal_with_multihost() { + // If we are currently logged in to some machine, but still + // end up on the login page, we are about to load resources + // from two machines into the same browser origin. This needs + // to be allowed explicitly via a configuration setting. + + const logged_into = environment.logged_into || []; + const cur_machine = logged_into.length > 0 ? logged_into[0] : null; + + function redirect_to_current_machine() { + if (cur_machine === ".") + login_reload("/"); + else + login_reload("/=" + cur_machine); + } + + if (cur_machine && !environment.page.allow_multihost) + redirect_to_current_machine(); + } + function boot() { window.onload = null; @@ -348,6 +368,8 @@ import "./login.scss"; document.documentElement.dir = window.cockpit_po[""]["language-direction"]; } + deal_with_multihost(); + setup_path_globals(window.location.pathname); /* Determine if we are nested or not, and switch styles */ diff --git a/src/ws/cockpitauth.c b/src/ws/cockpitauth.c index 99f5c33ab2c6..ef96e831647e 100644 --- a/src/ws/cockpitauth.c +++ b/src/ws/cockpitauth.c @@ -1241,13 +1241,38 @@ base64_decode_string (const char *enc) return dec; } +static CockpitSession * +session_for_cookie_value (CockpitAuth *self, + const gchar *cookie_value) +{ + gchar *decoded = NULL; + const char *prefix = "v=2;k="; + CockpitSession *ret = NULL; + + g_return_val_if_fail (self != NULL, FALSE); + + if (cookie_value) + { + decoded = base64_decode_string (cookie_value); + if (decoded != NULL) + { + if (g_str_has_prefix (decoded, prefix)) + ret = g_hash_table_lookup (self->sessions, decoded); + else + g_debug ("invalid or unsupported cookie: %s", decoded); + + g_free (decoded); + } + } + + return ret; +} + static CockpitSession * session_for_request (CockpitAuth *self, CockpitWebRequest *request) { - gchar *cookie = NULL; gchar *raw = NULL; - const char *prefix = "v=2;k="; CockpitSession *ret = NULL; gchar *application; gchar *cookie_name = NULL; @@ -1260,23 +1285,12 @@ session_for_request (CockpitAuth *self, cookie_name = application_cookie_name (application); raw = cockpit_web_request_parse_cookie (request, cookie_name); - if (raw) - { - cookie = base64_decode_string (raw); - if (cookie != NULL) - { - if (g_str_has_prefix (cookie, prefix)) - ret = g_hash_table_lookup (self->sessions, cookie); - else - g_debug ("invalid or unsupported cookie: %s", cookie); + ret = session_for_cookie_value (self, raw); - /* We must never find the default session based on a cookie */ - g_assert (!ret || !g_str_equal (ret->cookie, LOCAL_SESSION)); - g_assert (!ret || !g_str_equal (ret->name, LOCAL_SESSION)); - g_free (cookie); - } - g_free (raw); - } + /* We must never find the default session based on a cookie */ + g_assert (!ret || !g_str_equal (ret->cookie, LOCAL_SESSION)); + g_assert (!ret || !g_str_equal (ret->name, LOCAL_SESSION)); + g_free (raw); /* Check for a default session for auto-login */ if (!ret) @@ -1309,6 +1323,13 @@ cockpit_auth_check_cookie (CockpitAuth *self, } } +gboolean +cockpit_auth_is_valid_cookie_value (CockpitAuth *self, + const gchar *cookie_value) +{ + return session_for_cookie_value (self, cookie_value) != NULL; +} + void cockpit_auth_local_async (CockpitAuth *self, const gchar *user, diff --git a/src/ws/cockpitauth.h b/src/ws/cockpitauth.h index 038d5a294c73..f95b9fb49eab 100644 --- a/src/ws/cockpitauth.h +++ b/src/ws/cockpitauth.h @@ -102,7 +102,10 @@ gboolean cockpit_auth_local_finish (CockpitAuth *self, CockpitWebService * cockpit_auth_check_cookie (CockpitAuth *self, CockpitWebRequest *request); -gchar * cockpit_auth_parse_application (const gchar *path, +gboolean cockpit_auth_is_valid_cookie_value (CockpitAuth *self, + const gchar *cookie_value); + + gchar * cockpit_auth_parse_application (const gchar *path, gboolean *is_host); gchar * cockpit_auth_empty_cookie_value (const gchar *path, diff --git a/src/ws/cockpithandlers.c b/src/ws/cockpithandlers.c index 29bb690cdf1a..88a54b093d98 100644 --- a/src/ws/cockpithandlers.c +++ b/src/ws/cockpithandlers.c @@ -277,8 +277,47 @@ add_page_to_environment (JsonObject *object, json_object_set_object_member (object, "page", page); } +static void +add_logged_into_to_environment (JsonObject *object, + CockpitAuth *auth, + GHashTable *request_headers) +{ + JsonArray *logged_into = json_array_new (); + + gchar *h = g_hash_table_lookup (request_headers, "Cookie"); + while (h && *h) { + const gchar *start = h; + while (*h && *h != '=') + h++; + const gchar *equal = h; + while (*h && *h != ';') + h++; + const gchar *end = h; + if (*h) + h++; + while (*h && *h == ' ') + h++; + + if (*equal != '=') + continue; + + g_autofree gchar *value = g_strndup (equal + 1, end - equal - 1); + + if (!cockpit_auth_is_valid_cookie_value (auth, value)) + continue; + + g_autofree gchar *name = g_strndup (start, equal - start); + if (g_str_equal (name, "cockpit")) + json_array_add_string_element(logged_into, "."); + else if (g_str_has_prefix (name, "machine-cockpit+")) + json_array_add_string_element(logged_into, name + strlen("machine-cockpit+")); + } + + json_object_set_array_member (object, "logged_into", logged_into); +} + static GBytes * -build_environment (GHashTable *os_release) +build_environment (GHashTable *os_release, CockpitAuth *auth, GHashTable *request_headers) { /* * We don't include entirety of os-release into the @@ -310,6 +349,7 @@ build_environment (GHashTable *os_release) json_object_set_boolean_member (object, "is_cockpit_client", is_cockpit_client); add_page_to_environment (object, is_cockpit_client); + add_logged_into_to_environment (object, auth, request_headers); hostname = g_malloc0 (HOST_NAME_MAX + 1); gethostname (hostname, HOST_NAME_MAX); @@ -386,7 +426,7 @@ send_login_html (CockpitWebResponse *response, GBytes *po_bytes; CockpitWebFilter *filter3 = NULL; - environment = build_environment (ws->os_release); + environment = build_environment (ws->os_release, ws->auth, headers); filter = cockpit_web_inject_new (marker, environment, 1); g_bytes_unref (environment); cockpit_web_response_add_filter (response, filter); @@ -455,6 +495,7 @@ send_login_html (CockpitWebResponse *response, "Content-Security-Policy", content_security_policy, "Set-Cookie", cookie_line, NULL); + cockpit_web_response_set_cache_type (response, COCKPIT_WEB_RESPONSE_NO_CACHE); if (cockpit_web_response_queue (response, bytes)) cockpit_web_response_complete (response);