Files
Audiobuzz 3284a2713b docs: reflect chrony/uptime metrics, agent-cert renewer, and the chrony-access findings
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.
2026-07-10 22:45:00 +00:00
..

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)

  1. Reuses the TPM identity for mTLS. Loads the signing key at the persistent handle (0x81000100) as a crypto.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).
  2. Opens the long-lived Session bidi stream (host dials out; no inbound host port — works across the OPNsense L3 hop from the host VLAN to the mgmt plane).
  3. Sends Hello (image version, kernel, kiosk URL, root hash, starship name — the fleet-generic facts, pulled from fleet-hostapi on 127.0.0.1:9099).
  4. Sends a HealthSnapshot every FLEET_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. uptime lets the manager derive reboot-pending (boot_time = last_seen − uptime vs the requested reboot_at, all in the manager's clock frame).
  5. Receives DesiredState/Command and LOGS them. This build does not act on them. The reconcile actuators (apply kiosk URL, reboot, drain) are a deliberate follow-up (see docs/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 enables log tracking, and the agent reads the last line of /var/log/chrony/tracking.log. That read needs two access layers, both wired in: the chrony supplementary group (DAC — /var/log/chrony is 0750) and a minimal SELinux allow for the chronyd_var_log_t label (files/stig/fleet-agent-chronylog.te). The lab NTP server must also be reachable (a 123/udp host-firewall rule on the serving host) or the log only carries unsynced lines. See docs/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 ./... )