tpm-enrol: inline empty-NV first-boot enrolment (host side, TODO #11)
Builds the host side of TPM identity enrolment — the "inline single-UKI"
vehicle from docs/tpm-enrolment-design.md. A production (secure) UKI now
auto-enrols a fresh host with no client cert, then reboots into the normal
secure path.
New dracut hook files/dracut/95dot1x-macsec/tpm-enrol.sh (initqueue/settled
prio 25, BEFORE dot1x-macsec-up at 30):
- no-op when a cert is already present in NV 0x01800001 (normal boot) or
under rd.dot1x=0 (test path has no trustworthy identity to enrol);
- on EMPTY NV: generate the signing key in the TPM @0x81010001 (openssl tpm2
provider builds the CSR; key never leaves the TPM), pre-auth DHCP on the
PHYSICAL NIC (nmcli re-manages en*, overriding the secure-mode unmanaged
drop-in), POST CSR+context to rd.fleet.enrol_url, show the broker
fingerprint on Plymouth, poll /status, write the issued leaf cert (DER) to
NV, and reboot to converge.
- Empty-NV and the secure path are mutually exclusive, so this never reorders
the load-bearing boot sequence (invariant #2).
Wiring:
- module-setup.sh: require+install the tpm2 key-gen/NV tools, curl, awk, and
the openssl tpm2 PROVIDER (ossl-modules/tpm2.so, asserted present — tpm2-tss
alone can't sign with a handle: key); install the hook.
- diskless.pkr.hcl: install tpm2-openssl + curl; assert hook/tools/provider in
the initramfs (invariant #9). New enrol_url var threaded to extract-artifacts.
- extract-artifacts.sh: bake rd.fleet.enrol_url into the cmdline.
- fleet.script: enrol-active/enrol-wait/enrol-done banner; fingerprint on the
message line (public — states only, invariant #1).
Shell JSON build/parse/cert-extract pipeline (no jq in the initramfs) verified
end-to-end against a live broker. BENCH-VERIFY list in docs/TODO.md #11 (tpm2
provider key-ref on real/swtpm, empty-NV detect, pre-auth DHCP override, Plymouth
render, NV write + reboot convergence, prod HTTPS).
This commit is contained in:
+20
-2
@@ -100,6 +100,12 @@ variable "nvmet_traddr" {
|
||||
default = "nvme.fleet.lab"
|
||||
}
|
||||
|
||||
variable "enrol_url" {
|
||||
type = string
|
||||
description = "Enrolment broker base URL baked into the UKI cmdline (rd.fleet.enrol_url) for the inline empty-NV first-boot enrolment (docs/tpm-enrolment-design.md). A fresh host reaches this pre-auth to get its TPM identity signed; hosts that already hold a cert never use it. Per-environment DNS name resolved at boot (the serving nginx reverse-proxies /enrol/ to the broker). Immutable once the UKI is signed."
|
||||
default = "http://boot.fleet.lab/enrol"
|
||||
}
|
||||
|
||||
variable "ssh_authorized_key" {
|
||||
type = string
|
||||
description = "Single SSH public key authorized for the sudo-enabled admin user. Fleet-generic (same on every host — not per-host state). Baked into the immutable rootfs, so rotating it needs a rebuild. EMPTY = no admin user is created and root is left UNLOCKED (lab convenience, avoids a no-way-in image)."
|
||||
@@ -271,7 +277,11 @@ build {
|
||||
# zstd: dracut compresses the initramfs with compress="zstd"
|
||||
# (90-diskless.conf); the minimal ISO lacks the zstd binary, so without it
|
||||
# dracut warns and silently falls back to gzip (a larger initramfs).
|
||||
"dnf -y install wpa_supplicant tpm2-tools tpm2-tss nvme-cli cryptsetup dracut-network NetworkManager zstd openssl",
|
||||
# tpm2-openssl: the openssl PROVIDER that lets `openssl req -provider tpm2
|
||||
# -key handle:0x…` sign a CSR with a TPM-resident key (tpm2-tss alone can't).
|
||||
# Required by the inline empty-NV enrolment hook (tpm-enrol.sh); curl is its
|
||||
# broker HTTP client. Both get bundled into the initramfs by the dracut module.
|
||||
"dnf -y install wpa_supplicant tpm2-tools tpm2-tss tpm2-openssl nvme-cli cryptsetup dracut-network NetworkManager zstd openssl curl",
|
||||
|
||||
# EFI / Secure Boot management tools (efivar, mokutil, efibootmgr — all base
|
||||
# repos). Used over SSH on a workstation: `mokutil --sb-state/--pk/--kek/--db`
|
||||
@@ -387,6 +397,14 @@ build {
|
||||
# a dracut hook).
|
||||
"grep -q 'nm-unmanaged.sh' /tmp/lsinitrd.txt || { echo 'ERROR: nm-unmanaged.sh hook missing from initramfs' >&2; exit 1; }",
|
||||
"grep -q 'nvmf-connect.service' /tmp/lsinitrd.txt || { echo 'ERROR: nvmf-connect.service missing from initramfs' >&2; exit 1; }",
|
||||
# Assert the inline empty-NV enrolment path is fully present: the hook, the
|
||||
# TPM key-gen tool, curl, and the openssl tpm2 PROVIDER (the piece tpm2-tss
|
||||
# doesn't provide — without it the enrolment CSR can't be signed by the TPM
|
||||
# key). A fresh host silently unable to enrol is a bricked host.
|
||||
"grep -q 'tpm-enrol.sh' /tmp/lsinitrd.txt || { echo 'ERROR: tpm-enrol.sh hook missing from initramfs' >&2; exit 1; }",
|
||||
"grep -q 'tpm2_create' /tmp/lsinitrd.txt || { echo 'ERROR: tpm2 key-gen tools missing from initramfs' >&2; exit 1; }",
|
||||
"grep -Eq '/curl$|/curl ' /tmp/lsinitrd.txt || { echo 'ERROR: curl missing from initramfs (enrolment broker client)' >&2; exit 1; }",
|
||||
"grep -q 'ossl-modules/tpm2.so' /tmp/lsinitrd.txt || { echo 'ERROR: openssl tpm2 provider (tpm2.so) missing from initramfs — install tpm2-openssl' >&2; exit 1; }",
|
||||
# Plymouth must be in the initramfs to splash during the early boot phase.
|
||||
"grep -q 'plymouthd' /tmp/lsinitrd.txt || { echo 'ERROR: plymouth missing from initramfs (plymouth dracut module not installed)' >&2; exit 1; }",
|
||||
|
||||
@@ -855,7 +873,7 @@ build {
|
||||
# home dirs) — we dropped mkfs.erofs --all-root, which used to mask this by
|
||||
# flattening everything to root:root. sudo is a no-op if already root, and
|
||||
# the runner has passwordless sudo (cf. the `sudo apt-get` install step).
|
||||
"sudo bash -c \"IMAGE_VERSION='${var.image_version}' NVMET_TRADDR='${var.nvmet_traddr}' BOOT_SPLASH='${var.boot_splash}' STIG='${var.enable_stig}' bash scripts/extract-artifacts.sh output-qemu/diskless-build.qcow2 ${var.artifact_dir}/${var.image_version} ${var.disable_dot1x}\"",
|
||||
"sudo bash -c \"IMAGE_VERSION='${var.image_version}' NVMET_TRADDR='${var.nvmet_traddr}' ENROL_URL='${var.enrol_url}' BOOT_SPLASH='${var.boot_splash}' STIG='${var.enable_stig}' bash scripts/extract-artifacts.sh output-qemu/diskless-build.qcow2 ${var.artifact_dir}/${var.image_version} ${var.disable_dot1x}\"",
|
||||
# Hand the OUTPUT FILES back to the build user (their in-erofs ownership is
|
||||
# already baked correctly) so build-oci.sh can write the UKI and push with
|
||||
# the build user's `buildah login`.
|
||||
|
||||
Reference in New Issue
Block a user