Skip to content
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
14 changes: 7 additions & 7 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ jobs:
with:
name: ${{ matrix.arch }}
path: |
${{ matrix.arch }}/boot.disk
${{ matrix.arch }}/busybox
${{ matrix.arch }}/stage1
${{ matrix.arch }}/os-release
${{ matrix.arch }}/efi-vars.*
tools/build-uki/${{ matrix.arch }}/boot.disk
tools/build-uki/${{ matrix.arch }}/busybox
tools/build-uki/${{ matrix.arch }}/stage1
tools/build-uki/${{ matrix.arch }}/os-release
tools/build-uki/${{ matrix.arch }}/efi-vars.*

release-docker:
runs-on: ubuntu-latest
Expand All @@ -51,13 +51,13 @@ jobs:
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: x86_64
path: x86_64
path: tools/build-uki/x86_64

- name: Download aarch64 artifacts
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: aarch64
path: aarch64
path: tools/build-uki/aarch64

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1
Expand Down
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ qemu-10.*
__pycache__
old
.docker
aarch64
x86_64
user-data.json
.bashrc
.bashrc
.lesshst
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 14 additions & 15 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
FROM lockboot:build

RUN mkdir -p -m 755 /etc/apt/keyrings \
&& out=$(mktemp) && wget -nv -O$out https://cli.github.com/packages/githubcli-archive-keyring.gpg \
&& cat $out | tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \
&& chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
&& mkdir -p -m 755 /etc/apt/sources.list.d \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null

# Install development tools and Claude Code
RUN DEBIAN_FRONTEND=noninteractive apt-get -qq update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
procps nano net-tools iputils-ping ca-certificates less xz-utils openssh-client bash-completion \
docker.io \
qemu-system-x86 \
qemu-system-arm \
qemu-utils qemu-system-x86 qemu-system-arm \
git \
vim \
build-essential \
gpg \
iptables \
iproute2 \
dnsmasq \
swtpm swtpm-tools
swtpm swtpm-tools \
tpm2-tools xxd \
gh

# Install Claude Code
#RUN curl -fsSL https://claude.ai/install.sh | bash
# Download the Claude Code binary manually
ENV CLAUDE_CODE_URL=https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819
RUN ARCH=$(uname -m | sed 's/x86_64/x64/; s/aarch64/arm64/') && \
wget -O claude-version ${CLAUDE_CODE_URL}/claude-code-releases/stable && \
VERSION=$(cat claude-version) && \
wget -O /usr/local/bin/claude "${CLAUDE_CODE_URL}/claude-code-releases/${VERSION}/linux-${ARCH}/claude" && \
chmod +x /usr/local/bin/claude
ENV DISABLE_AUTOUPDATER=1
RUN groupadd -g 1000 vscode-dc && useradd -u 1000 -g 1000 -d /src -s /bin/bash vscode-dc

# Development environment - keep same reproducible build settings from base
WORKDIR /src
4 changes: 2 additions & 2 deletions Dockerfile.runtime
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ ARG TARGETARCH

# Map Docker's TARGETARCH to our directory structure
# Create symlinks to map amd64->x86_64 and arm64->aarch64
COPY x86_64/ /binaries/x86_64/
COPY aarch64/ /binaries/aarch64/
COPY tools/build-uki/x86_64/ /binaries/x86_64/
COPY tools/build-uki/aarch64/ /binaries/aarch64/

# Create arch-specific directory based on TARGETARCH
RUN mkdir -p /target && \
Expand Down
55 changes: 28 additions & 27 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
.PRECIOUS: keys/% downloads/%
.PRECIOUS: tools/build-uki/keys/% tools/build-uki/%

all: build

ARCHS=x86_64 aarch64
build: $(ARCHS)

amd64 x86_64: x86_64/boot.disk
arm64 aarch64: aarch64/boot.disk
amd64 x86_64: tools/build-uki/x86_64/boot.disk
arm64 aarch64: tools/build-uki/aarch64/boot.disk

DEFAULT_STAGE2_URL = https://lockboot.s3.us-east-1.amazonaws.com/examples/stage2/user-data.json
user-data.json:
Expand All @@ -17,29 +17,30 @@ BUILD_IMAGE = lockboot:build
DEV_IMAGE = lockboot:dev
RUNTIME_IMAGE ?= lockboot:latest

