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
42 changes: 42 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Per-repo Cargo config for vaportpm. vaportpm is a GNU-HOST repo: its default builds/tests and the
# attest/verify CLIs are host (x86_64-unknown-linux-gnu) executables. Host linking therefore uses the
# image's default `cc` (gcc, present in lockboot:build) -- we set NO host linker override, because
# rust-lld cannot link glibc executables (rustc does not hand it the system library search paths, only
# the self-contained crt for musl). Build scripts run at build time and don't affect artifact bytes,
# so linking them with gcc is fine for reproducibility. Only the musl release ARTIFACTS use rust-lld +
# bundled musl (fully static, reproducible, cc-free) -- see the sections below. This is the "gnu-host
# repos link C and need cc" case that stage0/stage1's config comments call out.
#
# Kept in the repo (not only the shared workspace config) because CI checks out each repo ALONE, and
# Cargo CONCATENATES rustflags across config files -- the shared /src/.cargo/config.toml sets no
# rustflags, so nothing doubles. No `[build] target = musl`: that would force musl onto the host tools
# and the test suite. Musl is opt-in via `--target` for `make release-x86` / `release-aarch64` below.

[target.x86_64-unknown-linux-musl]
# Fully static musl release artifacts. rust-lld + bundled musl, no system cc.
rustflags = [
"-C", "linker=rust-lld",
"-C", "target-feature=+crt-static",
"-C", "link-arg=-static",
# Remap embedded dep source paths to a fixed prefix so builds don't depend on CARGO_HOME
# (CI uses /tmp/.cargo, local uses /src/.cargo); keeps CI and local byte-identical.
"--remap-path-prefix=/src/.cargo=/cargo",
"--remap-path-prefix=/tmp/.cargo=/cargo",
# Remap the rust-src sysroot to the baked /rustc/<hash> so std panic-location paths match CI.
# UPDATE THE HASH on a toolchain bump (rustc -Vv commit-hash); a stale value silently no-ops.
"--remap-path-prefix=/src/.rustup/toolchains/1.91.1-x86_64-unknown-linux-gnu/lib/rustlib/src/rust=/rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb",
]

[target.aarch64-unknown-linux-musl]
rustflags = [
"-C", "linker=rust-lld",
"-C", "target-feature=+crt-static",
"-C", "link-arg=-static",
"--remap-path-prefix=/src/.cargo=/cargo",
"--remap-path-prefix=/tmp/.cargo=/cargo",
"--remap-path-prefix=/src/.rustup/toolchains/1.91.1-x86_64-unknown-linux-gnu/lib/rustlib/src/rust=/rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb",
]

[env]
# Reproducible builds when this repo is built standalone (no workspace parent in CI).
SOURCE_DATE_EPOCH = "0"
29 changes: 0 additions & 29 deletions .devcontainer/devcontainer.json

This file was deleted.

24 changes: 13 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@ on:
branches: [main]
pull_request:
branches: [main]

env:
CARGO_TERM_COLOR: always
workflow_dispatch:

jobs:
ci:
name: CI
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- run: make ci
- run: make build
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0

# Everything runs inside lockboot:build (built on demand by the Makefile's
# docker-build-base prerequisite) on the pinned 1.91.1 toolchain -- no host
# Rust toolchain is installed here. GitHub sets CI=true, so the Makefile's
# CACHE_ENV redirects cargo/rustup homes to /tmp inside the container.
- name: CI (fmt-check + check + clippy + test + doc)
run: make ci

- name: Release build
run: make build
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ hex = { version = "0.4", default-features = false, features = ["alloc"] }
# X.509 and crypto for verification
der = { version = "0.7", default-features = false, features = ["alloc", "pem", "oid"] }
spki = { version = "0.7", features = ["alloc"] }
# Keep no_std-base at the workspace level (attest's UEFI build). `builder`/`hazmat` are needed
# only by vaportpm-verify's tests, so they are opted in there (a std crate), not here.
x509-cert = { version = "0.2", default-features = false, features = ["pem"] }
p256 = { version = "0.13", features = ["ecdsa", "pem", "pkcs8"] }
p384 = { version = "0.13", features = ["ecdsa", "pem", "pkcs8"] }
Expand Down
48 changes: 48 additions & 0 deletions Dockerfile.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
FROM rust:1.91-slim-bookworm

# Add musl targets for both architectures
RUN rustup target add x86_64-unknown-linux-musl aarch64-unknown-linux-musl

# Install tools needed for UKI building
RUN apt-get -qq update && \
apt-get install -y --no-install-recommends \
binutils-aarch64-linux-gnu \
binutils-x86-64-linux-gnu \
cpio \
curl \
dosfstools \
efitools \
fdisk \
findutils \
gdisk \
gnupg \
gzip \
kmod \
make \
openssl \
ovmf \
python3 \
python3-pip \
qemu-efi-aarch64 \
rpm \
rpm2cpio \
sbsigntool \
util-linux \
uuid-runtime \
wget \
xz-utils && \
pip3 install --break-system-packages virt-firmware

