Skip to content
Open
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
35 changes: 27 additions & 8 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ jobs:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5

- name: Fetch Secboot key
env:
DB_CRT: ${{ secrets.DB_CRT }}
DB_KEY: ${{ secrets.DB_KEY }}
run: |
echo "Adding signing key from gha to db.crt"
echo "$DB_CRT" > keys/db.crt
echo "$DB_KEY" > keys/db.key

- name: Get current date
id: date
run: |
Expand Down Expand Up @@ -86,14 +95,24 @@ jobs:

- name: Build Image
id: build_image
uses: redhat-actions/buildah-build@7a95fa7ee0f02d552a32753e7414641a04307056 # v2
with:
containerfiles: |
./Containerfile
image: ${{ env.IMAGE_NAME }}
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
oci: true
shell: bash
run: |
BUILD_ELEVATE="" just build-containerfile "$IMAGE_NAME"

- name: Tag Image
id: tag
env:
TAGS: ${{ steps.metadata.outputs.tags }}
LABELS: ${{ steps.metadata.outputs.labels }}
shell: bash
run: |
set -xeuo pipefail
split=$(echo $TAGS | tr " " "\n")
for tag in $split
do
podman tag "$IMAGE_NAME" "$IMAGE_NAME:$tag"
done


- name: Login to GitHub Container Registry
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
cosign.key
*.img
target
*.crt
*.key
1 change: 1 addition & 0 deletions Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ RUN --mount=type=tmpfs,dst=/tmp --mount=type=tmpfs,dst=/root \
git clone "https://github.com/bootc-dev/bootc.git" /tmp/bootc && \
make -C /tmp/bootc bin install-all && \
printf "systemdsystemconfdir=/etc/systemd/system\nsystemdsystemunitdir=/usr/lib/systemd/system\n" | tee /usr/lib/dracut/dracut.conf.d/30-bootcrew-fix-bootc-module.conf && \
printf 'add_dracutmodules+=" fido2 tpm2-tss pkcs11 systemd-pcrphase "\n' | tee "/usr/lib/dracut/dracut.conf.d/20-bootcrew-tpm-luks.conf" && \
printf 'reproducible=yes\nhostonly=no\ncompress=zstd\nadd_dracutmodules+=" ostree bootc "' | tee "/usr/lib/dracut/dracut.conf.d/30-bootcrew-bootc-container-build.conf" && \
dracut --force "$(find /usr/lib/modules -maxdepth 1 -type d | grep -v -E "*.img" | tail -n 1)/initramfs.img" && \
pacman -Rns --noconfirm make git rust && \
Expand Down
80 changes: 80 additions & 0 deletions Dockerfile.cfsuki
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# from: https://github.com/bootc-dev/bootc/blob/main/Dockerfile.cfsuki

# Override via --build-arg=base=<image> to use a different base
ARG base=ghcr.io/bootcrew/arch-bootc:latest
# This is where we get the tools to build the UKI
ARG buildroot=docker.io/archlinux/archlinux:latest
FROM $base AS base

FROM $buildroot as buildroot-base
RUN \
# systemd-udev is required for /usr/lib/systemd/systemd-measure which
# is used by ukify as invoked with the `--measure` flag below. Not
# strictly required, but nice to have the measured PCR values in the
# output.
# rm /var/lib/pacman/db.lck && \
pacman -Sy --noconfirm systemd-ukify pesign openssl systemd-sysvcompat systemd && \
pacman -S --clean --noconfirm

FROM buildroot-base as kernel
# Must be passed
ARG COMPOSEFS_FSVERITY