keys/%:
$(MAKE) -C keys
tools/build-uki/keys/%:
$(MAKE) -C tools/build-uki/keys

clean:
rm -rf $(ARCHS)
rm -rf tools/build-uki/x86_64/boot.disk tools/build-uki/x86_64/stage1 tools/build-uki/x86_64/tmp tools/build-uki/x86_64/*.img tools/build-uki/x86_64/*.efi tools/build-uki/x86_64/config-* tools/build-uki/x86_64/efi-vars.ovmf
rm -rf tools/build-uki/aarch64/boot.disk tools/build-uki/aarch64/stage1 tools/build-uki/aarch64/tmp tools/build-uki/aarch64/*.img tools/build-uki/aarch64/*.efi tools/build-uki/aarch64/config-* tools/build-uki/aarch64/efi-vars.ovmf

distclean: clean
$(MAKE) -C downloads clean
$(MAKE) -C keys clean
$(MAKE) -C tools/build-uki clean
$(MAKE) -C tools/build-uki/keys clean
$(MAKE) -C tools/qemu-test clean

# Generate separate rules for each artifact to avoid grouped target behavior
define ARTIFACT_RULE
.PRECIOUS: %/$(1)
%/$(1)::
@mkdir -p $$*
@make -C downloads $$@
@cp downloads/$$@ $$@
endef
# Download dependencies via tools/build-uki Makefile
tools/build-uki/%/busybox:
$(MAKE) -C tools/build-uki $*/busybox

$(foreach artifact,busybox stub.efi kernel.rpm,$(eval $(call ARTIFACT_RULE,$(artifact))))
tools/build-uki/%/stub.efi:
$(MAKE) -C tools/build-uki $*/stub.efi

downloads/%:
$(MAKE) -C downloads $*
tools/build-uki/%/kernel.rpm:
$(MAKE) -C tools/build-uki $*/kernel.rpm

tools/qemu-test/%:
$(MAKE) -C tools/qemu-test $*


#####################################################################
Expand Down Expand Up @@ -72,15 +73,15 @@ docker-buildx-setup:
docker buildx inspect --bootstrap

# Build runtime image for current platform only and load into Docker
docker-runtime: x86_64/busybox x86_64/stage1 aarch64/busybox aarch64/stage1
docker-runtime: tools/build-uki/x86_64/busybox tools/build-uki/x86_64/stage1 tools/build-uki/aarch64/busybox tools/build-uki/aarch64/stage1
docker buildx build \
-f Dockerfile.runtime \
-t $(RUNTIME_IMAGE) \
--load \
.

# Build multi-arch and export to OCI tar (for local multi-arch without registry)
docker-runtime-oci: x86_64/busybox x86_64/stage1 aarch64/busybox aarch64/stage1
docker-runtime-oci: tools/build-uki/x86_64/busybox tools/build-uki/x86_64/stage1 tools/build-uki/aarch64/busybox tools/build-uki/aarch64/stage1
docker buildx build \
--platform linux/amd64,linux/arm64 \
-f Dockerfile.runtime \
Expand Down Expand Up @@ -129,17 +130,17 @@ 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
%/boot.disk: %/busybox %/stage1 %/stub.efi %/kernel.rpm 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.rpm tools/build-uki/keys/db.crt
$(DOCKER_RUN) $(DOCKER_OPT_DOCKER) -e ARCH=$* \
$(BUILD_IMAGE) ./scripts/build.sh
$(BUILD_IMAGE) ./tools/build-uki/build.sh

boot-%: downloads/ec2-metadata-mock-linux-amd64 %/boot.disk user-data.json
boot-%: tools/qemu-test/ec2-metadata-mock-linux-amd64 tools/build-uki/%/boot.disk user-data.json
$(DOCKER_RUN) -e ARCH=$* $(DOCKER_OPT_KVM) \
-e YES_INSIDE_DOCKER_DO_DANGEROUS_IPTABLES=1 --cap-add=NET_ADMIN --device=/dev/net/tun \
$(DEV_IMAGE) ./scripts/boot.sh
$(DEV_IMAGE) ./tools/qemu-test/boot.sh

