summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniil Baturin <daniil@vyos.io>2024-03-07 16:37:11 +0100
committerGitHub <noreply@github.com>2024-03-07 16:37:11 +0100
commit089b59560565388d1c21a440418be11618b32af1 (patch)
tree67de6e4e15dbbe7fb2dc1e25706b300a1be97c4f
parent6758b13c7ea096fcc0a13fcf036dcc76b4417624 (diff)
parent63a8f9d3c60e6dde04b0998701a40223d532128a (diff)
downloadvyos-build-089b59560565388d1c21a440418be11618b32af1.tar.gz
vyos-build-089b59560565388d1c21a440418be11618b32af1.zip
Merge pull request #297 from sarthurdev/tpm_luks
config: T4919: Add emulated TPM encryption test
-rw-r--r--Jenkinsfile8
-rw-r--r--Makefile5
-rw-r--r--docker/Dockerfile3
-rwxr-xr-xscripts/check-qemu-install177
4 files changed, 190 insertions, 3 deletions
diff --git a/Jenkinsfile b/Jenkinsfile
index 5e30c423..85399230 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -119,6 +119,14 @@ pipeline {
sh "sudo make testraid"
}
}
+ stage('Smoketests for TPM config encryption') {
+ when {
+ expression { fileExists 'build/live-image-amd64.hybrid.iso' }
+ }
+ steps {
+ sh "sudo make testtpm"
+ }
+ }
stage('Smoketests') {
when {
expression { return params.TEST_SMOKETESTS }
diff --git a/Makefile b/Makefile
index 393ef3b7..18fb4ef5 100644
--- a/Makefile
+++ b/Makefile
@@ -64,6 +64,11 @@ qemu-live: checkiso
oci: checkiso
scripts/iso-to-oci build/live-image-amd64.hybrid.iso
+.PHONY: testtpm
+.ONESHELL:
+testtpm: checkiso
+ scripts/check-qemu-install --debug --tpmtest build/live-image-amd64.hybrid.iso
+
.PHONY: clean
.ONESHELL:
clean:
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 8bc0c5c9..32cedb70 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -105,6 +105,9 @@ RUN apt-get update && apt-get install -y \
debootstrap \
live-build
+# Packages for TPM test
+RUN apt-get update && apt-get install -y swtpm
+
# Syslinux and Grub2 is only supported on x86 and x64 systems
RUN if dpkg-architecture -ii386 || dpkg-architecture -iamd64; then \
apt-get update && apt-get install -y \
diff --git a/scripts/check-qemu-install b/scripts/check-qemu-install
index e8a7cc6f..10c7fd48 100755
--- a/scripts/check-qemu-install
+++ b/scripts/check-qemu-install
@@ -50,6 +50,7 @@ from datetime import datetime
EXCEPTION = 0
now = datetime.now()
+tpm_folder = '/tmp/vyos_tpm_test'
parser = argparse.ArgumentParser(description='Install and start a test VyOS vm.')
parser.add_argument('iso', help='ISO file to install')
@@ -73,6 +74,8 @@ parser.add_argument('--no-interfaces', help='Execute testsuite without interface
action='store_true', default=False)
parser.add_argument('--configtest', help='Execute load/commit config tests',
action='store_true', default=False)
+parser.add_argument('--tpmtest', help='Execute TPM encrypted config tests',
+ action='store_true', default=False)
parser.add_argument('--qemu-cmd', help='Only generate QEMU launch command',
action='store_true', default=False)
@@ -113,7 +116,7 @@ def get_half_cpus():
cpu /= 2
return int(cpu)
-def get_qemu_cmd(name, enable_kvm, enable_uefi, disk_img, raid=None, iso_img=None):
+def get_qemu_cmd(name, enable_kvm, enable_uefi, disk_img, raid=None, iso_img=None, tpm=False):
kvm = "-enable-kvm"
cpu = "-cpu host"
if not enable_kvm:
@@ -173,6 +176,11 @@ def get_qemu_cmd(name, enable_kvm, enable_uefi, disk_img, raid=None, iso_img=Non
cmd += f' -drive format=raw,file={raid},if=none,media=disk,id=drive-hd2,readonly=off' \
f' -device scsi-hd,bus=scsi0.0,drive=drive-hd2,id=hd2,bootindex={bootindex}'
+ if tpm:
+ cmd += f' -chardev socket,id=chrtpm,path={tpm_folder}/swtpm-sock' \
+ ' -tpmdev emulator,id=tpm0,chardev=chrtpm' \
+ ' -device tpm-tis,tpmdev=tpm0'
+
return cmd
def shutdownVM(c, log, message=''):
@@ -270,6 +278,22 @@ if args.raid:
# must be called after the raid disk as args.disk name is altered in the RAID path
gen_disk(args.disk)
+# Create software emulated TPM
+def start_swtpm():
+ if not os.path.exists(tpm_folder):
+ os.mkdir(tpm_folder)
+
+ def swtpm_thread():
+ c = subprocess.check_output([
+ 'swtpm', 'socket', '--tpmstate', f'dir={tpm_folder}',
+ '--ctrl', f'type=unixio,path={tpm_folder}/swtpm-sock', '--tpm2', '--log', 'level=1'
+ ])
+
+ from multiprocessing import Process
+ tpm_process = Process(target=swtpm_thread)
+ tpm_process.start()
+ return tpm_process
+
if args.qemu_cmd:
tmp = get_qemu_cmd('TESTVM', kvm, args.uefi, args.disk, diskname_raid, args.iso)
os.system(tmp)
@@ -338,11 +362,14 @@ try:
shutdownVM(c, log, 'Shutting down installation system')
c.close()
+ if args.tpmtest:
+ tpm_process = start_swtpm()
+
#################################################
# Booting installed system
#################################################
log.info('Booting installed system')
- cmd = get_qemu_cmd('TESTVM', kvm, args.uefi, args.disk, diskname_raid)
+ cmd = get_qemu_cmd('TESTVM', kvm, args.uefi, args.disk, diskname_raid, tpm=args.tpmtest)
log.debug(f'Executing command: {cmd}')
c = pexpect.spawn(cmd, logfile=stl)
@@ -396,7 +423,147 @@ try:
#################################################
# Executing test-suite
#################################################
- if args.raid:
+ if args.tpmtest:
+ def verify_mount():
+ # Verify encrypted and mounted
+ c.sendline('mount | grep vyos_config')
+ c.expect('/dev/mapper/vyos_config on /config.*')
+ c.expect(op_mode_prompt)
+
+ def verify_config():
+ # Verify encrypted config is loaded
+ c.sendline('show config commands | cat')
+ c.expect('set system option performance \'latency\'')
+ c.expect('set system option reboot-on-panic')
+ c.expect(op_mode_prompt)
+
+ log.info('Running TPM encrypted config tests')
+
+ tpm_timeout = 600 # Give it 10 mins to encrypt
+
+ # Verify TPM is loaded
+ c.sendline('ls /dev/tpm0')
+ c.expect('/dev/tpm0')
+ c.expect(op_mode_prompt)
+
+ # Create recovery key
+ import string
+ from random import choices
+ test_recovery_key = ''.join(choices(string.ascii_uppercase + string.digits, k=32))
+
+ log.info('Encrypting config to TPM')
+ c.sendline('encryption enable')
+ c.expect('Automatically generate a recovery key\?.*')
+ c.sendline('n')
+ c.expect('Enter recovery key: ')
+ c.sendline(test_recovery_key)
+ c.expect('Enter size of encrypted config partition.*', timeout=30)
+ c.sendline('32')
+ c.expect('Encrypted config volume has been enabled', timeout=tpm_timeout)
+ c.expect('Backup the recovery key in a safe place!')
+ c.expect(f'Recovery key: {test_recovery_key}')
+ c.expect(op_mode_prompt)
+
+ verify_mount()
+
+ # Add some non-default config nodes this encrypted config
+ log.info('Adding nodes for encrypted config test')
+ c.sendline('configure')
+ c.expect(cfg_mode_prompt)
+ c.sendline('set system option performance latency')
+ c.expect(cfg_mode_prompt)
+ c.sendline('set system option reboot-on-panic')
+ c.expect(cfg_mode_prompt)
+ c.sendline('commit')
+ c.expect(cfg_mode_prompt)
+ c.sendline('save')
+ c.expect(cfg_mode_prompt)
+ c.sendline('exit')
+ c.expect(op_mode_prompt)
+
+ # Shutdown VM
+ shutdownVM(c, log, 'Shutdown VM after TPM encryption')
+
+ # Shutdown kills swtpm
+ tpm_process.join()
+ tpm_process.close()
+
+ # Start emulator again
+ tpm_process = start_swtpm()
+
+ # Booting back into VM
+ log.info('Booting TPM-backed system')
+ cmd = get_qemu_cmd('TESTVM', kvm, args.uefi, args.disk, diskname_raid, tpm=args.tpmtest)
+ log.debug(f'Executing command: {cmd}')
+ c = pexpect.spawn(cmd, logfile=stl)
+
+ try:
+ c.expect('The highlighted entry will be executed automatically in', timeout=10)
+ c.sendline('')
+ except pexpect.TIMEOUT:
+ log.warning('Did not find GRUB countdown window, ignoring')
+
+ # Check for vyos-router message
+ c.expect('.*Mounted encrypted config volume', timeout=120)
+
+ loginVM(c, log)
+
+ verify_mount()
+ verify_config()
+
+ # Shutdown VM
+ shutdownVM(c, log, 'Shutdown VM for TPM fail test')
+
+ # Clear swtpm
+ from glob import glob
+ for f in glob(f'{tpm_folder}/*'):
+ os.remove(f)
+
+ # Shutdown kills swtpm
+ tpm_process.join()
+ tpm_process.close()
+
+ # Start emulator again
+ tpm_process = start_swtpm()
+
+ # Booting back into VM
+ log.info('Booting system with cleared TPM')
+ cmd = get_qemu_cmd('TESTVM', kvm, args.uefi, args.disk, diskname_raid, tpm=args.tpmtest)
+ log.debug(f'Executing command: {cmd}')
+ c = pexpect.spawn(cmd, logfile=stl)
+
+ try:
+ c.expect('The highlighted entry will be executed automatically in', timeout=10)
+ c.sendline('')
+ except pexpect.TIMEOUT:
+ log.warning('Did not find GRUB countdown window, ignoring')
+
+ c.expect('.*Encrypted config volume has not been mounted', timeout=120)
+
+ loginVM(c, log)
+
+ # Test loading config with recovery key
+ c.sendline('encryption load')
+ c.expect('Enter recovery key: ')
+ c.sendline(test_recovery_key)
+ c.expect('Encrypted config volume has been mounted', timeout=120)
+ c.expect(op_mode_prompt)
+
+ verify_mount()
+
+ log.info('Loading encrypted config.boot')
+ c.sendline('configure')
+ c.expect(cfg_mode_prompt)
+ c.sendline('load /config/config.boot')
+ c.expect(cfg_mode_prompt)
+ c.sendline('commit')
+ c.expect(cfg_mode_prompt)
+ c.sendline('exit')
+ c.expect(op_mode_prompt)
+
+ verify_config()
+
+ elif args.raid:
# Verify RAID subsystem - by deleting a disk and re-create the array
# from scratch
c.sendline('cat /proc/mdstat')
@@ -571,6 +738,10 @@ except Exception:
#################################################
log.info("Cleaning up")
+if tpm_process:
+ tpm_process.terminate()
+ tpm_process.join()
+
if not args.keep:
log.info(f'Removing disk file: {args.disk}')
try: