diff --git a/tests/checkbox/checkbox-provider-tdx/units/tests/jobs.pxu b/tests/checkbox/checkbox-provider-tdx/units/tests/jobs.pxu index 78f5da7..0b94df3 100644 --- a/tests/checkbox/checkbox-provider-tdx/units/tests/jobs.pxu +++ b/tests/checkbox/checkbox-provider-tdx/units/tests/jobs.pxu @@ -143,6 +143,18 @@ command: export LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/:$LD_LIBRARY_PATH setup-env-and-run test_guest_ita.py +id: tdx-guest/td-libtdx-attest +category_id: tdx-guest +flags: simple +_summary: Test guest libtdx_attest +depends: +after: +requires: + executable.name == 'qemu-system-x86_64' +command: + export LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/:$LD_LIBRARY_PATH + setup-env-and-run test_guest_tdxattest.py + id: tdx-guest/td-guest-reboot category_id: tdx-guest flags: simple diff --git a/tests/lib/Qemu.py b/tests/lib/Qemu.py index 2371be4..40ad392 100644 --- a/tests/lib/Qemu.py +++ b/tests/lib/Qemu.py @@ -165,13 +165,21 @@ class QemuMachineType: Qemu_Machine_Params = { QemuEfiMachine.OVMF_Q35:['-machine', 'q35,kernel_irqchip=split'], QemuEfiMachine.OVMF_Q35_TDX:[ - '-object', 'tdx-guest,id=tdx', '-machine', 'q35,kernel_irqchip=split,confidential-guest-support=tdx'] } def __init__(self, machine = QemuEfiMachine.OVMF_Q35_TDX): self.machine = machine + self.quote_sock = False + def enable_quote_socket(self): + self.quote_sock = True def args(self): - return self.Qemu_Machine_Params[self.machine] + qemu_args = self.Qemu_Machine_Params[self.machine] + if self.machine == QemuEfiMachine.OVMF_Q35_TDX: + tdx_object = {'qom-type':'tdx-guest', 'id':'tdx'} + if self.quote_sock: + tdx_object.update({'quote-generation-socket':{'type': 'vsock', 'cid':'2','port':'4050'}}) + qemu_args = ['-object', str(tdx_object)] + qemu_args + return qemu_args class QemuBootType: def __init__(self, @@ -417,7 +425,25 @@ def rsync_file(self, fname, dest, sudo=False): shell=True, stdout=subprocess.DEVNULL) + def exec_command(self, cmd): + """ + Exec a command without checking the return code + Returns a triple: + - ret (return code) + - stdout + - stderr + """ + _, stdout, stderr = self.ssh_conn.exec_command(cmd) + ret_status = stdout.channel.recv_exit_status() + return ret_status, stdout, stderr + def check_exec(self, cmd, err_msg=None): + """ + Exec a command and check the return code + Returns a 2-tuple: + - stdout + - stderr + """ _, stdout, stderr = self.ssh_conn.exec_command(cmd) if err_msg==None: err_msg=f'Execution of {cmd} failed' diff --git a/tests/tests/test_guest_ita.py b/tests/tests/test_guest_ita.py index a872b2d..b86f1b1 100644 --- a/tests/tests/test_guest_ita.py +++ b/tests/tests/test_guest_ita.py @@ -52,12 +52,11 @@ def change_qgsd_state(state): def run_trust_authority(): - object = '{"qom-type":"tdx-guest","id":"tdx","quote-generation-socket":{"type": "vsock", "cid":"2","port":"4050"}}' - Qemu.QemuMachineType.Qemu_Machine_Params[Qemu.QemuEfiMachine.OVMF_Q35_TDX][1] = object - quote_str = "" with Qemu.QemuMachine() as qm: - qm.qcmd.add_vsock(3) + machine = qm.qcmd.plugins['machine'] + machine.enable_quote_socket() + qm.run() ssh = Qemu.QemuSSH(qm) diff --git a/tests/tests/test_guest_tdxattest.py b/tests/tests/test_guest_tdxattest.py new file mode 100644 index 0000000..afa33c6 --- /dev/null +++ b/tests/tests/test_guest_tdxattest.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +# +# Copyright 2024 Canonical Ltd. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 3, as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . +# + +import random +import string + +import Qemu + +# This file contains tests for the tdxattest lib in the guest +# The tdxattest library allows application to request a quote from the +# host system +# This request can be done through 2 channels: +# - vsock : the application connects directly to the QGSD service in the host +# - configfs tsm : the application use configsf tsm to ask the guest kernel +# to contact the QGSD service for the quote generation + +def test_guest_tdxattest_tsm(): + """ + TDX attest library + Success when only TSM is available + vsock support is disabled by removing the configuration file and not enabling + the vsock support in QEMU command line + """ + with Qemu.QemuMachine() as qm: + machine = qm.qcmd.plugins['machine'] + machine.enable_quote_socket() + + qm.run() + ssh = Qemu.QemuSSH(qm) + + ssh.check_exec('rm -f /etc/tdx-attest.conf') + stdout, _ = ssh.check_exec('/usr/share/doc/libtdx-attest-dev/examples/test_tdx_attest') + + assert 'Successfully get the TD Quote' in stdout.read().decode() + +def test_guest_tdxattest_tsm_failure(): + """ + TDX attest library + Failure if we force the lib to use TSM but QEMU does not have the + quote generation socket specified. + """ + with Qemu.QemuMachine() as qm: + qm.run() + ssh = Qemu.QemuSSH(qm) + + ssh.check_exec('rm -f /etc/tdx-attest.conf') + + ret, stdout, stderr = ssh.exec_command('/usr/share/doc/libtdx-attest-dev/examples/test_tdx_attest') + assert (ret != 0) and ('Failed to get the quote' in stderr.read().decode()) + +def test_guest_tdxattest_vsock(): + """ + TDX attest library + Success when only vsock is available + ConfigFs TSM is disabled + """ + with Qemu.QemuMachine() as qm: + qm.qcmd.add_vsock(10) + + qm.run() + ssh = Qemu.QemuSSH(qm) + + disable_tsm(ssh) + + stdout, _ = ssh.check_exec('/usr/share/doc/libtdx-attest-dev/examples/test_tdx_attest') + + assert 'Successfully get the TD Quote' in stdout.read().decode() + +def test_guest_tdxattest_vsock_failure(): + """ + TDX attest library + Failure if we force the lib to use vsock and QEMU does not have the + vsock arguments specified + """ + with Qemu.QemuMachine() as qm: + qm.run() + ssh = Qemu.QemuSSH(qm) + + disable_tsm(ssh) + + ret, stdout, stderr = ssh.exec_command('/usr/share/doc/libtdx-attest-dev/examples/test_tdx_attest') + assert (ret != 0) and ('Failed to get the quote' in stderr.read().decode()) + +def test_guest_tdxattest_failure(): + """ + TDX attest library + Fail when vsock and TSM are both disabled + """ + with Qemu.QemuMachine() as qm: + qm.run() + ssh = Qemu.QemuSSH(qm) + + disable_tsm(ssh) + ssh.check_exec('rm -f /etc/tdx-attest.conf') + + ret, stdout, stderr = ssh.exec_command('/usr/share/doc/libtdx-attest-dev/examples/test_tdx_attest') + + assert (ret != 0) and ('Failed to get the quote' in stderr.read().decode()) + +def disable_tsm(ssh): + """ + Disable the configfs tsm + There is no official way to disable the configfs tsm functionality + but we can simulate configfs tsm errors by bind mounting an empty folder + on top of the tsm folder. + """ + tmp_folder_name=''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(4)) + ssh.check_exec(f'mkdir -p /tmp/{tmp_folder_name}') + ssh.check_exec(f'mount --bind /tmp/{tmp_folder_name} /sys/kernel/config/tsm/report/')