%/stage1: docker-build-base
mkdir -p $*
tools/build-uki/%/stage1: docker-build-base
mkdir -p tools/build-uki/$*
$(DOCKER_RUN) -e ARCH=$* $(DOCKER_SAMEUSER) $(BUILD_IMAGE) \
bash -c "rustup target add $*-unknown-linux-musl && cargo build --release --locked --all --target $*-unknown-linux-musl"
cp target/$*-unknown-linux-musl/release/stage1 $@
Expand Down
12 changes: 12 additions & 0 deletions crates/stage1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,18 @@ Before executing stage2, the TPM PCRs are extended with cryptographic measuremen
| **PCR 14** | Stage2 Binary | SHA256 hash of the downloaded binary |
| **PCR 15** | Configuration | SHA256 hash of the entire JSON config |

## Attestation Trust Model

The attestation is generated in the **virgin state** - before any user-provided code runs and before PCRs are extended. This ordering is critical, but it only provides security guarantees because the AK is a **restricted signing key**.

A restricted signing key can only sign digests that the TPM itself produces. When the AK signs a `TPM2_Quote`, the quote contains the actual PCR values at signing time - generated internally by the TPM, not by software. This means even if stage2 has access to the AK handle, it can only produce quotes reflecting the *current* PCR state (with stage2 already measured into PCR14). It cannot forge a quote showing virgin-state PCRs.

Without the restricted key constraint, the trust model collapses into tautology: stage2 could simply sign "I'm in virgin state" and the attestation would prove nothing.

The attestation uses `H(H(binary) || H(config))` as the nonce - a hash of the binary hash concatenated with the config hash - binding the attestation to the specific intended workload. Once this virgin-state attestation exists, any future use of the same AK inherits this trust anchor. A verifier can reason: "this AK was attested in a clean PCR state with config X, therefore subsequent quotes from this AK come from a system that started from that trusted state."

The restricted signing constraint is imposed by cloud vTPMs (notably GCP), which limits the AK to operations like `TPM2_Quote`, `TPM2_Certify`, and `TPM2_CertifyCreation`. While this can feel limiting, it's precisely what makes the trust model sound.

## Building

From the repository root:
Expand Down
File renamed without changes.
File renamed without changes.
15 changes: 2 additions & 13 deletions downloads/Makefile → tools/build-uki/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,10 @@ AMAZON_KERNEL_RPM_aarch64 := $(AMAZON_KERNEL_PACKAGE)-$(AMAZON_KERNEL_VERSION).a
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)

# Amazon EC2 Metadata Mock
AEMM_VERSION := v1.13.0
AEMM_URL := https://github.com/aws/amazon-ec2-metadata-mock/releases/download/$(AEMM_VERSION)/ec2-metadata-mock-linux-amd64
AEMM_SHA256 := 4f89ddc71ac53ce540bda1f9c340526d558eed8e41349761f2798acf1b254950

ARCHS = x86_64 aarch64
TARGETS = busybox stub.efi kernel.rpm

all: $(ARCHS) ec2-metadata-mock-linux-amd64
all: $(ARCHS)

x86_64: $(addprefix x86_64/,$(TARGETS))
aarch64: $(addprefix aarch64/,$(TARGETS))
Expand Down Expand Up @@ -85,17 +80,12 @@ aarch64/$(AMAZON_KERNEL_RPM_aarch64):
aarch64/kernel.rpm: aarch64/$(AMAZON_KERNEL_RPM_aarch64)
@ln -sf $(notdir $<) $@

# Download Amazon EC2 Metadata Mock
ec2-metadata-mock-linux-amd64:
@./download-and-verify.sh "$@" "$(AEMM_SHA256)" "$(AEMM_URL)"
@chmod +x $@

# Retrieve package URLs from dnf
kernel-hash-%:
docker run --rm --platform linux/amd64 amazonlinux:latest bash -c "dnf repoquery --forcearch=$* --arch=$* --location $(AMAZON_KERNEL_PACKAGE)-$(AMAZON_KERNEL_VERSION).$*"

clean:
rm -rf x86_64 aarch64 ec2-metadata-mock-linux-amd64
rm -rf x86_64 aarch64

