Skip to content

Commit

Permalink
Merge pull request #1754 from ogayot/kvm-test-tpm
Browse files Browse the repository at this point in the history
kvm-test: support emulating a TPM 2.0
  • Loading branch information
ogayot authored Aug 3, 2023
2 parents c34e30f + 5244890 commit 03e9a40
Showing 1 changed file with 102 additions and 45 deletions.
147 changes: 102 additions & 45 deletions scripts/kvm-test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
import contextlib
import copy
import crypt
import dataclasses
import os
import pathlib
import shlex
import socket
import subprocess
import sys
import tempfile
from typing import List, Tuple
from typing import List, Optional, Tuple
import yaml


Expand Down Expand Up @@ -192,6 +194,9 @@ def load_config(self):
parser.add_argument('--force-no-autoinstall', default=None,
action='store_false', dest="autoinstall",
help='do not pass autoinstall on the kernel command line')
parser.add_argument('--with-tpm2', action='store_true',
help='''emulate a TPM 2.0 interface (requires swtpm
package)''')


cc_group = parser.add_mutually_exclusive_group()
Expand Down Expand Up @@ -419,6 +424,22 @@ def nets(ctx) -> List[str]:
return nics


@dataclasses.dataclass(frozen=True)
class TPMEmulator:
socket: pathlib.Path
logfile: pathlib.Path
tpmstate: pathlib.Path


def tpm(emulator: Optional[TPMEmulator]) -> List[str]:
if emulator is None:
return []

return ['-chardev', f'socket,id=chrtpm,path={emulator.socket}',
'-tpmdev', 'emulator,id=tpm0,chardev=chrtpm',
'-device', 'tpm-tis,tpmdev=tpm0']


def bios(ctx):
ret = []
# https://help.ubuntu.com/community/UEFI
Expand All @@ -431,15 +452,25 @@ def memory(ctx):
return ['-m', ctx.args.memory or ctx.default_mem]


def kvm_common(ctx):
@contextlib.contextmanager
def kvm_prepare_common(ctx):
'''Spawn needed background processes and return the CLI options for QEMU'''
ret = ['kvm', '-no-reboot']
ret.extend(('-vga', 'virtio'))
ret.extend(memory(ctx))
ret.extend(bios(ctx))
ret.extend(nets(ctx))
if ctx.args.sound:
ret.extend(('-device', 'AC97', '-device', 'usb-ehci'))
return ret

if ctx.args.with_tpm2:
tpm_emulator_context = tpm_emulator()
else:
tpm_emulator_context = contextlib.nullcontext()

with tpm_emulator_context as tpm_emulator_cm:
ret.extend(tpm(tpm_emulator_cm))
yield ret


def get_initrd(mntdir):
Expand All @@ -463,63 +494,89 @@ def install(ctx):
os.mkdir(mntdir)
appends = []

kvm = kvm_common(ctx)
with kvm_prepare_common(ctx) as kvm:

if ctx.args.iso:
iso = ctx.args.iso
elif ctx.args.base:
iso = ctx.baseiso
else:
iso = ctx.iso

kvm.extend(('-cdrom', iso))

if ctx.args.serial:
kvm.append('-nographic')
appends.append('console=ttyS0')

if ctx.args.cloud_config is not None or ctx.args.cloud_config_default:
if ctx.args.cloud_config is not None:
ctx.cloudconfig = ctx.args.cloud_config.read()
kvm.extend(drive(create_seed(ctx.cloudconfig, tempdir), 'raw'))
if ctx.args.autoinstall is None:
# Let's inspect the yaml and check if there is an autoinstall
# section.
autoinstall = "autoinstall" in yaml.safe_load(ctx.cloudconfig)
if ctx.args.iso:
iso = ctx.args.iso
elif ctx.args.base:
iso = ctx.baseiso
else:
autoinstall = ctx.args.autoinstall
iso = ctx.iso

kvm.extend(('-cdrom', iso))

if ctx.args.serial:
kvm.append('-nographic')
appends.append('console=ttyS0')

if ctx.args.cloud_config is not None or ctx.args.cloud_config_default:
if ctx.args.cloud_config is not None:
ctx.cloudconfig = ctx.args.cloud_config.read()
kvm.extend(drive(create_seed(ctx.cloudconfig, tempdir), 'raw'))
if ctx.args.autoinstall is None:
# Let's inspect the yaml and check if there is an autoinstall
# section.
autoinstall = "autoinstall" in yaml.safe_load(ctx.cloudconfig)
else:
autoinstall = ctx.args.autoinstall

if autoinstall:
appends.append('autoinstall')
if autoinstall:
appends.append('autoinstall')


if ctx.args.update:
appends.append('subiquity-channel=' + ctx.args.update)
if ctx.args.update:
appends.append('subiquity-channel=' + ctx.args.update)

kvm.extend(drive(ctx.target))
if not os.path.exists(ctx.target) or ctx.args.overwrite:
run(f'qemu-img create -f qcow2 {ctx.target} {ctx.args.disksize}')
kvm.extend(drive(ctx.target))
if not os.path.exists(ctx.target) or ctx.args.overwrite:
run(f'qemu-img create -f qcow2 {ctx.target} {ctx.args.disksize}')

if len(appends) > 0:
with mounter(iso, mntdir):
# if we're passing kernel args, we need to manually specify
# kernel / initrd
kvm.extend(('-kernel', f'{mntdir}/casper/vmlinuz'))
kvm.extend(('-initrd', get_initrd(mntdir)))
kvm.extend(('-append', ' '.join(appends)))
if len(appends) > 0:
with mounter(iso, mntdir):
# if we're passing kernel args, we need to manually specify
# kernel / initrd
kvm.extend(('-kernel', f'{mntdir}/casper/vmlinuz'))
kvm.extend(('-initrd', get_initrd(mntdir)))
kvm.extend(('-append', ' '.join(appends)))
run(kvm)
else:
run(kvm)
else:
run(kvm)


@contextlib.contextmanager
def tpm_emulator(directory=None):
if directory is None:
directory_context = tempfile.TemporaryDirectory()
else:
directory_context = contextlib.nullcontext(enter_result=directory)

with directory_context as tempdir:
socket = os.path.join(tempdir, 'swtpm-sock')
logfile = os.path.join(tempdir, 'log')
tpmstate = tempdir

ps = subprocess.Popen(['swtpm', 'socket',
'--tpmstate', f'dir={tpmstate}',
'--ctrl', f'type=unixio,path={socket}',
'--tpm2',
'--log', f'file={logfile},level=20'],
)
try:
yield TPMEmulator(socket=pathlib.Path(socket),
logfile=pathlib.Path(logfile),
tpmstate=pathlib.Path(tpmstate))
finally:
ps.communicate()


def boot(ctx):
target = ctx.target
if ctx.args.img:
target = ctx.args.img

kvm = kvm_common(ctx)
kvm.extend(drive(target))
run(kvm)
with kvm_prepare_common(ctx) as kvm:
kvm.extend(drive(target))
run(kvm)


def help():
Expand Down

0 comments on commit 03e9a40

Please sign in to comment.