From f90684d3d0539fa0ae919a2167043d5108f44f5f Mon Sep 17 00:00:00 2001 From: Marius Vollmer Date: Mon, 7 Oct 2024 17:54:39 +0300 Subject: [PATCH] shell: Require confirmation before connecting to remote machines --- doc/guide/Makefile-guide.am | 1 + doc/guide/cockpit-guide.xml | 1 + doc/guide/multi-host.xml | 54 ++++++++++ doc/man/cockpit.conf.xml | 12 +++ pkg/shell/hosts.jsx | 19 ++-- pkg/shell/hosts_dialog.jsx | 120 ++++++++++++++++++---- pkg/shell/shell.jsx | 5 +- pkg/shell/state.jsx | 21 +++- test/common/testlib.py | 20 +++- test/verify/check-shell-host-switching | 59 +++++++++-- test/verify/check-shell-multi-machine | 66 +++++++----- test/verify/check-shell-multi-machine-key | 8 ++ test/verify/check-shell-multi-os | 2 +- test/verify/check-superuser | 5 +- test/verify/check-system-realms | 4 + test/verify/check-system-shutdown-restart | 2 +- 16 files changed, 320 insertions(+), 79 deletions(-) create mode 100644 doc/guide/multi-host.xml diff --git a/doc/guide/Makefile-guide.am b/doc/guide/Makefile-guide.am index 66c425cbf27d..ad55f1efa844 100644 --- a/doc/guide/Makefile-guide.am +++ b/doc/guide/Makefile-guide.am @@ -21,6 +21,7 @@ GUIDE_INCLUDES = \ doc/guide/cockpit-session.xml \ doc/guide/cockpit-spawn.xml \ doc/guide/cockpit-util.xml \ + doc/guide/multi-host.xml \ doc/guide/authentication.xml \ doc/guide/embedding.xml \ doc/guide/feature-firewall.xml \ diff --git a/doc/guide/cockpit-guide.xml b/doc/guide/cockpit-guide.xml index 77e4cf923c94..13fc80fa98c9 100644 --- a/doc/guide/cockpit-guide.xml +++ b/doc/guide/cockpit-guide.xml @@ -27,6 +27,7 @@ + diff --git a/doc/guide/multi-host.xml b/doc/guide/multi-host.xml new file mode 100644 index 000000000000..c0034ed12cf2 --- /dev/null +++ b/doc/guide/multi-host.xml @@ -0,0 +1,54 @@ + + + + + Managing multiple hosts at the same time + + + + Cockpit allows you to access multiple hosts in a single session, + by establishing SSH connections to other hosts. This is quite + similar to logging into these other hosts using the "ssh" command + on the command line, with one very important difference: + + + Code from the local host and all the remote hosts run at the same + time, in the same browser context. They are not sufficiently + isolated from each other in the browser. All code effectively has + the same privileges as the primary session on the local host. + + + Thus, you should only only connect to remote hosts that + you trust. You must be sure that none of the hosts that + you connect to will cause Cockpit to load malicious JavaScript + code into your browser. + + + Going forward, Cockpit will try to provide sufficient isolation to + make it safe to manage multiple hosts in a single Cockpit + session. But until we get there, Cockpit will at least warn you + before connecting to more than one host. It is also possible to + disable multiple hosts entirely, and some operating systems do + this already by default. + + + You can prevent loading of JavaScript, HTML, etc from more than + one host by adding this to cockpit.conf: + + + [WebService] + AllowMultiHost=false + + + When you allow multiple hosts in a single Cockpit session by + setting AllowMultiHost to true, then the user will be + warned once per session, before connecting to the second host. If + that is still too much, you can switch it off completely by adding + the following to cockpit.conf: + + + [Session] + WarnBeforeConnecting=false + + diff --git a/doc/man/cockpit.conf.xml b/doc/man/cockpit.conf.xml index baf2c7d6f431..6bf03a14b8b0 100644 --- a/doc/man/cockpit.conf.xml +++ b/doc/man/cockpit.conf.xml @@ -272,6 +272,18 @@ IdleTimeout=15 When not specified, there is no idle timeout by default. + + + + Whether to warn before connecting to remote hosts from the Shell. Defaults to true + + +[Session] +WarnBeforeConnecting=false + + + + diff --git a/pkg/shell/hosts.jsx b/pkg/shell/hosts.jsx index 57f251550267..e8dd0366a3c0 100644 --- a/pkg/shell/hosts.jsx +++ b/pkg/shell/hosts.jsx @@ -16,7 +16,7 @@ import { Tooltip } from "@patternfly/react-core/dist/esm/components/Tooltip"; import 'polyfills'; import { CockpitNav, CockpitNavItem } from "./nav.jsx"; import { build_href, split_connection_string } from "./util.jsx"; -import { add_host, edit_host } from "./hosts_dialog.jsx"; +import { add_host, edit_host, connect_host } from "./hosts_dialog.jsx"; const _ = cockpit.gettext; @@ -122,17 +122,14 @@ export class CockpitHosts extends React.Component { } async onHostSwitch(machine) { - const { state } = this.props; - - // We could launch the connection dialogs here and not jump at - // all when the login fails (or is cancelled), but the - // traditional behavior is to jump and then try to connect. + const { state, host_modal_state } = this.props; - const connection_string = machine.connection_string; - const parts = split_connection_string(connection_string); - const addr = build_href({ host: parts.address }); - state.jump(addr); - state.ensure_connection(); + const connection_string = await connect_host(host_modal_state, state, machine); + if (connection_string) { + const parts = split_connection_string(connection_string); + const addr = build_href({ host: parts.address }); + state.jump(addr); + } } onEditHosts() { diff --git a/pkg/shell/hosts_dialog.jsx b/pkg/shell/hosts_dialog.jsx index ea5569c4cdfc..8b516ed350a6 100644 --- a/pkg/shell/hosts_dialog.jsx +++ b/pkg/shell/hosts_dialog.jsx @@ -38,10 +38,13 @@ import { Popover } from "@patternfly/react-core/dist/esm/components/Popover/inde import { Radio } from "@patternfly/react-core/dist/esm/components/Radio/index.js"; import { Stack } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; import { TextInput } from "@patternfly/react-core/dist/esm/components/TextInput/index.js"; -import { OutlinedQuestionCircleIcon } from "@patternfly/react-icons"; +import { OutlinedQuestionCircleIcon, ExternalLinkAltIcon } from "@patternfly/react-icons"; +import { HelperText, HelperTextItem } from "@patternfly/react-core/dist/esm/components/HelperText/index.js"; +import { Text, TextContent, TextVariants } from "@patternfly/react-core/dist/esm/components/Text"; import { FormHelper } from "cockpit-components-form-helper"; import { ModalError } from "cockpit-components-inline-notification.jsx"; +import { fmt_to_fragments } from "utils.js"; import { build_href, split_connection_string, generate_connection_string } from "./util.jsx"; @@ -119,6 +122,12 @@ export async function connect_host(state, shell_state, machine) { address: machine.address, template: codes[machine.problem], }); + } else if (!window.sessionStorage.getItem("connection-warning-shown")) { + // connect by launching into the "Connection warning" dialog. + connection_string = await state.show_modal({ + address: machine.address, + template: "connect" + }); } else { // Try to connect without any dialog try { @@ -145,6 +154,7 @@ export async function connect_host(state, shell_state, machine) { } export const codes = { + danger: "connect", "no-cockpit": "not-supported", "not-supported": "not-supported", "protocol-error": "not-supported", @@ -200,6 +210,74 @@ class NotSupported extends React.Component { } } +class Connect extends React.Component { + constructor(props) { + super(props); + + this.state = { + inProgress: false, + }; + } + + onConnect() { + window.sessionStorage.setItem("connection-warning-shown", true); + this.setState({ inProgress: true }); + this.props.run(this.props.try2Connect(this.props.full_address), ex => { + let keep_message = false; + if (ex.problem === "no-host") { + let host_id_port = this.props.full_address; + let port = "22"; + const port_index = host_id_port.lastIndexOf(":"); + if (port_index === -1) { + host_id_port = this.props.full_address + ":22"; + } else { + port = host_id_port.substr(port_index + 1); + } + + ex.message = cockpit.format(_("Unable to contact the given host $0. Make sure it has ssh running on port $1, or specify another port in the address."), host_id_port, port); + ex.problem = "not-found"; + keep_message = true; + } + this.setState({ inProgress: false }); + this.props.setError(ex, keep_message); + }); + } + + render() { + return ( + {this.props.host})} + titleIconVariant="warning" + footer={<> + + {_("You will be reminded once per session.")} + + + + } + > + + + {_("Remote hosts have the ability to run JavaScript on all connected hosts. Only connect to machines that you trust.")} + + + + {_("Read more")} + + + + + ); + } +} + class AddMachine extends React.Component { constructor(props) { super(props); @@ -323,23 +401,27 @@ class AddMachine extends React.Component { }); }); - this.props.run(this.props.try2Connect(address), ex => { - if (ex.problem === "no-host") { - let host_id_port = address; - let port = "22"; - const port_index = host_id_port.lastIndexOf(":"); - if (port_index === -1) { - host_id_port = address + ":22"; - } else { - port = host_id_port.substr(port_index + 1); - } + if (!window.sessionStorage.getItem("connection-warning-shown")) { + this.props.setError({ problem: "danger", command: "close" }); + } else { + this.props.run(this.props.try2Connect(address), ex => { + if (ex.problem === "no-host") { + let host_id_port = address; + let port = "22"; + const port_index = host_id_port.lastIndexOf(":"); + if (port_index === -1) { + host_id_port = address + ":22"; + } else { + port = host_id_port.substr(port_index + 1); + } - ex.message = cockpit.format(_("Unable to contact the given host $0. Make sure it has ssh running on port $1, or specify another port in the address."), host_id_port, port); - ex.problem = "not-found"; - } - this.setState({ inProgress: false }); - this.props.setError(ex); - }); + ex.message = cockpit.format(_("Unable to contact the given host $0. Make sure it has ssh running on port $1, or specify another port in the address."), host_id_port, port); + ex.problem = "not-found"; + } + this.setState({ inProgress: false }); + this.props.setError(ex); + }); + } } render() { @@ -1161,7 +1243,9 @@ class HostModalInner extends React.Component { complete: this.complete, }; - if (template === "add-machine") + if (template === "connect") + return ; + else if (template === "add-machine") return ; else if (template === "unknown-hostkey" || template === "unknown-host" || template === "invalid-hostkey") return ; diff --git a/pkg/shell/shell.jsx b/pkg/shell/shell.jsx index 489e4d53b59a..8741d2e5c17c 100644 --- a/pkg/shell/shell.jsx +++ b/pkg/shell/shell.jsx @@ -66,10 +66,7 @@ const Shell = () => { useEvent(host_modal_state, "changed"); useEvent(state, "connect", () => { - // We could launch some dialogs here, but the traditional - // behavior is to just connect the loader and open the dialogs - // from the troubleshoot button. - state.loader.connect(state.current_machine.address); + connect_host(host_modal_state, state, state.current_machine); }); const { diff --git a/pkg/shell/state.jsx b/pkg/shell/state.jsx index 3324bf89361c..9839a14f9893 100644 --- a/pkg/shell/state.jsx +++ b/pkg/shell/state.jsx @@ -45,6 +45,24 @@ export function ShellState() { if (meta_multihost instanceof HTMLMetaElement && meta_multihost.content == "yes") config.host_switcher_enabled = true; + /* Should show warning before connecting? */ + let config_ready = false; + cockpit.dbus(null, { bus: "internal" }).call("/config", "cockpit.Config", "GetString", + ["Session", "WarnBeforeConnecting"], []) + .then(([result]) => { + if (result == "false" || result == "no") { + window.sessionStorage.setItem("connection-warning-shown", "yes"); + } + }) + .catch(e => { + if (e.name != "cockpit.Config.KeyError") + console.warn("Error reading WarnBeforeConnecting configuration:", e.message); + }) + .finally(() => { + config_ready = true; + on_ready(); + }); + /* MACHINES DATABASE AND MANIFEST LOADER * * These are part of the machinery in the basement that maintains @@ -74,7 +92,7 @@ export function ShellState() { on_ready(); function on_ready() { - if (machines.ready) { + if (machines.ready && config_ready) { self.ready = true; window.addEventListener("popstate", ev => { update(); @@ -488,7 +506,6 @@ export function ShellState() { // Methods jump, - ensure_connection, remove_frame, most_recent_path_for_host, diff --git a/test/common/testlib.py b/test/common/testlib.py index b7e91197d15c..ca9d486d6443 100644 --- a/test/common/testlib.py +++ b/test/common/testlib.py @@ -1140,13 +1140,23 @@ def start_machine_troubleshoot( known_host: bool = False, password: str | None = None, expect_closed_dialog: bool = True, + expect_warning: bool = True, + need_click: bool = True ) -> None: - self.click('#machine-troubleshoot') + if need_click: + self.click('#machine-troubleshoot') + + if not new and expect_warning: + self.wait_visible('#hosts_connect_server_dialog') + self.click("#hosts_connect_server_dialog button.pf-m-warning") self.wait_visible('#hosts_setup_server_dialog') if new: self.wait_text("#hosts_setup_server_dialog button.pf-m-primary", "Add") self.click("#hosts_setup_server_dialog button.pf-m-primary") + if expect_warning: + self.wait_visible('#hosts_connect_server_dialog') + self.click("#hosts_connect_server_dialog button.pf-m-warning") if not known_host: self.wait_in_text('#hosts_setup_server_dialog', "You are connecting to") self.wait_in_text('#hosts_setup_server_dialog', "for the first time.") @@ -1160,10 +1170,14 @@ def start_machine_troubleshoot( if expect_closed_dialog: self.wait_not_present('#hosts_setup_server_dialog') - def add_machine(self, address: str, known_host: bool = False, password: str = "foobar") -> None: + def add_machine(self, address: str, known_host: bool = False, password: str = "foobar", + expect_warning: bool = True) -> None: self.switch_to_top() self.go(f"/@{address}") - self.start_machine_troubleshoot(new=True, known_host=known_host, password=password) + self.start_machine_troubleshoot(new=True, + known_host=known_host, + password=password, + expect_warning=expect_warning) self.enter_page("/system", host=address) def grant_permissions(self, *args: str) -> None: diff --git a/test/verify/check-shell-host-switching b/test/verify/check-shell-host-switching index 25acccc41a4c..ce279bd3a4c7 100755 --- a/test/verify/check-shell-host-switching +++ b/test/verify/check-shell-host-switching @@ -66,7 +66,8 @@ class HostSwitcherHelpers: # HACK: Dropping the machine does not terminate SSH connection; https://github.com/cockpit-project/cockpit/issues/19672 self.machine.execute(f"pkill -f [s]sh.*{address}; while pgrep -f [s]sh.*{address}; do sleep 1; done") - def add_new_machine(self, b, address, known_host=False, pixel_label=None, user=None, expect_password_auth=False): + def add_new_machine(self, b, address, known_host=False, pixel_label=None, user=None, expect_password_auth=False, + expect_warning=False): b.click("button:contains('Add new host')") b.wait_visible('#hosts_setup_server_dialog') b.set_input_text('#add-machine-address', address) @@ -75,6 +76,9 @@ class HostSwitcherHelpers: if pixel_label: b.assert_pixels("#hosts_setup_server_dialog", pixel_label) b.click('#hosts_setup_server_dialog .pf-m-primary:contains("Add")') + if expect_warning: + b.wait_visible('#hosts_connect_server_dialog') + b.click('#hosts_connect_server_dialog button.pf-m-warning') if not known_host: b.wait_in_text('#hosts_setup_server_dialog', f"You are connecting to {address.removeprefix('ssh://')} for the first time") @@ -86,8 +90,14 @@ class HostSwitcherHelpers: with b.wait_timeout(30): b.wait_not_present('#hosts_setup_server_dialog') - def connect_and_wait(self, b, address, expected_user=None): + def connect_and_wait(self, b, address, expected_user=None, expect_warning=False): b.click(f"a[href='/@{address}']") + if expect_warning: + b.wait_visible('#hosts_connect_server_dialog') + b.click('#hosts_connect_server_dialog button.pf-m-warning') + # wait for host switcher to close after connecting + b.wait_not_present("#nav-hosts.interact") + # open it again b.click("#hosts-sel button") b.wait_visible(f".connected a[href='/@{address}']") if expected_user: @@ -176,7 +186,30 @@ class TestHostSwitching(testlib.MachineCase, HostSwitcherHelpers): b.wait_in_text("#nav-hosts .nav-item a", "mydhcpname") m1.execute("hostnamectl set-hostname 'localhost'") - self.add_new_machine(b, "10.111.113.2", pixel_label="host-add-dialog") + # Add a host with a couple of mistakes on the way + b.click("button:contains('Add new host')") + b.wait_visible('#hosts_setup_server_dialog') + b.set_input_text('#add-machine-address', "10.111.113.2:1234") + b.click('#hosts_setup_server_dialog .pf-m-primary:contains("Add")') + # Port is wrong but we first have to confirm that we really want to connect + b.wait_visible('#hosts_connect_server_dialog') + b.assert_pixels("#hosts_connect_server_dialog", "host-connect-dialog") + b.click('#hosts_connect_server_dialog button.pf-m-warning') + # Now we are back in the "Add host" dialog with the error + b.wait_in_text('#hosts_setup_server_dialog', "Unable to contact the given host 10.111.113.2:1234. Make sure it has ssh running on port 1234, or specify another port in the address.") + # Give another wrong address + b.set_input_text('#add-machine-address', "10.111.113.2:4321") + b.click('#hosts_setup_server_dialog .pf-m-primary:contains("Add")') + # Error happens immediatly now. + b.wait_in_text('#hosts_setup_server_dialog', "Unable to contact the given host 10.111.113.2:4321. Make sure it has ssh running on port 4321, or specify another port in the address.") + # Now do it right + b.set_input_text('#add-machine-address', "10.111.113.2") + b.assert_pixels("#hosts_setup_server_dialog", "host-add-dialog") + b.click('#hosts_setup_server_dialog .pf-m-primary:contains("Add")') + b.wait_in_text('#hosts_setup_server_dialog', "You are connecting to 10.111.113.2 for the first time") + b.click('#hosts_setup_server_dialog .pf-m-primary') + b.wait_not_present('#hosts_setup_server_dialog') + self.wait_host_addresses(b, ["localhost", "10.111.113.2"]) # defaults to current host user name "admin" self.connect_and_wait(b, "10.111.113.2", "admin") @@ -299,12 +332,18 @@ class TestHostSwitching(testlib.MachineCase, HostSwitcherHelpers): self.connect_and_wait(b, "10.111.113.2", "someone") self.machine_remove(b, "10.111.113.2", second_to_last=True) + # switch off warnings for the rest of this test (nneds the + # relogin below to take effect) + m1.write("/etc/cockpit/cockpit.conf", + '[Session]\nWarnBeforeConnecting=false\n', + append=True) + # reset session store to forget previous user/host connections b.relogin() b.click("#hosts-sel button") - # ssh:// prefix and implied user - self.add_new_machine(b, "ssh://10.111.113.2", known_host=True) + # ssh:// prefix and implied user, no warning because we switched it off above + self.add_new_machine(b, "ssh://10.111.113.2", known_host=True, expect_warning=False) self.wait_host_addresses(b, ["localhost", "10.111.113.2"]) self.connect_and_wait(b, "10.111.113.2", "admin") self.machine_remove(b, "10.111.113.2", second_to_last=True) @@ -368,11 +407,11 @@ class TestHostSwitching(testlib.MachineCase, HostSwitcherHelpers): b2.click("#hosts-sel button") self.wait_host_addresses(b2, ["localhost"]) - self.add_new_machine(b, "10.111.113.2") + self.add_new_machine(b, "10.111.113.2", expect_warning=True) self.wait_host_addresses(b, ["localhost", "10.111.113.2"]) self.wait_host_addresses(b2, ["localhost", "10.111.113.2"]) self.connect_and_wait(b, "10.111.113.2") - self.connect_and_wait(b2, "10.111.113.2") + self.connect_and_wait(b2, "10.111.113.2", expect_warning=True) # Main host should have both buttons disabled, the second both enabled b.click("button:contains('Edit hosts')") @@ -448,7 +487,7 @@ class TestHostSwitching(testlib.MachineCase, HostSwitcherHelpers): self.login_and_go(superuser=False) b.click("#hosts-sel button") - self.add_new_machine(b, "10.111.113.3") + self.add_new_machine(b, "10.111.113.3", expect_warning=True) self.wait_host_addresses(b, ["localhost", "10.111.113.3"]) self.connect_and_wait(b, "10.111.113.3") @@ -529,9 +568,9 @@ class TestHostSwitching(testlib.MachineCase, HostSwitcherHelpers): self.enable_multihost(self.machine) self.login_and_go(None) - # And and connect to a second machine + # Add and connect to a second machine b.click("#hosts-sel button") - self.add_new_machine(b, "10.111.113.2") + self.add_new_machine(b, "10.111.113.2", expect_warning=True) b.click("a[href='/@10.111.113.2']") b.wait_visible("iframe.container-frame[name='cockpit1:10.111.113.2/system']") self.assertIn("admin", m2.execute("loginctl")) diff --git a/test/verify/check-shell-multi-machine b/test/verify/check-shell-multi-machine index 4d32f469230f..ef8343e22ade 100755 --- a/test/verify/check-shell-multi-machine +++ b/test/verify/check-shell-multi-machine @@ -149,7 +149,7 @@ class TestMultiMachineAdd(testlib.MachineCase): self.login_and_go(None) b.add_machine("10.111.113.2", password=None) - b.add_machine(m3_host, password=None) + b.add_machine(m3_host, password=None, expect_warning=False) b.switch_to_top() b.click("#hosts-sel button") @@ -226,7 +226,7 @@ class TestMultiMachineAdd(testlib.MachineCase): self.login_and_go(None) b.add_machine("m2", password=None) - b.add_machine("m3", password=None) + b.add_machine("m3", password=None, expect_warning=False) b.switch_to_top() b.click("#hosts-sel button") @@ -541,8 +541,11 @@ class TestMultiMachine(testlib.MachineCase): # navigating there again will fail b.go(m2_path) with b.wait_timeout(30): - b.wait_text(".curtains-ct h1", "Not connected to host") - b.wait_text("#machine-troubleshoot", "Log in") + b.wait_visible("#hosts_setup_server_dialog") + b.start_machine_troubleshoot(need_click=False, password="foobar", + expect_closed_dialog=False, expect_warning=False) + b.wait_in_text("#hosts_setup_server_dialog .pf-v5-c-alert", "Login failed") + b.click("#hosts_setup_server_dialog button:contains(Cancel)") # wait for system to load b.go("/system") @@ -557,8 +560,8 @@ class TestMultiMachine(testlib.MachineCase): b.reload() b.go(m2_path) with b.wait_timeout(30): - b.wait_text(".curtains-ct h1", "Not connected to host") - b.start_machine_troubleshoot(password="foobar") + b.wait_visible("#hosts_setup_server_dialog") + b.start_machine_troubleshoot(need_click=False, password="foobar", expect_warning=False) b.enter_page("/playground/test", "10.111.113.2", reconnect=True) # image is back because it page was reloaded after disconnection @@ -657,21 +660,21 @@ class TestMultiMachine(testlib.MachineCase): m1.execute("mkdir -p /home/admin/.ssh/") break_hostkey(m1, "10.111.113.2") - b.start_machine_troubleshoot(new=True, known_host=True, expect_closed_dialog=False) + b.start_machine_troubleshoot(new=True, known_host=True, expect_closed_dialog=False, expect_warning=False) b.wait_in_text('#hosts_setup_server_dialog', "10.111.113.2 key changed") b.click("#hosts_setup_server_dialog button:contains(Cancel)") fix_hostkey(m1) # Bad cockpit break_bridge(m2) - b.start_machine_troubleshoot(new=True, password="foobar", expect_closed_dialog=False) + b.start_machine_troubleshoot(new=True, password="foobar", expect_closed_dialog=False, expect_warning=False) check_failed_state(b, "Cockpit is not installed") fix_bridge(m2) # Troubleshoot existing # Properly add machine fix_hostkey(m1) - b.add_machine("10.111.113.2") + b.add_machine("10.111.113.2", expect_warning=False) b.logout() b.wait_visible("#login") @@ -679,16 +682,17 @@ class TestMultiMachine(testlib.MachineCase): break_bridge(m2) self.login_and_go(None) b.go(machine_path) - with b.wait_timeout(240): - b.start_machine_troubleshoot(password="foobar", expect_closed_dialog=False) + with b.wait_timeout(20): + b.start_machine_troubleshoot(need_click=False, password="foobar", expect_closed_dialog=False) check_failed_state(b, "Cockpit is not installed") - b.wait_visible("#machine-troubleshoot") + b.wait_visible("#machine-reconnect") fix_bridge(m2) # Clear host key fix_hostkey(m1) - b.start_machine_troubleshoot(expect_closed_dialog=False) + b.click("#machine-reconnect") + b.start_machine_troubleshoot(need_click=False, expect_closed_dialog=False, expect_warning=False) b.wait_in_text('#hosts_setup_server_dialog', "You are connecting to 10.111.113.2 for the first time.") # show fingerprint validation @@ -722,13 +726,10 @@ class TestMultiMachine(testlib.MachineCase): self.login_and_go(None) b.go(machine_path) - with b.wait_timeout(120): - b.wait_visible("#machine-troubleshoot") - b.start_machine_troubleshoot(expect_closed_dialog=False) + with b.wait_timeout(20): + b.wait_visible("#hosts_connect_server_dialog") + b.start_machine_troubleshoot(need_click=False, expect_closed_dialog=False) b.wait_in_text('#hosts_setup_server_dialog', "Unable to log in") - b.set_input_text('#login-custom-password', "") - fail_login(b) - b.set_input_text("#login-custom-password", "bad") fail_login(b) b.set_input_text("#login-custom-password", "alt-password") @@ -747,11 +748,11 @@ class TestMultiMachine(testlib.MachineCase): self.login_and_go(None) b.go(machine_path) - with b.wait_timeout(120): - b.wait_visible("#machine-troubleshoot") - b.start_machine_troubleshoot(expect_closed_dialog=False) - b.wait_in_text('#hosts_setup_server_dialog h1', "Could not contact") - b.set_input_text("#edit-machine-port", "2222") + with b.wait_timeout(20): + b.wait_visible("#hosts_connect_server_dialog") + b.start_machine_troubleshoot(need_click=False, expect_closed_dialog=False) + b.wait_in_text('#hosts_setup_server_dialog', "Unable to contact") + b.set_input_text("#add-machine-address", "10.111.113.2:2222") b.click(f'#hosts_setup_server_dialog {self.primary_btn_class}') # ssh(1) tracks known hosts by name/IP, not by port; so not expecting a host key prompt here b.wait_in_text('#hosts_setup_server_dialog h1', "Log in to") @@ -791,12 +792,13 @@ class TestMultiMachine(testlib.MachineCase): self.login_and_go(None) b.go("/@10.111.113.2") - b.start_machine_troubleshoot(expect_closed_dialog=False) b.click('#machine-troubleshoot') b.wait_visible('#hosts_setup_server_dialog') b.wait_in_text('#hosts_setup_server_dialog', "new host") b.set_input_text('#add-machine-user', "fred") b.click('#hosts_setup_server_dialog button:contains(Add)') + b.wait_visible('#hosts_connect_server_dialog') + b.click("#hosts_connect_server_dialog button.pf-m-warning") b.wait_in_text('#hosts_setup_server_dialog', "You are connecting to 10.111.113.2 for the first time.") b.click("#hosts_setup_server_dialog button:contains('Trust and add host')") b.wait_in_text('#hosts_setup_server_dialog', "Unable to log in") @@ -822,14 +824,17 @@ class TestMultiMachine(testlib.MachineCase): self.assertEqual(m1.execute("cat /home/admin/.ssh/id_rsa.pub"), m2.execute("cat /home/fred/.ssh/authorized_keys")) - # Relogin. This should now work seamlessly. + # Relogin. This should now work seamlessly (except for the warning). b.relogin(None, wait_remote_session_machine=m1) + b.wait_visible('#hosts_connect_server_dialog') + b.click("#hosts_connect_server_dialog button.pf-m-warning") b.enter_page("/system", host="fred@10.111.113.2") # De-authorize key and relogin, then re-authorize. m2.execute("rm /home/fred/.ssh/authorized_keys") b.relogin(None, wait_remote_session_machine=m1) - b.click('#machine-troubleshoot') + b.wait_visible('#hosts_connect_server_dialog') + b.click("#hosts_connect_server_dialog button.pf-m-warning") b.wait_visible('#hosts_setup_server_dialog') b.wait_in_text('#hosts_setup_server_dialog', "Unable to log in") b.wait_in_text("#hosts_setup_server_dialog", "Authorize SSH key") @@ -845,7 +850,8 @@ class TestMultiMachine(testlib.MachineCase): # change the passphrase back to the login password m1.execute("ssh-keygen -q -f /home/admin/.ssh/id_rsa -p -P foobar -N foobarfoo") b.relogin(None, wait_remote_session_machine=m1) - b.click('#machine-troubleshoot') + b.wait_visible('#hosts_connect_server_dialog') + b.click("#hosts_connect_server_dialog button.pf-m-warning") b.wait_visible('#hosts_setup_server_dialog') b.wait_in_text('#hosts_setup_server_dialog', "The SSH key for logging in") b.set_checked('#hosts_setup_server_dialog input[value=key]', val=True) @@ -861,6 +867,8 @@ class TestMultiMachine(testlib.MachineCase): # which don't have pam-ssh-add in its PAM stack.) if not m1.ostree_image: b.relogin(None, wait_remote_session_machine=m1) + b.wait_visible('#hosts_connect_server_dialog') + b.click("#hosts_connect_server_dialog button.pf-m-warning") b.enter_page("/system", host="fred@10.111.113.2") # The authorized_keys files should still only have a single key @@ -894,6 +902,8 @@ class TestMultiMachine(testlib.MachineCase): b.click('#machine-troubleshoot') b.wait_visible('#hosts_setup_server_dialog') b.click('#hosts_setup_server_dialog button:contains(Add)') + b.wait_visible('#hosts_connect_server_dialog') + b.click('#hosts_connect_server_dialog button:contains(Connect)') b.wait_in_text('#hosts_setup_server_dialog', "You are connecting to 10.111.113.2 for the first time.") b.click("#hosts_setup_server_dialog button:contains('Trust and add host')") b.wait_in_text('#hosts_setup_server_dialog', "The SSH key") diff --git a/test/verify/check-shell-multi-machine-key b/test/verify/check-shell-multi-machine-key index 354289ccfd00..cc8835e88a3b 100755 --- a/test/verify/check-shell-multi-machine-key +++ b/test/verify/check-shell-multi-machine-key @@ -152,6 +152,8 @@ class TestMultiMachineKeyAuth(testlib.MachineCase): b.wait_text(f'#hosts_setup_server_dialog {self.primary_btn_class}', "Add") b.click(f'#hosts_setup_server_dialog {self.primary_btn_class}') + b.wait_visible('#hosts_connect_server_dialog') + b.click('#hosts_connect_server_dialog button.pf-m-warning') b.wait_in_text('#hosts_setup_server_dialog', "You are connecting to 10.111.113.2 for the first time.") b.click(f'#hosts_setup_server_dialog {self.primary_btn_class}') b.wait_in_text('#hosts_setup_server_dialog h1', "Log in to") @@ -181,6 +183,8 @@ class TestMultiMachineKeyAuth(testlib.MachineCase): self.load_key('id_rsa', 'foobar') b.go("/@10.111.113.2") + b.wait_visible('#hosts_connect_server_dialog') + b.click('#hosts_connect_server_dialog button.pf-m-warning') b.wait_visible("iframe.container-frame[name='cockpit1:10.111.113.2/system']") # Change user @@ -249,6 +253,8 @@ Host 10.111.113.2 b.wait_visible('#hosts_setup_server_dialog') b.set_input_text('#add-machine-address', "10.111.113.2") b.click("#hosts_setup_server_dialog .pf-m-primary") + b.wait_visible('#hosts_connect_server_dialog') + b.click('#hosts_connect_server_dialog button.pf-m-warning') b.wait_in_text("#hosts_setup_server_dialog", "You are connecting to 10.111.113.2 for the first time.") b.click("#hosts_setup_server_dialog .pf-m-primary") b.wait_in_text("#hosts_setup_server_dialog", "/home/admin/.ssh/id_ed25519") @@ -284,6 +290,8 @@ Host 10.111.113.2 b.wait_visible('#hosts_setup_server_dialog') b.set_input_text("#add-machine-address", "10.111.113.2") b.click("#hosts_setup_server_dialog .pf-m-primary") + b.wait_visible('#hosts_connect_server_dialog') + b.click('#hosts_connect_server_dialog button.pf-m-warning') b.wait_in_text("#hosts_setup_server_dialog", "You are connecting to 10.111.113.2 for the first time.") b.click("#hosts_setup_server_dialog .pf-m-primary") b.wait_in_text("#hosts_setup_server_dialog", "/home/admin/.ssh/id_rsa") diff --git a/test/verify/check-shell-multi-os b/test/verify/check-shell-multi-os index a0ab399a3a05..6e380bd70b57 100755 --- a/test/verify/check-shell-multi-os +++ b/test/verify/check-shell-multi-os @@ -79,7 +79,7 @@ class TestRHEL8(testlib.MachineCase): # stock → dev: via shell Add host stock_b.login_and_go() - stock_b.add_machine("10.111.113.1") + stock_b.add_machine("10.111.113.1", expect_warning=False) stock_b.wait_in_text(".ct-overview-header-hostname", dev_hostname) diff --git a/test/verify/check-superuser b/test/verify/check-superuser index fb7b149a2865..2c5776325056 100755 --- a/test/verify/check-superuser +++ b/test/verify/check-superuser @@ -415,6 +415,8 @@ class TestSuperuserDashboard(testlib.MachineCase): b.wait_visible('#hosts_setup_server_dialog') b.wait_visible('#hosts_setup_server_dialog button:contains("Add")') b.click('#hosts_setup_server_dialog button:contains("Add")') + b.wait_visible('#hosts_connect_server_dialog') + b.click('#hosts_connect_server_dialog button.pf-m-warning') b.wait_in_text('#hosts_setup_server_dialog', "You are connecting to 10.111.113.2 for the first time.") b.click('#hosts_setup_server_dialog button.pf-m-primary') b.wait_in_text('#hosts_setup_server_dialog', "Unable to log in") @@ -444,7 +446,8 @@ class TestSuperuserDashboard(testlib.MachineCase): # superuser on m2 (once we have logged in there). self.allow_restart_journal_messages() b.relogin() - b.click('#machine-troubleshoot') + b.wait_visible('#hosts_connect_server_dialog') + b.click('#hosts_connect_server_dialog button.pf-m-warning') b.wait_visible('#hosts_setup_server_dialog') b.set_input_text("#login-custom-password", "foobar") b.click('#hosts_setup_server_dialog button:contains("Log in")') diff --git a/test/verify/check-system-realms b/test/verify/check-system-realms index a8dfe2c18a3d..b84f46638a9e 100755 --- a/test/verify/check-system-realms +++ b/test/verify/check-system-realms @@ -813,6 +813,8 @@ ipa-advise enable-admins-sudo | sh -ex b.wait_visible('#hosts_setup_server_dialog') b.click('#hosts_setup_server_dialog button:contains(Add)') b.wait_not_present('#hosts_setup_server_dialog') + b.wait_visible('#hosts_connect_server_dialog') + b.click('#hosts_connect_server_dialog button.pf-m-warning') # Getting root privs through sudo with kerberos in the remote SSH session does not currently work. # ssh -K is supposed to forward the credentials cache, but doesn't; klist in the ssh session is empty @@ -1071,6 +1073,8 @@ class TestKerberos(testlib.MachineCase): b.wait_visible('#hosts_setup_server_dialog') b.click('#hosts_setup_server_dialog button:contains(Add)') b.wait_not_present('#hosts_setup_server_dialog') + b.wait_visible('#hosts_connect_server_dialog') + b.click('#hosts_connect_server_dialog button.pf-m-warning') b.enter_page("/system/terminal", host="x0.cockpit.lan") b.wait_visible(".terminal") diff --git a/test/verify/check-system-shutdown-restart b/test/verify/check-system-shutdown-restart index b338338f1712..a56ce2e8ab2a 100755 --- a/test/verify/check-system-shutdown-restart +++ b/test/verify/check-system-shutdown-restart @@ -92,7 +92,7 @@ class TestShutdownRestart(testlib.MachineCase): with b2.wait_timeout(30): b2.wait_visible("#machine-troubleshoot") - b2.start_machine_troubleshoot(password="foobar") + b2.start_machine_troubleshoot(password="foobar", expect_warning=False) b2.enter_page("/system", host="10.111.113.1", reconnect=False)