cockpit/test/verify/check-ws-bastion

262 lines
11 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) 2022 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/>.
from testlib import MachineCase, nondestructive, onlyImage, test_main
HOST = "host.containers.internal"
@onlyImage("no cockpit/ws container on this image", "fedora-coreos", "rhel4edge")
@nondestructive
class TestWsBastionContainer(MachineCase):
def setUp(self):
super().setUp()
# stop ws container from previous runs
self.machine.stop_cockpit()
# undo cockpit/ws install steps
self.restore_file("/etc/systemd/system/cockpit.service")
self.addCleanup(self.machine.execute, "podman rm -f --all")
def approve_key(self, b, hostname):
b.wait_visible("#hostkey-group")
b.wait_in_text("#hostkey-message-1", f"You are connecting to {hostname} for the first time.")
b.click("#login-button")
def testPasswordLogin(self):
m = self.machine
b = self.browser
m.execute("podman run -d --name cockpit-bastion -p 9090:9090 localhost/cockpit/ws")
m.execute("until curl --fail --head -k https://localhost:9090/; do sleep 1; done")
b.ignore_ssl_certificate_errors(True)
b.open("/", tls=True)
b.wait_visible("#login")
# should be pre-configured to RequireHost
b.wait_not_visible("#option-group")
b.wait_visible("#server-field")
# LoginTitle from default-bastion.conf
b.wait_text("#server-name", "Cockpit Bastion")
# No branding by default
b.wait_text("#brand", "")
b.set_val("#login-user-input", "admin")
b.set_val("#login-password-input", "foobar")
# Requires a host
b.click("#login-button")
b.wait_in_text("#login-error-message", "host to connect")
# so connect to our own container host
b.set_val("#server-field", HOST)
b.set_val("#login-password-input", "foobar")
# key is unknown
b.click("#login-button")
self.approve_key(b, HOST)
b.wait_visible('#content')
b.wait_text('#current-username', 'admin')
b.logout()
# remembers the last host via URL, server field should be pre-filled
self.assertEqual(b.eval_js("window.location.pathname"), f"/={HOST}/system")
# FIXME: login page does not really set this in the DOM? DOM has empty value, but browser shows the value
# b.wait_text("#server-field", host)
# this is only for Cockpit Client
b.wait_not_visible("#recent-hosts")
b.set_val("#login-user-input", "admin")
b.set_val("#login-password-input", "foobar")
# second time SSH key is known
b.click("#login-button")
b.wait_visible('#content')
b.logout()
def testKnownHosts(self):
m = self.machine
b = self.browser
m.execute(f"ssh-keyscan localhost | sed 's/^localhost/{HOST}/' > /root/known_hosts")
self.addCleanup(m.execute, "rm /root/known_hosts")
b.ignore_ssl_certificate_errors(True)
def check_login():
m.execute("until curl --fail --head -k https://localhost:9090/; do sleep 1; done")
b.open("/", tls=True)
b.set_val("#login-user-input", "admin")
b.set_val("#login-password-input", "foobar")
b.set_val("#server-field", HOST)
b.click("#login-button")
b.wait_visible('#content')
b.logout()
m.execute("podman rm -f cockpit-bastion")
# default location
m.execute("podman run -d --name cockpit-bastion -p 9090:9090 "
"-v /root/known_hosts:/etc/ssh/ssh_known_hosts:ro,Z "
"localhost/cockpit/ws")
check_login()
# custom location
m.execute("podman run -d --name cockpit-bastion -p 9090:9090 "
"-v /root/known_hosts:/known_hosts:ro,Z "
"-e COCKPIT_SSH_KNOWN_HOSTS_FILE=/known_hosts "
"localhost/cockpit/ws")
check_login()
# connect to all unknown hosts
# FIXME: this does not work
# m.execute("podman run -d --name cockpit-bastion -p 9090:9090 "
# "-e COCKPIT_SSH_CONNECT_TO_UNKNOWN_HOSTS=1 "
# "localhost/cockpit/ws")
# check_login()
def testCustomConf(self):
m = self.machine
b = self.browser
# custom cockpit.conf and pretend we are Fedora CoreOS/RHEL
self.write_file("/root/cockpit.conf", """[WebService]
LoginTitle = My Walden
""")
m.execute("cp /etc/os-release /root; "
"podman run -d --name cockpit-bastion -p 9090:9090 "
"-v /root/cockpit.conf:/etc/cockpit/cockpit.conf:ro,Z "
"-v /root/os-release:/etc/os-release:ro,Z "
"localhost/cockpit/ws")
m.execute("until curl --fail --head -k https://localhost:9090/; do sleep 1; done")
b.ignore_ssl_certificate_errors(True)
b.open("/", tls=True)
b.wait_visible("#login")
b.wait_text("#server-name", "My Walden")
# custom conf does not have RequireHost
b.wait_visible("#option-group")
# Shows os-release branding
b.wait_in_text("#brand", "Fedora" if m.image == "fedora-coreos" else "Red Hat Enterprise Linux")
# pre-fill target host
b.open(f"/={HOST}/", tls=True)
b.wait_visible("#login")
b.set_val("#login-user-input", "admin")
b.set_val("#login-password-input", "foobar")
b.click("#login-button")
self.approve_key(b, HOST)
b.wait_visible('#content')
b.wait_text('#current-username', 'admin')
def testKeyLogin(self):
m = self.machine
b = self.browser
KEY_PASSWORD = "sshfoobar"
# old RSA/PEM format
m.execute(f"ssh-keygen -q -f /root/id_bastion -t rsa -m PEM -N {KEY_PASSWORD}")
m.execute(f"ssh-keyscan localhost | sed 's/^localhost/{HOST}/' > /root/known_hosts")
self.addCleanup(m.execute, "rm /root/known_hosts /root/id_bastion /root/id_bastion.pub")
m.execute("podman run -d --name cockpit-bastion -p 9090:9090 "
"-v /root/known_hosts:/etc/ssh/ssh_known_hosts:ro,Z "
"-v /root/id_bastion:/id_bastion:ro,Z "
"-e COCKPIT_SSH_KEY_PATH=/id_bastion "
"-e G_MESSAGES_DEBUG=cockpit-ssh "
"localhost/cockpit/ws")
m.execute("until curl --fail --head -k https://localhost:9090/; do sleep 1; done")
b.ignore_ssl_certificate_errors(True)
b.open("/", tls=True)
b.set_val("#login-user-input", "admin")
b.set_val("#server-field", HOST)
# the account password does not work
b.set_val("#login-password-input", "foobar")
b.click("#login-button")
b.wait_text_not("#login-error-message", "")
# SSH key password, but key is not authorized
b.set_val("#login-password-input", KEY_PASSWORD)
b.click("#login-button")
b.wait_text_not("#login-error-message", "")
# authorize key
self.restore_file("/home/admin/.ssh/authorized_keys")
# Do not use authorized_keys.d as that does not work on rhel4edge
# Do not append but overwrite so we are sure the right key is used
m.execute("cat /root/id_bastion.pub > /home/admin/.ssh/authorized_keys")
# fails with wrong key password
b.set_val("#login-password-input", "notthispassword")
b.click("#login-button")
b.wait_text_not("#login-error-message", "")
# works with correct key password
b.set_val("#login-password-input", KEY_PASSWORD)
b.click("#login-button")
b.wait_visible('#content')
b.logout()
# now test with current OpenSSH format
m.execute(f"yes | ssh-keygen -q -f /root/id_bastion -t rsa -N {KEY_PASSWORD}")
m.execute("cat /root/id_bastion.pub > /home/admin/.ssh/authorized_keys")
b.set_val("#login-user-input", "admin")
b.set_val("#server-field", HOST)
b.set_val("#login-password-input", KEY_PASSWORD)
b.click("#login-button")
b.wait_visible('#content')
@onlyImage("no cockpit/ws container on this image", "fedora-coreos", "rhel4edge")
@nondestructive
class TestWsPrivileged(MachineCase):
def testService(self):
# the install script should have created a cockpit.service, but not started it
m = self.machine
self.assertEqual(m.execute("! systemctl is-enabled cockpit.service").strip(), "disabled")
self.assertEqual(m.execute("! systemctl is-active cockpit.service").strip(), "inactive")
# stop ws container from previous test runs
self.machine.stop_cockpit()
self.assertEqual(m.execute("podman ps --noheading"), "")
self.addCleanup(m.execute, "systemctl disable --now cockpit.service")
m.execute("systemctl start cockpit.service")
self.assertEqual(m.execute("systemctl is-active cockpit.service").strip(), "active")
firstStartTime = m.execute("podman inspect cockpit-ws| jq '.[0].Created'")
m.execute("until curl --fail --head -k https://localhost:9090/; do sleep 1; done")
m.execute("systemctl restart cockpit.service")
self.assertEqual(m.execute("systemctl is-active cockpit.service").strip(), "active")
secondStartTime = m.execute("podman inspect cockpit-ws| jq '.[0].Created'")
m.execute("until curl --fail --head -k https://localhost:9090/; do sleep 1; done")
self.assertNotEqual(firstStartTime, secondStartTime)
m.execute("systemctl stop cockpit.service")
self.assertEqual(m.execute("! systemctl is-enabled cockpit.service").strip(), "disabled")
self.assertEqual(m.execute("! systemctl is-active cockpit.service").strip(), "inactive")
self.assertEqual(m.execute("podman ps --noheading"), "")
# current container has `set -x` in its startup script, which ends up in the journal
self.allow_journal_messages("^[/+'].*")
if __name__ == '__main__':
test_main()