diff --git a/image/system/BUILD.bazel b/image/system/BUILD.bazel new file mode 100644 index 000000000..a1be95698 --- /dev/null +++ b/image/system/BUILD.bazel @@ -0,0 +1,81 @@ +load("//bazel/mkosi:mkosi_image.bzl", "mkosi_image") +load(":variants.bzl", "CSPS", "STREAMS", "VARIANTS", "autologin", "constellation_packages", "images_for_csp", "images_for_csp_and_stream", "images_for_stream", "kernel_command_line", "kernel_command_line_dict") + +[ + mkosi_image( + name = variant["csp"] + "_" + variant["attestation_variant"] + "_" + stream, + srcs = [ + "mkosi.postinst", + ] + glob([ + "mkosi.repart/**", + ]), + autologin = autologin( + variant["csp"], + variant["attestation_variant"], + stream, + ), + base_trees = [ + "//image/base", + ], + extra_trees = constellation_packages(stream), + initrds = [ + "//image/initrd", + ], + kernel_command_line = kernel_command_line( + variant["csp"], + variant["attestation_variant"], + stream, + ), + kernel_command_line_dict = kernel_command_line_dict( + variant["csp"], + variant["attestation_variant"], + stream, + ), + mkosi_conf = "mkosi.conf", + out_dir = variant["csp"] + "_" + variant["attestation_variant"] + "_" + stream, + tags = [ + "manual", + "no-cache", + ], + version_file = "//bazel/settings:tag", + ) + for variant in VARIANTS + for stream in STREAMS +] + +[ + filegroup( + name = stream, + srcs = images_for_stream(stream), + tags = [ + "manual", + "no-cache", + ], + ) + for stream in STREAMS +] + +[ + filegroup( + name = csp, + srcs = images_for_csp(csp), + tags = [ + "manual", + "no-cache", + ], + ) + for csp in CSPS +] + +[ + filegroup( + name = csp + "_" + stream, + srcs = images_for_csp_and_stream(csp, stream), + tags = [ + "manual", + "no-cache", + ], + ) + for csp in CSPS + for stream in STREAMS +] diff --git a/image/system/mkosi.conf b/image/system/mkosi.conf new file mode 100644 index 000000000..317899e82 --- /dev/null +++ b/image/system/mkosi.conf @@ -0,0 +1,24 @@ +[Distribution] +Distribution=fedora +Release=38 + +[Output] +Format=disk +ManifestFormat=json +Output=constellation +ImageId=constellation +Seed=0e9a6fe0-68f6-408c-bbeb-136054d20445 +SourceDateEpoch=0 + +[Content] +Bootable=yes +Bootloader=uki +KernelCommandLine=preempt=full rd.shell=0 rd.emergency=reboot loglevel=8 console=ttyS0 +RemoveFiles=/var/log +RemoveFiles=/var/cache +RemoveFiles=/etc/pki/ca-trust/extracted/java/cacerts + /usr/lib/sysimage/libdnf5/transaction_history.sqlite* + /var/cache/ldconfig/aux-cache +# https://github.com/authselect/authselect/pull/348 +# RemoveFiles=/etc/authselect/* +CleanPackageMetadata=true diff --git a/image/system/mkosi.postinst b/image/system/mkosi.postinst new file mode 100755 index 000000000..75ae9b68b --- /dev/null +++ b/image/system/mkosi.postinst @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euxo pipefail + +# add motd for constellation console access +if [[ ${CONSOLE_MOTD:-false} == "true" ]]; then + cat << EOF > "${BUILDROOT}/usr/lib/motd.d/10-constellation-console-access.motd" +~ Welcome to Constellation! ~ +Usually, on release versions of Constellation running in the cloud, you are not able to login through the serial console. +This shell access is specifically granted for debug images and MiniConstellation to allow users to research the environment Constellation runs in. +Have fun! Feel free to report any issues to GitHub or security@edgeless.systems (for security vulnerabilities only). +EOF +fi + +# update /etc/os-release +echo "IMAGE_ID=\"${IMAGE_ID}\"" >> "${BUILDROOT}/etc/os-release" +# TODO(malt3): ensure IMAGE_VERSION is actually set (shell wrapper) +export IMAGE_VERSION=${IMAGE_VERSION-v0.0.0} +echo "IMAGE_VERSION=\"${IMAGE_VERSION}\"" >> "${BUILDROOT}/etc/os-release" + +# enable debugd +ln -s /usr/lib/systemd/system/debugd.service "${BUILDROOT}/etc/systemd/system/multi-user.target.wants/debugd.service" + +# ensure google_nvme_id is executable +chmod o+x "${BUILDROOT}/usr/lib/udev/google_nvme_id" +chmod g+x "${BUILDROOT}/usr/lib/udev/google_nvme_id" +chmod u+x "${BUILDROOT}/usr/lib/udev/google_nvme_id" + +# mask unwanted services +ln -s /dev/null "${BUILDROOT}/etc/systemd/system/systemd-pcrmachine.service" +ln -s /dev/null "${BUILDROOT}/etc/systemd/system/systemd-pcrfs-root.service" +ln -s /dev/null "${BUILDROOT}/etc/systemd/system/systemd-pcrfs@.service" +ln -s /dev/null "${BUILDROOT}/etc/systemd/system/systemd-pcrphase@.service" +ln -s /dev/null "${BUILDROOT}/etc/systemd/system/systemd-pcrphase-initrd.service" +ln -s /dev/null "${BUILDROOT}/etc/systemd/system/systemd-pcrphase-sysinit.service" +ln -s /dev/null "${BUILDROOT}/etc/systemd/system/systemd-pcrphase.service" diff --git a/image/system/mkosi.repart/00-esp.conf b/image/system/mkosi.repart/00-esp.conf new file mode 100644 index 000000000..2876e4107 --- /dev/null +++ b/image/system/mkosi.repart/00-esp.conf @@ -0,0 +1,6 @@ +[Partition] +Type=esp +Format=vfat +CopyFiles=/efi:/ +SizeMinBytes=256M +SizeMaxBytes=512M diff --git a/image/system/mkosi.repart/10-root.conf b/image/system/mkosi.repart/10-root.conf new file mode 100644 index 000000000..10ac90529 --- /dev/null +++ b/image/system/mkosi.repart/10-root.conf @@ -0,0 +1,7 @@ +[Partition] +Type=root +Format=squashfs +Verity=data +VerityMatchKey=root +CopyFiles=/ +Minimize=guess diff --git a/image/system/mkosi.repart/20-root-verity.conf b/image/system/mkosi.repart/20-root-verity.conf new file mode 100644 index 000000000..352c50d55 --- /dev/null +++ b/image/system/mkosi.repart/20-root-verity.conf @@ -0,0 +1,6 @@ +[Partition] +Type=root-verity +Verity=hash +VerityMatchKey=root +SizeMinBytes=64M +SizeMaxBytes=64M diff --git a/image/system/variants.bzl b/image/system/variants.bzl new file mode 100644 index 000000000..9f96ba7a8 --- /dev/null +++ b/image/system/variants.bzl @@ -0,0 +1,239 @@ +""" Constellation OS image configuration / variants """ + +VARIANTS = [ + { + "attestation_variant": "aws-sev-snp", + "csp": "aws", + }, + { + "attestation_variant": "aws-nitro-tpm", + "csp": "aws", + }, + { + "attestation_variant": "azure-sev-snp", + "csp": "azure", + }, + { + "attestation_variant": "gcp-sev-es", + "csp": "gcp", + }, + { + "attestation_variant": "gcp-sev-snp", + "csp": "gcp", + }, + { + "attestation_variant": "qemu-vtpm", + "csp": "openstack", + }, + { + "attestation_variant": "qemu-vtpm", + "csp": "qemu", + }, +] + +STREAMS = [ + "stable", + "nightly", + "console", + "debug", +] + +CSPS = [ + "aws", + "azure", + "gcp", + "openstack", + "qemu", +] + +base_cmdline = "selinux=1 enforcing=0 audit=0" + +csp_settings = { + "aws": { + "kernel_command_line_dict": { + "constel.csp": "aws", + "idle": "poll", + "mitigations": "auto", + }, + }, + "azure": { + "kernel_command_line_dict": { + "constel.csp": "azure", + "mitigations": "auto,nosmt", + }, + }, + "gcp": { + "kernel_command_line_dict": { + "constel.csp": "gcp", + "mitigations": "auto,nosmt", + }, + }, + "openstack": { + "autologin": True, + "kernel_command_line": "console=tty0 console=ttyS0", + "kernel_command_line_dict": { + "console": "tty0", + "constel.csp": "openstack", + "kvm_amd.sev": "1", + "mem_encrypt": "on", + "mitigations": "auto,nosmt", + "module_blacklist": "qemu_fw_cfg", + }, + }, + "qemu": { + "autologin": True, + "kernel_command_line_dict": { + "constel.csp": "qemu", + "mitigations": "auto,nosmt", + }, + }, +} + +attestation_variant_settings = { + "aws-nitro-tpm": { + "kernel_command_line_dict": { + "constel.attestation-variant": "aws-nitro-tpm", + }, + }, + "aws-sev-snp": { + "kernel_command_line_dict": { + "constel.attestation-variant": "aws-sev-snp", + }, + }, + "azure-sev-snp": { + "kernel_command_line_dict": { + "constel.attestation-variant": "azure-sev-snp", + }, + }, + "gcp-sev-es": { + "kernel_command_line_dict": { + "constel.attestation-variant": "gcp-sev-es", + }, + }, + "gcp-sev-snp": { + "kernel_command_line_dict": { + "constel.attestation-variant": "gcp-sev-snp", + }, + }, + "qemu-vtpm": { + "kernel_command_line_dict": { + "constel.attestation-variant": "qemu-vtpm", + }, + }, +} + +stream_settings = { + "console": { + "autologin": True, + }, + "debug": { + "autologin": True, + "kernel_command_line": "constellation.debug", + }, + "nightly": {}, + "stable": {}, +} + +def from_settings(csp, attestation_variant, stream, strict = True, default = None): + """Generates a list of settings dictionaries for the given csp, attestation_variant and stream. + + Args: + csp: The cloud service provider to use. + attestation_variant: The attestation variant to use. + stream: The stream to use. + strict: If True, fail if any of the given csp, attestation_variant or stream is unknown. + default: The default value to use if any of the given csp, attestation_variant or stream is unknown. + + Returns: + A list of settings dictionaries. + """ + if strict: + if not csp in csp_settings: + fail("Unknown csp: " + csp) + if not attestation_variant in attestation_variant_settings: + fail("Unknown attestation_variant: " + attestation_variant) + if not stream in stream_settings: + fail("Unknown stream: " + stream) + return [ + csp_settings.get(csp, default), + attestation_variant_settings.get(attestation_variant, default), + stream_settings.get(stream, default), + ] + +def constellation_packages(stream): + base_packages = ["//measurement-reader/cmd:measurement-reader-package"] + if stream == "debug": + return ["//debugd/cmd/debugd:debugd-package"] + base_packages + return [ + "//upgrade-agent/cmd:upgrade-agent-package", + "//bootstrapper/cmd/bootstrapper:bootstrapper-package", + ] + base_packages + +def autologin(csp, attestation_variant, stream): + """Generates a boolean indicating whether autologin should be enabled for the given csp, attestation_variant and stream. + + Args: + csp: The cloud service provider to use. + attestation_variant: The attestation variant to use. + stream: The stream to use. + + Returns: + A boolean indicating whether autologin should be enabled. + """ + out = None + for settings in from_settings(csp, attestation_variant, stream): + if not "autologin" in settings: + continue + if out != None and out != settings["autologin"]: + fail("Inconsistent autologin settings") + out = settings["autologin"] + return out + +def kernel_command_line(csp, attestation_variant, stream): + cmdline = base_cmdline + for settings in from_settings(csp, attestation_variant, stream, default = {}): + cmdline = append_cmdline(cmdline, settings.get("kernel_command_line", "")) + return cmdline + +def kernel_command_line_dict(csp, attestation_variant, stream): + commandline_dict = {} + for settings in from_settings(csp, attestation_variant, stream, default = {}): + commandline_dict = commandline_dict | settings.get("kernel_command_line_dict", {}) + return commandline_dict + +def append_cmdline(current, append): + """Append a string to an existing commandline, separating them with a space. + + Args: + current: The existing commandline. May be empty. + append: The string to append. May be empty. + + Returns: + The combined commandline. + """ + if len(current) == 0: + return append + if len(append) == 0: + return current + return current + " " + append + +def images_for_stream(stream): + return [ + variant["csp"] + "_" + variant["attestation_variant"] + "_" + stream + for variant in VARIANTS + ] + +def images_for_csp(csp): + return [ + csp + "_" + variant["attestation_variant"] + "_" + stream + for variant in VARIANTS + if variant["csp"] == csp + for stream in STREAMS + ] + +def images_for_csp_and_stream(csp, stream): + return [ + csp + "_" + variant["attestation_variant"] + "_" + stream + for variant in VARIANTS + if variant["csp"] == csp + ]