262 lines
11 KiB
Python
Executable File
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()
|