From 0c64a1e5302fcf6458249fb91e387704690ef35e Mon Sep 17 00:00:00 2001 From: Marius Vollmer Date: Fri, 13 Sep 2024 14:10:28 +0300 Subject: [PATCH] ws: Conditionally block channel requests to remote hosts When AllowMultiHost is false, cockpit-ws will reject all GET requests that would load from a non-localhost bridge. --- src/ws/cockpitchannelresponse.c | 9 ++++++ test/verify/check-connection | 49 +++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/src/ws/cockpitchannelresponse.c b/src/ws/cockpitchannelresponse.c index bf01dba30233..fe30e207d264 100644 --- a/src/ws/cockpitchannelresponse.c +++ b/src/ws/cockpitchannelresponse.c @@ -601,6 +601,7 @@ cockpit_channel_response_serve (CockpitWebService *service, gchar *channel = NULL; gpointer key; gpointer value; + gboolean allow_multihost; g_return_if_fail (COCKPIT_IS_WEB_SERVICE (service)); g_return_if_fail (in_headers != NULL); @@ -614,6 +615,14 @@ cockpit_channel_response_serve (CockpitWebService *service, goto out; } + allow_multihost = cockpit_conf_bool ("WebService", "AllowMultiHost", ALLOW_MULTIHOST_DEFAULT); + if (!allow_multihost && g_strcmp0 (host, "localhost") != 0) + { + cockpit_web_response_error (response, 403, NULL, NULL); + handled = TRUE; + goto out; + } + if (quoted_etag) { cache_type = COCKPIT_WEB_RESPONSE_CACHE; diff --git a/test/verify/check-connection b/test/verify/check-connection index ec2e2ef8c87a..40151f72655c 100755 --- a/test/verify/check-connection +++ b/test/verify/check-connection @@ -1443,5 +1443,54 @@ server { self.allow_journal_messages("couldn't change to runtime dir.*Permission denied") +class TestConnectionMultiHost(testlib.MachineCase): + provision = { + 'm1': {"address": "10.111.113.1/20", "memory_mb": 512}, + 'm2': {"address": "10.111.113.2/20", "memory_mb": 512}, + } + + def testBasic(self): + b = self.browser + m1 = self.machines['m1'] + m2 = self.machines['m2'] + + self.enable_multihost(m1) + + # setup seamless ssh auth from machine1 to machine2 + self.setup_ssh_auth() + + # login into cockpit on machine1 + self.login_and_go("/system") + cookie = b.cookie("cockpit") + + def http_code(url): + return int(subprocess.check_output(["curl", + "--silent", + "-b", f"cockpit={cookie['value']}", + "-o", "/dev/null", "-w", "%{http_code" + "}", + f"http://{b.address}:{b.port}{url}"])) + + # Now we can get resources from m1 + self.assertEqual(200, http_code("/cockpit/@localhost/manifests.json")) + + # But not from m2 + self.assertEqual(502, http_code("/cockpit/@10.111.113.2/manifests.json")) + + # Unless we have stored the hostkey of m2 + hk = m2.execute("cat /etc/ssh/ssh_host_ecdsa_key.pub").strip() + m1.execute(f"f=/home/admin/.ssh/known_hosts; echo 10.111.113.2 {hk} >$f; chown admin:admin $f") + self.assertEqual(200, http_code("/cockpit/@10.111.113.2/manifests.json")) + + # But not when AllowMultiHost is false + m1.write("/etc/cockpit/cockpit.conf", + '[WebService]\nAllowMultiHost=no\n') + m1.restart_cockpit() + b.relogin("/system") + cookie = b.cookie("cockpit") + + self.assertEqual(200, http_code("/cockpit/@localhost/manifests.json")) + self.assertEqual(403, http_code("/cockpit/@10.111.113.2/manifests.json")) + + if __name__ == '__main__': testlib.test_main()