diff options
author | Jonathan Cave <jonathan.cave@canonical.com> | 2018-06-25 17:49:56 +0100 |
---|---|---|
committer | Jonathan Cave <jonathan.cave@canonical.com> | 2018-06-28 10:32:59 +0100 |
commit | 9c6c81064ff028d32d3bf1a6f4fefe83e78567aa (patch) | |
tree | abde424004ae525658589b133a1cd05fd1245fb2 | |
parent | 9eeddb666d6eed2bf69bdd9e03ddd9a0b8f42d7f (diff) |
Add secure boot test for Ubuntu Core
-rwxr-xr-x | bin/boot_mode_test_snappy.py | 127 | ||||
-rw-r--r-- | units/miscellanea/jobs.pxu | 16 |
2 files changed, 143 insertions, 0 deletions
diff --git a/bin/boot_mode_test_snappy.py b/bin/boot_mode_test_snappy.py new file mode 100755 index 0000000..2795a6f --- /dev/null +++ b/bin/boot_mode_test_snappy.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +# Copyright 2018 Canonical Ltd. +# Written by: +# Jonathan Cave <jonathan.cave@canonical.com> + +import io +import os +import re +import sys +import subprocess as sp + +import yaml + + +def fitdumpimage(filename): + cmd = 'dumpimage -l {}'.format(filename) + out = sp.check_output(cmd, shell=True).decode(sys.stdout.encoding) + buf = io.StringIO(out) + + # first line should identify FIT file + desc = buf.readline() + fit_re = re.compile(r'^FIT description') + if not fit_re.search(desc): + raise SystemExit('ERROR: expected FIT image description') + + # second line contains some metadata, skip it + buf.readline() + + # from then on should get blocks of text describing the objects that were + # combined in to the FIT image e.g. kernel, ramdisk, device tree + image_re = re.compile(r'^\ Image') + config_re = re.compile(r'^\ Default Configuration|^\ Configuration') + objects = {} + name = '' + while True: + line = buf.readline() + # stop at end + if line == '': + break + # interested in storing image information + if image_re.search(line): + name = line[line.find("(")+1:line.find(")")] + objects[name] = {} + continue + # not interested in configurations + if config_re.search(line): + name = '' + continue + # while in an image section store the info + if name != '': + entries = [s.strip() for s in line.split(':', 1)] + objects[name][entries[0]] = entries[1] + return objects + + +def main(): + if len(sys.argv) != 3: + raise SystemExit('ERROR: please supply gadget & kernel name') + gadget = sys.argv[1] + kernel = sys.argv[2] + + gadget_yaml = os.path.join('/snap', gadget, 'current/meta/gadget.yaml') + + if not os.path.exists(gadget_yaml): + raise SystemExit( + 'ERROR: failed to find gadget.yaml at {}'.format(gadget_yaml)) + + with open(gadget_yaml) as f: + data = yaml.load(f) + for k in data['volumes'].keys(): + bootloader = data['volumes'][k]['bootloader'] + if not bootloader: + raise SystemExit('ERROR: could not find name of bootloader') + + if bootloader not in ('u-boot', 'grub'): + raise SystemExit( + 'ERROR: Unexpected bootloader name {}'.format(bootloader)) + print('Bootloader is {}\n'.format(bootloader)) + + if bootloader == 'u-boot': + print('Parsing FIT image information...\n') + + kernel_rev = os.path.basename( + os.path.realpath('/snap/{}/current'.format(kernel))) + boot_kernel = '/boot/uboot/{}_{}.snap/kernel.img'.format( + kernel, kernel_rev) + boot_objects = fitdumpimage(boot_kernel) + + for obj, attrs in boot_objects.items(): + print('Checking object {}'.format(obj)) + if 'Sign value' not in attrs: + raise SystemExit('ERROR: no sign value found for object') + else: + print('Found "Sign value"') + if len(attrs['Sign value']) != 512: + raise SystemExit('ERROR: unexpected sign value size') + if all(s in attrs['Sign algo'] for s in ['sha256', 'rsa2048']): + print('Found expected signing algorithms') + else: + raise SystemExit( + 'ERROR: unexpected signing algorithms {}'.format( + attrs['Sign algo'])) + print() + + # check that all parts of the fit image have + snap_kernel = '/snap/{}/current/kernel.img'.format(kernel) + snap_objects = fitdumpimage(snap_kernel) + if snap_objects != boot_objects: + raise SystemExit( + 'ERROR: boot kernel and current snap kernel do not match') + print('Kernel images in current snap and u-boot match\n') + + print('Secure Boot appears to be enabled on this system') + + if bootloader == 'grub': + cmd = 'mokutil --sb-state' + print('+', cmd, flush=True) + out = sp.check_output(cmd, shell=True).decode(sys.stdout.encoding) + print(out, flush=True) + if out != 'SecureBoot enabled\n': + raise SystemExit('ERROR: mokutil reports Secure Boot not in use') + + print('Secure Boot appears to be enabled on this system') + + +if __name__ == '__main__': + main() diff --git a/units/miscellanea/jobs.pxu b/units/miscellanea/jobs.pxu index 4a9980b..a1d4f5a 100644 --- a/units/miscellanea/jobs.pxu +++ b/units/miscellanea/jobs.pxu @@ -148,6 +148,22 @@ command: boot_mode_test secureboot plugin: shell category_id: com.canonical.plainbox::miscellanea estimated_duration: 0.5 +unit: template +template-resource: model_assertion +template-unit: job +requires: + executable.name == 'dumpimage' + executable.name == 'mokutil' +id: miscellanea/secure_boot_mode_{gadget} +_summary: Test that {gadget} Ubuntu Core system booted with Secure Boot active +_description: + Test to verify that the system booted with Secure Boot active. +command: + boot_mode_test_snappy.py {gadget} {kernel} + +plugin: shell +category_id: com.canonical.plainbox::miscellanea +estimated_duration: 0.5 user: root id: miscellanea/efi_pxeboot requires: |