No description
  • Shell 50.4%
  • Nix 49.6%
Find a file
2026-09-07 12:38:54 +02:00
amd-files add build script, verifier script and amd files 2026-08-09 15:44:48 +02:00
config.nix add a static ip address to survive in this new, harsh environmant 2026-09-07 12:38:54 +02:00
convert-gitlab-ci.sh add executable bit to the converter script 2026-08-09 19:37:18 +02:00
default-job-script Add default job script and output paths as flake inputs 2026-08-07 23:18:24 +02:00
default-output-paths Add default job script and output paths as flake inputs 2026-08-07 23:18:24 +02:00
flake.lock update nixpkgs 2026-08-13 14:38:28 +02:00
flake.nix Implement attested build service with dm-verity 2026-08-09 13:02:22 +02:00
LICENSE Add LICENSE and update flake.nix outputs 2026-08-07 23:08:33 +02:00
readme.md publish readme.md 2026-08-10 10:52:22 +02:00
run_attested_builder.sh Remove local attestation verification from script 2026-08-09 23:38:45 +02:00
verify_attested_build.sh fix verification script 2026-08-10 00:52:09 +02:00

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

  1. flake.nix + config.nix define a NixOS system that builds into:

    • a systemd-repart disk image with an ephemeral tmpfs root and a squashfs, dm-verity-verified /nix/store (via NixOS's image.repart.verityStore module),
    • 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 / mksquashfs are 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-guest kernel module, virtio drivers, serial console, no GRUB).
  2. 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 (jobSource flake 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 buildOutputPaths into build-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, and buildOutputPaths are all flake inputs, so a specific job can be substituted at build time with --override-input instead of editing this repo.

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 (for convert-gitlab-ci.sh)
  • sev-snp-measure (fetched via nix 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 \
  .

License

EUPL-1.2