# Check all download URLs are still valid
check-urls:
Expand All @@ -106,4 +96,3 @@ check-urls:
@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 "$(AEMM_URL)" > /dev/null && echo "OK: ec2-metadata-mock" || echo "FAIL: ec2-metadata-mock"
20 changes: 10 additions & 10 deletions scripts/build.sh → tools/build-uki/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ set -euo pipefail
# Get the absolute path of the script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Setup secure boot keys
KEYDIR=keys
# Setup secure boot keys (relative to script directory)
KEYDIR="${SCRIPT_DIR}/keys"

# Get architecture from environment (default to x86_64)
ARCH=${ARCH:-x86_64}

echo "=== Building UKI for Amazon Linux 2023 (${ARCH}) ==="

# Create architecture-specific output directory
OUTPUT_DIR="${ARCH}"
# Output directory (same as dependencies - everything self-contained)
OUTPUT_DIR="${SCRIPT_DIR}/${ARCH}"
mkdir -p "${OUTPUT_DIR}/tmp"

# Determine the correct systemd-efistub filename, PE format, and objcopy command based on architecture
Expand All @@ -29,14 +29,14 @@ else
fi

# Use systemd-boot stub extracted by Makefile from Amazon Linux RPM
if [ ! -f "${ARCH}/stub.efi" ]; then
echo "ERROR: systemd-boot stub not found at ${ARCH}/stub.efi"
echo "Please run 'make ${ARCH}/stub.efi' first"
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 "downloads/${ARCH}"/kernel6.12-*.rpm | (cd "${OUTPUT_DIR}/tmp" && cpio --quiet -idmu)
rpm2cpio "${OUTPUT_DIR}"/kernel6.12-*.rpm | (cd "${OUTPUT_DIR}/tmp" && cpio --quiet -idmu)
if [ ! -d "${OUTPUT_DIR}/tmp/lib/modules" ]; then
echo "ERROR: kernel modules not found after extraction!"
exit 1
Expand Down Expand Up @@ -78,11 +78,11 @@ mkdir -p "${INITRAMFS_DIR}"/{bin,sbin,etc,proc,sys,dev,lib,lib64,tmp}

# Copy busybox and create symlinks
echo "Installing busybox..."
cp "${ARCH}/busybox" "${INITRAMFS_DIR}/bin/"
cp "${OUTPUT_DIR}/busybox" "${INITRAMFS_DIR}/bin/"

# Copy stage1 binary
echo "Installing stage1..."
cp "${ARCH}/stage1" "${INITRAMFS_DIR}/bin/stage1"
cp "${OUTPUT_DIR}/stage1" "${INITRAMFS_DIR}/bin/stage1"
chmod +x "${INITRAMFS_DIR}/bin/stage1"

# Copy required kernel modules
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Empty file added tools/publish/azure/.gitkeep
Empty file.
6 changes: 4 additions & 2 deletions scripts/ec2-create-ami.sh → tools/publish/ec2/create-ami.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ else
EC2_ARCH="${ARCH}"
fi

WORK_DIR="${ARCH}"

# Download and verify from GitHub release or use local files
if [ "${VERSION}" != "local" ]; then
echo "=== Downloading release ${VERSION} from GitHub ==="
Expand Down Expand Up @@ -70,6 +68,10 @@ if [ "${VERSION}" != "local" ]; then
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"
Expand Down
Empty file added tools/publish/gcp/.gitkeep
Empty file.
File renamed without changes.
19 changes: 19 additions & 0 deletions tools/qemu-test/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.PHONY: all clean

# Amazon EC2 Metadata Mock
AEMM_VERSION := v1.13.0
AEMM_URL := https://github.com/aws/amazon-ec2-metadata-mock/releases/download/$(AEMM_VERSION)/ec2-metadata-mock-linux-amd64
AEMM_SHA256 := 4f89ddc71ac53ce540bda1f9c340526d558eed8e41349761f2798acf1b254950

all: ec2-metadata-mock-linux-amd64

ec2-metadata-mock-linux-amd64:
@echo "Downloading EC2 metadata mock..."
@curl -fsSL -o "$@.tmp" "$(AEMM_URL)"
@echo "$(AEMM_SHA256) $@.tmp" | sha256sum -c - || (rm -f "$@.tmp"; exit 1)
@mv "$@.tmp" "$@"
@chmod +x "$@"
@echo "✓ Downloaded and verified: $@"

clean:
rm -f ec2-metadata-mock-linux-amd64
Loading