diff options
author | Daniil Baturin <daniil@vyos.io> | 2024-03-07 16:37:11 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-03-07 16:37:11 +0100 |
commit | 089b59560565388d1c21a440418be11618b32af1 (patch) | |
tree | 67de6e4e15dbbe7fb2dc1e25706b300a1be97c4f | |
parent | 6758b13c7ea096fcc0a13fcf036dcc76b4417624 (diff) | |
parent | 63a8f9d3c60e6dde04b0998701a40223d532128a (diff) | |
download | vyos-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-- | Jenkinsfile | 8 | ||||
-rw-r--r-- | Makefile | 5 | ||||
-rw-r--r-- | docker/Dockerfile | 3 | ||||
-rwxr-xr-x | scripts/check-qemu-install | 177 |
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 } @@ -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: |