# 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, 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). 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**. ## 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 ```bash 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 ./... ) ```