From d1dccf8b236f006151b15cc2e682b9d2a5934e43 Mon Sep 17 00:00:00 2001 From: RJ Trujillo Date: Wed, 20 Sep 2023 16:01:43 -0600 Subject: [PATCH] feat: Introduce support for signing images If the user's current installation isn't signed, ublue-update will sign the installation the first time updates are ran until the image is signed Following a successful run, the updater will create a file that it checks for when running updates to evaluate the state of the image Image identifers are used as a fallback in the case that a user installs from an offline ISO. Image maintainers must include the image name, vendor (your GitHub usernmae or org), and Fedora version For instance, for Bazzite GNOME Nvida images, this looks like: IMAGE_NAME=bazzite-gnome-nvidia IMAGE_VENDOR=ublue-os FEDORA_MAJOR_VERSION=38 Existing Universal Blue images can implement this by adding... RUN echo -e "IMAGE_NAME=${IMAGE_NAME}\nIMAGE_VENDOR=${IMAGE_VENDOR}\nFEDORA_MAJOR_VERSION=${FEDORA_MAJOR_VERSION}" \ >> /usr/etc/default/image-info ... within their Containerfiles for each image they build Additionally, the image flavor and base image names may be placed here as well --- src/ublue_update/cli.py | 17 ++++++++ src/ublue_update/update_checks/sign.py | 54 ++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 src/ublue_update/update_checks/sign.py diff --git a/src/ublue_update/cli.py b/src/ublue_update/cli.py index e6062f1..e6ec22d 100644 --- a/src/ublue_update/cli.py +++ b/src/ublue_update/cli.py @@ -3,6 +3,8 @@ import logging import argparse + +from ublue_update.update_checks.sign import sign_image from ublue_update.update_checks.system import system_update_check from ublue_update.update_checks.wait import transaction_wait from ublue_update.update_inhibitors.hardware import check_hardware_inhibitors @@ -142,6 +144,21 @@ def run_updates(args): "System Updater", "System passed checks, updating ...", ) + + # Sign image before proceeding with updates + if not os.path.exists("/etc/ublue-update/image-signed"): + notify( + "System Updater", + "Signing your current installation...", + ) + log.info("Signing image...") + sign_image() + notify( + "System Updater", + "Installation successfully signed. Please reboot.", + ) + log.info("Image successfully signed.") + users = [] try: users = get_active_sessions() diff --git a/src/ublue_update/update_checks/sign.py b/src/ublue_update/update_checks/sign.py new file mode 100644 index 0000000..ca9cacd --- /dev/null +++ b/src/ublue_update/update_checks/sign.py @@ -0,0 +1,54 @@ +from json import loads +from os import mknod +from re import match +from subprocess import run, PIPE + + +def get_image_ref(): + """Pull image identifiers""" + with open("/etc/default/image-info", "r") as image_info: + image_identifiers = {} + for identifier in image_info: + val, key = identifier.split("=") + image_identifiers[val] = str(key).rstrip() + + """Set image identifiers""" + image_name = image_identifiers.get("IMAGE_NAME") + image_vendor = image_identifiers.get("IMAGE_VENDOR") + fedora_version = image_identifiers.get("FEDORA_MAJOR_VERSION") + + """Construct image reference""" + image = "ghcr.io/" + image_vendor + "/" + image_name + ":" + fedora_version + return image + + +def rebase_image(image): + """Regex in case vendor isn't ublue-os""" + if match("/var/.+/image", image): + image = get_image_ref() + + """Set protocol if unset""" + protocol = "docker://" + if protocol not in image: + image = protocol + image + + """Rebase to signed image""" + image = "ostree-image-signed:" + image + rpm_ostree_rebase = ["rpm-ostree", "rebase", image] + rebase = run(rpm_ostree_rebase, stdout=PIPE) + if rebase.returncode == 0: + mknod("/etc/ublue-update/image-signed") + + +def sign_image(): + """Pull ostree status""" + rpm_ostree_status = ["rpm-ostree", "status", "--json"] + status = run(rpm_ostree_status, stdout=PIPE).stdout + + """Parse current image""" + deployments = loads(status)["deployments"][0] + image_ref = deployments["container-image-reference"].split(":", 1) + image = image_ref[1] + + """Rebase""" + rebase_image(image)