RUN --mount=type=secret,id=key \
--mount=type=secret,id=cert \
--mount=type=bind,from=base,target=/target \
# Should be generated externally
test -n "${COMPOSEFS_FSVERITY}" && \
# Inject the composefs kernel argument and specify a root with the x86_64 DPS UUID.
# TODO: Discoverable partition fleshed out, or drop root UUID as systemd-stub extension
# TODO: https://github.com/containers/composefs-rs/issues/183
cmdline="composefs=${COMPOSEFS_FSVERITY} console=ttyS0,115200n8 root=gpt-auto enforcing=0 rw" && \
# pesign uses NSS database so create it from input cert/key
mkdir pesign && \
certutil -N -d pesign --empty-password && \
openssl pkcs12 -export -password 'pass:' -inkey /run/secrets/key -in /run/secrets/cert -out db.p12 && \
pk12util -i db.p12 -W '' -d pesign && \
subject=$(openssl x509 -in /run/secrets/cert -subject | grep '^subject=CN=' | sed 's/^subject=CN=//') && \
kver=$(cd /target/usr/lib/modules && echo *) && \
ukify build \
--linux "/target/usr/lib/modules/$kver/vmlinuz" \
--initrd "/target/usr/lib/modules/$kver/initramfs.img" \
--uname="${kver}" \
--cmdline "${cmdline}" \
--os-release "@/target/usr/lib/os-release" \
--signtool pesign \
--secureboot-certificate-dir "pesign" \
--secureboot-certificate-name "${subject}" \
--measure \
--json pretty \
--output "/boot/$kver.efi" && \
# Sign systemd-boot as well
sdboot="/usr/lib/systemd/boot/efi/systemd-bootx64.efi" && \
pesign \
--certdir "pesign" \
--certificate "${subject}" \
--in "${sdboot}" \
--out "${sdboot}.signed" \
--sign && \
mv "${sdboot}.signed" "${sdboot}"
# EOF

FROM base as final

RUN --mount=type=bind,from=kernel,target=/run/kernel \
kver=$(cd /usr/lib/modules && echo *) && \
mkdir -p /boot/EFI/Linux && \
# We put the UKI in /boot for now due to composefs verity not being the
# same due to mtime of /usr/lib/modules being changed
target=/boot/EFI/Linux/$kver.efi && \
cp /run/kernel/boot/$kver.efi $target && \
# And remove the defaults
rm -v /usr/lib/modules/${kver}/{vmlinuz,initramfs.img} && \
# Symlink into the /usr/lib/modules location
ln -sr $target /usr/lib/modules/${kver}/$(basename $kver.efi) && \
bootc container lint # --fatal-warnings, warnings no need to be fatel :))

FROM base as final-final
COPY --from=final /boot /boot
# Override the default
LABEL containers.bootc=sealed
50 changes: 45 additions & 5 deletions Justfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,45 @@
image_name := env("BUILD_IMAGE_NAME", "arch-bootc")
image_tag := env("BUILD_IMAGE_TAG", "latest")
base_dir := env("BUILD_BASE_DIR", ".")
base_dir := env("BUILD_BASE_DIR", "/tmp")
filesystem := env("BUILD_FILESYSTEM", "ext4")

build-containerfile $image_name=image_name:
sudo podman build -t "${image_name}:latest" .
# variant can be either "ostree" or "composefs-sealeduki"
# "ostree" here just means the image is "unsealed" and just gets tagged
variant := env("BUILD_VARIANT", "ostree")

namespace := env("BUILD_NAMESPACE", "bootcrew")
sudo := env("BUILD_ELEVATE", "sudo")
just_exe := just_executable()

enroll-secboot-key:
#!/usr/bin/bash
ENROLLMENT_PASSWORD=""
SECUREBOOT_KEY=keys/db.cer
"{{sudo}}" mokutil --timeout -1
echo -e "$ENROLLMENT_PASSWORD\n$ENROLLMENT_PASSWORD" | "{{sudo}}" mokutil --import "$SECUREBOOT_KEY"
echo 'At next reboot, the mokutil UEFI menu UI will be displayed (*QWERTY* keyboard input and navigation).\nThen, select "Enroll MOK", and input "bootcrew" as the password'

gen-secboot-keys:
#!/usr/bin/env bash
set -xeuo pipefail

openssl req -quiet -newkey rsa:4096 -nodes -keyout keys/db.key -new -x509 -sha256 -days 3650 -subj '/CN=Arch Bootc Signature Database key/' -out keys/db.crt
openssl x509 -outform DER -in keys/db.crt -out keys/db.cer

build-containerfile $image_name=image_name $variant=variant:
#!/usr/bin/env bash
set -xeuo pipefail

