Skip to content

Commit

Permalink
login: Prevent multiple logins in a single browser session
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mvollmer committed Aug 12, 2024
1 parent 680decc commit 6e0d509
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 21 deletions.
22 changes: 22 additions & 0 deletions pkg/static/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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 */
Expand Down
57 changes: 39 additions & 18 deletions src/ws/cockpitauth.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 4 additions & 1 deletion src/ws/cockpitauth.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
45 changes: 43 additions & 2 deletions src/ws/cockpithandlers.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand Down

0 comments on commit 6e0d509

Please sign in to comment.