Recent work added telemetry fields and a cert path not yet in the docs: - files/agent/README.md: add uptime to the health list; document that chrony is read from /var/log/chrony/tracking.log (not the command socket) and needs BOTH the chrony supplementary group (DAC) and the fleet_agent_chronylog SELinux allow (TE), plus a reachable lab NTP (123/udp firewall); note the chrony group on the unit row. - serving/fleet-manager/README.md: add uptime + chrony detail to the health list; note the cert-auto agent-cert-renewer sidecar + GetCertificate hot-reload (certreload.go) in the bring-up and checkpoint 7. - docs/TODO.md #14: probe list gains uptime + chrony-from-tracking.log; new dated block records uptime/reboot-pending, the 4-layer chrony-access chase (all silent-failing; inventory.json is the isolator), and the agent-plane cert auto-renewal, all bench-verified 2026-07-10. - docs/fleet-management-design.md: health record gains uptime + chrony source/stratum/offset detail.
4.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, uptime, nvme-root, 802.1X-macsec, selinux, kiosk-active — probed read-only from/proc+/sys, plus chrony clock health (sync state + source/stratum/offset relative to the NTP source; see note). Each probe degrades gracefully; a failure never kills the stream.uptimelets the manager derive reboot-pending (boot_time = last_seen − uptime vs the requestedreboot_at, all in the manager's clock frame). - 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.
Note — the chrony probe reads a file, not the command socket. chronyd's unix command socket is owner-write-only and STIG sets
cmdport 0(no UDP command port), so the sandboxed agent can't query it. Instead the image enableslog tracking, and the agent reads the last line of/var/log/chrony/tracking.log. That read needs two access layers, both wired in: thechronysupplementary group (DAC —/var/log/chronyis0750) and a minimal SELinux allow for thechronyd_var_log_tlabel (files/stig/fleet-agent-chronylog.te). The lab NTP server must also be reachable (a123/udphost-firewall rule on the serving host) or the log only carriesunsyncedlines. Seedocs/stig-deviations.md#16.
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, chrony group to read chrony's tracking.log, 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 ./... )