# Reproducible builds environment
ENV SOURCE_DATE_EPOCH=0
ENV CARGO_INCREMENTAL=0
# rustflags are per-target in .cargo/config.toml; a global RUSTFLAGS would force +crt-static onto
# the host and break proc-macros.

# User environment (project-specific cargo/rustup data)
ENV HOME=/src
ENV CARGO_HOME=/src/.cargo
ENV RUSTUP_HOME=/src/.rustup
ENV HISTFILE=/dev/null

WORKDIR /src
69 changes: 38 additions & 31 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,49 +1,56 @@
.PHONY: all build release-x86 check test fmt fmt-check clippy doc clean ci coverage setup-coverage
.PHONY: all build release-x86 release-aarch64 check test fmt fmt-check clippy doc clean ci \
setup-coverage coverage-html coverage-text

# Shared build harness (docker images + DOCKER_RUN plumbing). Vendored byte-identically from
# stage0/build.mk (the canonical source) via the workspace `make sync-harness`; do not hand-edit.
# `make check-harness` guards against drift. Every cargo recipe runs inside lockboot:build so the
# build is cc-free-by-design and reproducible (rust-lld + the shared /src/.cargo, /src/.rustup).
include build.mk

# vaportpm is a gnu-host repo: the std tooling (attest/verify CLIs) and the test suite build for the
# host target inside the image (glibc is present there); musl is opt-in via the release-* targets.
CARGO = $(DOCKER_RUN) $(DOCKER_SAMEUSER) $(BUILD_IMAGE) cargo

all: build

build:
cargo build --workspace --release
build: docker-build-base
$(CARGO) build --workspace --release

release-x86:
cargo build --workspace --release --target x86_64-unknown-linux-musl
release-x86: docker-build-base
$(CARGO) build --workspace --release --target x86_64-unknown-linux-musl

release-aarch64:
cargo build --workspace --release --target aarch64-unknown-linux-musl
release-aarch64: docker-build-base
$(CARGO) build --workspace --release --target aarch64-unknown-linux-musl

check:
cargo check --workspace --all-targets
check: docker-build-base
$(CARGO) check --workspace --all-targets

test:
cargo test --workspace
test: docker-build-base
$(CARGO) test --workspace

fmt:
cargo fmt --all
fmt: docker-build-base
$(CARGO) fmt --all

fmt-check:
cargo fmt --all -- --check
fmt-check: docker-build-base
$(CARGO) fmt --all -- --check

clippy:
cargo clippy --workspace --all-targets -- -D warnings
clippy: docker-build-base
$(CARGO) clippy --workspace --all-targets -- -D warnings

doc:
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps
doc: docker-build-base
$(DOCKER_RUN) $(DOCKER_SAMEUSER) -e RUSTDOCFLAGS="-D warnings" $(BUILD_IMAGE) cargo doc --workspace --no-deps

clean:
cargo clean
clean: docker-build-base
$(CARGO) clean

ci: fmt-check check clippy test doc

setup-coverage:
rustup component add llvm-tools-preview
cargo install cargo-llvm-cov
setup-coverage: docker-build-base
$(DOCKER_RUN) $(DOCKER_SAMEUSER) $(BUILD_IMAGE) bash -c "rustup component add llvm-tools-preview && cargo install cargo-llvm-cov"

coverage-html:
cargo llvm-cov -p vaportpm-verify --html
coverage-html: docker-build-base
$(DOCKER_RUN) $(DOCKER_SAMEUSER) $(BUILD_IMAGE) cargo llvm-cov -p vaportpm-verify --html
@echo "Coverage report: target/llvm-cov/html/index.html"

coverage-text:
cargo llvm-cov -p vaportpm-verify

rustup:
rustup default stable
coverage-text: docker-build-base
$(DOCKER_RUN) $(DOCKER_SAMEUSER) $(BUILD_IMAGE) cargo llvm-cov -p vaportpm-verify
77 changes: 77 additions & 0 deletions build.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# ---- Lock.Boot shared build harness ----------------------------------------------------------
# Runs every cargo/tool invocation inside the locally-built lockboot:build image, so builds are
# cc-free-by-design and byte-reproducible (rust-lld + musl, shared /src/.cargo + /src/.rustup).
#
# CANONICAL SOURCE: stage0/build.mk. This file is vendored byte-identically into each participating
# repo (stage1, vaportpm, ...) because CI checks out each repo ALONE (no workspace parent), so a
# shared harness cannot be a cross-repo include -- it must live in the repo. Do NOT hand-edit the
# copies: edit stage0/build.mk, then run `make sync-harness` from the workspace ($(CANON)=stage0),
# guarded by `make check-harness`.
#
# Each repo's Makefile does `include build.mk` and defines its own targets, invoking cargo as
# $(DOCKER_RUN) $(DOCKER_SAMEUSER) $(BUILD_IMAGE) cargo ...
# with a `docker-build-base` prerequisite so the image is built on demand (incl. standalone CI).

