fleet-enrol: content-aware NV check (-check-only), not blind "index defined"
The enrol trigger keyed on "is NV 0x01800001 defined/readable", so leftover state (an earlier tpm-provision run — bench-seen on the 820: "cert present — normal boot" with no real identity) or random bytes (tpm-selftest) silently suppressed enrolment. Now content-aware: - tpmkey.ReadNVCert reads the full NV contents (chunked). - classifyNVCert (TPM-agnostic, unit-tested): parses X.509, checks validity window, and when a real fleet CA bundle is present verifies the cert chains to it. Only a valid fleet cert = enrolled. - New `fleet-enrol -check-only` (no network/no key-gen) exits 0=valid cert, 20=enrol needed, 1=error. The full enrol path uses the same classification instead of NVPresent. - The dracut hook calls -check-only as the single gate, replacing the blind tpm2_nvread (and its now-dead TPM2TOOLS_TCTI export). tpm2_nvread stays in the initramfs for the secure path (dot1x-macsec-up.sh). Lab fallback: the shipped radius-ca.pem is a placeholder (not a cert) -> loadCAPool returns nil -> chain-less "parseable + unexpired" acceptance, so reboot-converge doesn't loop; production replaces radius-ca.pem with the real fleet CA for strict chaining. Tests: classifyNVCert table (valid/expired/garbage/foreign/no-CA) + simtest ReadNVCert roundtrip across the 512B chunk. All pass; packer validate clean.
This commit is contained in:
+31
-8
@@ -12,10 +12,14 @@
|
||||
// The hook then reboots into the normal secure path. Everything host-specific
|
||||
// (key, cert, hostnqn) is created here at runtime — nothing is baked (invariant #1).
|
||||
//
|
||||
// Exit codes: 0 = enrolled (cert written to NV; caller should reboot);
|
||||
// Exit codes (default/enrol mode): 0 = enrolled (cert written to NV; caller
|
||||
// should reboot); 10 = a VALID fleet cert is already present (normal boot —
|
||||
// caller proceeds); 1 = failure (caller leaves NV empty -> boot fails -> emergency).
|
||||
//
|
||||
// 10 = cert already present (normal boot — caller proceeds);
|
||||
// 1 = failure (caller leaves NV empty -> boot fails -> emergency).
|
||||
// Exit codes (-check-only mode, no network/no key-gen): 0 = a valid fleet cert is
|
||||
// present (caller: normal boot); 20 = enrolment needed (NV empty/garbage/foreign/
|
||||
// expired); 1 = error (e.g. TPM open failed). The dracut hook uses this as the
|
||||
// single content-aware gate instead of a blind "is the NV index defined" check.
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -36,7 +40,10 @@ import (
|
||||
"github.com/google/go-tpm/tpm2/transport"
|
||||
)
|
||||
|
||||
const exitAlreadyEnrolled = 10
|
||||
const (
|
||||
exitAlreadyEnrolled = 10 // enrol mode: a valid fleet cert is already present
|
||||
exitNeedEnrol = 20 // -check-only: no valid fleet cert → enrolment needed
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
@@ -46,9 +53,10 @@ func main() {
|
||||
handleHex = flag.String("handle", "0x81000100", "TPM persistent handle for the signing key (non-reserved owner handle; NOT the EK slot 0x81010001)")
|
||||
cn = flag.String("cn", "fleet-host", "CSR CommonName (generic; identity is the cert serial)")
|
||||
iface = flag.String("iface", "", "network interface for the device-context MAC")
|
||||
caFile = flag.String("cacert", "/etc/dot1x/radius-ca.pem", "CA bundle for an https broker URL")
|
||||
caFile = flag.String("cacert", "/etc/dot1x/radius-ca.pem", "fleet CA bundle: verifies an https broker URL AND the NV cert's chain")
|
||||
waitFor = flag.Duration("timeout", 30*time.Minute, "max wait for admin approval")
|
||||
pollEvery = flag.Duration("poll", 5*time.Second, "status poll interval")
|
||||
checkOnly = flag.Bool("check-only", false, "classify the NV cert and exit (no network, no key-gen): 0=valid fleet cert, 20=enrol needed, 1=error")
|
||||
)
|
||||
flag.Parse()
|
||||
log.SetFlags(0)
|
||||
@@ -75,9 +83,24 @@ func main() {
|
||||
defer f.Close()
|
||||
tpm := transport.FromReadWriter(f)
|
||||
|
||||
// Already enrolled? Then this is a normal boot; let the secure path run.
|
||||
if tpmkey.NVPresent(tpm, nvIndex) {
|
||||
log.Printf("cert already present at NV %s — normal boot", *nvHex)
|
||||
// Content-aware enrolment decision: read the NV cert, parse it, and (when the
|
||||
// fleet CA is available) verify it chains — so leftover/foreign/garbage NV does
|
||||
// NOT masquerade as "enrolled" (a bare "index defined" check let an old
|
||||
// tpm-provision cert or random bytes silently suppress enrolment).
|
||||
enrolled, detail := nvEnrolled(tpm, nvIndex, *caFile)
|
||||
|
||||
if *checkOnly {
|
||||
if enrolled {
|
||||
log.Printf("check-only: %s — normal boot", detail)
|
||||
os.Exit(0)
|
||||
}
|
||||
log.Printf("check-only: enrolment needed — %s", detail)
|
||||
os.Exit(exitNeedEnrol)
|
||||
}
|
||||
|
||||
// Full enrol mode: a valid fleet cert means this is a normal boot.
|
||||
if enrolled {
|
||||
log.Printf("%s — normal boot", detail)
|
||||
os.Exit(exitAlreadyEnrolled)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user