{{sudo}} podman build -t "localhost/${image_name}_unsealed" .
# TODO: we can make this a CLI program with better UX: https://github.com/bootc-dev/bootc/issues/1498
{{sudo}} ./build-sealed "${variant}" "localhost/${image_name}_unsealed" "${image_name}" "keys"


fix-var-containers-selinux:
{{sudo}} restorecon -RFv /var/lib/containers/storage

bootc *ARGS:
sudo podman run \
{{sudo}} podman run \
--rm --privileged --pid=host \
-it \
-v /sys/fs/selinux:/sys/fs/selinux \
Expand All @@ -19,9 +51,17 @@ bootc *ARGS:
--security-opt label=type:unconfined_t \
"{{image_name}}:{{image_tag}}" bootc {{ARGS}}


# installs on a physical target device
install-image $target_device $filesystem=filesystem:
#!/usr/bin/env bash
set -xeuo pipefail
{{just_exe}} bootc install to-disk --composefs-backend --filesystem "${filesystem}" --wipe --bootloader systemd {{target_device}}

# installs onto an img file for testing in a VM
generate-bootable-image $base_dir=base_dir $filesystem=filesystem:
#!/usr/bin/env bash
if [ ! -e "${base_dir}/bootable.img" ] ; then
fallocate -l 20G "${base_dir}/bootable.img"
fi
just bootc install to-disk --composefs-backend --via-loopback /data/bootable.img --filesystem "${filesystem}" --wipe --bootloader systemd
{{just_exe}} bootc install to-disk --composefs-backend --via-loopback /data/bootable.img --filesystem "${filesystem}" --wipe --bootloader systemd
66 changes: 66 additions & 0 deletions build-sealed
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/bin/bash
set -euo pipefail
# This should turn into https://github.com/bootc-dev/bootc/issues/1498

variant=$1
shift
# The un-sealed container image we want to use
input_image=$1
shift
# The output container image
output_image=$1
shift
# Optional directory with secure boot keys; if none are provided, then we'll
# generate some under target/
secureboot=${1:-}

runv() {
set -x
"$@"
}

case $variant in
ostree)
# Nothing to do
echo "Not building a sealed image; forwarding tag"
runv podman tag $input_image $output_image
exit 0
;;
composefs-sealeduki*)
;;
*)
echo "Unknown variant=$variant" 1>&2; exit 1
;;
esac


graphroot=$(podman system info -f '{{.Store.GraphRoot}}')
echo "Computing composefs digest..."
cfs_digest=$(podman run --rm --privileged --read-only --security-opt=label=disable -v /sys:/sys:ro --net=none \
-v ${graphroot}:/run/host-container-storage:ro --tmpfs /var "$input_image" bootc container compute-composefs-digest)

if test -z "${secureboot}"; then
secureboot=$(pwd)/keys
mkdir -p ${secureboot}
cd $secureboot
if test '!' -f db.cer; then
echo "Generating test Secure Boot keys"
systemd-id128 new -u > GUID.txt

openssl req -quiet -newkey rsa:4096 -nodes -keyout PK.key -new -x509 -sha256 -days 3650 -subj '/CN=Test Platform Key/' -out PK.crt
openssl x509 -outform DER -in PK.crt -out PK.cer

openssl req -quiet -newkey rsa:4096 -nodes -keyout KEK.key -new -x509 -sha256 -days 3650 -subj '/CN=Test Key Exchange Key/' -out KEK.crt
openssl x509 -outform DER -in KEK.crt -out KEK.cer

openssl req -quiet -newkey rsa:4096 -nodes -keyout db.key -new -x509 -sha256 -days 3650 -subj '/CN=Test Signature Database key/' -out db.crt
openssl x509 -outform DER -in db.crt -out db.cer
else
echo "Reusing Secure Boot keys in ${secureboot}"
fi
cd -
fi

runv podman build -t $output_image --build-arg=COMPOSEFS_FSVERITY=${cfs_digest} --build-arg=base=${input_image} \
--secret=id=key,src=${secureboot}/db.key \
--secret=id=cert,src=${secureboot}/db.crt -f Dockerfile.cfsuki .