Authenticated HTTP UEFI Secure NetBoot for the Cloud
stage0 boots a cloud VM, it uses EC2, GCP, Azure or Alibaba Cloud JSON metadata JSON you provide to fetch a UEFI payload (typically a Linux UKI) over HTTP, then verifies it with the pinned hash or ed25519 signature, measures it into TPM PCR 14, and chain-loads it.
Basically it's netboot without PXE: no TFTP, no DHCP options, no in-guest agent. Update the served payload and VMs roll forward on reboot; read the TPM and you know precisely which binary is running. All authenticated from your product updates URL and release keys.
stage0 ships as a db-signed boot disk; use it as your VM's boot volume. Point
it at your payload with a _stage1 user-data document:
{
"_stage1": {
"x86_64": {
"url": "http://cdn.example.com/app.efi",
"sha256": "<64-hex sha256>"
},
"aarch64": {
"url": "http://cdn.example.com/app.efi",
"ed25519": "<base64 pubkey>",
"args_url": "http://cdn.example.com/app.args", // optional
}
}
}Per arch, pick the admission mode:
sha256: pin an exact hash. Immutable; re-pin for every build.ed25519: pin a long-term release public key. The payload rolls forward without editing metadata: sign each build offline and serve the detached signature at<url>.sig, or at asig_urlof your choice. A{sha256}insig_urlis replaced with the payload's hash, so signatures can be content-addressed (e.g.http://cdn.example.com/sigs/{sha256}.sig).
The payload must be a UEFI PE. However the firmware db feels about it, stage0
admits it by your pin/signature and measures it into PCR 14 (= its SHA-256).
A _stage1 object with an optional args and one entry per architecture. Each
arch entry needs url and exactly one of sha256 or ed25519.
| Field | In | Type | Rules |
|---|---|---|---|
args |
_stage1 |
string[] |
optional; passed to the payload as UEFI load options |
x86_64 / aarch64 |
_stage1 |
object | per-arch entry; the running arch's must be present |
url |
arch entry | string |
http://…, printable ASCII (TLS is not used) |
sha256 |
arch entry | string |
exactly 64 hex characters |
ed25519 |
arch entry | string |
base64 of a 32-byte public key |
sig_url |
arch entry | string |
optional (signed mode); payload signature location, {sha256} → payload hash. Defaults to <url>.sig |
args_url |
arch entry | string |
optional (signed mode only); fetch signed load options here, {sha256} → payload hash. Overrides inline args |
args_sig_url |
arch entry | string |
optional; signature for args_url, {sha256} → payload hash. Defaults to <args_url>.sig. Requires args_url |
args_url content is verified against ed25519 (the same release key as the
payload) and used verbatim, trimmed, as the load-options string.
The _stage1 document can be embedded in stage0's PE before Authenticode
signing. If a .stage0 section is present, stage0 reads the document from that
section and does not contact the metadata service. The metadata is either embedded
or fetched, never both.
The section holds the complete user-data JSON: the same { "_stage1": { ... } }
document the metadata service would return, not just the inner object. It is part
of the signed, firmware-measured image, so the key, URL and args it pins are fixed
at signing time. The result is a single file that runs one fixed configuration,
with the payload still gated by your release key.
Embed the document, then sign:
objcopy --add-section .stage0=user-data.json \
--set-section-flags .stage0=alloc,load,readonly,data \
stage0.efi netboot.efi
sbsign --key db.key --cert db.crt --output netboot.efi netboot.efi
The section must be loaded: mapped at its virtual address, with SizeOfImage
covering it. If it is not, stage0 ignores it and falls back to the metadata
service.
On boot, in order:
- Brings the NIC up via DHCP (
EFI_IP4_CONFIG2). - Fetches
_stage1user-data from the metadata service, trying EC2 IMDSv2, GCP, Azure & Alibaba Cloud at their fixed IPs. - Downloads the per-arch payload from
url(hostnames resolved viaEFI_DNS4). All networking is rawEFI_TCP4, noEFI_HTTPor TLS; integrity comes from the pin/signature, not the transport. - Admits it: its SHA-256 must equal the pinned
sha256, or a detached ed25519 signature (<url>.sig) must verify against the pinneded25519key. - Measures it:
PCR 14 ← SHA-256(payload)viaEFI_TCG2_PROTOCOL. Nothing else is measured; attestation is simply "stage0 ran and loaded this hash" (no config, key, or PCR 15). - Chain-loads it (
LoadImagefrom memory +StartImage), bypassing the firmwaredbcheck with a temporaryFileAuthenticationoverride so late-bound payloads need nodbsignature.
stage0 is itself db-signed and measured, so the chain stays attestable; the
pin/signature is admission control only and is never attested.