From 7f55f24398c72482ef66f488547b30ef83bf9b67 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 22 Mar 2024 12:37:56 +0100 Subject: [PATCH] test: update tests to allow ssh keys With ssh a root login is only possible via a sshkey. So let's support this so that we can run `bootc status` which requires root privs. With the switch to bootc we need to adjust the testing. We inject a root ssh key now and just use that for login. --- test/test_build.py | 46 ++++++++++++++++++++++++++++++++++------------ test/vm.py | 10 ++++++++-- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/test/test_build.py b/test/test_build.py index 2aa7eec81..8a68b513a 100644 --- a/test/test_build.py +++ b/test/test_build.py @@ -39,6 +39,7 @@ class ImageBuildResult(NamedTuple): container_ref: str username: str password: str + ssh_keyfile_private_path: str kargs: str bib_output: str journal_output: str @@ -142,6 +143,10 @@ def build_images(shared_tmpdir, build_container, request, force_aws_upload): journal_log_path = output_path / "journal.log" bib_output_path = output_path / "bib-output.log" + + ssh_keyfile_private_path = output_path / "ssh-keyfile" + ssh_keyfile_public_path = ssh_keyfile_private_path.with_suffix(".pub") + artifact = { "qcow2": pathlib.Path(output_path) / "qcow2/disk.qcow2", "ami": pathlib.Path(output_path) / "image/disk.raw", @@ -174,9 +179,21 @@ def build_images(shared_tmpdir, build_container, request, force_aws_upload): bib_output = bib_output_path.read_text(encoding="utf8") results.append(ImageBuildResult( image_type, generated_img, target_arch, container_ref, - username, password, + username, password, ssh_keyfile_private_path, kargs, bib_output, journal_output)) + # generate new keyfile + if not ssh_keyfile_private_path.exists(): + subprocess.run([ + "ssh-keygen", + "-N", "", + # be very conservative with keys for paramiko + "-b", "2048", + "-t", "rsa", + "-f", os.fspath(ssh_keyfile_private_path), + ], check=True) + ssh_pubkey = ssh_keyfile_public_path.read_text(encoding="utf8") + # Because we always build all image types, regardless of what was requested, we should either have 0 results or all # should be available, so if we found at least one result but not all of them, this is a problem with our setup assert not results or len(results) == len(image_types), \ @@ -194,8 +211,14 @@ def build_images(shared_tmpdir, build_container, request, force_aws_upload): "customizations": { "user": [ { + "name": "root", + "key": ssh_pubkey, + # cannot use default /root as is on a read-only place + "home": "/var/roothome", + }, { "name": username, "password": password, + "key": ssh_pubkey, "groups": ["wheel"], }, ], @@ -295,7 +318,7 @@ def del_ami(): for image_type in image_types: results.append(ImageBuildResult( image_type, artifact[image_type], target_arch, container_ref, - username, password, + username, password, ssh_keyfile_private_path, kargs, bib_output, journal_output, metadata)) yield results @@ -340,18 +363,16 @@ def assert_kernel_args(test_vm, image_type): @pytest.mark.parametrize("image_type", gen_testcases("qemu-boot"), indirect=["image_type"]) def test_image_boots(image_type): with QEMU(image_type.img_path, arch=image_type.img_arch) as test_vm: + # user/password login works exit_status, _ = test_vm.run("true", user=image_type.username, password=image_type.password) assert exit_status == 0 - exit_status, output = test_vm.run("echo hello", user=image_type.username, password=image_type.password) + # ssh login also works + exit_status, output = test_vm.run("id", user="root", keyfile=image_type.ssh_keyfile_private_path) assert exit_status == 0 - assert "hello" in output + assert "uid=0" in output assert_kernel_args(test_vm, image_type) # ensure bootc points to the right image - # TODO: replace this ssh root instead login, see PR#357 - _, output = test_vm.run( - f"echo {image_type.password} | sudo -S bootc status", - user=image_type.username, password=image_type.password, - ) + _, output = test_vm.run("bootc status", user="root", keyfile=image_type.ssh_keyfile_private_path) # XXX: read the fully yaml instead? assert f"image: {image_type.container_ref}" in output @@ -368,9 +389,10 @@ def test_ami_boots_in_aws(image_type, force_aws_upload): # 4.30 GiB / 10.00 GiB [------------>____________] 43.02% 58.04 MiB p/s assert "] 100.00%" in image_type.bib_output with AWS(image_type.metadata["ami_id"]) as test_vm: - exit_status, _ = test_vm.run("true", user=image_type.username, password=image_type.password) + exit_status, _ = test_vm.run("true", user=image_type.username, keyfile=image_type.ssh_keyfile_private_path) assert exit_status == 0 - exit_status, output = test_vm.run("echo hello", user=image_type.username, password=image_type.password) + exit_status, output = test_vm.run( + "echo hello", user=image_type.username, keyfile=image_type.ssh_keyfile_private_path) assert exit_status == 0 assert "hello" in output @@ -431,7 +453,7 @@ def test_iso_installs(image_type): # boot test disk and do extremly simple check with QEMU(test_disk_path) as vm: vm.start(use_ovmf=True) - exit_status, _ = vm.run("true", user=image_type.username, password=image_type.password) + exit_status, _ = vm.run("true", user=image_type.username, keyfile=image_type.ssh_keyfile_private_path) assert exit_status == 0 assert_kernel_args(vm, image_type) diff --git a/test/vm.py b/test/vm.py index 43c376363..1bb21f5b7 100644 --- a/test/vm.py +++ b/test/vm.py @@ -1,5 +1,6 @@ import abc import os +import paramiko import pathlib import platform import subprocess @@ -43,7 +44,7 @@ def force_stop(self): Stop the VM and clean up any resources that were created when setting up and starting the machine. """ - def run(self, cmd, user, password): + def run(self, cmd, user, password="", keyfile=None): """ Run a command on the VM via SSH using the provided credentials. """ @@ -51,8 +52,13 @@ def run(self, cmd, user, password): self.start() client = SSHClient() client.set_missing_host_key_policy(AutoAddPolicy) + # workaround, see https://github.com/paramiko/paramiko/issues/2048 + pkey = None + if keyfile: + pkey = paramiko.RSAKey.from_private_key_file(keyfile) client.connect( - self._address, self._ssh_port, user, password, + self._address, self._ssh_port, + user, password, pkey=pkey, allow_agent=False, look_for_keys=False) chan = client.get_transport().open_session() chan.get_pty()