Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build and publish UKI images in our pipeline #800

Merged
merged 7 commits into from
May 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,27 @@ jobs:
with:
sarif_file: 'sarif'
category: ${{ matrix.flavor }}
build-uki:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: |
git fetch --prune --unshallow
- name: Install earthly
uses: Luet-lab/luet-install-action@v1
with:
repository: quay.io/kairos/packages
packages: utils/earthly
- name: Build uki image 🔧
run: |
# Do fedora as its the smaller uki possible
earthly +uki --FLAVOR=fedora
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
files: |
build/efi
# build-vm-images:
# needs: build
# runs-on: macos-12
Expand Down
141 changes: 132 additions & 9 deletions Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ base-image:
ARG MODEL
ARG FLAVOR
ARG VARIANT
ARG BUILD_INITRD="true"
IF [ "$BASE_IMAGE" = "" ]
# Source the flavor-provided docker file
FROM DOCKERFILE --build-arg MODEL=$MODEL -f images/Dockerfile.$FLAVOR .
Expand Down Expand Up @@ -330,16 +331,17 @@ base-image:
RUN find /usr/lib/modules -type f -name "*.ko" -execdir zstd --rm -9 {} \+
END


IF [ "$FLAVOR" = "debian" ]
RUN rm -rf /boot/initrd.img-*
END
IF [ "$BUILD_INITRD" = "true" ]
IF [ "$FLAVOR" = "debian" ]
RUN rm -rf /boot/initrd.img-*
END


IF [ -e "/usr/bin/dracut" ]
# Regenerate initrd if necessary
RUN --no-cache kernel=$(ls /lib/modules | head -n1) && depmod -a "${kernel}"
RUN --no-cache kernel=$(ls /lib/modules | head -n1) && dracut -f "/boot/initrd-${kernel}" "${kernel}" && ln -sf "initrd-${kernel}" /boot/initrd
IF [ -e "/usr/bin/dracut" ]
# Regenerate initrd if necessary
RUN --no-cache kernel=$(ls /lib/modules | head -n1) && depmod -a "${kernel}"
RUN --no-cache kernel=$(ls /lib/modules | head -n1) && dracut -f "/boot/initrd-${kernel}" "${kernel}" && ln -sf "initrd-${kernel}" /boot/initrd
END
END

# Set /boot/vmlinuz pointing to our kernel so kairos-agent can use it
Expand All @@ -366,10 +368,12 @@ base-image:
END
END


