863 lines
34 KiB
Python
Executable File
863 lines
34 KiB
Python
Executable File
#!/usr/bin/python3 -cimport os, sys; os.execv(os.path.dirname(sys.argv[1]) + "/../common/pywrap", sys.argv)
|
|
|
|
# This file is part of Cockpit.
|
|
#
|
|
# Copyright (C) 2013 Red Hat, Inc.
|
|
#
|
|
# Cockpit is free software; you can redistribute it and/or modify it
|
|
# under the terms of the GNU Lesser General Public License as published by
|
|
# the Free Software Foundation; either version 2.1 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Cockpit is distributed in the hope that it will be useful, but
|
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
# Lesser General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Lesser General Public License
|
|
# along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import re
|
|
import subprocess
|
|
import time
|
|
|
|
from testlib import MachineCase, skipDistroPackage, skipImage, test_main, todoPybridge, todoPybridgeRHEL8
|
|
|
|
|
|
def break_hostkey(m, address):
|
|
filename = "/home/admin/.ssh/known_hosts"
|
|
|
|
m.execute(f'su admin -c "mkdir -p -m 700 `dirname {filename}`"')
|
|
m.execute(f'su admin -c "touch {filename}"')
|
|
|
|
line = f"{address} ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJqfgO2FPiix1n2sCJCXbaffwog1Vvi3zRdmcAxG//5T"
|
|
m.execute(f"echo '{line}'> {filename}")
|
|
|
|
|
|
def fix_hostkey(m, key=None):
|
|
filename = "/home/admin/.ssh/known_hosts"
|
|
|
|
if not key:
|
|
key = ''
|
|
|
|
m.execute(f'su admin -c "mkdir -p -m 700 `dirname {filename}`"')
|
|
m.execute(f'su admin -c "touch {filename}"')
|
|
m.execute(f"echo '{key}' > {filename}")
|
|
|
|
|
|
def break_bridge(m):
|
|
m.execute("ln -snf /bin/false /usr/local/bin/cockpit-bridge")
|
|
|
|
|
|
def fix_bridge(m):
|
|
m.execute("rm /usr/local/bin/cockpit-bridge")
|
|
|
|
|
|
def check_failed_state(b, expected_title):
|
|
b.wait_in_text('#hosts_setup_server_dialog h1', expected_title)
|
|
b.click("#hosts_setup_server_dialog button:contains(Close)")
|
|
b.wait_not_present('#hosts_setup_server_dialog')
|
|
|
|
|
|
def start_machine_troubleshoot(b, new=False, known_host=False, password=None):
|
|
b.wait_visible("#machine-troubleshoot")
|
|
b.click('#machine-troubleshoot')
|
|
b.wait_visible('#hosts_setup_server_dialog')
|
|
if new:
|
|
b.click('#hosts_setup_server_dialog button:contains(Add)')
|
|
if not known_host:
|
|
b.wait_in_text('#hosts_setup_server_dialog', "You are connecting to")
|
|
b.wait_in_text('#hosts_setup_server_dialog', "for the first time.")
|
|
b.click('#hosts_setup_server_dialog button:contains(Accept key and connect)')
|
|
if password:
|
|
b.wait_in_text('#hosts_setup_server_dialog', "Unable to log in")
|
|
b.set_input_text('#login-custom-password', password)
|
|
b.click('#hosts_setup_server_dialog button:contains(Log in)')
|
|
|
|
|
|
def fail_login(b):
|
|
b.click('#hosts_setup_server_dialog button:contains(Log in)')
|
|
b.wait_visible('#hosts_setup_server_dialog button:contains(Log in):not([disabled])')
|
|
b.wait_in_text("#hosts_setup_server_dialog .pf-v5-c-alert", "Login failed")
|
|
|
|
|
|
def add_machine(b, address, known_host=False, password="foobar"):
|
|
b.switch_to_top()
|
|
b.go(f"/@{address}")
|
|
start_machine_troubleshoot(b, new=True, known_host=known_host, password=password)
|
|
b.wait_not_present('#hosts_setup_server_dialog')
|
|
b.enter_page("/system", host=address)
|
|
|
|
|
|
def kill_user_admin(machine):
|
|
machine.execute("loginctl terminate-user admin")
|
|
|
|
|
|
def change_ssh_port(machine, address, port=None, timeout_sec=120):
|
|
try:
|
|
port = int(port)
|
|
except (ValueError, TypeError):
|
|
port = 22
|
|
|
|
# Keep in mind that not all operating systems have firewalld
|
|
machine.execute(f"firewall-cmd --permanent --zone=public --add-port={port}/tcp || true")
|
|
machine.execute("firewall-cmd --reload || true")
|
|
if machine.ostree_image: # no semanage
|
|
machine.execute("setenforce 0")
|
|
else:
|
|
machine.execute(f"! selinuxenabled || semanage port -a -t ssh_port_t -p tcp {port}")
|
|
if machine.image in ["ubuntu-stable"]: # always socket activated
|
|
machine.write("/etc/systemd/system/ssh.socket.d/override.conf",
|
|
f"[Socket]\nListenStream=\nListenStream=127.27.0.15:22\nListenStream={address}:{port}")
|
|
machine.execute("systemctl daemon-reload")
|
|
machine.execute("systemctl restart ssh.socket")
|
|
else:
|
|
machine.execute("sed -i 's/.*Port .*/#\\0/' /etc/ssh/sshd_config")
|
|
machine.execute(
|
|
f"printf 'ListenAddress 127.27.0.15:22\nListenAddress {address}:{port}\n' >> /etc/ssh/sshd_config")
|
|
# We stop the sshd.socket unit and just go with a regular
|
|
# daemon. This is more portable and reloading/restarting the
|
|
# socket doesn't seem to work well.
|
|
#
|
|
machine.execute("( ! systemctl is-active sshd.socket || systemctl stop sshd.socket) && systemctl restart sshd.service")
|
|
|
|
start_time = time.time()
|
|
error = None
|
|
while (time.time() - start_time) < timeout_sec:
|
|
try:
|
|
machine.execute(
|
|
f"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o CheckHostIP=no -o PasswordAuthentication=no -p {port} {address} 2>&1 | grep -q 'Permission denied'", quiet=True)
|
|
return
|
|
except Exception as e:
|
|
error = e
|
|
time.sleep(0.5)
|
|
raise error
|
|
|
|
|
|
@skipDistroPackage()
|
|
@todoPybridgeRHEL8()
|
|
class TestMultiMachineAdd(MachineCase):
|
|
provision = {
|
|
"machine1": {"address": "10.111.113.1/20", "memory_mb": 660},
|
|
"machine2": {"address": "10.111.113.2/20", "memory_mb": 660},
|
|
"machine3": {"address": "10.111.113.3/20", "memory_mb": 660},
|
|
}
|
|
|
|
def setup_ssh_auth(self):
|
|
self.machine.execute("d=/home/admin/.ssh; mkdir -p $d; chown admin:admin $d; chmod 700 $d")
|
|
self.machine.execute("test -f /home/admin/.ssh/id_rsa || ssh-keygen -f /home/admin/.ssh/id_rsa -t rsa -N ''")
|
|
self.machine.execute("chown admin:admin /home/admin/.ssh/id_rsa*")
|
|
pubkey = self.machine.execute("cat /home/admin/.ssh/id_rsa.pub")
|
|
|
|
for m in self.machines:
|
|
self.machines[m].execute("d=/home/admin/.ssh; mkdir -p $d; chown admin:admin $d; chmod 700 $d")
|
|
self.machines[m].write("/home/admin/.ssh/authorized_keys", pubkey)
|
|
self.machines[m].execute("chown admin:admin /home/admin/.ssh/authorized_keys")
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.machine2 = self.machines['machine2']
|
|
self.machine3 = self.machines['machine3']
|
|
|
|
# Disable preloading on all machines
|
|
# Preloading on machines debug build can overload the browser and cause slowness and browser crashes,
|
|
# and failing to load sofware updates breaks pixel tests in release builds
|
|
self.setup_provisioned_hosts(disable_preload=True)
|
|
self.setup_ssh_auth()
|
|
|
|
def testBasic(self):
|
|
b = self.browser
|
|
m2 = self.machine2
|
|
m3 = self.machine3
|
|
m3_host = "10.111.113.3:2222"
|
|
change_ssh_port(m3, "10.111.113.3", 2222)
|
|
|
|
hostname_selector = "#system_information_hostname_text"
|
|
|
|
self.login_and_go(None)
|
|
add_machine(b, "10.111.113.2", password=None)
|
|
add_machine(b, m3_host, password=None)
|
|
|
|
b.switch_to_top()
|
|
b.click("#hosts-sel button")
|
|
|
|
kill_user_admin(m2)
|
|
with b.wait_timeout(30):
|
|
b.wait_visible("#machine2-error")
|
|
|
|
kill_user_admin(m3)
|
|
with b.wait_timeout(30):
|
|
b.wait_visible("#machine3-error")
|
|
|
|
# Navigating reconnects
|
|
b.click("a[href='/@10.111.113.2']")
|
|
|
|
b.wait_js_cond('window.location.pathname == "/@10.111.113.2/system"')
|
|
b.enter_page("/system", host="10.111.113.2")
|
|
b.wait_text(hostname_selector, "machine2")
|
|
|
|
b.switch_to_top()
|
|
b.click("#hosts-sel button")
|
|
b.wait_visible("a[href='/@10.111.113.2']")
|
|
b.wait_not_present("#machine2-error")
|
|
|
|
b.click("a[href='/@10.111.113.3']")
|
|
|
|
b.wait_js_cond('window.location.pathname == "/@10.111.113.3/system"')
|
|
b.enter_page("/system", host=m3_host)
|
|
b.wait_text(hostname_selector, "machine3")
|
|
|
|
b.switch_to_top()
|
|
b.click("#hosts-sel button")
|
|
b.wait_visible("a[href='/@10.111.113.3']")
|
|
b.wait_not_present("#machine3-error")
|
|
|
|
self.allow_restart_journal_messages()
|
|
self.allow_hostkey_messages()
|
|
# Might happen when killing the bridge.
|
|
self.allow_journal_messages("localhost: dropping message while waiting for child to exit",
|
|
"Received message for unknown channel: .*",
|
|
'.*: Socket error: disconnected',
|
|
".*: error reading from ssh",
|
|
".*: bridge failed: .*",
|
|
".*: bridge program failed: Child process exited with code .*")
|
|
|
|
def testGlobalSSHConfig(self):
|
|
b = self.browser
|
|
m = self.machine
|
|
m3 = self.machine3
|
|
|
|
change_ssh_port(m3, "10.111.113.3", 2222)
|
|
m.execute("echo -e 'Host m2\n\tHostName 10.111.113.2\n' >> /etc/ssh/ssh_config")
|
|
m.execute("echo -e 'Host m3\n\tHostName 10.111.113.3\n\tPort 2222\n' >> /etc/ssh/ssh_config")
|
|
|
|
self.login_and_go(None)
|
|
add_machine(b, "m2", password=None)
|
|
add_machine(b, "m3", password=None)
|
|
|
|
b.switch_to_top()
|
|
b.click("#hosts-sel button")
|
|
b.wait_visible("a[href='/@m2']")
|
|
b.wait_visible("a[href='/@m3']")
|
|
b.wait_not_present("#page-sidebar .nav-status")
|
|
|
|
self.allow_hostkey_messages()
|
|
|
|
|
|
@skipDistroPackage()
|
|
class TestMultiMachine(MachineCase):
|
|
provision = {
|
|
"machine1": {"address": "10.111.113.1/20", "memory_mb": 660},
|
|
"machine2": {"address": "10.111.113.2/20", "memory_mb": 660},
|
|
"machine3": {"address": "10.111.113.3/20", "memory_mb": 660},
|
|
}
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
|
|
self.machine2 = self.machines['machine2']
|
|
self.machine3 = self.machines['machine3']
|
|
self.allow_journal_messages("sudo: unable to resolve host machine1: .*")
|
|
|
|
self.setup_provisioned_hosts(disable_preload=True)
|
|
|
|
def checkDirectLogin(self, root='/', known_host=False):
|
|
b = self.browser
|
|
m2 = self.machine2
|
|
m = self.machine
|
|
|
|
hostname_selector = "#system_information_hostname_text"
|
|
|
|
# Direct to machine2, new login
|
|
m2.execute("echo admin:alt-password | chpasswd")
|
|
b.switch_to_top()
|
|
b.open(f"{root}=10.111.113.2")
|
|
b.wait_visible("#login")
|
|
b.wait_visible("#server-name")
|
|
b.wait_not_visible("#badge")
|
|
b.wait_not_visible("#brand")
|
|
b.wait_in_text("#server-name", "10.111.113.2")
|
|
b.wait_val("#server-field", "10.111.113.2")
|
|
b.set_input_text("#login-user-input", "admin")
|
|
b.set_input_text("#login-password-input", "alt-password")
|
|
b.click('#login-button')
|
|
if not known_host:
|
|
b.wait_in_text("#hostkey-message-1", "10.111.113.2")
|
|
match = re.match(r'\((?:ssh-)?([^-]*).*\)', b.text("#hostkey-type"))
|
|
self.assertIsNotNone(match)
|
|
algo = match.groups()[0]
|
|
try:
|
|
# This assumes that all fingerprints use SHA256.
|
|
line = m2.execute("ssh-keygen -l -E SHA256 -f /etc/ssh/ssh_host_%s_key.pub" %
|
|
(algo.lower()), quiet=True)
|
|
fp = line.split(" ")[1]
|
|
self.assertEqual(b.text('#hostkey-fingerprint'), fp)
|
|
except subprocess.CalledProcessError:
|
|
# ssh-keygen doesn't support -E, just make sure we have a fp
|
|
self.assertTrue(b.val('#hostkey-fingerprint'))
|
|
b.click('#login-button')
|
|
|
|
b.enter_page("/system")
|
|
b.wait_in_text(hostname_selector, "machine2")
|
|
b.switch_to_top()
|
|
|
|
b.wait_js_cond(f'window.location.pathname == "{root}=10.111.113.2/system"')
|
|
|
|
b.click("#hosts-sel button")
|
|
b.wait_in_text(f"a[href='{root}=10.111.113.2/@localhost']", "machine2")
|
|
b.wait_not_present("a[href='/@10.111.113.2']")
|
|
b.logout()
|
|
|
|
# Bad host key
|
|
m.execute("echo '10.111.113.2 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDgPMmTosSQ4NxMtq+aL2NKLC+W4I9/jbD1e74cnOKTW' > /etc/ssh/ssh_known_hosts")
|
|
b.open(f"{root}=10.111.113.2")
|
|
b.wait_visible("#login")
|
|
b.set_input_text("#login-user-input", "admin")
|
|
b.set_input_text("#login-password-input", "alt-password")
|
|
b.click('#login-button')
|
|
|
|
b.wait_not_visible("#conversation-group")
|
|
b.wait_visible("#password-group")
|
|
b.wait_visible("#user-group")
|
|
b.wait_visible("#option-group")
|
|
b.wait_visible("#server-group")
|
|
b.wait_in_text("#login-error-message", "Hostkey does not match")
|
|
|
|
# Clear bad host key in /etc and set bad host key in
|
|
# localStorage.
|
|
m.execute("echo '' > /etc/ssh/ssh_known_hosts")
|
|
b.eval_js("""window.localStorage.setItem("known_hosts", '{"10.111.113.2":"BAD"}')""")
|
|
b.set_input_text("#login-user-input", "admin")
|
|
b.set_input_text("#login-password-input", "alt-password")
|
|
b.click('#login-button')
|
|
b.wait_visible("#hostkey-group")
|
|
b.wait_in_text("#hostkey-title", "10.111.113.2 key changed")
|
|
b.click('#login-button')
|
|
|
|
b.enter_page("/system")
|
|
b.wait_in_text(hostname_selector, "machine2")
|
|
b.logout()
|
|
|
|
# Clear localStorage and set correct host key in /etc
|
|
b.eval_js("""window.localStorage.setItem("known_hosts", '{}')""")
|
|
m.execute("ssh-keyscan 10.111.113.2 > /etc/ssh/ssh_known_hosts")
|
|
b.set_input_text("#login-user-input", "admin")
|
|
b.set_input_text("#login-password-input", "alt-password")
|
|
b.click('#login-button')
|
|
b.enter_page("/system")
|
|
b.wait_in_text(hostname_selector, "machine2")
|
|
b.logout()
|
|
|
|
login_options = '#show-other-login-options'
|
|
|
|
# Connect to bad machine
|
|
b.open(f"{root}other")
|
|
b.set_input_text("#login-user-input", "admin")
|
|
b.set_input_text("#login-password-input", "bad-password")
|
|
b.click(login_options)
|
|
b.wait_visible("#server-group")
|
|
b.set_input_text("#server-field", "bad")
|
|
b.click(login_options)
|
|
b.wait_not_visible("#server-group")
|
|
b.click('#login-button')
|
|
b.wait_visible("#server-group")
|
|
b.wait_in_text("#login-error-message", "Unable to connect")
|
|
|
|
# Might happen when we switch away.
|
|
self.allow_hostkey_messages()
|
|
self.allow_journal_messages(".* Failed to resolve hostname bad .*")
|
|
|
|
def testDirectLogin(self):
|
|
self.machine.start_cockpit()
|
|
self.checkDirectLogin('/')
|
|
|
|
@todoPybridgeRHEL8()
|
|
def testUrlRoot(self):
|
|
b = self.browser
|
|
m = self.machine
|
|
|
|
hostname_selector = "#system_information_hostname_text"
|
|
|
|
m.write("/etc/cockpit/cockpit.conf", "[WebService]\nUrlRoot = cockpit-new")
|
|
m.start_cockpit()
|
|
|
|
# Make sure normal urls don't work.
|
|
output = m.execute('curl -s -o /dev/null -w "%{http_code}" http://localhost:9090/cockpit/socket')
|
|
self.assertIn('404', output)
|
|
|
|
output = m.execute('curl -s -o /dev/null -w "%{http_code}" http://localhost:9090/cockpit/socket')
|
|
self.assertIn('404', output)
|
|
|
|
b.open("/cockpit-new/system")
|
|
b.wait_visible("#login")
|
|
b.set_input_text("#login-user-input", "admin")
|
|
b.set_input_text("#login-password-input", "foobar")
|
|
b.click('#login-button')
|
|
b.enter_page("/system")
|
|
b.switch_to_top()
|
|
b.wait_js_cond('window.location.pathname == "/cockpit-new/system"')
|
|
|
|
# Test 2nd machine
|
|
add_machine(b, "10.111.113.2")
|
|
b.enter_page("/system", host="10.111.113.2")
|
|
b.wait_text(hostname_selector, "machine2")
|
|
b.switch_to_top()
|
|
b.wait_js_cond('window.location.pathname == "/cockpit-new/@10.111.113.2/system"')
|
|
|
|
# Test subnav
|
|
b.click_system_menu("/cockpit-new/@10.111.113.2/users", enter=False)
|
|
b.enter_page("/users", host="10.111.113.2")
|
|
b.click('#accounts-list td[data-label="Username"] a[href="#/admin"]')
|
|
b.wait_text("#account-user-name", "admin")
|
|
b.switch_to_top()
|
|
b.wait_js_cond('window.location.pathname == "/cockpit-new/@10.111.113.2/users"')
|
|
b.wait_js_cond('window.location.hash == "#/admin"')
|
|
|
|
b.logout()
|
|
self.checkDirectLogin('/cockpit-new/')
|
|
self.allow_hostkey_messages()
|
|
|
|
def testUrlRootWithQuery(self):
|
|
b = self.browser
|
|
m = self.machine
|
|
|
|
m.write("/etc/cockpit/cockpit.conf", "[WebService]\nUrlRoot = cockpit-new")
|
|
m.start_cockpit()
|
|
|
|
b.open("/cockpit-new/system?access_token=XXXX")
|
|
b.wait_visible("#login")
|
|
b.set_input_text("#login-user-input", "admin")
|
|
b.set_input_text("#login-password-input", "foobar")
|
|
b.click('#login-button')
|
|
b.enter_page("/system")
|
|
b.switch_to_top()
|
|
b.wait_js_cond('window.location.pathname == "/cockpit-new/system"')
|
|
|
|
@todoPybridgeRHEL8()
|
|
def testExternalPage(self):
|
|
b = self.browser
|
|
m1 = self.machine
|
|
m2 = self.machine2
|
|
|
|
# Modify the terminals to be different on the two machines.
|
|
for machine, name in zip((m1, m2), ('m1', 'm2')):
|
|
# This page may be either compressed or uncompressed
|
|
machine.execute(f"""
|
|
FILENAME=/usr/share/cockpit/systemd/terminal.html*
|
|
cp $FILENAME /tmp
|
|
test -f /tmp/terminal.html || gzip -d /tmp/terminal.html.gz
|
|
sed -ie 's|</body>|magic-{name}-token</body>|' /tmp/terminal.html
|
|
gzip < /tmp/terminal.html > /tmp/terminal.html.gz
|
|
mount -o bind /tmp/"$(basename $FILENAME)" $FILENAME""")
|
|
|
|
self.login_and_go("/system")
|
|
add_machine(b, "10.111.113.2")
|
|
|
|
b.leave_page()
|
|
b.go("/@10.111.113.2/system/terminal")
|
|
b.enter_page("/system/terminal", host="10.111.113.2")
|
|
b.wait_in_text("body", "magic-m2-token")
|
|
|
|
b.leave_page()
|
|
b.go("/@localhost/system/terminal")
|
|
b.enter_page("/system/terminal")
|
|
b.wait_in_text("body", "magic-m1-token")
|
|
|
|
self.allow_hostkey_messages()
|
|
|
|
@todoPybridgeRHEL8()
|
|
def testFrameNavigation(self):
|
|
b = self.browser
|
|
m2 = self.machine2
|
|
|
|
m2_path = "/@10.111.113.2/playground/test"
|
|
|
|
# Add a machine
|
|
self.login_and_go(None)
|
|
add_machine(b, "10.111.113.2")
|
|
|
|
# Go to the path, remove the image
|
|
b.go(m2_path)
|
|
b.enter_page("/playground/test", "10.111.113.2")
|
|
b.click("img[src='hammer.gif']")
|
|
b.switch_to_top()
|
|
|
|
# kill admin, lock account
|
|
m2.execute('passwd -l admin')
|
|
kill_user_admin(m2)
|
|
|
|
with b.wait_timeout(30):
|
|
b.wait_text(".curtains-ct h1", "Not connected to host")
|
|
b.wait_text("#machine-reconnect", "Reconnect")
|
|
|
|
b.click("#hosts-sel button")
|
|
b.wait_visible("a[href='/@10.111.113.2']")
|
|
b.wait_visible("#machine2-error")
|
|
b.go("/system")
|
|
b.enter_page("/system")
|
|
b.wait_in_text("#system_information_hostname_text", "machine1")
|
|
b.switch_to_top()
|
|
|
|
# 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")
|
|
|
|
# wait for system to load
|
|
b.go("/system")
|
|
b.enter_page("/system")
|
|
b.wait_in_text("#system_information_hostname_text", "machine1")
|
|
b.switch_to_top()
|
|
|
|
# renable admin
|
|
m2.execute('passwd -u admin')
|
|
|
|
# path should reconnect at this point
|
|
b.reload()
|
|
b.go(m2_path)
|
|
with b.wait_timeout(30):
|
|
b.wait_text(".curtains-ct h1", "Not connected to host")
|
|
b.click("#machine-troubleshoot")
|
|
b.wait_visible('#hosts_setup_server_dialog')
|
|
b.wait_in_text('#hosts_setup_server_dialog', "Unable to log in")
|
|
b.set_input_text('#login-custom-password', "foobar")
|
|
b.click('#hosts_setup_server_dialog button:contains(Log in)')
|
|
b.wait_not_present('#hosts_setup_server_dialog')
|
|
|
|
b.enter_page("/playground/test", "10.111.113.2", reconnect=True)
|
|
# image is back because it page was reloaded after disconnection
|
|
b.wait_visible("img[src='hammer.gif']")
|
|
b.switch_to_top()
|
|
|
|
# Host shows it is up
|
|
b.click("#hosts-sel button")
|
|
b.wait_visible("a[href='/@10.111.113.2']")
|
|
b.wait_not_present("#page-sidebar .nav-status")
|
|
|
|
# Bad host also bounces
|
|
b.go("/@10.0.0.0/playground/test")
|
|
with b.wait_timeout(30):
|
|
b.wait_text(".curtains-ct h1", "Not connected to host")
|
|
self.assertEqual(b.text(".curtains-ct .pf-v5-c-empty-state__body"), "Cannot connect to an unknown host")
|
|
|
|
self.allow_hostkey_messages()
|
|
# Might happen when killing the bridge.
|
|
self.allow_journal_messages("localhost: dropping message while waiting for child to exit",
|
|
"Received message for unknown channel: .*",
|
|
'.*: Socket error: disconnected',
|
|
".*: error reading from ssh",
|
|
".*: bridge failed: .*",
|
|
".*: bridge program failed: Child process exited with code .*",
|
|
"/playground/test.html: failed to retrieve resource: authentication-failed")
|
|
|
|
@todoPybridgeRHEL8()
|
|
def testFrameReload(self):
|
|
b = self.browser
|
|
|
|
frame = "cockpit1:10.111.113.2/playground/test"
|
|
m2_path = "/@10.111.113.2/playground/test"
|
|
|
|
# Add a machine
|
|
self.login_and_go(None)
|
|
add_machine(b, "10.111.113.2")
|
|
|
|
b.switch_to_top()
|
|
b.go(m2_path)
|
|
b.enter_page("/playground/test", "10.111.113.2")
|
|
|
|
b.wait_text('#file-content', "0")
|
|
b.click("#modify-file")
|
|
b.wait_text('#file-content', "1")
|
|
|
|
# load the same page on m1
|
|
b.switch_to_top()
|
|
b.go("/@localhost/playground/test")
|
|
b.enter_page("/playground/test")
|
|
b.wait_text('#file-content', "0")
|
|
|
|
# go back to m2 and reload frame.
|
|
b.switch_to_top()
|
|
b.go(m2_path)
|
|
b.enter_page("/playground/test", "10.111.113.2")
|
|
b.wait_text('#file-content', "1")
|
|
b.switch_to_top()
|
|
|
|
b.eval_js('ph_set_attr("iframe[name=\'%s\']", "data-ready", null)' % frame)
|
|
b.eval_js('ph_set_attr("iframe[name=\'%s\']", "src", "../playground/test.html?i=1#/")' % frame)
|
|
b.wait_visible(f"iframe.container-frame[name='{frame}'][data-ready]")
|
|
|
|
b.enter_page("/playground/test", "10.111.113.2")
|
|
|
|
b.wait_text('#file-content', "1")
|
|
|
|
self.allow_hostkey_messages()
|
|
|
|
@todoPybridge(reason="https://github.com/cockpit-project/cockpit/issues/18713")
|
|
def testTroubleshooting(self):
|
|
b = self.browser
|
|
m1 = self.machine
|
|
m2 = self.machine2
|
|
|
|
# Logging in as root is no longer allowed by default by sshd
|
|
m2.execute("sed -ri 's/#?PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config")
|
|
m2.execute("systemctl restart sshd")
|
|
|
|
machine_path = "/@10.111.113.2"
|
|
|
|
self.login_and_go(None)
|
|
|
|
# Troubleshoot while adding
|
|
b.go(machine_path)
|
|
|
|
# Bad hostkey
|
|
break_hostkey(m1, "10.111.113.2")
|
|
start_machine_troubleshoot(b, True, True)
|
|
b.wait_in_text('#hosts_setup_server_dialog', "10.111.113.2 key changed")
|
|
b.click("#hosts_setup_server_dialog button:contains(Cancel)")
|
|
b.wait_not_present('#hosts_setup_server_dialog')
|
|
fix_hostkey(m1)
|
|
|
|
# Host key path is correct
|
|
m1.execute("mkdir -p /home/admin/.ssh/")
|
|
break_hostkey(m1, "10.111.113.2")
|
|
|
|
start_machine_troubleshoot(b, True, True)
|
|
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)
|
|
start_machine_troubleshoot(b, True, password="foobar")
|
|
check_failed_state(b, "Cockpit is not installed")
|
|
fix_bridge(m2)
|
|
|
|
# Troubleshoot existing
|
|
# Properly add machine
|
|
fix_hostkey(m1)
|
|
add_machine(b, "10.111.113.2")
|
|
b.logout()
|
|
b.wait_visible("#login")
|
|
|
|
# Bad cockpit
|
|
break_bridge(m2)
|
|
self.login_and_go(None)
|
|
b.go(machine_path)
|
|
with b.wait_timeout(240):
|
|
start_machine_troubleshoot(b, password="foobar")
|
|
|
|
check_failed_state(b, "Cockpit is not installed")
|
|
b.wait_visible("#machine-troubleshoot")
|
|
fix_bridge(m2)
|
|
|
|
# Clear host key
|
|
fix_hostkey(m1)
|
|
start_machine_troubleshoot(b)
|
|
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(Accept key and connect)')
|
|
b.wait_in_text('#hosts_setup_server_dialog', "Unable to log in")
|
|
b.set_input_text('#login-custom-password', "foobar")
|
|
b.click('#hosts_setup_server_dialog button:contains(Log in)')
|
|
b.wait_not_present('#hosts_setup_server_dialog')
|
|
|
|
# Reconnect
|
|
b.wait_not_visible(".curtains-ct")
|
|
b.enter_page('/system', "10.111.113.2")
|
|
|
|
b.logout()
|
|
b.wait_visible("#login")
|
|
|
|
# Break auth
|
|
m2.execute("echo admin:alt-password | chpasswd")
|
|
self.login_and_go(None)
|
|
b.go(machine_path)
|
|
|
|
with b.wait_timeout(120):
|
|
b.wait_visible("#machine-troubleshoot")
|
|
start_machine_troubleshoot(b)
|
|
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")
|
|
b.click(f'#hosts_setup_server_dialog {self.primary_btn_class}')
|
|
b.wait_not_present('#hosts_setup_server_dialog')
|
|
|
|
# Reconnect
|
|
b.wait_not_visible(".curtains-ct")
|
|
b.enter_page('/system', "10.111.113.2")
|
|
b.logout()
|
|
b.wait_visible("#login")
|
|
|
|
change_ssh_port(m2, "10.111.113.2", 2222)
|
|
m2.disconnect()
|
|
del m2 # No more access to m2
|
|
|
|
self.login_and_go(None)
|
|
b.go(machine_path)
|
|
with b.wait_timeout(120):
|
|
b.wait_visible("#machine-troubleshoot")
|
|
start_machine_troubleshoot(b)
|
|
b.wait_in_text('#hosts_setup_server_dialog h1', "Could not contact")
|
|
b.set_input_text("#edit-machine-port", "2222")
|
|
b.click(f'#hosts_setup_server_dialog {self.primary_btn_class}')
|
|
# Using libssh's knownhosts api port is taken into account when verifying a host
|
|
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(Accept key and connect)')
|
|
b.wait_in_text('#hosts_setup_server_dialog h1', "Log in to")
|
|
b.set_input_text("#login-custom-password", "alt-password")
|
|
b.click(f'#hosts_setup_server_dialog {self.primary_btn_class}')
|
|
b.wait_not_present('#hosts_setup_server_dialog')
|
|
|
|
b.wait_not_visible(".curtains-ct")
|
|
b.enter_page('/system', "10.111.113.2:2222")
|
|
b.logout()
|
|
|
|
self.allow_hostkey_messages()
|
|
self.allow_journal_messages('.* couldn\'t connect: .*',
|
|
'.* failed to retrieve resource: invalid-hostkey',
|
|
'.* host key for server has changed to: .*',
|
|
'.* spawning remote bridge failed .*',
|
|
'.*: bridge failed: .*',
|
|
'.*: received truncated .*',
|
|
'.*: Socket error: disconnected',
|
|
'.*: host key for this server changed key type: .*',
|
|
'.*: server offered unsupported authentication methods: .*')
|
|
|
|
@skipImage("TODO: Broken on Arch Linux", "arch")
|
|
@todoPybridge(reason="https://github.com/cockpit-project/cockpit/issues/18714")
|
|
def testSshKeySetup(self):
|
|
b = self.browser
|
|
m1 = self.machine
|
|
m2 = self.machine2
|
|
|
|
# Let's not use "admin" on the remote machine. Creating a
|
|
# dedicated user gives us a guaranteed clean slate and also
|
|
# tests more code paths.
|
|
|
|
m2.execute("useradd -m fred")
|
|
m2.execute("echo fred:foobar | chpasswd")
|
|
|
|
self.login_and_go(None)
|
|
b.go("/@10.111.113.2")
|
|
b.wait_visible("#machine-troubleshoot")
|
|
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_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(Accept key and connect)')
|
|
b.wait_in_text('#hosts_setup_server_dialog', "Unable to log in")
|
|
|
|
# There is no key yet. Create and authorize it.
|
|
|
|
m1.execute("! test -f /home/admin/.ssh/id_rsa")
|
|
m2.execute("! test -f /home/fred/.ssh/authorized_keys")
|
|
|
|
b.wait_in_text("#hosts_setup_server_dialog", "Create a new SSH key and authorize it")
|
|
b.set_input_text('#login-custom-password', "foobar")
|
|
b.set_checked("#login-setup-keys", True)
|
|
# Leave passphrase empty on Coreos, since it can't load keys into the agent
|
|
if not m1.ostree_image:
|
|
b.set_input_text('#hosts_setup_server_dialog #login-setup-new-key-password', "foobar")
|
|
b.set_input_text('#hosts_setup_server_dialog #login-setup-new-key-password2', "foobar")
|
|
b.click('#hosts_setup_server_dialog button:contains(Log in)')
|
|
with b.wait_timeout(30):
|
|
b.wait_not_present('#hosts_setup_server_dialog')
|
|
|
|
b.enter_page("/system", host="fred@10.111.113.2")
|
|
m1.execute("test -f /home/admin/.ssh/id_rsa; test -f /home/admin/.ssh/id_rsa.pub")
|
|
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.
|
|
b.relogin(None, wait_remote_session_machine=m1)
|
|
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.wait_visible("#machine-troubleshoot")
|
|
b.click('#machine-troubleshoot')
|
|
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")
|
|
b.set_checked("#login-setup-keys", True)
|
|
b.set_input_text('#login-custom-password', "foobar")
|
|
b.click('#hosts_setup_server_dialog button:contains(Log in)')
|
|
b.wait_not_present('#hosts_setup_server_dialog')
|
|
b.enter_page("/system", host="fred@10.111.113.2")
|
|
self.assertEqual(m1.execute("cat /home/admin/.ssh/id_rsa.pub"),
|
|
m2.execute("cat /home/fred/.ssh/authorized_keys"))
|
|
|
|
# Put a 'better' passphrase on the key and relogin, then
|
|
# 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.wait_visible("#machine-troubleshoot")
|
|
b.click('#machine-troubleshoot')
|
|
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]', True)
|
|
b.set_input_text('#hosts_setup_server_dialog #locked-identity-password', "foobarfoo")
|
|
b.set_checked("#login-setup-keys", True)
|
|
b.set_input_text('#hosts_setup_server_dialog #login-setup-new-key-password', "foobar")
|
|
b.set_input_text('#hosts_setup_server_dialog #login-setup-new-key-password2', "foobar")
|
|
b.click('#hosts_setup_server_dialog button:contains(Log in)')
|
|
b.wait_not_present('#hosts_setup_server_dialog')
|
|
b.enter_page("/system", host="fred@10.111.113.2")
|
|
|
|
# Relogin. This should now work seamlessly (except on fedora-coreos and rhel4edge
|
|
# 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.enter_page("/system", host="fred@10.111.113.2")
|
|
|
|
# The authorized_keys files should still only have a single key
|
|
self.assertEqual(m1.execute("cat /home/admin/.ssh/id_rsa.pub"),
|
|
m2.execute("cat /home/fred/.ssh/authorized_keys"))
|
|
|
|
self.allow_hostkey_messages()
|
|
|
|
@todoPybridgeRHEL8()
|
|
def testSshKeySetupCustom(self):
|
|
b = self.browser
|
|
m1 = self.machine
|
|
m2 = self.machine2
|
|
|
|
# This tests how the ssh key setup reacts to a already
|
|
# existing configuration involving a custom key with a
|
|
# passphrase.
|
|
|
|
m1.execute("d=/home/admin/.ssh; mkdir -p $d; chown admin:admin $d; chmod 700 $d")
|
|
m1.execute("ssh-keygen -f /home/admin/.ssh/id_local -t rsa -N 'foobar'")
|
|
m1.execute("chown admin:admin /home/admin/.ssh/id_local*")
|
|
m1.write("/home/admin/.ssh/config", "Host 10.111.113.2\n IdentityFile /home/admin/.ssh/id_local\n")
|
|
pubkey = self.machine.execute("cat /home/admin/.ssh/id_local.pub")
|
|
|
|
m2.execute("d=/home/admin/.ssh; mkdir -p $d; chown admin:admin $d; chmod 700 $d")
|
|
m2.write("/home/admin/.ssh/authorized_keys", pubkey)
|
|
m2.execute("chown admin:admin /home/admin/.ssh/authorized_keys")
|
|
|
|
self.login_and_go(None)
|
|
b.go("/@10.111.113.2")
|
|
b.wait_visible("#machine-troubleshoot")
|
|
b.click('#machine-troubleshoot')
|
|
b.wait_visible('#hosts_setup_server_dialog')
|
|
b.click('#hosts_setup_server_dialog button: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 button:contains(Accept key and connect)')
|
|
b.wait_in_text('#hosts_setup_server_dialog', "The SSH key")
|
|
b.wait_not_present('.password-change-advice')
|
|
b.wait_not_present('.login-setup-auto')
|
|
|
|
self.allow_hostkey_messages()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
test_main()
|