From debee560188b9e6b4f27a6656f15980f1e77a14e Mon Sep 17 00:00:00 2001 From: user <303926+HarryR@users.noreply.github.com> Date: Mon, 9 Feb 2026 19:13:05 +0530 Subject: [PATCH 1/3] Made it work on GCP, upgrade to Fedora Core 41 for kernel + systemd-stub --- Cargo.lock | 2 +- Dockerfile.build | 3 +- Dockerfile.dev | 2 +- Makefile | 9 +- tools/build-uki/Makefile | 85 +++++--- tools/build-uki/build.sh | 120 +++++++----- tools/build-uki/init | 8 +- tools/build-uki/udhcpc.script | 6 +- tools/publish/gcp/.gitignore | 4 + tools/publish/gcp/.gitkeep | 0 tools/publish/gcp/NOTES.md | 151 +++++++++++++++ tools/publish/gcp/create-image.sh | 278 +++++++++++++++++++++++++++ tools/publish/gcp/get-console.sh | 94 +++++++++ tools/publish/gcp/install-gcloud.sh | 170 ++++++++++++++++ tools/publish/gcp/launch-instance.sh | 187 ++++++++++++++++++ tools/qemu-test/boot.sh | 12 +- 16 files changed, 1046 insertions(+), 85 deletions(-) create mode 100644 tools/publish/gcp/.gitignore delete mode 100644 tools/publish/gcp/.gitkeep create mode 100644 tools/publish/gcp/NOTES.md create mode 100755 tools/publish/gcp/create-image.sh create mode 100755 tools/publish/gcp/get-console.sh create mode 100755 tools/publish/gcp/install-gcloud.sh create mode 100755 tools/publish/gcp/launch-instance.sh diff --git a/Cargo.lock b/Cargo.lock index 20d34e7..6bb6099 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1655,7 +1655,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "vaportpm-attest" version = "0.1.0" -source = "git+https://github.com/lockboot/vaportpm#785438f42e76111a33116535ecfa523e0400a9f2" +source = "git+https://github.com/lockboot/vaportpm#eb643add961b72c8aa43a7674ff824d9db02646b" dependencies = [ "anyhow", "base64", diff --git a/Dockerfile.build b/Dockerfile.build index 5e4a561..f8a0d28 100644 --- a/Dockerfile.build +++ b/Dockerfile.build @@ -26,7 +26,8 @@ RUN apt-get -qq update && \ sbsigntool \ util-linux \ uuid-runtime \ - wget && \ + wget \ + xz-utils && \ pip3 install --break-system-packages virt-firmware # Reproducible builds environment diff --git a/Dockerfile.dev b/Dockerfile.dev index 1aa1baa..8eb8f45 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -21,7 +21,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get -qq update && \ dnsmasq \ swtpm swtpm-tools \ tpm2-tools xxd \ - gh + gh jq RUN groupadd -g 1000 vscode-dc && useradd -u 1000 -g 1000 -d /src -s /bin/bash vscode-dc diff --git a/Makefile b/Makefile index d657977..6870de7 100644 --- a/Makefile +++ b/Makefile @@ -36,8 +36,11 @@ tools/build-uki/%/busybox: tools/build-uki/%/stub.efi: $(MAKE) -C tools/build-uki $*/stub.efi -tools/build-uki/%/kernel.rpm: - $(MAKE) -C tools/build-uki $*/kernel.rpm +tools/build-uki/%/kernel-core.rpm: + $(MAKE) -C tools/build-uki $*/kernel-core.rpm + +tools/build-uki/%/kernel-modules-core.rpm: + $(MAKE) -C tools/build-uki $*/kernel-modules-core.rpm tools/qemu-test/%: $(MAKE) -C tools/qemu-test $* @@ -130,7 +133,7 @@ docker-shell-dev: docker-build-dev # Build the UKI and boot disk for a specific architecture # This creates: UKI, disk image with EFI boot structure -tools/build-uki/%/boot.disk: tools/build-uki/%/busybox tools/build-uki/%/stage1 tools/build-uki/%/stub.efi tools/build-uki/%/kernel.rpm tools/build-uki/keys/db.crt +tools/build-uki/%/boot.disk: tools/build-uki/%/busybox tools/build-uki/%/stage1 tools/build-uki/%/stub.efi tools/build-uki/%/kernel-core.rpm tools/build-uki/%/kernel-modules-core.rpm tools/build-uki/keys/db.crt $(DOCKER_RUN) $(DOCKER_OPT_DOCKER) -e ARCH=$* \ $(BUILD_IMAGE) ./tools/build-uki/build.sh diff --git a/tools/build-uki/Makefile b/tools/build-uki/Makefile index 4d67dd0..fed1b17 100644 --- a/tools/build-uki/Makefile +++ b/tools/build-uki/Makefile @@ -12,29 +12,40 @@ BUSYBOX_APK := busybox-static-$(BUSYBOX_VERSION).apk BUSYBOX_URL_x86_64 := $(ALPINE_BASE_URL)/x86_64/$(BUSYBOX_APK) BUSYBOX_URL_aarch64 := $(ALPINE_BASE_URL)/aarch64/$(BUSYBOX_APK) -# systemd-boot-unsigned from Amazon Linux (provides EFI stub) +# systemd-boot-unsigned from Fedora 41 (provides EFI stub) +# v256 supports PE vmlinuz on aarch64 (LoadImage/StartImage instead of direct jump) # x86_64 provides: /usr/lib/systemd/boot/efi/linuxx64.efi.stub # aarch64 provides: /usr/lib/systemd/boot/efi/linuxaa64.efi.stub -SYSTEMD_BOOT_VERSION := 252.23-8.amzn2023 -SYSTEMD_BOOT_SHA256_x86_64 := d036e8fa194d4d660ef05fd2d4f12037a251fee0f20adfd6a45992d83532bbc9 -SYSTEMD_BOOT_SHA256_aarch64 := ad89706bf213071aa3053fa23a19452bac5d95ab618f32ace3e485eea0e6eea3 +SYSTEMD_BOOT_VERSION := 256.17-1.fc41 +SYSTEMD_BOOT_SHA256_x86_64 := ec5ce2a3e685750a7743cf933ed9ff583fe4e90cef743c76349a158631b3dae5 +SYSTEMD_BOOT_SHA256_aarch64 := f188ea9f918be9c2314c4208c0c247bd6dc7eae96df85fa9e3c672619853ddcf SYSTEMD_BOOT_RPM_x86_64 := systemd-boot-unsigned-$(SYSTEMD_BOOT_VERSION).x86_64.rpm SYSTEMD_BOOT_RPM_aarch64 := systemd-boot-unsigned-$(SYSTEMD_BOOT_VERSION).aarch64.rpm -SYSTEMD_BOOT_URL_x86_64 := https://cdn.amazonlinux.com/al2023/blobstore/$(SYSTEMD_BOOT_SHA256_x86_64)/$(SYSTEMD_BOOT_RPM_x86_64) -SYSTEMD_BOOT_URL_aarch64 := https://cdn.amazonlinux.com/al2023/blobstore/$(SYSTEMD_BOOT_SHA256_aarch64)/$(SYSTEMD_BOOT_RPM_aarch64) - -# Amazon Linux 2023 kernel 6.12 and modules -AMAZON_KERNEL_PACKAGE := kernel6.12 -AMAZON_KERNEL_SHA256_x86_64 := 2583dc9eb7ab114238aaecbbe76ab3d0bb650a8ed0ef0b666b54573dc4c1da5e -AMAZON_KERNEL_SHA256_aarch64 := 5eba5a6cffb9a5798533834bff1cb39d5d8303a454dc639c97179914ecafa7fb -AMAZON_KERNEL_VERSION := 6.12.53-69.119.amzn2023 -AMAZON_KERNEL_RPM_x86_64 := $(AMAZON_KERNEL_PACKAGE)-$(AMAZON_KERNEL_VERSION).x86_64.rpm -AMAZON_KERNEL_RPM_aarch64 := $(AMAZON_KERNEL_PACKAGE)-$(AMAZON_KERNEL_VERSION).aarch64.rpm -AMAZON_KERNEL_URL_x86_64 := https://cdn.amazonlinux.com/al2023/blobstore/$(AMAZON_KERNEL_SHA256_x86_64)/$(AMAZON_KERNEL_RPM_x86_64) -AMAZON_KERNEL_URL_aarch64 := https://cdn.amazonlinux.com/al2023/blobstore/$(AMAZON_KERNEL_SHA256_aarch64)/$(AMAZON_KERNEL_RPM_aarch64) +SYSTEMD_BOOT_URL_x86_64 := https://kojipkgs.fedoraproject.org/packages/systemd/256.17/1.fc41/x86_64/$(SYSTEMD_BOOT_RPM_x86_64) +SYSTEMD_BOOT_URL_aarch64 := https://kojipkgs.fedoraproject.org/packages/systemd/256.17/1.fc41/aarch64/$(SYSTEMD_BOOT_RPM_aarch64) + +# Fedora 41 kernel 6.12 (has both ena and gve drivers) +FEDORA_KERNEL_VERSION := 6.12.4-200.fc41 +FEDORA_KERNEL_BASE_URL := https://kojipkgs.fedoraproject.org/packages/kernel/6.12.4/200.fc41 + +# kernel-core (contains vmlinuz) +FEDORA_KERNEL_CORE_SHA256_x86_64 := f7df5e7d36b7ed4c311f358340fcf2ae414cd1b4b8ad8d8555bb5cae2403cfc3 +FEDORA_KERNEL_CORE_SHA256_aarch64 := 5494b00c95832eb52b39b306fd4ab912893824cd0419612fb52fc97643fa56cb +FEDORA_KERNEL_CORE_RPM_x86_64 := kernel-core-$(FEDORA_KERNEL_VERSION).x86_64.rpm +FEDORA_KERNEL_CORE_RPM_aarch64 := kernel-core-$(FEDORA_KERNEL_VERSION).aarch64.rpm +FEDORA_KERNEL_CORE_URL_x86_64 := $(FEDORA_KERNEL_BASE_URL)/x86_64/$(FEDORA_KERNEL_CORE_RPM_x86_64) +FEDORA_KERNEL_CORE_URL_aarch64 := $(FEDORA_KERNEL_BASE_URL)/aarch64/$(FEDORA_KERNEL_CORE_RPM_aarch64) + +# kernel-modules-core (contains core .ko modules including gve and ena) +FEDORA_KERNEL_MODULES_SHA256_x86_64 := 7d9e7d9f925de56f04c103a65011285d0ded2fc45a79ca1b300828316916200c +FEDORA_KERNEL_MODULES_SHA256_aarch64 := 4bbe3e2922b044519d126cc1a06f0416d3f4f26523f0bae75d5fe355d4abb7ac +FEDORA_KERNEL_MODULES_RPM_x86_64 := kernel-modules-core-$(FEDORA_KERNEL_VERSION).x86_64.rpm +FEDORA_KERNEL_MODULES_RPM_aarch64 := kernel-modules-core-$(FEDORA_KERNEL_VERSION).aarch64.rpm +FEDORA_KERNEL_MODULES_URL_x86_64 := $(FEDORA_KERNEL_BASE_URL)/x86_64/$(FEDORA_KERNEL_MODULES_RPM_x86_64) +FEDORA_KERNEL_MODULES_URL_aarch64 := $(FEDORA_KERNEL_BASE_URL)/aarch64/$(FEDORA_KERNEL_MODULES_RPM_aarch64) ARCHS = x86_64 aarch64 -TARGETS = busybox stub.efi kernel.rpm +TARGETS = busybox stub.efi kernel-core.rpm kernel-modules-core.rpm all: $(ARCHS) @@ -49,7 +60,7 @@ aarch64: $(addprefix aarch64/,$(TARGETS)) @tar --warning=no-unknown-keyword -xzOf $< bin/busybox.static > "$@" @chmod +x "$@" -# Download systemd-boot-unsigned RPM from Amazon Linux +# Download systemd-boot-unsigned RPM from Fedora x86_64/$(SYSTEMD_BOOT_RPM_x86_64): @./download-and-verify.sh "$@" "$(SYSTEMD_BOOT_SHA256_x86_64)" "$(SYSTEMD_BOOT_URL_x86_64)" @@ -67,22 +78,36 @@ aarch64/stub.efi: aarch64/$(SYSTEMD_BOOT_RPM_aarch64) @rpm2cpio $< | cpio -i --quiet --to-stdout ./usr/lib/systemd/boot/efi/linuxaa64.efi.stub > $@ @chmod +x $@ -# Download Amazon Linux kernel RPM from blobstore -x86_64/$(AMAZON_KERNEL_RPM_x86_64): - @./download-and-verify.sh "$@" "$(AMAZON_KERNEL_SHA256_x86_64)" "$(AMAZON_KERNEL_URL_x86_64)" +# Download Fedora kernel-core RPMs from kojipkgs +x86_64/$(FEDORA_KERNEL_CORE_RPM_x86_64): + @./download-and-verify.sh "$@" "$(FEDORA_KERNEL_CORE_SHA256_x86_64)" "$(FEDORA_KERNEL_CORE_URL_x86_64)" -x86_64/kernel.rpm: x86_64/$(AMAZON_KERNEL_RPM_x86_64) +x86_64/kernel-core.rpm: x86_64/$(FEDORA_KERNEL_CORE_RPM_x86_64) @ln -sf $(notdir $<) $@ -aarch64/$(AMAZON_KERNEL_RPM_aarch64): - @./download-and-verify.sh "$@" "$(AMAZON_KERNEL_SHA256_aarch64)" "$(AMAZON_KERNEL_URL_aarch64)" +aarch64/$(FEDORA_KERNEL_CORE_RPM_aarch64): + @./download-and-verify.sh "$@" "$(FEDORA_KERNEL_CORE_SHA256_aarch64)" "$(FEDORA_KERNEL_CORE_URL_aarch64)" -aarch64/kernel.rpm: aarch64/$(AMAZON_KERNEL_RPM_aarch64) +aarch64/kernel-core.rpm: aarch64/$(FEDORA_KERNEL_CORE_RPM_aarch64) @ln -sf $(notdir $<) $@ -# Retrieve package URLs from dnf +# Download Fedora kernel-modules-core RPMs from kojipkgs +x86_64/$(FEDORA_KERNEL_MODULES_RPM_x86_64): + @./download-and-verify.sh "$@" "$(FEDORA_KERNEL_MODULES_SHA256_x86_64)" "$(FEDORA_KERNEL_MODULES_URL_x86_64)" + +x86_64/kernel-modules-core.rpm: x86_64/$(FEDORA_KERNEL_MODULES_RPM_x86_64) + @ln -sf $(notdir $<) $@ + +aarch64/$(FEDORA_KERNEL_MODULES_RPM_aarch64): + @./download-and-verify.sh "$@" "$(FEDORA_KERNEL_MODULES_SHA256_aarch64)" "$(FEDORA_KERNEL_MODULES_URL_aarch64)" + +aarch64/kernel-modules-core.rpm: aarch64/$(FEDORA_KERNEL_MODULES_RPM_aarch64) + @ln -sf $(notdir $<) $@ + +# Retrieve package URLs from koji (for reference) kernel-hash-%: - docker run --rm --platform linux/amd64 amazonlinux:latest bash -c "dnf repoquery --forcearch=$* --arch=$* --location $(AMAZON_KERNEL_PACKAGE)-$(AMAZON_KERNEL_VERSION).$*" + @echo "Fedora kernel: $(FEDORA_KERNEL_PACKAGE)-$(FEDORA_KERNEL_VERSION).$*" + @echo "Download from: https://koji.fedoraproject.org/koji/buildinfo?buildID=2599252" clean: rm -rf x86_64 aarch64 @@ -94,5 +119,7 @@ check-urls: @curl -sfI "$(BUSYBOX_URL_aarch64)" > /dev/null && echo "OK: busybox aarch64" || echo "FAIL: busybox aarch64" @curl -sfI "$(SYSTEMD_BOOT_URL_x86_64)" > /dev/null && echo "OK: systemd-boot x86_64" || echo "FAIL: systemd-boot x86_64" @curl -sfI "$(SYSTEMD_BOOT_URL_aarch64)" > /dev/null && echo "OK: systemd-boot aarch64" || echo "FAIL: systemd-boot aarch64" - @curl -sfI "$(AMAZON_KERNEL_URL_x86_64)" > /dev/null && echo "OK: kernel x86_64" || echo "FAIL: kernel x86_64" - @curl -sfI "$(AMAZON_KERNEL_URL_aarch64)" > /dev/null && echo "OK: kernel aarch64" || echo "FAIL: kernel aarch64" + @curl -sfI "$(FEDORA_KERNEL_CORE_URL_x86_64)" > /dev/null && echo "OK: kernel-core x86_64" || echo "FAIL: kernel-core x86_64" + @curl -sfI "$(FEDORA_KERNEL_CORE_URL_aarch64)" > /dev/null && echo "OK: kernel-core aarch64" || echo "FAIL: kernel-core aarch64" + @curl -sfI "$(FEDORA_KERNEL_MODULES_URL_x86_64)" > /dev/null && echo "OK: kernel-modules-core x86_64" || echo "FAIL: kernel-modules-core x86_64" + @curl -sfI "$(FEDORA_KERNEL_MODULES_URL_aarch64)" > /dev/null && echo "OK: kernel-modules-core aarch64" || echo "FAIL: kernel-modules-core aarch64" diff --git a/tools/build-uki/build.sh b/tools/build-uki/build.sh index 4729817..48fb73f 100755 --- a/tools/build-uki/build.sh +++ b/tools/build-uki/build.sh @@ -10,7 +10,7 @@ KEYDIR="${SCRIPT_DIR}/keys" # Get architecture from environment (default to x86_64) ARCH=${ARCH:-x86_64} -echo "=== Building UKI for Amazon Linux 2023 (${ARCH}) ===" +echo "=== Building UKI for Fedora 41 (${ARCH}) ===" # Output directory (same as dependencies - everything self-contained) OUTPUT_DIR="${SCRIPT_DIR}/${ARCH}" @@ -28,20 +28,24 @@ else exit 1 fi -# Use systemd-boot stub extracted by Makefile from Amazon Linux RPM +# Use systemd-boot stub extracted by Makefile from Fedora 41 RPM (systemd v256) if [ ! -f "${OUTPUT_DIR}/stub.efi" ]; then echo "ERROR: systemd-boot stub not found at ${OUTPUT_DIR}/stub.efi" echo "Please run 'make tools/build-uki/${ARCH}/stub.efi' first" exit 1 fi -echo "Extracting kernel RPM..." -rpm2cpio "${OUTPUT_DIR}"/kernel6.12-*.rpm | (cd "${OUTPUT_DIR}/tmp" && cpio --quiet -idmu) +echo "Extracting kernel-core RPM (vmlinuz)..." +rpm2cpio "${OUTPUT_DIR}"/kernel-core-*.rpm | (cd "${OUTPUT_DIR}/tmp" && cpio --quiet -idmu) + +echo "Extracting kernel-modules-core RPM (core drivers including gve and ena)..." +rpm2cpio "${OUTPUT_DIR}"/kernel-modules-core-*.rpm | (cd "${OUTPUT_DIR}/tmp" && cpio --quiet -idmu) + if [ ! -d "${OUTPUT_DIR}/tmp/lib/modules" ]; then - echo "ERROR: kernel modules not found after extraction!" + echo "ERROR: kernel modules directory not found after extraction!" exit 1 fi -echo "Kernel extracted successfully" +echo "Kernel and modules extracted successfully" # Find the installed kernel version KERNEL_VERSION=$(ls "${OUTPUT_DIR}/tmp/lib/modules/" | head -n1) @@ -91,23 +95,10 @@ MODULES_SRC="${OUTPUT_DIR}/tmp/lib/modules/${KERNEL_VERSION}" MODULES_DST="${INITRAMFS_DIR}/lib/modules/${KERNEL_VERSION}" mkdir -p "${MODULES_DST}/kernel/drivers" -# Copy required modules for Nitro Enclave host -# Note: This is for the HOST EC2 instance, not the enclave itself +# Copy required modules for cloud instances (AWS EC2, GCP Confidential VMs) +# Note: Some modules like virtio, virtio_pci, hw_random, efivarfs are built into the kernel (=y) REQUIRED_MODULES=( - # EFI variable filesystem (for Secure Boot key management) - "fs/efivarfs/efivarfs.ko" - - # Virtio base modules - "drivers/virtio/virtio.ko" - "drivers/virtio/virtio_ring.ko" - - # Virtio PCI dependencies (must come before virtio_pci) - "drivers/virtio/virtio_pci_modern_dev.ko" - "drivers/virtio/virtio_pci_legacy_dev.ko" - "drivers/virtio/virtio_pci.ko" - - # Hardware RNG modules - "drivers/char/hw_random/rng-core.ko" + # Hardware RNG modules (base is built-in, vendor-specific are modules) "drivers/char/hw_random/intel-rng.ko" "drivers/char/hw_random/amd-rng.ko" @@ -116,14 +107,17 @@ REQUIRED_MODULES=( "drivers/net/net_failover.ko" "drivers/net/virtio_net.ko" - # EC2 ENA network driver (for real EC2 instances) - "drivers/amazon/net/ena/ena.ko" + # EC2 ENA network driver (for AWS EC2 instances) + "drivers/net/ethernet/amazon/ena/ena.ko" + + # GCP GVE network driver (for GCP Confidential VMs) + "drivers/net/ethernet/google/gve/gve.ko" # Vsock modules "net/vmw_vsock/vsock.ko" "net/vmw_vsock/vmw_vsock_virtio_transport_common.ko" - # Vhost dependencies (must come before vhost) + # Vhost dependencies "drivers/vhost/vhost_iotlb.ko" "drivers/vhost/vhost.ko" "drivers/vhost/vhost_vsock.ko" @@ -134,13 +128,20 @@ REQUIRED_MODULES=( ) for mod_path in "${REQUIRED_MODULES[@]}"; do + # Try both .ko and .ko.xz (Fedora compresses modules, need to decompress for busybox modprobe) if [ -f "${MODULES_SRC}/kernel/${mod_path}" ]; then mod_dir=$(dirname "${mod_path}") mkdir -p "${MODULES_DST}/kernel/${mod_dir}" cp "${MODULES_SRC}/kernel/${mod_path}" "${MODULES_DST}/kernel/${mod_dir}/" echo " Copied ${mod_path}" + elif [ -f "${MODULES_SRC}/kernel/${mod_path}.xz" ]; then + mod_dir=$(dirname "${mod_path}") + mkdir -p "${MODULES_DST}/kernel/${mod_dir}" + # Decompress xz modules for busybox modprobe (kernel doesn't support compressed modules) + xz -dc "${MODULES_SRC}/kernel/${mod_path}.xz" > "${MODULES_DST}/kernel/${mod_path}" + echo " Copied and decompressed ${mod_path}.xz" else - echo " Warning: Module ${mod_path} not found" + echo " Warning: Module ${mod_path} not found (may be built into kernel)" fi done @@ -201,16 +202,16 @@ UKI_PATH="${OUTPUT_DIR}/linux.efi" # Create cmdline file with architecture-specific console settings CMDLINE_PATH="${OUTPUT_DIR}/cmdline.txt" if [ "${ARCH}" = "x86_64" ]; then - # x86_64: ttyS0 for serial console - # Last console= becomes the primary output device - echo "console=tty0 console=ttyS0,115200n8 ro" > "${CMDLINE_PATH}" + # earlycon: auto-detect via ACPI SPCR table for early boot output + # console=ttyS0: EC2 PCI UART (only serial device, gets ttyS0) + # console=ttyS1: QEMU q35 ISA serial (default COM1 is ttyS0 with no backend, + # explicit isa-serial device becomes ttyS1) + echo "earlycon console=ttyS0,115200n8 console=ttyS1,115200n8 ro" > "${CMDLINE_PATH}" else - # aarch64: Support both QEMU (ttyAMA0/pl011) and EC2 (ttyS0/uart) - # QEMU SPCR: pl011,mmio,0x9000000 -> ttyAMA0 - # EC2 SPCR: uart,mmio,0x90a0000 -> ttyS0 - # Last console becomes primary, kernel uses whichever exists - #echo "earlyprintk=serial,ttyS0 console=ttyAMA0 ro" > "${CMDLINE_PATH}" - echo "console=tty0 console=ttyAMA0,115200n8 console=ttyS0,115200n8 ro" > "${CMDLINE_PATH}" + # earlycon: auto-detect via SPCR (arm64 also auto-registers SPCR as regular console) + # console=ttyAMA0: PL011 UART on QEMU virt + # console=ttyS0: 16550 PCI UART on EC2 Graviton and QEMU (PCI serial) + echo "earlycon console=ttyAMA0,115200n8 console=ttyS0,115200n8 ro" > "${CMDLINE_PATH}" fi UNAME_PATH="${OUTPUT_DIR}/uname.txt" @@ -219,15 +220,15 @@ echo "${KERNEL_VERSION}" > "${UNAME_PATH}" # Set SOURCE_DATE_EPOCH for reproducible builds export SOURCE_DATE_EPOCH=0 -# Generate version with year.month format (e.g., 25.11) +# Generate version with year.month format (e.g., 26.02) YEAR_MONTH=$(date +%y.%m) -OSREL_VERSION="${YEAR_MONTH} ${ARCH} (Amazon Linux 2023)" +OSREL_VERSION="${YEAR_MONTH} ${ARCH} (Fedora 41)" OSREL_NAME="Lock.Boot" # Combine stub + kernel + initrd into UKI # Use os-release from extracted RPM if available, otherwise create minimal one OSREL_PATH="${OUTPUT_DIR}/os-release" echo "ID=lockboot" > "${OSREL_PATH}" -echo "VERSION_ID=${YEAR_MONTH}.al2023" >> "${OSREL_PATH}" +echo "VERSION_ID=${YEAR_MONTH}.fc41" >> "${OSREL_PATH}" echo "VERSION=\"${OSREL_VERSION}\"" >> "${OSREL_PATH}" echo "NAME=\"${OSREL_NAME}\"" >> "${OSREL_PATH}" echo "PRETTY_NAME=\"${OSREL_NAME} ${OSREL_VERSION}\"" >> "${OSREL_PATH}" @@ -239,14 +240,47 @@ CMDLINE_HASH=$(sha256sum "${CMDLINE_PATH}" | cut -d' ' -f1 | cut -c1-8) BUILD_ID="kernel-${KERNEL_VERSION}-${KERNEL_HASH}.cmdline-${CMDLINE_HASH}.initrd-${INITRD_HASH}" echo "BUILD_ID=${BUILD_ID}" >> "${OSREL_PATH}" +# Calculate section VMAs dynamically based on the stub's layout. +# Newer systemd stubs (v256+) use high VMAs that differ from v252's layout, +# so we append our sections after the stub's last section (same approach as ukify). +SECTION_ALIGN=0x1000 +NEXT_VMA=$(${OBJCOPY%%objcopy}objdump -h "${STUB_PATH}" | \ + awk '/^ [0-9]/ { print $3, $4 }' | \ + while read size_hex vma_hex; do + echo $(( 0x${vma_hex} + 0x${size_hex} )) + done | sort -n | tail -1) +NEXT_VMA=$(( (NEXT_VMA + SECTION_ALIGN - 1) / SECTION_ALIGN * SECTION_ALIGN )) + +# Place each section sequentially, aligned to SECTION_ALIGN +calc_next_vma() { + local current_vma=$1 + local file=$2 + local size + size=$(stat -c%s "$file") + echo $(( (current_vma + size + SECTION_ALIGN - 1) / SECTION_ALIGN * SECTION_ALIGN )) +} + +OSREL_VMA=${NEXT_VMA} +CMDLINE_VMA=$(calc_next_vma ${OSREL_VMA} "${OSREL_PATH}") +UNAME_VMA=$(calc_next_vma ${CMDLINE_VMA} "${CMDLINE_PATH}") +LINUX_VMA=$(calc_next_vma ${UNAME_VMA} "${UNAME_PATH}") +INITRD_VMA=$(calc_next_vma ${LINUX_VMA} "${KERNEL_PATH}") + +echo "UKI section layout:" +printf " .osrel @ 0x%x\n" ${OSREL_VMA} +printf " .cmdline @ 0x%x\n" ${CMDLINE_VMA} +printf " .uname @ 0x%x\n" ${UNAME_VMA} +printf " .linux @ 0x%x\n" ${LINUX_VMA} +printf " .initrd @ 0x%x\n" ${INITRD_VMA} + ${OBJCOPY} \ --input-target="${PE_FORMAT}" \ --output-target="${PE_FORMAT}" \ - --add-section .osrel="${OSREL_PATH}" --change-section-vma .osrel=0x20000 \ - --add-section .cmdline="${CMDLINE_PATH}" --change-section-vma .cmdline=0x30000 \ - --add-section .uname="${UNAME_PATH}" --change-section-vma .uname=0x40000 \ - --add-section .linux="${KERNEL_PATH}" --change-section-vma .linux=0x2000000 \ - --add-section .initrd="${INITRD_PATH}" --change-section-vma .initrd=0x3000000 \ + --add-section .osrel="${OSREL_PATH}" --change-section-vma .osrel=${OSREL_VMA} \ + --add-section .cmdline="${CMDLINE_PATH}" --change-section-vma .cmdline=${CMDLINE_VMA} \ + --add-section .uname="${UNAME_PATH}" --change-section-vma .uname=${UNAME_VMA} \ + --add-section .linux="${KERNEL_PATH}" --change-section-vma .linux=${LINUX_VMA} \ + --add-section .initrd="${INITRD_PATH}" --change-section-vma .initrd=${INITRD_VMA} \ "${STUB_PATH}" "${UKI_PATH}" sbsign --key "$KEYDIR/db.crt.key" --cert "$KEYDIR/db.crt" --output "${UKI_PATH}" "${UKI_PATH}" diff --git a/tools/build-uki/init b/tools/build-uki/init index 298085a..759627b 100644 --- a/tools/build-uki/init +++ b/tools/build-uki/init @@ -7,8 +7,9 @@ $BB mount -t sysfs none /sys $BB mount -t devtmpfs none /dev $BB mount -t tmpfs none /tmp -$BB modprobe efivarfs -$BB mount -t efivarfs none /sys/firmware/efi/efivars +# Skip efivarfs - not needed after firmware stage +# $BB modprobe efivarfs +# $BB mount -t efivarfs none /sys/firmware/efi/efivars # Load hardware RNG modules $BB modprobe rng-core @@ -19,7 +20,8 @@ $BB modprobe failover $BB modprobe net_failover $BB modprobe virtio_pci $BB modprobe virtio_net -$BB modprobe ena +$BB modprobe gve 2>/dev/null || echo "[WARN] gve driver not available" +$BB modprobe ena 2>/dev/null || true $BB modprobe vsock $BB modprobe vhost $BB modprobe vhost_vsock diff --git a/tools/build-uki/udhcpc.script b/tools/build-uki/udhcpc.script index 5965c33..bc1e5aa 100644 --- a/tools/build-uki/udhcpc.script +++ b/tools/build-uki/udhcpc.script @@ -10,7 +10,11 @@ case "$1" in ;; bound|renew) /bin/busybox ip addr add $ip/$mask dev $interface - [ -n "$router" ] && /bin/busybox ip route add default via $router dev $interface + if [ -n "$router" ]; then + # Add link-local route to gateway (required for /32 subnets like GCP) + /bin/busybox ip route add $router dev $interface 2>/dev/null || true + /bin/busybox ip route add default via $router dev $interface + fi # Set hostname from DHCP (option 12) [ -n "$hostname" ] && /bin/busybox hostname "$hostname" diff --git a/tools/publish/gcp/.gitignore b/tools/publish/gcp/.gitignore new file mode 100644 index 0000000..1e86b33 --- /dev/null +++ b/tools/publish/gcp/.gitignore @@ -0,0 +1,4 @@ +# Local gcloud SDK installation +google-cloud-sdk/ +google-cloud-cli-*.tar.gz +gcloud diff --git a/tools/publish/gcp/.gitkeep b/tools/publish/gcp/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tools/publish/gcp/NOTES.md b/tools/publish/gcp/NOTES.md new file mode 100644 index 0000000..c9dee80 --- /dev/null +++ b/tools/publish/gcp/NOTES.md @@ -0,0 +1,151 @@ +# GCP Confidential VM Image Publishing + +## Trust Model Requirements + +**CRITICAL**: Live migration is **DISABLED** for Confidential VMs. Live migration would break the trust model by allowing the hypervisor to access decrypted memory during migration. + +- `--maintenance-policy=TERMINATE` is **REQUIRED** +- Never use `SEV_LIVE_MIGRATABLE` guest OS feature +- Instances will terminate (not migrate) during host maintenance + +## Confidential VM Technologies + +### x86_64 +- **AMD SEV** (1st gen): Memory encryption with single key +- **AMD SEV-SNP** (Secure Nested Paging): Memory encryption + integrity protection +- **Intel TDX** (Trust Domain Extensions): Intel's confidential computing technology + +GCP automatically selects based on: +- Machine type (N2D = SEV, C2D/N2D = SEV-SNP, C3 = TDX) +- Region/zone availability + +### ARM64 (aarch64) +- Uses ARM TrustZone-based confidential computing +- No special guest OS features required +- Automatically enabled with `--confidential-compute` flag + +## Image Creation Workflow + +**Two-step approach** (required for custom guest OS features): + +```bash +# Use the provided script +./create-image.sh [version] + +# Example: +./create-image.sh my-gcp-project x86_64 v0.1.0 +./create-image.sh my-gcp-project x86_64 local +``` + +### What it does internally + +1. Uploads the disk image to Google Cloud Storage (GCS) +2. Creates custom image from GCS with specific guest OS features +3. Reuses existing GCS upload if it already exists (idempotent) + +**Why not use `gcloud compute images import`?** +The import command requires a predefined `--os` type (like "ubuntu-2204") and doesn't support custom guest OS features for custom UKI images. We need full control over UEFI_COMPATIBLE, SEV_CAPABLE, etc. + +### Manual approach (if needed) + +For **x86_64** (SEV/SEV-SNP/TDX): +```bash +# Upload to GCS +gcloud storage cp boot.disk gs://${BUCKET}/${PATH} + +# Create image with custom features +gcloud compute images create ${IMAGE_NAME} \ + --source-uri=gs://${BUCKET}/${PATH} \ + --guest-os-features=UEFI_COMPATIBLE,SEV_CAPABLE,SEV_SNP_CAPABLE,GVNIC \ + --family=${IMAGE_FAMILY} +``` + +For **aarch64**: +```bash +# Upload to GCS +gcloud storage cp boot.disk gs://${BUCKET}/${PATH} + +# Create image with custom features +gcloud compute images create ${IMAGE_NAME} \ + --source-uri=gs://${BUCKET}/${PATH} \ + --guest-os-features=UEFI_COMPATIBLE,GVNIC \ + --family=${IMAGE_FAMILY} +``` + +### Guest OS Features Explained + +- `UEFI_COMPATIBLE` - **REQUIRED** for UEFI boot +- `SEV_CAPABLE` - Mark as compatible with AMD SEV Confidential VMs +- `SEV_SNP_CAPABLE` - Mark as compatible with AMD SEV-SNP Confidential VMs (recommended) +- `GVNIC` - Use Google Virtual NIC (better performance, recommended) +- ~~`SEV_LIVE_MIGRATABLE`~~ - **NEVER USE** (breaks trust model) + +## Instance Creation + +### Quick Launch (Recommended) + +Use the provided launch script which has all Confidential VM settings baked in: + +```bash +# Launch with required user-data configuration +./launch-instance.sh my-vm us-central1-a n2d-standard-2 lockboot-x86_64 config.json + +# With additional network settings +./launch-instance.sh my-vm us-central1-a t2a-standard-1 lockboot-aarch64 config.json \ + --network-interface=network=my-vpc,subnet=my-subnet +``` + +**Note**: The `config.json` user-data file is **REQUIRED** - it contains the lockboot configuration for stage2 download and verification. + +The script automatically: +- Uses your default gcloud project +- Validates user-data file exists +- Validates machine type supports Confidential Compute +- Sets all required security flags +- Prevents accidental live migration + +### Manual Instance Creation + +```bash +gcloud compute instances create ${INSTANCE_NAME} \ + --zone=${ZONE} \ + --machine-type=${MACHINE_TYPE} \ + --image=${IMAGE_NAME} \ + --confidential-compute \ + --maintenance-policy=TERMINATE \ + --shielded-secure-boot \ + --shielded-vtpm \ + --shielded-integrity-monitoring \ + --metadata-from-file=user-data=config.json +``` + +### Machine Type Selection (x86_64) + +For **SEV-SNP** (recommended): +- N2D series: `n2d-standard-*` (AMD Milan) +- C2D series: `c2d-standard-*` (AMD Milan, compute-optimized) + +For **TDX** (Intel): +- C3 series: `c3-standard-*` (Intel Sapphire Rapids) + +For **ARM64**: +- T2A series: `t2a-standard-*` (Ampere Altra) + +### Required Flags Explained + +- `--confidential-compute` - **REQUIRED** enables memory encryption +- `--maintenance-policy=TERMINATE` - **REQUIRED** prevents live migration +- `--shielded-secure-boot` - Enables UEFI Secure Boot validation +- `--shielded-vtpm` - Provides virtual TPM 2.0 (measured boot) +- `--shielded-integrity-monitoring` - Baseline integrity measurement + +## Key Differences from AWS + +| Feature | AWS (Nitro) | GCP (Confidential VM) | +|---------|-------------|----------------------| +| UEFI vars | Provided via `--uefi-data` | Managed by platform | +| vTPM | Enabled via `--tpm-support v2.0` | Automatic with `--shielded-vtpm` | +| Memory encryption | Nitro Enclaves | SEV/SEV-SNP/TDX | +| Metadata | IMDSv2 | Metadata server | +| User data | `--user-data` | `--metadata-from-file=user-data=` | +| User data required | Optional | **Required** (stage1 config) | diff --git a/tools/publish/gcp/create-image.sh b/tools/publish/gcp/create-image.sh new file mode 100755 index 0000000..8911351 --- /dev/null +++ b/tools/publish/gcp/create-image.sh @@ -0,0 +1,278 @@ +#!/bin/bash +# Script to create a GCP custom image from disk image (one-shot) +# Usage: ./create-image.sh [version] +# version can be a GitHub release tag (e.g., v0.1.0) or 'local' to use locally built files + +set -euo pipefail + +# Find gcloud: check local installation first, then system +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -x "${SCRIPT_DIR}/gcloud" ]; then + GCLOUD="${SCRIPT_DIR}/gcloud" +elif [ -x "${SCRIPT_DIR}/google-cloud-sdk/bin/gcloud" ]; then + GCLOUD="${SCRIPT_DIR}/google-cloud-sdk/bin/gcloud" +elif command -v gcloud &> /dev/null; then + GCLOUD="gcloud" +else + echo "Error: gcloud not found. Install it with:" + echo " ./install-gcloud.sh" + exit 1 +fi + +if [ $# -lt 2 ]; then + echo "Usage: $0 [version]" + echo "Example: $0 my-gcp-project x86_64 v0.1.0 # Use GitHub release" + echo "Example: $0 my-gcp-project x86_64 local # Use local build" + exit 1 +fi + +PROJECT="$1" +ARCH="$2" +VERSION="${3:-local}" + +# Validate architecture +if [ "${ARCH}" != "x86_64" ] && [ "${ARCH}" != "aarch64" ]; then + echo "Error: Architecture must be either 'x86_64' or 'aarch64'" + exit 1 +fi + +# Map to GCP architecture naming +if [ "${ARCH}" == "aarch64" ]; then + GCP_ARCH="ARM64" +else + GCP_ARCH="X86_64" +fi + +# Download and verify from GitHub release or use local files +if [ "${VERSION}" != "local" ]; then + echo "=== Downloading release ${VERSION} from GitHub ===" + + # Create temporary directory for downloads + TEMP_DIR=$(mktemp -d) + trap "rm -rf ${TEMP_DIR}" EXIT + + # Determine GitHub repository from git remote + GH_REPO=$(git remote get-url origin | sed 's/.*github.com[:/]\(.*\)\.git/\1/' || echo "") + if [ -z "${GH_REPO}" ]; then + echo "Error: Could not determine GitHub repository" + exit 1 + fi + + echo "Repository: ${GH_REPO}" + echo "Downloading ${ARCH}.zip from release ${VERSION}..." + + # Download the zip file + gh release download "${VERSION}" \ + --repo "${GH_REPO}" \ + --pattern "${ARCH}.zip" \ + --dir "${TEMP_DIR}" + + echo "Verifying attestation..." + # Verify the attestation using gh + gh attestation verify "${TEMP_DIR}/${ARCH}.zip" \ + --repo "${GH_REPO}" \ + || { echo "Error: Attestation verification failed"; exit 1; } + + echo "Extracting files..." + unzip -q "${TEMP_DIR}/${ARCH}.zip" -d "${TEMP_DIR}" + + # Use extracted files + WORK_DIR="${TEMP_DIR}" + echo "Using verified release files from ${VERSION}" +else + echo "=== Using local build files ===" + # Get script directory and compute repo root + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" + WORK_DIR="${REPO_ROOT}/tools/build-uki/${ARCH}" +fi + +IMAGE_FILE="${WORK_DIR}/boot.disk" +OS_RELEASE_FILE="${WORK_DIR}/os-release" + +# Get keys directory (same for local and release builds) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" +KEYS_DIR="${REPO_ROOT}/tools/build-uki/keys" + +if [ ! -f "${OS_RELEASE_FILE}" ]; then + echo "Error: ${OS_RELEASE_FILE} not found" + exit 1 +fi + +if [ ! -f "${IMAGE_FILE}" ]; then + echo "Error: ${IMAGE_FILE} not found" + exit 1 +fi + +# Check for UEFI Secure Boot keys +if [ ! -f "${KEYS_DIR}/PK.cer" ] || [ ! -f "${KEYS_DIR}/KEK.cer" ] || [ ! -f "${KEYS_DIR}/db.cer" ]; then + echo "Error: UEFI Secure Boot keys not found in ${KEYS_DIR}" + echo "Required: PK.cer, KEK.cer, db.cer" + exit 1 +fi + +# Source os-release to get ID, VERSION_ID, BUILD_ID, NAME, VERSION +source "${OS_RELEASE_FILE}" + +# Create simple image name: lockboot-x86-64-26-02-al2023 +# (Only one release per month, so BUILD_ID hash not needed in name) +# GCP image names: lowercase, numbers, hyphens only (no underscores) +VERSION_DASH=$(echo "${VERSION_ID}" | tr '.' '-') +ARCH_DASH=$(echo "${ARCH}" | tr '_' '-') +IMAGE_NAME="${ID}-${ARCH_DASH}-${VERSION_DASH}" +IMAGE_DESC="${PRETTY_NAME} version: ${VERSION_ID} build: ${BUILD_ID} arch: ${ARCH}" +IMAGE_FAMILY="${ID}" # Just "lockboot" - family groups all architectures together + +# Map to GCP architecture naming for --architecture flag +if [ "${ARCH}" == "aarch64" ]; then + GCP_ARCH="ARM64" +else + GCP_ARCH="X86_64" +fi + +# Set guest OS features based on architecture +if [ "${ARCH}" == "x86_64" ]; then + # x86_64: Enable SEV and SEV-SNP for Confidential VMs + # GVNIC required for Confidential Compute (gve driver needed in kernel) + GUEST_OS_FEATURES="UEFI_COMPATIBLE,SEV_CAPABLE,SEV_SNP_CAPABLE,GVNIC" +else + # aarch64: UEFI + GVNIC (Confidential Compute automatic) + GUEST_OS_FEATURES="UEFI_COMPATIBLE,GVNIC" +fi + +echo "" +echo "=== Checking for existing image ===" +EXISTING_IMAGE=$(${GCLOUD} compute images list \ + --project="${PROJECT}" \ + --filter="name=${IMAGE_NAME}" \ + --format="value(name)" \ + 2>/dev/null || echo "") + +if [ -n "${EXISTING_IMAGE}" ]; then + echo "Found existing image: ${EXISTING_IMAGE}" + echo "Image already exists, skipping creation" + IMAGE_FINAL="${EXISTING_IMAGE}" +else + echo "No existing image found, creating new one..." + echo "" + + # Use bucket name from os-release ID + GCS_BUCKET="${ID}" + # GCP requires .tar.gz format containing disk.raw + # Path: gs://lockboot/26.02.al2023/kernel-...-7f25e43a.tar.gz + GCS_PATH="${VERSION_ID}/${BUILD_ID}.tar.gz" + GCS_URI="gs://${GCS_BUCKET}/${GCS_PATH}" + + echo "=== Checking/Creating Google Cloud Storage Bucket ===" + echo "Bucket: ${GCS_BUCKET}" + echo "" + + # Check if bucket exists, create if not + if ${GCLOUD} storage buckets describe "gs://${GCS_BUCKET}" --project="${PROJECT}" &>/dev/null; then + echo "Bucket already exists: gs://${GCS_BUCKET}" + else + # Get default region from gcloud config (falls back to compute/region or us-central1) + DEFAULT_LOCATION=$(${GCLOUD} config get-value compute/region 2>/dev/null || echo "us-central1") + echo "Creating bucket: gs://${GCS_BUCKET} in ${DEFAULT_LOCATION}" + ${GCLOUD} storage buckets create "gs://${GCS_BUCKET}" \ + --project="${PROJECT}" \ + --location="${DEFAULT_LOCATION}" \ + --uniform-bucket-level-access + fi + + echo "" + echo "=== Preparing disk image for GCP ===" + echo "GCP requires: .tar.gz containing disk.raw" + echo "" + + # Create temporary tar.gz if it doesn't exist in GCS + if ${GCLOUD} storage ls "${GCS_URI}" --project="${PROJECT}" &>/dev/null; then + echo "Image already exists in GCS: ${GCS_URI}" + else + # Create tar.gz with disk.raw inside (GCP requirement) + # Use --format=oldgnu and -S for sparse file handling + TEMP_DIR=$(mktemp -d) + trap "rm -rf ${TEMP_DIR}" EXIT + + echo "Creating tar.gz with disk.raw inside..." + cp "${IMAGE_FILE}" "${TEMP_DIR}/disk.raw" + + echo "Compressing (this may take a minute)..." + tar --format=oldgnu -Sczf "${TEMP_DIR}/${BUILD_ID}.tar.gz" -C "${TEMP_DIR}" disk.raw + + echo "" + echo "=== Uploading to Google Cloud Storage ===" + echo "Path: ${GCS_PATH}" + echo "" + + echo "Uploading ${BUILD_ID}.tar.gz to ${GCS_URI} with metadata..." + # Upload with metadata from os-release + ${GCLOUD} storage cp "${TEMP_DIR}/${BUILD_ID}.tar.gz" "${GCS_URI}" \ + --project="${PROJECT}" \ + --custom-metadata="version-id=${VERSION_ID},build-id=${BUILD_ID},name=${NAME},pretty-name=${PRETTY_NAME},id=${ID},arch=${ARCH}" + fi + + echo "" + echo "=== Creating GCP Image from GCS ===" + echo "Image name: ${IMAGE_NAME}" + echo "Family: ${IMAGE_FAMILY}" + echo "Architecture: ${GCP_ARCH}" + echo "Guest OS features: ${GUEST_OS_FEATURES}" + echo "UEFI Secure Boot: Custom keys (PK, KEK, db)" + echo "" + + # Create image from GCS with custom guest OS features and Secure Boot keys + # Note: Full build details are in the image name and description + ${GCLOUD} compute images create "${IMAGE_NAME}" \ + --project="${PROJECT}" \ + --source-uri="${GCS_URI}" \ + --guest-os-features="${GUEST_OS_FEATURES}" \ + --architecture="${GCP_ARCH}" \ + --family="${IMAGE_FAMILY}" \ + --description="${IMAGE_DESC}" \ + --platform-key-file="${KEYS_DIR}/PK.cer" \ + --key-exchange-key-file="${KEYS_DIR}/KEK.cer" \ + --signature-database-file="${KEYS_DIR}/db.cer" + + IMAGE_FINAL="${IMAGE_NAME}" +fi + +echo "" +echo "=== Image Created Successfully ===" +echo "Image: ${IMAGE_FINAL}" +echo "Project: ${PROJECT}" +echo "Family: ${IMAGE_FAMILY}" +echo "Architecture: ${GCP_ARCH}" +echo "" + +# Get default zone from gcloud config +DEFAULT_ZONE=$(${GCLOUD} config get-value compute/zone 2>/dev/null || echo "us-central1-a") + +# Suggest appropriate machine type based on architecture +if [ "${ARCH}" == "aarch64" ]; then + MACHINE_TYPE="t2a-standard-1" + TECH="ARM TrustZone" +else + MACHINE_TYPE="n2d-standard-2" + TECH="AMD SEV-SNP" +fi + +echo "Launch a Confidential VM instance using:" +echo "" +echo " ./launch-instance.sh my-instance ${DEFAULT_ZONE} ${MACHINE_TYPE} ${IMAGE_FINAL} config.json" +echo "" +echo "Or use gcloud directly:" +echo " ${GCLOUD} compute instances create my-instance \\" +echo " --zone=${DEFAULT_ZONE} \\" +echo " --machine-type=${MACHINE_TYPE} \\" +echo " --image=${IMAGE_FINAL} \\" +echo " --metadata-from-file=user-data=config.json \\" +echo " --confidential-compute-type=SEV \\" +echo " --maintenance-policy=TERMINATE \\" +echo " --shielded-secure-boot \\" +echo " --shielded-vtpm \\" +echo " --shielded-integrity-monitoring" +echo "" +echo "Confidential compute technology: ${TECH}" +echo "Default zone: ${DEFAULT_ZONE} (configure with: ${GCLOUD} config set compute/zone )" diff --git a/tools/publish/gcp/get-console.sh b/tools/publish/gcp/get-console.sh new file mode 100755 index 0000000..7245ae9 --- /dev/null +++ b/tools/publish/gcp/get-console.sh @@ -0,0 +1,94 @@ +#!/bin/bash +# Script to fetch serial console output from a Confidential VM instance +# Usage: ./get-console.sh [options] + +set -euo pipefail + +# Find gcloud: check local installation first, then system +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -x "${SCRIPT_DIR}/gcloud" ]; then + GCLOUD="${SCRIPT_DIR}/gcloud" +elif [ -x "${SCRIPT_DIR}/google-cloud-sdk/bin/gcloud" ]; then + GCLOUD="${SCRIPT_DIR}/google-cloud-sdk/bin/gcloud" +elif command -v gcloud &> /dev/null; then + GCLOUD="gcloud" +else + echo "Error: gcloud not found. Install it with:" + echo " ./install-gcloud.sh" + exit 1 +fi + +if [ $# -lt 2 ]; then + echo "Usage: $0 [--follow|--tail N]" + echo "" + echo "Examples:" + echo " # Get full console output" + echo " $0 my-vm us-central1-a" + echo "" + echo " # Get last 50 lines" + echo " $0 my-vm us-central1-a --tail 50" + echo "" + echo " # Follow console output (poll every 2 seconds)" + echo " $0 my-vm us-central1-a --follow" + echo "" + echo "Useful for viewing stage2 attestation output dumped to console" + echo "" + echo "Documentation:" + echo " https://cloud.google.com/compute/docs/troubleshooting/viewing-serial-port-output" + exit 1 +fi + +INSTANCE_NAME="$1" +ZONE="$2" +MODE="${3:-full}" + +# Get current project +PROJECT=$(${GCLOUD} config get-value project 2>/dev/null || echo "") +if [ -z "${PROJECT}" ]; then + echo "Error: No default project set. Run: ${GCLOUD} config set project " + exit 1 +fi + +case "${MODE}" in + --follow) + echo "Following console output for ${INSTANCE_NAME} (Ctrl+C to stop)..." + echo "---" + + LAST_LINE=0 + while true; do + OUTPUT=$(${GCLOUD} compute instances get-serial-port-output "${INSTANCE_NAME}" \ + --zone="${ZONE}" \ + --project="${PROJECT}" \ + --start="${LAST_LINE}" 2>/dev/null || echo "") + + if [ -n "${OUTPUT}" ]; then + echo "${OUTPUT}" + # Count total lines to update start position + NEW_LINES=$(echo "${OUTPUT}" | wc -l) + LAST_LINE=$((LAST_LINE + NEW_LINES)) + fi + + sleep 2 + done + ;; + + --tail) + if [ $# -lt 4 ]; then + echo "Error: --tail requires a line count" + echo "Example: $0 ${INSTANCE_NAME} ${ZONE} --tail 50" + exit 1 + fi + + LINES="$4" + ${GCLOUD} compute instances get-serial-port-output "${INSTANCE_NAME}" \ + --zone="${ZONE}" \ + --project="${PROJECT}" | tail -n "${LINES}" + ;; + + *) + # Full output + ${GCLOUD} compute instances get-serial-port-output "${INSTANCE_NAME}" \ + --zone="${ZONE}" \ + --project="${PROJECT}" + ;; +esac diff --git a/tools/publish/gcp/install-gcloud.sh b/tools/publish/gcp/install-gcloud.sh new file mode 100755 index 0000000..e0e1831 --- /dev/null +++ b/tools/publish/gcp/install-gcloud.sh @@ -0,0 +1,170 @@ +#!/bin/bash +# Script to download and install gcloud CLI locally in this directory +# Usage: ./install-gcloud.sh [--init|--no-init] [--force] +# --init Automatically run gcloud init after installation +# --no-init Skip gcloud init after installation +# --force Force reinstall if already installed +# (no flags: prompt user for both) + +set -euo pipefail + +# Parse flags +AUTO_INIT="" +FORCE_REINSTALL=false + +for arg in "$@"; do + case $arg in + --init) + AUTO_INIT="yes" + shift + ;; + --no-init) + AUTO_INIT="no" + shift + ;; + --force) + FORCE_REINSTALL=true + shift + ;; + *) + echo "Usage: $0 [--init|--no-init] [--force]" + echo " --init Automatically run gcloud init" + echo " --no-init Skip gcloud init" + echo " --force Force reinstall if already installed" + exit 1 + ;; + esac +done + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +INSTALL_DIR="${SCRIPT_DIR}/google-cloud-sdk" +GCLOUD_SYMLINK="${SCRIPT_DIR}/gcloud" + +echo "=== Google Cloud SDK Installer ===" +echo "Installing to: ${INSTALL_DIR}" +echo "" + +# Check if already installed locally +if [ -x "${INSTALL_DIR}/bin/gcloud" ]; then + CURRENT_VERSION=$(${INSTALL_DIR}/bin/gcloud version --format="value(version)" 2>/dev/null || echo "unknown") + echo "gcloud is already installed locally: ${CURRENT_VERSION}" + echo "" + + if [ "${FORCE_REINSTALL}" = true ]; then + echo "Force reinstall requested, removing existing installation..." + rm -rf "${INSTALL_DIR}" + else + read -p "Reinstall anyway? (y/N) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 0 + fi + rm -rf "${INSTALL_DIR}" + fi +fi + +# Detect OS and architecture +OS=$(uname -s | tr '[:upper:]' '[:lower:]') +ARCH=$(uname -m) + +case "${OS}" in + linux) + case "${ARCH}" in + x86_64) + PACKAGE="google-cloud-cli-linux-x86_64.tar.gz" + ;; + aarch64|arm64) + PACKAGE="google-cloud-cli-linux-arm.tar.gz" + ;; + *) + echo "Error: Unsupported architecture: ${ARCH}" + exit 1 + ;; + esac + ;; + darwin) + case "${ARCH}" in + x86_64) + PACKAGE="google-cloud-cli-darwin-x86_64.tar.gz" + ;; + arm64) + PACKAGE="google-cloud-cli-darwin-arm.tar.gz" + ;; + *) + echo "Error: Unsupported architecture: ${ARCH}" + exit 1 + ;; + esac + ;; + *) + echo "Error: Unsupported OS: ${OS}" + echo "For Windows, download from: https://cloud.google.com/sdk/docs/install" + exit 1 + ;; +esac + +DOWNLOAD_URL="https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/${PACKAGE}" +ARCHIVE_PATH="${SCRIPT_DIR}/${PACKAGE}" + +echo "Detected platform: ${OS} ${ARCH}" +echo "Package: ${PACKAGE}" +echo "" + +# Download archive if it doesn't exist +if [ -f "${ARCHIVE_PATH}" ]; then + echo "Using cached archive: ${ARCHIVE_PATH}" +else + echo "Downloading gcloud SDK..." + curl -L -o "${ARCHIVE_PATH}" "${DOWNLOAD_URL}" +fi + +echo "Extracting to ${SCRIPT_DIR}..." +tar -xzf "${ARCHIVE_PATH}" -C "${SCRIPT_DIR}" + +# Create symlink to gcloud binary +echo "Creating symlink: ${GCLOUD_SYMLINK} -> ${INSTALL_DIR}/bin/gcloud" +ln -sf "${INSTALL_DIR}/bin/gcloud" "${GCLOUD_SYMLINK}" + +echo "" +echo "=== Installation Complete ===" +echo "" +echo "gcloud installed to: ${INSTALL_DIR}/bin/gcloud" +echo "Symlink created: ./gcloud" +echo "" + +# Handle gcloud init based on flags or prompt +case "${AUTO_INIT}" in + yes) + echo "Running gcloud init..." + "${INSTALL_DIR}/bin/gcloud" init + ;; + no) + echo "Skipping gcloud init." + echo "" + echo "Initialize gcloud later with:" + echo " ./gcloud init" + echo "" + echo "Or set project manually:" + echo " ./gcloud config set project " + echo " ./gcloud auth login" + ;; + *) + # Prompt user + read -p "Initialize gcloud now? (authenticate and set project) (y/N) " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + "${INSTALL_DIR}/bin/gcloud" init + else + echo "" + echo "Initialize gcloud later with:" + echo " ./gcloud init" + echo "" + echo "Or set project manually:" + echo " ./gcloud config set project " + echo " ./gcloud auth login" + fi + ;; +esac + +echo "" +echo "The scripts in this directory will automatically use this local gcloud installation." diff --git a/tools/publish/gcp/launch-instance.sh b/tools/publish/gcp/launch-instance.sh new file mode 100755 index 0000000..e570a13 --- /dev/null +++ b/tools/publish/gcp/launch-instance.sh @@ -0,0 +1,187 @@ +#!/bin/bash +# Script to launch a Confidential VM instance with all required security settings +# Usage: ./launch-instance.sh [additional-gcloud-options...] +# Uses default gcloud project from active configuration + +set -euo pipefail + +# Find gcloud: check local installation first, then system +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -x "${SCRIPT_DIR}/gcloud" ]; then + GCLOUD="${SCRIPT_DIR}/gcloud" +elif [ -x "${SCRIPT_DIR}/google-cloud-sdk/bin/gcloud" ]; then + GCLOUD="${SCRIPT_DIR}/google-cloud-sdk/bin/gcloud" +elif command -v gcloud &> /dev/null; then + GCLOUD="gcloud" +else + echo "Error: gcloud not found. Install it with:" + echo " ./install-gcloud.sh" + exit 1 +fi + +if [ $# -lt 5 ]; then + echo "Usage: $0 [additional-options...]" + echo "" + echo "Examples:" + echo " # Launch x86_64 instance" + echo " $0 my-vm us-central1-a n2d-standard-2 lockboot-x86_64 config.json" + echo "" + echo " # Launch aarch64 instance with extra network settings" + echo " $0 my-vm us-central1-a t2a-standard-1 lockboot-aarch64 config.json --network=my-vpc" + echo "" + echo "Recommended machine types:" + echo " x86_64 SEV-SNP: n2d-standard-2, n2d-standard-4, c2d-standard-*" + echo " x86_64 TDX: c3-standard-*" + echo " aarch64: t2a-standard-1, t2a-standard-2, t2a-standard-4" + echo "" + echo "Common zones: us-central1-a, us-east1-b, europe-west1-b" + echo "" + echo "REQUIRED: user-data-file must contain lockboot configuration (JSON)" + echo "" + echo "Documentation:" + echo " Confidential VMs: https://cloud.google.com/confidential-computing/confidential-vm/docs" + echo " Machine types: https://cloud.google.com/compute/docs/machine-resource" + echo " Zones/Regions: https://cloud.google.com/compute/docs/regions-zones" + echo " Shielded VMs: https://cloud.google.com/security/shielded-cloud/shielded-vm" + echo " Metadata: https://cloud.google.com/compute/docs/metadata/setting-custom-metadata" + exit 1 +fi + +INSTANCE_NAME="$1" +ZONE="$2" +MACHINE_TYPE="$3" +IMAGE="$4" +USER_DATA_FILE="$5" +shift 5 # Remove first 5 args, remaining are passed through + +# Validate user-data file exists +if [ ! -f "${USER_DATA_FILE}" ]; then + echo "Error: User data file '${USER_DATA_FILE}' not found" + echo "Lockboot requires a configuration file for stage2 download and verification" + exit 1 +fi + +# Get current project from gcloud config +PROJECT=$(${GCLOUD} config get-value project 2>/dev/null || echo "") +if [ -z "${PROJECT}" ]; then + echo "Error: No default project set. Run: ${GCLOUD} config set project " + exit 1 +fi + +echo "=== Launching Confidential VM Instance ===" +echo "Instance: ${INSTANCE_NAME}" +echo "Project: ${PROJECT}" +echo "Zone: ${ZONE}" +echo "Machine: ${MACHINE_TYPE}" +echo "Image: ${IMAGE}" +echo "User-data: ${USER_DATA_FILE}" +echo "" + +# Validate machine type supports Confidential Compute +if [[ "${MACHINE_TYPE}" =~ ^n2d- ]] || [[ "${MACHINE_TYPE}" =~ ^c2d- ]]; then + TECH="AMD SEV-SNP" +elif [[ "${MACHINE_TYPE}" =~ ^c3- ]]; then + TECH="Intel TDX" +elif [[ "${MACHINE_TYPE}" =~ ^t2a- ]]; then + TECH="ARM TrustZone" +else + echo "Warning: Machine type '${MACHINE_TYPE}' may not support Confidential Computing" + echo "Recommended: n2d-*, c2d-*, c3-*, or t2a-*" + read -p "Continue anyway? (y/N) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 + fi + TECH="Unknown" +fi + +echo "Confidential Compute: ${TECH}" +echo "" + +# Check if instance already exists +EXISTING=$(${GCLOUD} compute instances list \ + --project="${PROJECT}" \ + --filter="name=${INSTANCE_NAME} AND zone:${ZONE}" \ + --format="value(name)" \ + 2>/dev/null || echo "") + +if [ -n "${EXISTING}" ]; then + echo "Error: Instance '${INSTANCE_NAME}' already exists in zone ${ZONE}" + exit 1 +fi + +echo "Creating instance with REQUIRED Confidential VM settings:" +echo " ✓ --confidential-compute-type (enables memory encryption)" +echo " ✓ --maintenance-policy=TERMINATE (prevents live migration)" +echo " ✓ --shielded-secure-boot (UEFI Secure Boot)" +echo " ✓ --shielded-vtpm (virtual TPM 2.0)" +echo " ✓ --shielded-integrity-monitoring" +echo " ✓ --boot-disk-type=pd-standard (cheapest, loaded once into memory)" +echo " ✓ --boot-disk-auto-delete (cleanup on instance delete)" +echo "" + +# Determine confidential compute type based on machine type +if [[ "${MACHINE_TYPE}" =~ ^c3- ]]; then + CONF_COMPUTE_TYPE="TDX" +elif [[ "${MACHINE_TYPE}" =~ ^(n2d-|c2d-) ]]; then + CONF_COMPUTE_TYPE="SEV" +elif [[ "${MACHINE_TYPE}" =~ ^t2a- ]]; then + # ARM doesn't need explicit type + CONF_COMPUTE_TYPE="" +else + CONF_COMPUTE_TYPE="SEV" # Default to SEV +fi + +# Launch instance with all required Confidential VM settings +if [ -n "${CONF_COMPUTE_TYPE}" ]; then + ${GCLOUD} compute instances create "${INSTANCE_NAME}" \ + --project="${PROJECT}" \ + --zone="${ZONE}" \ + --machine-type="${MACHINE_TYPE}" \ + --image="${IMAGE}" \ + --network-interface=nic-type=GVNIC \ + --boot-disk-type=pd-standard \ + --boot-disk-auto-delete \ + --metadata-from-file=user-data="${USER_DATA_FILE}" \ + --metadata=serial-port-enable=true \ + --confidential-compute-type="${CONF_COMPUTE_TYPE}" \ + --maintenance-policy=TERMINATE \ + --shielded-secure-boot \ + --shielded-vtpm \ + --shielded-integrity-monitoring \ + "$@" +else + # ARM: use old flag for now (TODO: check if ARM needs specific type) + ${GCLOUD} compute instances create "${INSTANCE_NAME}" \ + --project="${PROJECT}" \ + --zone="${ZONE}" \ + --machine-type="${MACHINE_TYPE}" \ + --image="${IMAGE}" \ + --network-interface=nic-type=GVNIC \ + --boot-disk-type=pd-standard \ + --boot-disk-auto-delete \ + --metadata-from-file=user-data="${USER_DATA_FILE}" \ + --metadata=serial-port-enable=true \ + --confidential-compute \ + --maintenance-policy=TERMINATE \ + --shielded-secure-boot \ + --shielded-vtpm \ + --shielded-integrity-monitoring \ + "$@" +fi + +echo "" +echo "=== Instance Created Successfully ===" +echo "" +echo "Connect to instance:" +echo " ${GCLOUD} compute ssh ${INSTANCE_NAME} --zone=${ZONE}" +echo "" +echo "View serial console:" +echo " ${GCLOUD} compute instances get-serial-port-output ${INSTANCE_NAME} --zone=${ZONE}" +echo " Or use: ./get-console.sh ${INSTANCE_NAME} ${ZONE}" +echo "" +echo "View instance details:" +echo " ${GCLOUD} compute instances describe ${INSTANCE_NAME} --zone=${ZONE}" +echo "" +echo "IMPORTANT: This instance will TERMINATE (not migrate) during maintenance events." +echo "This is required to maintain the Confidential Computing trust model." diff --git a/tools/qemu-test/boot.sh b/tools/qemu-test/boot.sh index 6b675c7..9ef5537 100755 --- a/tools/qemu-test/boot.sh +++ b/tools/qemu-test/boot.sh @@ -50,6 +50,9 @@ if [ "${ARCH}" = "x86_64" ]; then QEMU_MACHINE="-machine q35,smm=on" QEMU_CPU="" QEMU_EXTRA="-enable-kvm" + # ISA serial at 0x3f8 = ttyS0, matches how GRUB/Fedora expects serial on x86_64 + QEMU_SERIAL="-serial none" + QEMU_SERIAL_DEVICE="-device isa-serial,chardev=char0" TPM_DEVICE="tpm-tis" elif [ "${ARCH}" = "aarch64" ]; then OVMF_CODE="/usr/share/AAVMF/AAVMF_CODE.fd" @@ -64,7 +67,10 @@ elif [ "${ARCH}" = "aarch64" ]; then # -cpu max (all features, but may cause issues) QEMU_CPU="-cpu cortex-a72" #QEMU_CPU="" - QEMU_EXTRA="" # ttyS0 + QEMU_EXTRA="" + # -serial none: PL011 exists but with no backend, PCI serial is ttyS0 + QEMU_SERIAL="-serial none" + QEMU_SERIAL_DEVICE="-device pci-serial,id=serial0,chardev=char0" TPM_DEVICE="tpm-tis-device" else echo "Error: Unsupported architecture: ${ARCH}" @@ -190,8 +196,8 @@ ${QEMU_CMD} \ -netdev tap,id=net0,ifname=tap0,script=no \ -device virtio-net-pci,netdev=net0 \ -display none \ - -serial none \ + ${QEMU_SERIAL} \ -chardev stdio,mux=on,id=char0 \ - -device pci-serial,id=serial0,chardev=char0 \ + ${QEMU_SERIAL_DEVICE} \ -drive if=pflash,format=raw,unit=0,file="${OVMF_CODE}",readonly=on \ -drive if=pflash,format=raw,unit=1,file="${OVMF_VARS}" || true From e6827a830b3fd81ca04dd94ecdfb8274ccaea878 Mon Sep 17 00:00:00 2001 From: user <303926+HarryR@users.noreply.github.com> Date: Mon, 9 Feb 2026 20:04:38 +0530 Subject: [PATCH 2/3] minor tweaks to aws publish (vmdk compressed) + include UEFI certs in release archives --- .github/workflows/build.yml | 7 +++++++ tools/publish/ec2/create-ami.sh | 22 +++++++++++++++++----- tools/publish/gcp/create-image.sh | 8 ++++++-- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5c3de93..fff0d93 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,6 +22,11 @@ jobs: - name: Build UKI for ${{ matrix.arch }} run: make ${{ matrix.arch }} + - name: Copy public keys to arch directory + run: | + cp tools/build-uki/keys/*.cer tools/build-uki/${{ matrix.arch }}/ + cp tools/build-uki/keys/*.guid tools/build-uki/${{ matrix.arch }}/ + - name: Upload UKI artifacts for ${{ matrix.arch }} uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: @@ -32,6 +37,8 @@ jobs: tools/build-uki/${{ matrix.arch }}/stage1 tools/build-uki/${{ matrix.arch }}/os-release tools/build-uki/${{ matrix.arch }}/efi-vars.* + tools/build-uki/${{ matrix.arch }}/*.cer + tools/build-uki/${{ matrix.arch }}/*.guid release-docker: runs-on: ubuntu-latest diff --git a/tools/publish/ec2/create-ami.sh b/tools/publish/ec2/create-ami.sh index 275d1d3..f028e1d 100755 --- a/tools/publish/ec2/create-ami.sh +++ b/tools/publish/ec2/create-ami.sh @@ -126,12 +126,21 @@ else echo "No existing snapshot found, creating new one..." echo "" echo "=== Checking if image exists in S3 ===" + S3_KEY="${S3_KEY}.vmdk" if aws s3api head-object --bucket "${S3_BUCKET}" --key "${S3_KEY}" --region "${REGION}" &>/dev/null; then echo "Image already exists in S3: s3://${S3_BUCKET}/${S3_KEY}" else - echo "Uploading image to S3..." + echo "Converting raw disk to stream-optimized VMDK (compresses sparse regions)..." + VMDK_FILE="${WORK_DIR}/boot.vmdk" + qemu-img convert -f raw -O vmdk -o subformat=streamOptimized "${IMAGE_FILE}" "${VMDK_FILE}" + RAW_SIZE=$(stat -c%s "${IMAGE_FILE}" 2>/dev/null || stat -f%z "${IMAGE_FILE}") + VMDK_SIZE=$(stat -c%s "${VMDK_FILE}" 2>/dev/null || stat -f%z "${VMDK_FILE}") + echo "Compressed: $(( RAW_SIZE / 1024 / 1024 ))MB raw -> $(( VMDK_SIZE / 1024 / 1024 ))MB vmdk" + + echo "Uploading VMDK to S3..." echo "Bucket: s3://${S3_BUCKET}/${S3_KEY}" - aws s3 cp "${IMAGE_FILE}" "s3://${S3_BUCKET}/${S3_KEY}" --region "${REGION}" + aws s3 cp "${VMDK_FILE}" "s3://${S3_BUCKET}/${S3_KEY}" --region "${REGION}" + rm -f "${VMDK_FILE}" fi echo "" @@ -141,7 +150,7 @@ else cat > containers.json << EOF { "Description": "${SNAPSHOT_DESC}", - "Format": "raw", + "Format": "vmdk", "UserBucket": { "S3Bucket": "${S3_BUCKET}", "S3Key": "${S3_KEY}" @@ -252,10 +261,13 @@ echo "" # Suggest appropriate instance type based on architecture if [ "${ARCH}" == "aarch64" ]; then - INSTANCE_TYPE="t4g.micro" + INSTANCE_TYPE="c7g.medium" else INSTANCE_TYPE="c6i.large" fi -echo "You can now launch instances using:" +echo "Launch an instance:" echo " aws ec2 run-instances --image-id ${AMI_ID} --instance-type ${INSTANCE_TYPE} --region ${REGION} --user-data file://config.json" +echo "" +echo "Get serial console output:" +echo " aws ec2 get-console-output --output text --latest --region ${REGION} --instance-id " diff --git a/tools/publish/gcp/create-image.sh b/tools/publish/gcp/create-image.sh index 8911351..924b881 100755 --- a/tools/publish/gcp/create-image.sh +++ b/tools/publish/gcp/create-image.sh @@ -90,10 +90,14 @@ fi IMAGE_FILE="${WORK_DIR}/boot.disk" OS_RELEASE_FILE="${WORK_DIR}/os-release" -# Get keys directory (same for local and release builds) +# Get keys directory: from extracted release (flat) or from repo (keys/ subdir) SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" -KEYS_DIR="${REPO_ROOT}/tools/build-uki/keys" +if [ -f "${WORK_DIR}/db.cer" ]; then + KEYS_DIR="${WORK_DIR}" +else + KEYS_DIR="${REPO_ROOT}/tools/build-uki/keys" +fi if [ ! -f "${OS_RELEASE_FILE}" ]; then echo "Error: ${OS_RELEASE_FILE} not found" From 513f2602f145a6f865783fff9844fd975660364f Mon Sep 17 00:00:00 2001 From: user <303926+HarryR@users.noreply.github.com> Date: Mon, 9 Feb 2026 20:22:59 +0530 Subject: [PATCH 3/3] Update readme + notes on spot instances --- README.md | 32 ++++++++++++++++++++++------ tools/publish/ec2/create-ami.sh | 4 ++++ tools/publish/gcp/launch-instance.sh | 3 +++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index af56059..d1fe260 100644 --- a/README.md +++ b/README.md @@ -44,16 +44,36 @@ You can run any statically linked Linux ELF, but the minimal filesystem only has ## Cloud Deployment -Deploy the same config across AWS, GCP, or Azure: +Deploy the same config across AWS, GCP, or Azure. Publish scripts are provided in `tools/publish/`. + +### AWS EC2 + +Requires Nitro v4+ instances with TPM 2.0 and UEFI boot support: + +| Architecture | Tested Instance | Notes | +|---|---|---| +| x86_64 | `c6i.large` | Intel Xeon Gen 3, Nitro v4 | +| aarch64 | `c7g.medium` | Graviton 3, Nitro v4 | ```bash -# AWS -aws ec2 run-instances --user-data file://user-data.json --tpm-support v2.0 ... +tools/publish/ec2/create-ami.sh us-east-1 x86_64 local +``` + +### GCP Confidential VMs + +Requires Confidential VM instances with Shielded VM and custom Secure Boot keys. Uses GVE network driver (virtio-net not available on Confidential VMs). -# GCP -gcloud compute instances create --metadata user-data="$(cat user-data.json)" ... +| Architecture | Tested Instance | Notes | +|---|---|---| +| x86_64 | `n2d-standard-2` | AMD SEV-SNP | -# Azure +```bash +tools/publish/gcp/create-image.sh my-project x86_64 local +``` + +### Azure + +```bash az vm create --user-data "$(cat user-data.json | base64 -w0)" ... ``` diff --git a/tools/publish/ec2/create-ami.sh b/tools/publish/ec2/create-ami.sh index f028e1d..a1adc52 100755 --- a/tools/publish/ec2/create-ami.sh +++ b/tools/publish/ec2/create-ami.sh @@ -269,5 +269,9 @@ fi echo "Launch an instance:" echo " aws ec2 run-instances --image-id ${AMI_ID} --instance-type ${INSTANCE_TYPE} --region ${REGION} --user-data file://config.json" echo "" +echo "Launch with spot pricing (up to 90% cheaper):" +echo " aws ec2 run-instances --image-id ${AMI_ID} --instance-type ${INSTANCE_TYPE} --region ${REGION} --user-data file://config.json \\" +echo " --instance-market-options '{\"MarketType\":\"spot\",\"SpotOptions\":{\"SpotInstanceType\":\"one-time\"}}'" +echo "" echo "Get serial console output:" echo " aws ec2 get-console-output --output text --latest --region ${REGION} --instance-id " diff --git a/tools/publish/gcp/launch-instance.sh b/tools/publish/gcp/launch-instance.sh index e570a13..4bc6b63 100755 --- a/tools/publish/gcp/launch-instance.sh +++ b/tools/publish/gcp/launch-instance.sh @@ -183,5 +183,8 @@ echo "" echo "View instance details:" echo " ${GCLOUD} compute instances describe ${INSTANCE_NAME} --zone=${ZONE}" echo "" +echo "Launch with spot/preemptible pricing (up to 90% cheaper):" +echo " Add --provisioning-model=SPOT --instance-termination-action=DELETE to the create command" +echo "" echo "IMPORTANT: This instance will TERMINATE (not migrate) during maintenance events." echo "This is required to maintain the Confidential Computing trust model."