- Shell 50.4%
- Nix 49.6%
|
|
||
|---|---|---|
| amd-files | ||
| config.nix | ||
| convert-gitlab-ci.sh | ||
| default-job-script | ||
| default-output-paths | ||
| flake.lock | ||
| flake.nix | ||
| LICENSE | ||
| readme.md | ||
| run_attested_builder.sh | ||
| verify_attested_build.sh | ||
small-dm-verity-nixos-image
Minimal, reproducible NixOS guest image with a dm-verity-protected /nix/store, built to run as an AMD SEV‑SNP confidential VM that executes a single build job and produces a hardware-attested build output.
The idea: because the guest image (squashfs store + kernel + initrd + boot cmdline) is built fully reproducibly, a client can independently recompute the exact SEV‑SNP launch measurement that guest should produce. The guest itself then binds the hash of its build output into a SEV‑SNP attestation report. Given a matching measurement and a valid attestation report, you get a verifiable guarantee that a specific, unmodified environment produced a specific output — i.e. reproducible and attested CI builds.
How it works
-
flake.nix+config.nixdefine a NixOS system that builds into:- a
systemd-repartdisk image with an ephemeraltmpfsroot and a squashfs, dm-verity-verified/nix/store(via NixOS'simage.repart.verityStoremodule), - a kernel, initrd, and a direct-boot kernel command line that embeds the store's verity
usrhash, so the boot chain doesn't depend on a bootloader or on trusting anything not covered by the measurement. - a reproducibility fix:
mkfs.erofs/mksquashfsare wrapped to sort input file lists before building the filesystem image, so the store partition builds byte-identically across machines/runs. - AMD SEV‑SNP guest support (
sev-guestkernel module, virtio drivers, serial console, no GRUB).
- a
-
Inside the guest, a oneshot systemd service (
attested-build) does the actual work:- waits for a second virtio block device (
/dev/vdb) and mounts it as the output disk, - copies the job source (
jobSourceflake input) into a workdir and runs the job script (jobScript), - live-tees all output to
build.log(and the serial console), - tars up the paths listed in
buildOutputPathsintobuild-output.tar, - hashes the tarball and calls out to a SEV‑SNP report generator to produce
attestation-report.bin, which binds that hash into the hardware attestation, - writes
build-exit-code, then powers the VM off.
jobSource,jobScript, andbuildOutputPathsare all flake inputs, so a specific job can be substituted at build time with--override-inputinstead of editing this repo. - waits for a second virtio block device (
Client-side scripts
| Script | What it does |
|---|---|
convert-gitlab-ci.sh <job> [.gitlab-ci.yml] <out-dir> |
Extracts one job's flat script: and artifacts.paths: out of a .gitlab-ci.yml into a standalone job-script.sh + build-output-paths.txt that the tools below expect. Deliberately does not resolve extends:/include:/rules:/YAML anchors — attested jobs need to stay flat to remain reproducible outside GitLab's pipeline compiler. |
run_attested_builder.sh <build-source> <build-script> <build-output-paths> <output-dir> <image-url-reference> |
Builds the attested guest image locally for the given job (overriding the flake inputs above), computes the expected SEV‑SNP launch measurement with sev-snp-measure, uploads the image/kernel/initrd/cmdline/OVMF to a remote attested-builder-server over HTTP, streams the live build log, and downloads build-output.tar, build.log, attestation-report.bin, qemu-console.log, qemu-exit-code back into <output-dir>. |
verify_attested_build.sh <build-source> <build-script> <build-output-paths> <artifacts-dir> <image-url-reference> |
Doesn't run a build. Given the same job inputs plus a directory of already-fetched artifacts, it rebuilds the same kernel/initrd/cmdline locally, recomputes the expected SEV‑SNP measurement, and verifies attestation-report.bin against that measurement and the SHA‑256 of build-output.tar, using AMD's VCEK cert and Milan cert chain. |
Repository layout
| Path | Purpose |
|---|---|
flake.nix / flake.lock |
Flake definition: nixosConfiguration + image / kernel / initrd / cmdline outputs |
config.nix |
The NixOS module described above (verity store, SEV‑SNP guest, attested-build service) |
amd-files/ |
OVMF firmware and AMD certs (VCEK, Milan chain) used to boot/verify SEV‑SNP guests |
default-job-script, default-output-paths |
Default values for the jobScript / buildOutputPaths flake inputs |
convert-gitlab-ci.sh |
Extracts a flat GitLab CI job into standalone script + output-paths files |
run_attested_builder.sh |
Builds the image, submits a job to a remote attested-builder server, fetches artifacts |
verify_attested_build.sh |
Independently verifies a completed build's attestation report |
LICENSE |
EUPL‑1.2 |
Requirements
- Nix with flakes enabled
yq-go(forconvert-gitlab-ci.sh)sev-snp-measure(fetched vianix run nixpkgs#sev-snp-measure)- Access to
generate-and-verify-amd-sev-report(used both inside the guest to generate reports and client-side to verify them) - A running
attested-builder-server(AMD SEV‑SNP capable host, not part of this repo) to actually execute jobs - OVMF firmware + AMD VCEK/Milan certs in
amd-files/
Typical flow
# 1. Turn an existing flat GitLab CI job into standalone job inputs
./convert-gitlab-ci.sh build-job .gitlab-ci.yml ./job-inputs
# 2. Build the attested image for that job and run it on a remote SEV-SNP host
./run_attested_builder.sh \
./my-source-repo \
./job-inputs/job-script.sh \
./job-inputs/build-output-paths.txt \
./out \
.
# 3. Independently verify the resulting attestation report
./verify_attested_build.sh \
./my-source-repo \
./job-inputs/job-script.sh \
./job-inputs/build-output-paths.txt \
./out \
.