Build the on-host agent that pairs with fleet-hostapi and feeds the fleet-manager control plane (TODO #14 "STILL TO BUILD (a)"). Nothing on the workstation talked to the manager before this; Monitor + InfluxDB stayed empty. Scope: telemetry only. The agent reuses the host TPM identity for mTLS (read-only tpmkey.LoadSigner at 0x81000100 + cert from NV 0x01800100; the private key never leaves the TPM), opens the long-lived FleetAgent.Session bidi stream, sends Hello + periodic HealthSnapshot (load/cpu/temp/mem/tmpfs/nvme/auth/selinux/chrony/kiosk, probed read-only from /proc+/sys plus hostapi facts), and LOGS but does not act on the DesiredState/Command it receives. Reconcile actuators (kiosk URL, reboot, drain) are deferred until the telemetry half is bench-verified. - files/agent/: new module (go-tpm + grpc), static CGO_ENABLED=0 like enrol/hostapi. Copies the enrol tpmkey (+ read-only LoadSigner) and the generated fleet.agent.v1 proto so it stays self-contained without importing the initramfs enrol module. - files/agent/simtest/: proves the TPM-signed cert completes a real mTLS client-auth handshake (RequireAndVerifyClientCert) against the go-tpm software simulator. - files/etc/fleet-agent/agent.env: fleet-generic config (manager DNS name, fleet CA reused from radius-ca.pem, TPM handle/nvindex, interval). No per-host state. - fleet-agent.service: hardened unit (tss group for /dev/tpmrm0, INET egress). - CI builds fleet-agent out-of-VM + runs vet/test + the simulator handshake test. - Packer: enable_agent flag (default on) + 2b-agent provisioner + fapolicyd trust. Post-switch-root only, so no dracut/initramfs changes. - docs: TODO #14 + fleet-management-design as-built updated. Requires the host already enrolled (broker approve births the inventory row) — else the manager returns PermissionDenied, the correct "not enrolled yet" signal.
3.6 KiB
fleet-agent — on-host control-plane client (telemetry v1)
The workstation-side half of the fleet management plane. It dials the
fleet-manager agent plane, identifies with the host's TPM, and streams
health telemetry up. It is the client peer of the server hub in
serving/fleet-manager/ (agent.go + proto/fleet_agent.proto).
Baked into the immutable verity image, so — like fleet-hostapi and fleet-enrol
— it is a static, reproducible, CGO_ENABLED=0 Go binary built out of the
build VM by CI (deps proxy-fetched, go.sum-pinned) and file-provisioned in.
What it does (v1 — telemetry only)
- Reuses the TPM identity for mTLS. Loads the signing key at the persistent
handle (
0x81000100) as acrypto.Signer(tpmkey.LoadSigner— the private key never leaves the TPM) and the enrolment cert from NV (0x01800100), and presents them as the mTLS client cert. The manager verifies the chain to the fleet CA and derives the cert serial = inventory key — a host can only ever report as itself (agent.go:peerSerial). - Opens the long-lived
Sessionbidi stream (host dials out; no inbound host port — works across the OPNsense L3 hop from the host VLAN to the mgmt plane). - Sends
Hello(image version, kernel, kiosk URL, root hash, starship name — the fleet-generic facts, pulled fromfleet-hostapion127.0.0.1:9099). - Sends a
HealthSnapshoteveryFLEET_AGENT_INTERVAL— load/cpu/temp/mem/ tmpfs, nvme-root, 802.1X-macsec, selinux, chrony, kiosk-active — probed read-only from/proc+/sys(each probe degrades gracefully; a failure never kills the stream). - Receives
DesiredState/Commandand LOGS them. This build does not act on them. The reconcile actuators (apply kiosk URL, reboot, drain) are a deliberate follow-up (seedocs/TODO.md#14) — the receive path is wired now so adding them needs no protocol change.
A dropped stream is retried with jittered backoff; the manager treats stream loss as the host going offline.
Prerequisite: the host must already be enrolled
The manager rejects the stream (PermissionDenied) until an inventory record
exists for the host's cert serial — that record is born when an admin approves
the host's TPM enrolment (fleet-enrol → enrol-broker → manager). Enrolment
precedes the agent; this ordering is expected.
Config (/etc/fleet-agent/agent.env)
All values are fleet-generic (invariant #1). See the file for the full list:
FLEET_AGENT_MANAGER (manager host:port), FLEET_AGENT_CA (fleet root, reused
from /etc/dot1x/radius-ca.pem), FLEET_AGENT_HANDLE / FLEET_AGENT_NVINDEX (TPM
identity, mirror rd.dot1x.*), FLEET_AGENT_INTERVAL, FLEET_AGENT_HOSTAPI.
Layout
| file | purpose |
|---|---|
main.go |
config, connect loop (jittered backoff), Session send/recv |
identity.go |
TPM-backed mTLS client cert + fleet-CA-verifying tls.Config |
snapshot.go |
read-only /proc+/sys health probes + hostapi facts fetch |
tpmkey/ |
copy of files/enrol/tpmkey + read-only LoadSigner (see its header) |
proto/ |
copy of the generated fleet.agent.v1 package (one .proto contract, two peers) |
fleet-agent.service |
hardened unit (tss group for the TPM, INET egress) |
Build / test
cd files/agent
CGO_ENABLED=0 go build -trimpath -buildvcs=false -ldflags '-s -w -buildid=' -o bin/fleet-agent .
go test ./...
# TPM handshake end-to-end (go-tpm software simulator, CGO — isolated module):
( cd simtest && CGO_ENABLED=1 go test ./... )