diff --git a/pkg/static/login.html b/pkg/static/login.html
index d2b20438653a..91dbba0d4087 100644
--- a/pkg/static/login.html
+++ b/pkg/static/login.html
@@ -23,6 +23,14 @@
+
+
diff --git a/pkg/static/login.js b/pkg/static/login.js
index 70cfcbc4aa61..d49080f39ea9 100644
--- a/pkg/static/login.js
+++ b/pkg/static/login.js
@@ -369,8 +369,16 @@ function debug(...args) {
}
}
- if (cur_machine && !environment.page.allow_multihost)
- redirect_to_current_machine();
+ if (cur_machine) {
+ if (!environment.page.allow_multihost)
+ redirect_to_current_machine();
+ else {
+ id("multihost-message").textContent = format(_("You are already connected to '$0' in this browser session. Connecting to other hosts will allow them to execute arbitrary code on each other. Please be careful."),
+ cur_machine == "." ? "localhost" : cur_machine);
+ id("multihost-get-me-there").addEventListener("click", redirect_to_current_machine);
+ show('#multihost-warning');
+ }
+ }
}
function boot() {
diff --git a/pkg/static/login.scss b/pkg/static/login.scss
index 203f32a74b3e..c8ba14571fe3 100644
--- a/pkg/static/login.scss
+++ b/pkg/static/login.scss
@@ -355,14 +355,14 @@ label.checkbox {
display: none;
}
-.login-pf #banner {
+.login-pf #banner, .login-pf #multihost-warning {
margin-block: 1rem 0.5rem;
margin-inline: 0;
grid-area: banner;
inline-size: 100%;
}
-#banner-message {
+#banner-message, #multihost-message {
white-space: pre-wrap;
max-block-size: 12em;
overflow: auto;
diff --git a/test/reference b/test/reference
index c83ced24f3d8..0d2849e2a944 160000
--- a/test/reference
+++ b/test/reference
@@ -1 +1 @@
-Subproject commit c83ced24f3d8a9d0279808014598b9a85893d061
+Subproject commit 0d2849e2a944573b64cd2fad8a023d84d6b8c413
diff --git a/test/verify/check-static-login b/test/verify/check-static-login
index 6c6ae0925d24..c7e046b84750 100755
--- a/test/verify/check-static-login
+++ b/test/verify/check-static-login
@@ -1111,5 +1111,90 @@ matchrule = ^DC=LAN,DC=COCKPIT,CN=alice$
check_store_hostkey(real_hostkey)
+@testlib.skipBeiboot("host switching disabled in beiboot mode")
+class TestLoginMultiHost(testlib.MachineCase):
+ def testBasic(self):
+ b = self.browser
+ m1 = self.machines["machine1"]
+
+ ip = "172.27.0.15"
+
+ # Disallow multiple sessions initially
+
+ if self.multihost_enabled:
+ m1.write("/etc/cockpit/cockpit.conf",
+ '[WebService]\nAllowMultiHost=no\n')
+
+ m1.start_cockpit()
+
+ # Log in via SSH
+
+ b.open(f"/={ip}/playground")
+ b.set_val('#login-user-input', "admin")
+ b.set_val('#login-password-input', "foobar")
+ b.click("#login-button")
+ b.wait_in_text("#hostkey-fingerprint", "SHA256:")
+ b.click("#login-button")
+ b.wait_visible('#content')
+ self.assertEqual(b.eval_js("window.location.pathname"), f"/={ip}/playground")
+
+ # Go back to login page. The SSH session from above will still
+ # be active (for some time until it times out) and the login
+ # page will immediately redirect us to it. We will end up on
+ # the Overview (instead of the playground).
+
+ b.eval_js("window.location = '/'") # b.go would run into a "navigation canceled error"
+ b.wait_js_cond(f"window.location.pathname == '/={ip}/system'")
+ b.wait_visible('#content')
+ b.logout()
+
+ # Enable host switcher for the rest of the test
+
+ if self.multihost_enabled:
+ # clean up AllowMultiHost=no from above
+ m1.execute("rm /etc/cockpit/cockpit.conf")
+ self.enable_multihost(m1)
+ m1.restart_cockpit()
+
+ # Log in via SSH
+
+ b.open("/")
+ b.set_val('#login-user-input', "admin")
+ b.set_val('#login-password-input', "foobar")
+ b.click("#show-other-login-options")
+ b.set_val("#server-field", ip)
+ b.click("#login-button")
+ b.wait_visible('#content')
+
+ # Go back to login page. The SSH session will still be active
+ # as before, but now the login page will merely warn us about
+ # it.
+
+ b.open("/")
+ b.wait_in_text("#multihost-warning", f"You are already connected to '{ip}' in this browser session")
+ b.assert_pixels("body", "warning")
+
+ # Clicking on the button should redirect us to that session
+
+ b.click("#multihost-warning button")
+ b.wait_visible('#content')
+ self.assertEqual(b.eval_js("window.location.pathname"), f"/={ip}/system")
+
+ # Go back to login and log in locally anyway.
+
+ b.open("/")
+ b.set_val('#login-user-input', "admin")
+ b.set_val('#login-password-input', "foobar")
+ b.click("#login-button")
+ b.wait_visible('#content')
+ self.assertEqual(b.eval_js("window.location.pathname"), "/system")
+
+ # Go back to first session, it should still be there
+
+ b.open(f"/={ip}")
+ b.wait_visible('#content')
+ self.assertEqual(b.eval_js("window.location.pathname"), f"/={ip}/system")
+
+
if __name__ == '__main__':
testlib.test_main()