Skip to content

Commit

Permalink
Merge pull request #160 from xcp-ng/vtpm-basic-tests
Browse files Browse the repository at this point in the history
vTPM basic tests
  • Loading branch information
stormi authored Sep 14, 2023
2 parents 930885e + 43d3391 commit 4e21ee0
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 6 deletions.
14 changes: 14 additions & 0 deletions jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,20 @@
"params": {},
"paths": ["tests/xen"],
},
"vtpm": {
"description": "Testing vTPM functionalities",
"requirements": [
"A XCP-ng host >= 8.3 and a Unix RPM-based or DEB-based UEFI VM with "
"tpm2-tools installable from default repositories.",
],
"nb_pools": 1,
"params": {
# The test also works on CentOS, for example, but in this job definition
# we settle for a debian VM
"--vm": "single/debian_uefi_vm",
},
"paths": ["tests/vtpm"],
},
"flaky": {
"description": "tests that usually pass, but sometimes fail unexpectedly",
"requirements": [
Expand Down
6 changes: 4 additions & 2 deletions lib/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,17 @@ def scp(self, src, dest, check=True, suppress_fingerprint_warnings=True, local_d
suppress_fingerprint_warnings=suppress_fingerprint_warnings, local_dest=local_dest
)

def xe(self, action, args={}, check=True, simple_output=True, minimal=False):
def xe(self, action, args={}, check=True, simple_output=True, minimal=False, force=False):
maybe_param_minimal = ['--minimal'] if minimal else []
maybe_param_force = ['--force'] if force else []

def stringify(key, value):
if isinstance(value, bool):
return "{}={}".format(key, to_xapi_bool(value))
return "{}={}".format(key, shlex.quote(value))

command = ['xe', action] + maybe_param_minimal + [stringify(key, value) for key, value in args.items()]
command = ['xe', action] + maybe_param_minimal + maybe_param_force + \
[stringify(key, value) for key, value in args.items()]
result = self.ssh(
command,
check=check,
Expand Down
31 changes: 27 additions & 4 deletions lib/vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,21 @@ def start(self, on=None):
args['on'] = on
return self.host.xe('vm-start', args)

def shutdown(self, force=False, verify=False):
def shutdown(self, force=False, verify=False, force_if_fails=False):
assert not (force and force_if_fails), "force and force_if_fails cannot be both True"
logging.info("Shutdown VM" + (" (force)" if force else ""))
ret = self.host.xe('vm-shutdown', {'uuid': self.uuid, 'force': force})
if verify:
wait_for(self.is_halted, "Wait for VM halted")

try:
ret = self.host.xe('vm-shutdown', {'uuid': self.uuid, 'force': force})
if verify:
wait_for(self.is_halted, "Wait for VM halted")
except Exception as e:
if force_if_fails:
logging.warning("Shutdown failed: %s" % e)
ret = self.shutdown(force=True, verify=verify)
else:
raise

return ret

def reboot(self, force=False, verify=False):
Expand Down Expand Up @@ -401,6 +411,19 @@ def get_all_efi_bins(self):

return binaries

def get_vtpm_uuid(self):
return self.host.xe('vtpm-list', {'vm': self.uuid}, minimal=True)

def create_vtpm(self):
logging.info("Creating vTPM for vm %s" % self.uuid)
return self.host.xe('vtpm-create', {'vm-uuid': self.uuid})

def destroy_vtpm(self):
vtpm_uuid = self.get_vtpm_uuid()
assert vtpm_uuid, "A vTPM must be present"
logging.info("Destroying vTPM %s" % vtpm_uuid)
return self.host.xe('vtpm-destroy', {'uuid': vtpm_uuid}, force=True)

def clone(self):
name = self.name() + '_clone_for_tests'
logging.info("Clone VM")
Expand Down
Empty file added tests/vtpm/__init__.py
Empty file.
62 changes: 62 additions & 0 deletions tests/vtpm/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import pytest
import logging

from lib.common import PackageManagerEnum

@pytest.fixture(scope='module')
def halted_uefi_unix_vm(uefi_vm, unix_vm):
assert uefi_vm.is_halted(), "The VM must be halted for these tests"
yield uefi_vm

@pytest.fixture(scope='module')
def snapshotted_halted_uefi_unix_vm(halted_uefi_unix_vm):
vm = halted_uefi_unix_vm
snapshot = vm.snapshot()

yield vm

try:
snapshot.revert()
finally:
snapshot.destroy()

@pytest.fixture(scope='module')
def unix_vm_with_vtpm(snapshotted_halted_uefi_unix_vm):
vm = snapshotted_halted_uefi_unix_vm

vm.create_vtpm()
yield vm
# Tear down
vm.destroy_vtpm()

@pytest.fixture(scope='module')
def started_unix_vm_with_vtpm(unix_vm_with_vtpm):
vm = unix_vm_with_vtpm

vm.start()
try:
vm.wait_for_os_booted()
except Exception:
vm.shutdown(force=True, verify=True)
raise

yield vm
# Tear down
vm.shutdown(verify=True, force_if_fails=True)

@pytest.fixture(scope='module')
def unix_vm_with_tpm2_tools(started_unix_vm_with_vtpm):
vm = started_unix_vm_with_vtpm

pkg_mgr = vm.detect_package_manager()
if pkg_mgr == PackageManagerEnum.APT_GET:
pkg_mgr = 'apt-get'
elif pkg_mgr == PackageManagerEnum.RPM:
pkg_mgr = 'yum'
else:
pytest.fail("Unsupported package manager for this test. Cannot install tpm2-tools")

logging.info("Installing tpm2-tools package using '%s'" % pkg_mgr)
vm.ssh([pkg_mgr, 'install', '-y', 'tpm2-tools'])

yield vm
82 changes: 82 additions & 0 deletions tests/vtpm/test_vtpm_basic_operations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import logging
import pytest
import lib.commands as commands

# These tests are basic tests for vTPM devices.
# - Create / Destroy a vTPM device on a VM
# - Do some basic encryption tests using tpm2-tools package
#
# Requirements:
# - an XCP-ng host >= 8.3
# - a RPM-based or DEB-based UEFI VM with `tpm2-tools` installable
# from default repositories

vtpm_signing_test_script = """#!/bin/env bash
set -ex
tpm2_selftest --fulltest
tpm2_getrandom 32 > /dev/null
TMPDIR=`mktemp -d`
# Create an Endorsement primary key
tpm2_createprimary --hierarchy e --key-context ${TMPDIR}/primary.ctx > /dev/null
# Create key objects
tpm2_create --key-algorithm rsa --public ${TMPDIR}/rsa.pub --private ${TMPDIR}/rsa.priv --parent-context \
${TMPDIR}/primary.ctx > /dev/null
# Load keys into the TPM
tpm2_load --parent-context ${TMPDIR}/primary.ctx --public ${TMPDIR}/rsa.pub --private ${TMPDIR}/rsa.priv \
--key-context ${TMPDIR}/rsa.ctx > /dev/null
# Delete loaded key files
rm -f ${TMPDIR}/rsa.pub ${TMPDIR}/rsa.priv
# Message to sign
echo 'XCP-ng Rulez' > ${TMPDIR}/message.dat
# Sign the message
tpm2_sign --key-context ${TMPDIR}/rsa.ctx --hash-algorithm sha256 --signature ${TMPDIR}/sig.rssa \
${TMPDIR}/message.dat > /dev/null
# Verify signature
tpm2_verifysignature --key-context ${TMPDIR}/rsa.ctx --hash-algorithm sha256 --message ${TMPDIR}/message.dat \
--signature ${TMPDIR}/sig.rssa > /dev/null
# Verify with another message
echo "XCP-ng Still Rulez" > ${TMPDIR}/message.dat
# Verify signature !!!!! THIS MUST FAIL !!!!!
if tpm2_verifysignature --key-context ${TMPDIR}/rsa.ctx --hash-algorithm sha256 --message ${TMPDIR}/message.dat \
--signature ${TMPDIR}/sig.rssa > /dev/null 2>&1; then
echo "Should not succeed"
exit 1
fi
rm -rf ${TMPDIR}
"""

@pytest.mark.small_vm
@pytest.mark.usefixtures("host_at_least_8_3")
def test_create_and_destroy_vtpm(halted_uefi_unix_vm):
vm = halted_uefi_unix_vm
assert not vm.get_vtpm_uuid(), "there must be no vTPM before we create it"
vtpm_uuid = vm.create_vtpm()
assert vtpm_uuid
assert vm.get_vtpm_uuid(), "a vTPM must be present after creation"
logging.info("vTPM created with uuid: %s" % vtpm_uuid)
vm.destroy_vtpm()
assert not vm.get_vtpm_uuid(), "there must be no vTPM after we deleted it"

@pytest.mark.small_vm
@pytest.mark.usefixtures("host_at_least_8_3")
def test_vtpm(unix_vm_with_tpm2_tools):
global vtpm_signing_test_script
vm = unix_vm_with_tpm2_tools

# Basic TPM2 tests with tpm2-tools
logging.info("Running TPM2 test script on the VM")
vm.execute_script(vtpm_signing_test_script)
2 changes: 2 additions & 0 deletions vm_data.py-dist
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ VMS = {
"small_vm_efitools": "",
# "small" Windows VM (UEFI)
"small_vm_windows": "",
# Debian VM (UEFI, no GUI)
"debian_uefi_vm": "",
},
"multi": {
# all VMs we want to run "multi_vms" tests on
Expand Down

0 comments on commit 4e21ee0

Please sign in to comment.