Repository navigation
Conversation
…ipeline eiffel-build reads the two layout YAMLs, packs each into a gzipped newc cpio archive and assembles the EIF with aws-nitro-enclaves-image-format. It does in one process what eiffel.sh did with the linuxkit binary and the eif_build example CLI, and its defaults reproduce the paths that script used. The cpio encoder is written by hand: newc is a 110-byte ASCII header and a crate buys nothing, while the determinism the enclave needs (uid/gid 0, mtime 0, inodes numbered in emission order, a gzip header with no timestamp) is easier to guarantee than to verify through someone else's writer. A golden test locks the header layout against GNU cpio's output. The layout schema is the LinuxKit `files:` subset and nothing more, with deny_unknown_fields on both structs, so an old layout carrying `init:` or `onboot:` fails loudly instead of producing a ramdisk missing its contents. Alongside the .eif and its checksum the tool now writes <name>.pcrs.json, so consumers can pin PCR0/1/2 from the build rather than reading them back out of nitro-cli. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…eif_build The final image now carries one host-side binary instead of two downloaded or cloned ones: the eif_build stage (a git clone of aws-nitro-enclaves-image-format plus a vendored Cargo.lock to stop its dependencies floating), the linuxkit release binary and eiffel.sh are all gone, replaced by a cargo build of eiffel-build from this repo. The final stage moves from ubuntu:26.04 to debian:bookworm-slim with libssl3, which is what the image-format crate links against; its KMS signing path is never entered but ca-certificates comes along for it. The kernel, init, pid1, chrony and certs stages are untouched, so nothing measured into PCR0 or PCR1 moves because of this commit. The customer example takes the base image as a build arg so CI can point it at an image it just built; its default still pins the published tag. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three jobs on every pull request: the Rust checks, an image build that runs the customer example end to end and boots the result, and a double build of the blobs that land in PCR0 and PCR1 to catch a stage that stopped being reproducible. release.yml is unchanged. The boot test needs QEMU 9.2 or newer for its nitro-enclave machine type and vhost-device-vsock for the parent side of the heartbeat; neither is on a stock runner yet, so the job checks for both and skips with a notice rather than failing when they are missing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… output Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The image job compiled the 6.18 kernel on every PR and the reproducible job compiled it twice more, while the QEMU boot test never ran at all: Debian and Ubuntu build QEMU without libcbor, so the nitro-enclave machine and virtio-nsm are compiled out at every version and the machine-type probe skipped the test silently. ci.yml keeps only the Rust checks and stays on pull_request. The image and reproducible jobs move to enclave.yml, which runs on master, on v* tags and on demand; reproducible narrows further to tags and dispatch, being two full kernel builds. The boot test now runs inside a Fedora container, whose QEMU does have the machine type, and the script fails loudly rather than skipping when it does not. Splitting the Dockerfile's kernel stage in two lets anything that only needs the unpacked tree stop at kernel-src. The build steps are unchanged, so bzImage is byte-for-byte what it was. Runners are pinned to ubuntu-24.04 while GitHub rolls ubuntu-latest to 26.04. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
patrislav
force-pushed
the
feat/rust-eiffel-build
branch
from
November 2, 2026 16:15
23225a8 to
9e70dc3
Compare
CodeQL flagged all three workflows for running with the default write-all
GITHUB_TOKEN. Give each one a top-level permissions block: ci and enclave
need only contents:read, and release raises packages:write at job level so
the top level can stay empty.
While here, pin what CI pulls and make a hung run cheap:
- actions/checkout by commit SHA (v7.0.1). v6 writes credentials to a
separate file rather than the git config and v7 refuses to check out
fork PRs under pull_request_target/workflow_run, so take the bump
rather than pinning the old major; nothing here depends on what
changed in between.
- persist-credentials: false on every checkout. Nothing pushes with git.
- the Fedora boot-test container by digest, like the Dockerfile's images.
- vhost-device-vsock to 0.3.0, the only thing CI installs from crates.io
and the version the boot test was verified against.
- release.yml's runner to ubuntu-24.04, matching the others while the
ubuntu-latest label rolls over.
release.yml also stops interpolating ${{ secrets.GITHUB_TOKEN }} and
${{ github.actor }} into shell source; both go through env instead.
Add timeout-minutes to every job and concurrency to every workflow, except
that tag runs are never cancelled: those are what validate and publish a
release.
Finally, dependabot for github-actions and cargo, monthly. SHA pins rot
silently without something to bump them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Today the build is a shell script driving two binaries we do not own: a downloaded
linuxkitrelease, used purely as a YAML-driven cpio packer, and AWS'seif_buildexample, compiled from a pinned commit against a vendored lock file. Three moving parts, none of them ours, to turn a layout file into an image. This replaces all of it with one program in this repo that we can read, test and change.First of three stacked pull requests. The enclave's contents do not change here, so the only PCR movement comes from cpio and gzip byte differences against LinuxKit's encoder.
What lands
eiffel-build: a CLI, a layout parser, a hand-written newc cpio writer, gzip with a zeroed header timestamp, and image assembly throughaws-nitro-enclaves-image-format. It forbids unsafe code.files:subset our two layouts actually use and rejects everything else, so a layout carryinginit:oronboot:fails loudly instead of quietly producing an empty ramdisk. It also rejects absolute paths,.., empty segments, duplicates and ambiguous entries, and creates missing parent directories the way the kernel's unpacker requires.The published image keeps its contract: same entrypoint behaviour, same blob, config, workspace and output paths, so existing customer builds work unchanged.
Verification
cpiois bsdcpio and rejects the reproducible flag. Two deviations from GNU's output are documented in the test: the mtime is zeroed and inodes are numbered from one.gzip -t, and listing them shows exactly the entries and modes the layouts call for, with zero ownership.An extra dependency
serde_json, beyond the small set this crate otherwise needs. The image-format crate types two of its metadata fields as JSON values, so the struct cannot be constructed without naming the crate. It was already in the tree transitively, and it also serialises the PCR output.Not verified
The full image was never assembled, since the kernel stage takes eight minutes under emulation on the machine this was built on. No image was booted, on Nitro hardware or otherwise, and no PCR value was compared against a running enclave. The QEMU boot test is written but has never had a chance to run.
Impact
PCR0, PCR1 and PCR2 all move. Consumers must re-pin from the new PCR output.
🤖 Generated with Claude Code