RUN rm -rf /tmp/*

image:
FROM +base-image
ARG BUILD_INITRD="true"
FROM +base-image --BUILD_INITRD=$BUILD_INITRD
ARG FLAVOR
ARG VARIANT
ARG MODEL
Expand All @@ -395,6 +399,125 @@ image-rootfs:
FROM +image
SAVE ARTIFACT --keep-own /. rootfs

uki-artifacts:
FROM +image --BUILD_INITRD=false
RUN /usr/bin/immucore version
RUN ln -s /usr/bin/immucore /init
RUN find . \( -path ./sys -prune -o -path ./run -prune -o -path ./dev -prune -o -path ./tmp -prune -o -path ./proc -prune \) -o -print | cpio -R root:root -H newc -o | gzip -2 > /tmp/initramfs.cpio.gz
RUN echo "console=tty1 console=ttyS0 net.ifnames=1 rd.immucore.debug rd.immucore.uki selinux=0" > /tmp/Cmdline
RUN basename $(ls /boot/vmlinuz-* |grep -v rescue | head -n1)| sed --expression "s/vmlinuz-//g" > /tmp/Uname
SAVE ARTIFACT /boot/vmlinuz Kernel
SAVE ARTIFACT /etc/os-release Osrelease
SAVE ARTIFACT /tmp/Cmdline Cmdline
SAVE ARTIFACT /tmp/Uname Uname
SAVE ARTIFACT /tmp/initramfs.cpio.gz Initrd

# Base image for uki operations so we only run the install once
uki-tools-image:
FROM fedora:38
# objcopy from binutils and systemd-stub from systemd
RUN dnf install -y binutils systemd-boot mtools efitools sbsigntools shim openssl

uki:
FROM +uki-tools-image
WORKDIR build
COPY +uki-artifacts/Kernel Kernel
COPY +uki-artifacts/Initrd Initrd
COPY +uki-artifacts/Osrelease Osrelease
COPY +uki-artifacts/Uname Uname
COPY +uki-artifacts/Cmdline Cmdline
ARG KVERSION=$(cat Uname)
RUN objcopy /usr/lib/systemd/boot/efi/linuxx64.efi.stub \
--add-section .osrel=Osrelease --set-section-flags .osrel=data,readonly \
--add-section .cmdline=Cmdline --set-section-flags .cmdline=data,readonly \
--add-section .initrd=Initrd --set-section-flags .initrd=data,readonly \
--add-section .uname=Uname --set-section-flags .uname=data,readonly \
--add-section .linux=Kernel --set-section-flags .linux=code,readonly \
$ISO_NAME.unsigned.efi \
--change-section-vma .osrel=0x17000 \
--change-section-vma .cmdline=0x18000 \
--change-section-vma .initrd=0x19000 \
--change-section-vma .uname=0x5a0ed000 \
--change-section-vma .linux=0x5a0ee000
SAVE ARTIFACT Uname Uname
SAVE ARTIFACT $ISO_NAME.unsigned.efi uki.efi AS LOCAL build/$ISO_NAME.unsigned-$KVERSION.efi


uki-signed:
FROM +uki-tools-image
# Platform key
RUN openssl req -new -x509 -subj "/CN=Kairos PK/" -days 3650 -nodes -newkey rsa:2048 -sha256 -keyout PK.key -out PK.crt
# CER keys are for FW install
RUN openssl x509 -in PK.crt -out PK.cer -outform DER
# Key exchange
RUN openssl req -new -x509 -subj "/CN=Kairos KEK/" -days 3650 -nodes -newkey rsa:2048 -sha256 -keyout KEK.key -out KEK.crt
# CER keys are for FW install
RUN openssl x509 -in KEK.crt -out KEK.cer -outform DER
# Signature DB
RUN openssl req -new -x509 -subj "/CN=Kairos DB/" -days 3650 -nodes -newkey rsa:2048 -sha256 -keyout DB.key -out DB.crt
# CER keys are for FW install
RUN openssl x509 -in DB.crt -out DB.cer -outform DER
COPY +uki/uki.efi uki.efi
COPY +uki/Uname Uname
ARG KVERSION=$(cat Uname)

RUN sbsign --key DB.key --cert DB.crt --output uki.signed.efi uki.efi


SAVE ARTIFACT /boot/efi/EFI/fedora/mmx64.efi MokManager.efi
SAVE ARTIFACT PK.key PK.key AS LOCAL build/PK.key
SAVE ARTIFACT PK.crt PK.crt AS LOCAL build/PK.crt
SAVE ARTIFACT PK.cer PK.cer AS LOCAL build/PK.cer
SAVE ARTIFACT KEK.key KEK.key AS LOCAL build/KEK.key
SAVE ARTIFACT KEK.crt KEK.crt AS LOCAL build/KEK.crt
SAVE ARTIFACT KEK.cer KEK.cer AS LOCAL build/KEK.cer
SAVE ARTIFACT DB.key DB.key AS LOCAL build/DB.key
SAVE ARTIFACT DB.crt DB.crt AS LOCAL build/DB.crt
SAVE ARTIFACT DB.cer DB.cer AS LOCAL build/DB.cer
SAVE ARTIFACT uki.signed.efi uki.efi AS LOCAL build/$ISO_NAME.signed-$KVERSION.efi

# This target will prepare a disk.img ready with the uki artifact on it for qemu. Just attach it to qemu and mark you vm to boot from that disk
# here we take advantage of the uefi fallback method, which will load an efi binary in /EFI/BOOT/BOOTX64.efi if there is nothing
# else that it can boot from :D Just make sure to have your disk.img set as boot device in qemu.
prepare-uki-disk-image:
FROM +uki-tools-image
ARG SIGNED_EFI=false
IF [ "$SIGNED_EFI" = "true" ]
COPY +uki-signed/uki.efi .
COPY +uki-signed/PK.key .
COPY +uki-signed/PK.crt .
COPY +uki-signed/PK.cer .
COPY +uki-signed/KEK.key .
COPY +uki-signed/KEK.crt .
COPY +uki-signed/KEK.cer .
COPY +uki-signed/DB.key .
COPY +uki-signed/DB.crt .
COPY +uki-signed/DB.cer .
COPY +uki-signed/MokManager.efi .
ELSE
COPY +uki/uki.efi .
END
RUN dd if=/dev/zero of=disk.img bs=1G count=1
RUN mformat -i disk.img -F ::
RUN mmd -i disk.img ::/EFI
RUN mmd -i disk.img ::/EFI/BOOT
RUN mcopy -i disk.img uki.efi ::/EFI/BOOT/BOOTX64.efi
IF [ "$SIGNED_EFI" = "true" ]
RUN mcopy -i disk.img PK.key ::/EFI/BOOT/PK.key
RUN mcopy -i disk.img PK.crt ::/EFI/BOOT/PK.crt
RUN mcopy -i disk.img PK.cer ::/EFI/BOOT/PK.cer
RUN mcopy -i disk.img KEK.key ::/EFI/BOOT/KEK.key
RUN mcopy -i disk.img KEK.crt ::/EFI/BOOT/KEK.crt
RUN mcopy -i disk.img KEK.cer ::/EFI/BOOT/KEK.cer
RUN mcopy -i disk.img DB.key ::/EFI/BOOT/DB.key
RUN mcopy -i disk.img DB.crt ::/EFI/BOOT/DB.crt
RUN mcopy -i disk.img DB.cer ::/EFI/BOOT/DB.cer
RUN mcopy -i disk.img MokManager.efi ::/EFI/BOOT/mmx64.efi
END
RUN mdir -i disk.img ::/EFI/BOOT
SAVE ARTIFACT disk.img AS LOCAL build/disk.img


###
### Artifacts targets (ISO, netboot, ARM)
###
Expand Down
92 changes: 92 additions & 0 deletions UKI-experimental.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# UKI: Unified Kernel Image


It's basically a kernel, initrd and cmdline for the kernel all lumped up together in an efi binary. Mixing it with something like systemd-stub
means that you can boot from the EFI shell directly into the system.

You can add more stuff to it like the os-release info, the kernel version (uname), splash image, Devicetree , etc...

This way you got everything in one nice package and can sign the whole thing for secureboot or calculate the hashes for measured boot.


Usually under secureboot the initrd is not signed (as its generated locally), so once the kernel is run initrd signature is not verified. Nor you can measure it with TPM PCRs

UKI bundles the kernel with initrd and everything else, so you can sign the whole thing AND pre-calculate the hashes for TPM PCRs in advance.


Good writeup: https://0pointer.net/blog/brave-new-trusted-boot-world.html


### So why not a bit more?

So why not store the whole system in the initramfs?

In this branch on the earthfile there is a new target called uki. This will generate an efi with the whole kairos system under the initramfs.
This uses immucore to mount and set up the whole system.

There is an extra target called `prepare-uki-disk-image` which will generate a disk.img with the efi file inside in the proper place, so you
can just attach that image to a qemu vm and boot from there. An extra arg `SIGNED_EFI` will provide the same image but with a signed efi and all the keys needed
to insert hem into the uefo firmware and test secureboot.

The only special thing the target does is use objcopy to add sections to the systemd-stub pointing to the correct data:

```bash
RUN objcopy /usr/lib/systemd/boot/efi/linuxx64.efi.stub \
--add-section .osrel=Osrelease --set-section-flags .osrel=data,readonly \
--add-section .cmdline=Cmdline --set-section-flags .cmdline=data,readonly \
--add-section .initrd=Initrd --set-section-flags .initrd=data,readonly \
--add-section .uname=Uname --set-section-flags .uname=data,readonly \
--add-section .linux=Kernel --set-section-flags .linux=code,readonly \
$ISO_NAME.unsigned.efi \
--change-section-vma .osrel=0x17000 \
--change-section-vma .cmdline=0x18000 \
--change-section-vma .initrd=0x19000 \
--change-section-vma .uname=0x5a0ed000 \
--change-section-vma .linux=0x5a0ee000
```

Where:
* Kernel is the kernel that will be booted.
* Initrd is the initramfs that will be booted by the kernel. Currently, a dump of the docker-rootfs...rootfs
* Uname the output of `uname -r` (Optional content)
* Osrelease is the /etc/os-release file from the kairos rootfs (Optional content)
* Cmdline is the line to be passed to the kernel (Optional content, but needed in our case)


# Running the efi locally with qemu

For ease of use there is a target in the earthly file that will generate a disk.img with the efi inside.
Run `earthly +prepare-disk-image` and you will get a `build/disk.img` ready to be consumed

To run it under qemu use the following arguments:

```bash
qemu-system-x86_64 -bios $EFI_FIRMWARE -accel kvm -cpu host -m $MEMORY -machine pc \
-drive file=disk.img,if=none,index=0,media=disk,format=raw,id=disk1 -device virtio-blk-pci,drive=disk1,bootindex=0 \
-boot menu=on
```

Where `$EFI_FIRMWARE` is the OVMF efi firmware and `$MEMORY` is at least 4000.


Note that you can also build the uki image signed by passing the `--SIGNED_EFI=true` to earthly. That would produce the same
`build/disk.img` but with some extra files inside, like the certificates needed to be added to the firmware and the MokManager util to install those certificates.

With those certs and MokManager is possible to install the generated certs to test booting with SecureBoot enabled.

Note that the `$EFI_FIRMWARE` needs to be set to the OVMF SecureBoot enabled file to test SecureBoot.

For example under Fedora, the normal firmware with no SecureBoot is found at `/usr/share/edk2/ovmf/OVMF_CODE.fd` while
the SecureBoot enabled one is `/usr/share/edk2/ovmf/OVMF_CODE.secboot.fd`

Good links:

- https://man.archlinux.org/man/systemd-stub.7
- https://wiki.osdev.org/UEFI#UEFI_applications_in_detail
- https://github.com/uapi-group/specifications/blob/main/specs/unified_kernel_image.md
- https://man.archlinux.org/man/systemd-measure.1.en
- https://manuais.iessanclemente.net/images/a/a6/EFI-ShellCommandManual.pdf
- https://0pointer.net/blog/brave-new-trusted-boot-world.html



2 changes: 1 addition & 1 deletion overlay/files/system/oem/00_rootfs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ stages:
providers: ["aws", "gcp", "openstack", "cdrom"]
path: "/oem"
rootfs:
- if: '[ ! -f "/run/cos/recovery_mode" ]'
- if: '[ ! -f "/run/cos/recovery_mode" ] && [ ! -e "/run/cos/uki_mode" ]'
name: "Layout configuration"
environment_file: /run/cos/cos-layout.env
environment:
Expand Down
4 changes: 2 additions & 2 deletions overlay/files/system/oem/10_accounting.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ stages:
homedir: "/home/kairos"
groups:
- "admin"
- name: "Set user password if running in live"
if: "[ -e /run/cos/live_mode ]"
- name: "Set user password if running in live or uki"
if: "[ -e /run/cos/live_mode ] || [ -e /run/cos/uki_mode ]"
users:
kairos:
passwd: "kairos"
Expand Down
42 changes: 39 additions & 3 deletions overlay/files/system/oem/11_persistency.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,44 @@
name: "Configure persistent dirs bind-mounts"
stages:
rootfs.after:
- if: '[ ! -f "/run/cos/recovery_mode" ]'
name: "Layout configuration"
- if: '[ -e "/run/cos/uki_mode" ]'
# omit the persistent partition on uki mode
# And mount all persistent mounts under the overlay
name: "Layout configuration for UKI"
environment_file: /run/cos/cos-layout.env
environment:
RW_PATHS: "/var /etc /srv /usr"
OVERLAY: "tmpfs:25%"
PERSISTENT_STATE_PATHS: >-
/var
/etc
/etc/systemd
/etc/modprobe.d
/etc/rancher
/etc/sysconfig
/etc/runlevels
/etc/ssh
/etc/ssl/certs
/etc/iscsi
/etc/cni
/etc/kubernetes
/home
/opt
/root
/var/snap
/usr/libexec
/var/log
/var/lib/rancher
/var/lib/kubelet
/var/lib/snapd
/var/lib/wicked
/var/lib/longhorn
/var/lib/cni
/usr/share/pki/trust
/usr/share/pki/trust/anchors
/var/lib/ca-certificates
- if: '[ ! -f "/run/cos/recovery_mode" ] && [ ! -e "/run/cos/uki_mode" ]'
name: "Layout configuration for active/passive"
environment_file: /run/cos/cos-layout.env
environment:
VOLUMES: "LABEL=COS_OEM:/oem LABEL=COS_PERSISTENT:/usr/local"
Expand Down Expand Up @@ -56,7 +92,7 @@ stages:
echo PERSISTENT_STATE_PATHS=\"${PERSISTENT_STATE_PATHS}\" >> /run/cos/cos-layout.env
- if: |
cat /proc/cmdline | grep -q "kairos.boot_live_mode"
name: "Layout configuration"
name: "Layout configuration for boot_live_mode"
environment_file: /run/cos/cos-layout.env
environment:
VOLUMES: "LABEL=COS_OEM:/oem LABEL=COS_PERSISTENT:/usr/local"
Expand Down