diff --git a/build-tests/x86/tumbleweed/test-image-nitro-enclave/appliance.kiwi b/build-tests/x86/tumbleweed/test-image-nitro-enclave/appliance.kiwi new file mode 100644 index 0000000000..73640eb4aa --- /dev/null +++ b/build-tests/x86/tumbleweed/test-image-nitro-enclave/appliance.kiwi @@ -0,0 +1,60 @@ + + + + + Marcus Schäfer + marcus.schaefer@suse.com + AWS Nitro Enclave test build + + + + + + + 1.1.1 + zypper + true + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build-tests/x86/tumbleweed/test-image-nitro-enclave/config.sh b/build-tests/x86/tumbleweed/test-image-nitro-enclave/config.sh new file mode 100644 index 0000000000..60fda26719 --- /dev/null +++ b/build-tests/x86/tumbleweed/test-image-nitro-enclave/config.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +set -ex + +declare kiwi_iname=${kiwi_iname} + +echo "Configure image: [${kiwi_iname}]..." + +#====================================== +# Setup services +#-------------------------------------- +for service in nitro-enclave-alive sshd;do + systemctl enable "${service}" +done + +#====================================== +# Allow ssh root login +#-------------------------------------- +echo "PermitRootLogin yes" > /etc/ssh/sshd_config.d/root.conf + +#====================================== +# load virtio_mmio +#-------------------------------------- +echo virtio_mmio > /etc/modules-load.d/virtio-mmio.conf diff --git a/doc/source/building_images.rst b/doc/source/building_images.rst index d8451e208e..22324dbc9b 100644 --- a/doc/source/building_images.rst +++ b/doc/source/building_images.rst @@ -17,3 +17,4 @@ Building Images for Supported Types building_images/build_container_image building_images/build_wsl_container building_images/build_kis + building_images/build_enclave diff --git a/doc/source/building_images/build_enclave.rst b/doc/source/building_images/build_enclave.rst new file mode 100644 index 0000000000..03eabec9d8 --- /dev/null +++ b/doc/source/building_images/build_enclave.rst @@ -0,0 +1,103 @@ +.. _eif: + +Build an AWS Nitro Enclave +============================== + +.. sidebar:: Abstract + + This page explains how to build AWS Nitro Enclaves. It covers the following topics: + + * how to build an AWS Nitro Enclave + * how to test the enclave via QEMU + +AWS Nitro Enclaves enables customers to create isolated compute environments +to further protect and securely process highly sensitive data such as personally +identifiable information (PII), healthcare, financial, and intellectual property +data within their Amazon EC2 instances. Nitro Enclaves uses the same Nitro +Hypervisor technology that provides CPU and memory isolation for EC2 instances. +For further details please visit https://aws.amazon.com/ec2/nitro/nitro-enclaves + +To add an enclave build to your appliance, create a `type` element with +`image` set to `enclave` in the :file:`config.xml` file as shown below: + +.. code:: xml + + + + + + + + + + + + + + + + + + +The following attributes of the `type` element are relevant: + +- `enclave_format`: Specifies the enclave target + + As of today only the `aws-nitro` enclave target is supported + + +- `kernelcmdline`: Specifies the kernel commandline suitable for the enclave + + An enclave is a system that runs completely in RAM loaded from + an enclave binary format which includes the kernel, initrd and + the kernel commandline suitable for the target system. + +With the appropriate settings specified in :file:`config.xml`, you can build an +image using {kiwi}: + +.. code:: bash + + $ sudo kiwi-ng system build \ + --description kiwi/build-tests/{exc_description_enclave} \ + --set-repo {exc_repo_tumbleweed} \ + --target-dir /tmp/myimage + +The resulting image is saved in :file:`/tmp/myimage`, and the image can +be tested with QEMU: + +.. code:: bash + + $ sudo qemu-system-x86_64 \ + -M nitro-enclave,vsock=c \ + -m 4G \ + -nographic \ + -chardev socket,id=c,path=/tmp/vhost4.socket \ + -kernel {exc_image_base_name_enclave}.eif + +The image is now complete and ready to use. Access to the system is +possible via ssh through a vsock connection into the guest. To establish +a vsock connection it's required to forward the connection through the +guest AF_VSOCK socket. This can be done via a ProxyCommand setup of the +host ssh as follows: + +.. code:: bash + + $ vi ~/bin/vsock-ssh.sh + + #!/bin/bash + CID=$(echo "$1" | cut -d . -f 1) + socat - VSOCK-CONNECT:$CID:22 + +.. code:: bash + + $ vi ~/.ssh/config + + host *.vsock + ProxyCommand ~/bin/vsock-ssh.sh %h + +After the ssh proxy setup login to the enclave with a custom vsock port +as follows: + +.. code:: bash + + $ ssh root@21.vsock diff --git a/doc/source/conf.py b/doc/source/conf.py index 50ab4e5dfc..65ed220ee4 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -70,6 +70,7 @@ def setup(app): '{exc_image_base_name_disk_simple}': 'kiwi-test-image-disk-simple', '{exc_image_base_name_live}': 'kiwi-test-image-live', '{exc_image_base_name_docker}': 'kiwi-test-image-docker', + '{exc_image_base_name_enclave}': 'kiwi-test-image-nitro-enclave', '{exc_netboot}': 'netboot/suse-tumbleweed', '{exc_description_pxe}': 'x86/tumbleweed/test-image-pxe', '{exc_description_vagrant}': 'x86/leap/test-image-vagrant', @@ -78,6 +79,7 @@ def setup(app): '{exc_description_live}': 'x86/leap/test-image-live', '{exc_description_wsl}': 'x86/tumbleweed/test-image-wsl', '{exc_description_docker}': 'x86/leap/test-image-docker', + '{exc_description_enclave}': 'x86/tumbleweed/test-image-nitro-enclave', '{exc_os_version}': '15.5', '{exc_image_version}': '1.15.3', '{exc_repo_leap}': 'obs://openSUSE:Leap:15.5/standard', diff --git a/doc/source/image_types_and_results.rst b/doc/source/image_types_and_results.rst index 510948c0e6..e19f39549e 100644 --- a/doc/source/image_types_and_results.rst +++ b/doc/source/image_types_and_results.rst @@ -48,6 +48,11 @@ KIS Root File System Image Many different deployment strategies are possible. For further details refer to: :ref:`kis` +AWS Nitro Enclave + An initrd based image using the `eif` binary format. The image is + expected to be used in the AWS Nitro Enclave system or for testing + in QEMU. For further details refer to: :ref:`eif` + Image Results ------------- diff --git a/kiwi/builder/__init__.py b/kiwi/builder/__init__.py index 53128a09fa..4e6d732bb3 100644 --- a/kiwi/builder/__init__.py +++ b/kiwi/builder/__init__.py @@ -49,6 +49,8 @@ def new( name_token = ('live', 'LiveImageBuilder') elif image_type in Defaults.get_kis_image_types(): name_token = ('kis', 'KisBuilder') + elif image_type in Defaults.get_enclaves_image_types(): + name_token = ('enclave', 'EnclaveBuilder') elif image_type in Defaults.get_archive_image_types(): name_token = ('archive', 'ArchiveBuilder') elif image_type in Defaults.get_container_image_types(): diff --git a/kiwi/builder/enclave.py b/kiwi/builder/enclave.py new file mode 100644 index 0000000000..3203bcbf14 --- /dev/null +++ b/kiwi/builder/enclave.py @@ -0,0 +1,195 @@ +# Copyright (c) 2024 SUSE Software Solutions Germany GmbH. All rights reserved. +# +# This file is part of kiwi. +# +# kiwi is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kiwi is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kiwi. If not, see +# +import os +import logging +from typing import Dict + +# project +from kiwi.defaults import Defaults +from kiwi.boot.image import BootImage +from kiwi.system.setup import SystemSetup +from kiwi.system.kernel import Kernel +from kiwi.system.result import Result +from kiwi.runtime_config import RuntimeConfig +from kiwi.xml_state import XMLState +from kiwi.command import Command + +from kiwi.exceptions import ( + KiwiEnclaveFormatError, + KiwiEnclaveBootImageError +) + +log = logging.getLogger('kiwi') + + +class EnclaveBuilder: + """ + **Enclave Builder** + + Enclaves defines initrd-only image types. + + :param object xml_state: instance of :class:`XMLState` + :param str target_dir: target directory path name + :param str root_dir: system image root directory + :param dict custom_args: Custom processing arguments defined as hash keys: + * signing_keys: list of package signing keys + * xz_options: string of XZ compression parameters + """ + def __init__( + self, xml_state: XMLState, target_dir: str, + root_dir: str, custom_args: Dict = None + ): + self.target_dir = target_dir + self.custom_cmdline = xml_state.build_type.get_kernelcmdline() + self.format = xml_state.build_type.get_enclave_format() + + self.system_setup = SystemSetup( + xml_state=xml_state, root_dir=root_dir + ) + xml_state.build_type.set_initrd_system('kiwi') + xml_state.build_type.set_boot(f'{root_dir}/image') + + self.boot_signing_keys = custom_args['signing_keys'] if custom_args \ + and 'signing_keys' in custom_args else None + + self.xz_options = custom_args['xz_options'] if custom_args \ + and 'xz_options' in custom_args else None + + self.boot_image_task = BootImage.new( + xml_state, target_dir, root_dir, + signing_keys=self.boot_signing_keys + ) + # Force BootImageKiwi instance to use existing root_dir + self.boot_image_task.boot_root_directory = root_dir + + self.bundle_format = xml_state.get_build_type_bundle_format() + self.image_name = ''.join( + [ + target_dir, '/', + xml_state.xml_data.get_name(), + '.' + Defaults.get_platform_name(), + '-' + xml_state.get_image_version() + ] + ) + self.image: str = '' + self.initrd: str = '' + self.kernel_filename: str = '' + self.enclave: str = '' + self.result = Result(xml_state) + self.runtime_config = RuntimeConfig() + + def create(self) -> Result: + """ + Build an eif image using the eif-cli + + Image types which triggers this builder are: + + * image="enclave" + + :return: result + + :rtype: instance of :class:`Result` + """ + if not self.format: + raise KiwiEnclaveFormatError( + 'No enclave_format= specified in build type' + ) + + # Create initrd + self.boot_image_task.create_initrd() + + # extract kernel from boot system + kernel = Kernel(self.boot_image_task.boot_root_directory) + kernel_data = kernel.get_kernel() + if kernel_data: + self.kernel_filename = ''.join( + [ + os.path.basename(self.image_name), '-', + kernel_data.version, '.kernel' + ] + ) + kernel.copy_kernel( + self.target_dir, self.kernel_filename + ) + else: + raise KiwiEnclaveBootImageError( + 'No kernel in boot image tree %s found' % + self.boot_image_task.boot_root_directory + ) + + self.initrd = os.path.basename(self.boot_image_task.initrd_filename) + + if self.format == 'aws-nitro': + self.enclave = self.image_name + ".eif" + Command.run( + [ + 'eif_build', + '--kernel', '/'.join([self.target_dir, self.kernel_filename]), + '--ramdisk', '/'.join([self.target_dir, self.initrd]), + '--cmdline', self.custom_cmdline, + '--output', self.enclave + ] + ) + + Result.verify_image_size( + self.runtime_config.get_max_size_constraint(), + self.initrd + ) + # store image bundle_format in result + if self.bundle_format: + self.result.add_bundle_format(self.bundle_format) + + self.result.add( + key='enclave', + filename=self.enclave, + use_for_bundle=True, + compress=self.runtime_config.get_bundle_compression( + default=False + ), + shasum=True + ) + + # create image root metadata + self.result.add( + key='image_packages', + filename=self.system_setup.export_package_list( + self.target_dir + ), + use_for_bundle=True, + compress=False, + shasum=False + ) + self.result.add( + key='image_changes', + filename=self.system_setup.export_package_changes( + self.target_dir + ), + use_for_bundle=True, + compress=True, + shasum=False + ) + self.result.add( + key='image_verified', + filename=self.system_setup.export_package_verification( + self.target_dir + ), + use_for_bundle=True, + compress=False, + shasum=False + ) + return self.result diff --git a/kiwi/defaults.py b/kiwi/defaults.py index abe67d411d..0deb1707c5 100644 --- a/kiwi/defaults.py +++ b/kiwi/defaults.py @@ -1646,6 +1646,17 @@ def get_kis_image_types(): """ return ['kis', 'pxe'] + @staticmethod + def get_enclaves_image_types(): + """ + Provides supported enclave(initrd-only) image types + + :return: enclave image type names + + :rtype: list + """ + return ['enclave'] + @staticmethod def get_boot_image_description_path(): """ diff --git a/kiwi/exceptions.py b/kiwi/exceptions.py index 8c2d1e31bd..63f8e85fdf 100644 --- a/kiwi/exceptions.py +++ b/kiwi/exceptions.py @@ -492,6 +492,13 @@ class KiwiKisBootImageError(KiwiError): """ +class KiwiEnclaveBootImageError(KiwiError): + """ + Exception raised if no kernel image was found while + building an enclave image. + """ + + class KiwiRaidSetupError(KiwiError): """ Exception raised if invalid or not enough user data is provided @@ -853,3 +860,10 @@ class KiwiBootLoaderDiskPasswordError(KiwiError): """ Exception raised if the disk password could not be set """ + + +class KiwiEnclaveFormatError(KiwiError): + """ + Exception raised if no enclave_format attribute specified + for the selected build type + """ diff --git a/kiwi/schema/kiwi.rnc b/kiwi/schema/kiwi.rnc index a6ddae0285..a6e099a229 100644 --- a/kiwi/schema/kiwi.rnc +++ b/kiwi/schema/kiwi.rnc @@ -1869,6 +1869,13 @@ div { sch:param [ name = "attr" value = "format" ] sch:param [ name = "types" value = "oem" ] ] + k.type.enclave_format.attribute = + ## Specifies the format of the virtual disk. + attribute enclave_format { "aws-nitro" } + >> sch:pattern [ id = "enclave_format" is-a = "image_type" + sch:param [ name = "attr" value = "enclave_format" ] + sch:param [ name = "types" value = "enclave" ] + ] k.type.formatoptions.attribute = ## Specifies additional format options passed on to qemu-img ## formatoptions is a comma separated list of format specific @@ -1942,7 +1949,7 @@ div { attribute image { "btrfs" | "cpio" | "docker" | "ext2" | "ext3" | "ext4" | "iso" | "oem" | "pxe" | "kis" | "squashfs" | "tbz" | - "xfs" | "oci" | "appx" + "xfs" | "oci" | "appx" | "enclave" } >> sch:pattern [ id = "metadata_path_mandatory" is-a = "image_type_requirement" @@ -2023,7 +2030,7 @@ div { attribute kernelcmdline { text } >> sch:pattern [ id = "kernelcmdline" is-a = "image_type" sch:param [ name = "attr" value = "kernelcmdline" ] - sch:param [ name = "types" value = "oem iso pxe kis" ] + sch:param [ name = "types" value = "oem iso pxe kis enclave" ] ] k.type.luks_version.attribute = ## Specify LUKS version. This can be either set to "luks", "luks1" @@ -2261,6 +2268,7 @@ div { k.type.editbootinstall.attribute? & k.type.filesystem.attribute? & k.type.flags.attribute? & + k.type.enclave_format.attribute? & k.type.format.attribute? & k.type.formatoptions.attribute? & k.type.fsmountoptions.attribute? & diff --git a/kiwi/schema/kiwi.rng b/kiwi/schema/kiwi.rng index b50bf6f790..e4d4b66ac9 100644 --- a/kiwi/schema/kiwi.rng +++ b/kiwi/schema/kiwi.rng @@ -2690,6 +2690,16 @@ a different set of live features. + + + Specifies the format of the virtual disk. + aws-nitro + + + + + + Specifies additional format options passed on to qemu-img @@ -2801,6 +2811,7 @@ initrd architecture. xfs oci appx + enclave @@ -2908,7 +2919,7 @@ kernel command line options - + @@ -3266,6 +3277,9 @@ kiwi-ng result bundle ... + + + diff --git a/kiwi/xml_parse.py b/kiwi/xml_parse.py index d6168ed838..620367a5be 100644 --- a/kiwi/xml_parse.py +++ b/kiwi/xml_parse.py @@ -3082,7 +3082,7 @@ class type_(GeneratedsSuper): """The Image Type of the Logical Extend""" subclass = None superclass = None - def __init__(self, boot=None, bootfilesystem=None, firmware=None, bootkernel=None, bootpartition=None, bootpartsize=None, efipartsize=None, efifatimagesize=None, eficsm=None, efiparttable=None, dosparttable_extended_layout=None, bootprofile=None, btrfs_quota_groups=None, btrfs_root_is_snapshot=None, btrfs_root_is_subvolume=None, btrfs_set_default_volume=None, btrfs_root_is_readonly_snapshot=None, compressed=None, devicepersistency=None, editbootconfig=None, editbootinstall=None, filesystem=None, flags=None, format=None, formatoptions=None, fsmountoptions=None, fscreateoptions=None, squashfscompression=None, gcelicense=None, hybridpersistent=None, hybridpersistent_filesystem=None, gpt_hybrid_mbr=None, force_mbr=None, initrd_system=None, image=None, metadata_path=None, installboot=None, install_continue_on_timeout=None, installprovidefailsafe=None, installiso=None, installstick=None, installpxe=None, mediacheck=None, kernelcmdline=None, luks=None, luks_version=None, luksOS=None, luks_randomize=None, luks_pbkdf=None, mdraid=None, overlayroot=None, overlayroot_write_partition=None, overlayroot_readonly_partsize=None, verity_blocks=None, embed_verity_metadata=None, standalone_integrity=None, embed_integrity_metadata=None, integrity_legacy_hmac=None, integrity_metadata_key_description=None, integrity_keyfile=None, primary=None, ramonly=None, rootfs_label=None, spare_part=None, spare_part_mountpoint=None, spare_part_fs=None, spare_part_fs_attributes=None, spare_part_is_last=None, target_blocksize=None, target_removable=None, selinux_policy=None, vga=None, vhdfixedtag=None, volid=None, application_id=None, wwid_wait_timeout=None, derived_from=None, delta_root=None, ensure_empty_tmpdirs=None, xen_server=None, publisher=None, disk_start_sector=None, root_clone=None, boot_clone=None, bundle_format=None, bootloader=None, containerconfig=None, machine=None, oemconfig=None, size=None, systemdisk=None, partitions=None, vagrantconfig=None, installmedia=None, luksformat=None): + def __init__(self, boot=None, bootfilesystem=None, firmware=None, bootkernel=None, bootpartition=None, bootpartsize=None, efipartsize=None, efifatimagesize=None, eficsm=None, efiparttable=None, dosparttable_extended_layout=None, bootprofile=None, btrfs_quota_groups=None, btrfs_root_is_snapshot=None, btrfs_root_is_subvolume=None, btrfs_set_default_volume=None, btrfs_root_is_readonly_snapshot=None, compressed=None, devicepersistency=None, editbootconfig=None, editbootinstall=None, filesystem=None, flags=None, enclave_format=None, format=None, formatoptions=None, fsmountoptions=None, fscreateoptions=None, squashfscompression=None, gcelicense=None, hybridpersistent=None, hybridpersistent_filesystem=None, gpt_hybrid_mbr=None, force_mbr=None, initrd_system=None, image=None, metadata_path=None, installboot=None, install_continue_on_timeout=None, installprovidefailsafe=None, installiso=None, installstick=None, installpxe=None, mediacheck=None, kernelcmdline=None, luks=None, luks_version=None, luksOS=None, luks_randomize=None, luks_pbkdf=None, mdraid=None, overlayroot=None, overlayroot_write_partition=None, overlayroot_readonly_partsize=None, verity_blocks=None, embed_verity_metadata=None, standalone_integrity=None, embed_integrity_metadata=None, integrity_legacy_hmac=None, integrity_metadata_key_description=None, integrity_keyfile=None, primary=None, ramonly=None, rootfs_label=None, spare_part=None, spare_part_mountpoint=None, spare_part_fs=None, spare_part_fs_attributes=None, spare_part_is_last=None, target_blocksize=None, target_removable=None, selinux_policy=None, vga=None, vhdfixedtag=None, volid=None, application_id=None, wwid_wait_timeout=None, derived_from=None, delta_root=None, ensure_empty_tmpdirs=None, xen_server=None, publisher=None, disk_start_sector=None, root_clone=None, boot_clone=None, bundle_format=None, bootloader=None, containerconfig=None, machine=None, oemconfig=None, size=None, systemdisk=None, partitions=None, vagrantconfig=None, installmedia=None, luksformat=None): self.original_tagname_ = None self.boot = _cast(None, boot) self.bootfilesystem = _cast(None, bootfilesystem) @@ -3107,6 +3107,7 @@ def __init__(self, boot=None, bootfilesystem=None, firmware=None, bootkernel=Non self.editbootinstall = _cast(None, editbootinstall) self.filesystem = _cast(None, filesystem) self.flags = _cast(None, flags) + self.enclave_format = _cast(None, enclave_format) self.format = _cast(None, format) self.formatoptions = _cast(None, formatoptions) self.fsmountoptions = _cast(None, fsmountoptions) @@ -3316,6 +3317,8 @@ def get_filesystem(self): return self.filesystem def set_filesystem(self, filesystem): self.filesystem = filesystem def get_flags(self): return self.flags def set_flags(self, flags): self.flags = flags + def get_enclave_format(self): return self.enclave_format + def set_enclave_format(self, enclave_format): self.enclave_format = enclave_format def get_format(self): return self.format def set_format(self, format): self.format = format def get_formatoptions(self): return self.formatoptions @@ -3596,6 +3599,9 @@ def exportAttributes(self, outfile, level, already_processed, namespaceprefix_=' if self.flags is not None and 'flags' not in already_processed: already_processed.add('flags') outfile.write(' flags=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.flags), input_name='flags')), )) + if self.enclave_format is not None and 'enclave_format' not in already_processed: + already_processed.add('enclave_format') + outfile.write(' enclave_format=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.enclave_format), input_name='enclave_format')), )) if self.format is not None and 'format' not in already_processed: already_processed.add('format') outfile.write(' format=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.format), input_name='format')), )) @@ -3973,6 +3979,11 @@ def buildAttributes(self, node, attrs, already_processed): already_processed.add('flags') self.flags = value self.flags = ' '.join(self.flags.split()) + value = find_attr_value_('enclave_format', node) + if value is not None and 'enclave_format' not in already_processed: + already_processed.add('enclave_format') + self.enclave_format = value + self.enclave_format = ' '.join(self.enclave_format.split()) value = find_attr_value_('format', node) if value is not None and 'format' not in already_processed: already_processed.add('format') diff --git a/test/unit/builder/enclave_test.py b/test/unit/builder/enclave_test.py new file mode 100644 index 0000000000..bb50adeec6 --- /dev/null +++ b/test/unit/builder/enclave_test.py @@ -0,0 +1,108 @@ +from collections import namedtuple +from unittest.mock import ( + patch, Mock, MagicMock +) +from pytest import ( + raises, fixture +) +import kiwi + +from kiwi.builder.enclave import EnclaveBuilder +from kiwi.exceptions import ( + KiwiEnclaveBootImageError, + KiwiEnclaveFormatError +) + + +class TestEnclaveBuilder: + @fixture(autouse=True) + def inject_fixtures(self, caplog): + self._caplog = caplog + + @patch('kiwi.builder.enclave.BootImage') + def setup(self, mock_boot): + self.setup = Mock() + self.runtime_config = Mock() + self.runtime_config.get_max_size_constraint = Mock( + return_value=None + ) + kiwi.builder.enclave.RuntimeConfig = Mock( + return_value=self.runtime_config + ) + kiwi.builder.enclave.SystemSetup = Mock( + return_value=self.setup + ) + self.boot_image_task = MagicMock() + self.boot_image_task.boot_root_directory = 'initrd_dir' + self.boot_image_task.initrd_filename = 'initrd_file_name' + mock_boot.new.return_value = self.boot_image_task + self.xml_state = Mock() + self.xml_state.profiles = None + self.xml_state.get_image_version = Mock( + return_value='1.2.3' + ) + self.xml_state.get_initrd_system = Mock( + return_value='dracut' + ) + self.xml_state.xml_data.get_name = Mock( + return_value='some-image' + ) + self.xml_state.build_type = Mock() + self.xml_state.build_type.get_kernelcmdline = Mock( + return_value='some' + ) + kernel_type = namedtuple( + 'kernel', ['filename', 'version'] + ) + self.kernel = Mock() + self.kernel.get_kernel = Mock( + return_value=kernel_type(filename='some-kernel', version='42') + ) + kiwi.builder.enclave.Kernel = Mock( + return_value=self.kernel + ) + self.enclave = EnclaveBuilder( + self.xml_state, 'target_dir', 'root_dir', + custom_args={'signing_keys': ['key_file_a', 'key_file_b']} + ) + self.enclave.compressed = True + + @patch('kiwi.builder.enclave.BootImage') + def setup_method(self, cls, mock_boot): + self.setup() + + @patch('kiwi.builder.enclave.BootImage') + def test_create_invalid_enclave_format(self, mock_boot): + self.enclave.format = '' + with raises(KiwiEnclaveFormatError): + self.enclave.create() + + @patch('kiwi.builder.enclave.Command.run') + def test_create_aws_nitro(self, mock_Command_run): + self.enclave.format = 'aws-nitro' + self.boot_image_task.required = Mock( + return_value=True + ) + self.enclave.create() + + self.boot_image_task.create_initrd.assert_called_once_with() + self.setup.export_package_list.assert_called_once_with( + 'target_dir' + ) + self.setup.export_package_verification.assert_called_once_with( + 'target_dir' + ) + mock_Command_run.assert_called_once_with( + [ + 'eif_build', + '--kernel', 'target_dir/some-image.x86_64-1.2.3-42.kernel', + '--ramdisk', 'target_dir/initrd_file_name', + '--cmdline', 'some', + '--output', 'target_dir/some-image.x86_64-1.2.3.eif' + ] + ) + + def test_create_no_kernel_found(self): + self.kernel.get_kernel.return_value = False + with raises(KiwiEnclaveBootImageError): + self.enclave.create() diff --git a/test/unit/builder/init_test.py b/test/unit/builder/init_test.py index c70450d692..443dc39f81 100644 --- a/test/unit/builder/init_test.py +++ b/test/unit/builder/init_test.py @@ -61,6 +61,17 @@ def test_kis_builder(self, mock_builder): xml_state, 'target_dir', 'root_dir', None ) + @patch('kiwi.builder.enclave.EnclaveBuilder') + def test_enclave_builder(self, mock_builder): + xml_state = Mock() + xml_state.get_build_type_name = Mock( + return_value='enclave' + ) + ImageBuilder.new(xml_state, 'target_dir', 'root_dir') + mock_builder.assert_called_once_with( + xml_state, 'target_dir', 'root_dir', None + ) + @patch('kiwi.builder.archive.ArchiveBuilder') def test_archive_builder(self, mock_builder): xml_state = Mock() diff --git a/tox.ini b/tox.ini index 369f8fbf99..fe791d19da 100644 --- a/tox.ini +++ b/tox.ini @@ -49,6 +49,7 @@ basepython = check: python3 devel: python3 packagedoc: python3 + doc: python3 passenv = * usedevelop = True