# ---- Docker images (shared lockboot family; built locally, never published) ----
BUILD_IMAGE = lockboot:build
HARNESS_IMAGE = lockboot:harness

.PHONY: docker-build-base
docker-build-base:
docker build -f Dockerfile.build -t $(BUILD_IMAGE) .

# ---- Docker run plumbing (keep identical across repos) ----
# Own build artifacts by whoever owns the checkout, not the caller's euid. Under
# `gh act` the caller is root but the bind-mounted tree is still yours, so stat
# keeps output user-owned instead of trampling the project dir with root files.
# On a normal host/devcontainer run this equals `id -u`/`id -g`, so nothing changes.
USER_ID := $(shell stat -c %u .)
GROUP_ID := $(shell stat -c %g .)

KVM_GID := $(shell stat -c %g /dev/kvm 2>/dev/null || echo "")
KVM_MOUNT := $(shell test -e /dev/kvm && echo "-v /dev/kvm:/dev/kvm")
DOCKER_OPT_KVM := $(if $(KVM_GID),--group-add $(KVM_GID)) $(KVM_MOUNT)

# Recursive-docker passthrough: rules that shell out to the HOST docker daemon (e.g. stage1's UKI
# rootfs extraction / runtime-image buildx) forward the socket + its gid. Defined here for every
# repo; harmless (expands empty) when a repo has no such rule.
DOCKER_SOCK_GID := $(shell stat -c %g /var/run/docker.sock 2>/dev/null || echo "")
DOCKER_SOCK_MOUNT := $(shell test -e /var/run/docker.sock && echo "-v /var/run/docker.sock:/var/run/docker.sock")
DOCKER_OPT_DOCKER := $(DOCKER_SOCK_MOUNT) $(if $(DOCKER_SOCK_GID),--group-add $(DOCKER_SOCK_GID))

DOCKER_SAMEUSER := -u $(USER_ID):$(GROUP_ID)

# Host-path translation for docker-in-devcontainer. Inside the devcontainer /src is
# a host bind mount and the inner Docker talks to the HOST daemon, which cannot
# resolve /src/... paths; translate $(CURDIR) to the real host path (the bracketed
# subpath findmnt reports for the /src bind). On the host CURDIR is not under /src,
# so this is a pass-through and your workflow is unchanged. Keep identical across repos.
HOST_DIR := $(CURDIR)
ifneq ($(filter /src/%,$(CURDIR)),)
SRC_BIND := $(shell findmnt -fnro SOURCE --target /src 2>/dev/null | sed -n 's/.*\[\(.*\)\]$$/\1/p')
ifneq ($(SRC_BIND),)
HOST_DIR := $(SRC_BIND)$(CURDIR:/src%=%)
endif
endif

# Mount the WORKSPACE (parent of this repo) at /src so builds reuse the shared
# workspace-level .cargo/.rustup (matching the devcontainer), instead of creating
# per-repo copies. The repo then lives at /src/$(REPO_NAME).
REPO_NAME := $(notdir $(HOST_DIR))
HOST_WS := $(patsubst %/,%,$(dir $(HOST_DIR)))

# Under CI / `gh act` (CI=true, runs as root) keep cargo/rustup caches ephemeral
# inside the container, so root-owned dirs never land in the bind-mounted project.
# Locally (no CI) the image's CARGO_HOME=/src/.cargo + RUSTUP_HOME=/src/.rustup win,
# i.e. the shared workspace caches.
CACHE_ENV := $(if $(CI),-e CARGO_HOME=/tmp/.cargo -e RUSTUP_HOME=/tmp/.rustup)

DOCKER_RUN = docker run --rm \
--privileged \
-v $(HOST_WS):/src \
-h lockboot \
--add-host lockboot:127.0.0.1 \
-e OWNER_UID=$(USER_ID) \
-e OWNER_GID=$(GROUP_ID) \
$(CACHE_ENV) \
-w /src/$(REPO_NAME)
2 changes: 1 addition & 1 deletion crates/vaportpm-attest/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "vaportpm-attest"
version = "0.2.0"
version = "0.3.0"
edition = "2021"
description = "Cloud vTPM attestation - minimal TPM 2.0 implementation without C dependencies"
license.workspace = true
Expand Down
Loading