fleet-manager: checkpoint 4 — InfluxDB metrics gateway (write + Flux read)

The manager is the ONLY thing that talks to InfluxDB, both directions; hosts hold
no token and never reach it.

- influx.go: influxSink implements MetricsSink — each agent HealthSnapshot is
  written as a fleet_health point, identity (serial/hostnqn/starship) as TAGS
  from the AUTHENTICATED stream (a host can only write its own series), telemetry
  as fields. QueryField answers the SPA's read: a Flux query built with a strict
  FIELD WHITELIST + hex-serial guard + parsed-duration range, so no caller input
  is interpolated raw (injection-safe). Server-side influxdb-client-go/v2.
- admin.go: GET /api/hosts/{serial}/metrics?field=&range= -> time-series JSON;
  503 when InfluxDB isn't wired, 400 on a bad range, 404 for unknown host.
- main.go: sink created when FLEET_MGR_INFLUX_URL+_TOKEN set, fed to the agent
  hub; nil -> no-op sink so the manager runs without metrics.

Tests: snapshotPoint tags/fields/time, fluxQuery contents + %q quoting, and
QueryField rejects a non-hex serial + non-whitelisted field. build/test/vet/gofmt
clean. Live InfluxDB path validated in the compose bench (ckpt 6).
This commit is contained in:
Audiobuzz
2026-07-04 03:59:18 +00:00
parent df4fa95bdd
commit 9c86cd4c5a
7 changed files with 316 additions and 6 deletions
+11 -3
View File
@@ -66,7 +66,7 @@ inspectable, reproducible; a DB swap is a drop-in behind the `Store` interface.
| `GET /api/enrol/pending` | ✅ ckpt 3 — proxies the broker queue |
| `POST /api/enrol/{id}/approve` | ✅ ckpt 3 — signs via broker + **births inventory** (identity/binding/enrolled) |
| `POST /api/enrol/{id}/deny` | ✅ ckpt 3 |
| `GET /api/hosts/{serial}/metrics` | ⏳ ckpt 4 (InfluxDB/Flux) |
| `GET /api/hosts/{serial}/metrics?field=&range=` | ✅ ckpt 4 — Flux time-series (field whitelist) |
The authorise pillar drives the enrol-broker's admin plane over mTLS (the manager
is a service client, gated by the dedicated admin CA) asking for JSON, so an
@@ -95,6 +95,10 @@ inventory for offline delivery. Metrics writing is behind a `MetricsSink`
| `FLEET_MGR_BROKER_URL` | — | enrol-broker admin base URL (e.g. `https://127.0.0.1:9443`). Authorise pillar enabled only when set. |
| `FLEET_MGR_BROKER_CLIENT_CERT` / `_KEY` | — | manager's client cert for the broker admin mTLS (admin CA) |
| `FLEET_MGR_BROKER_CA` | — | CA that signs the broker's admin server cert |
| `FLEET_MGR_INFLUX_URL` | — | InfluxDB URL (private mgmt net). Metrics enabled only when URL + token set. |
| `FLEET_MGR_INFLUX_TOKEN` | — | InfluxDB token — **server-side only**, never on hosts |
| `FLEET_MGR_INFLUX_ORG` | `fleet` | InfluxDB org |
| `FLEET_MGR_INFLUX_BUCKET` | `fleet_health` | InfluxDB bucket |
| `FLEET_MGR_INVENTORY` | `/var/lib/fleet-manager/inventory.json` | inventory file (mount a volume) |
| `FLEET_MGR_SEED` | `false` | inject demo hosts so the SPA has data — dev only |
| `HOSTNQN_BASE` | `nqn.2026-06.org.example.fleet:host-` | hostnqn prefix (match the broker) |
@@ -121,5 +125,9 @@ curl -s localhost:9500/api/hosts | jq '.hosts[] | {name:.identity.starship_name,
`Accept`-gated JSON approve response (browsers/`verify-serving.sh` unchanged);
the manager drives it over mTLS and births the inventory record on approve.
httptest-mock-broker test.
- Next: InfluxDB read/write (ckpt 4) → themed SPA (ckpt 5) → Docker/compose + PKI
+ docs (ckpt 6).
- **Checkpoint 4 (done):** InfluxDB metrics gateway — snapshots written as points
tagged with the authenticated identity (`MetricsSink`→`influxSink`), and a
read API (`/api/hosts/{serial}/metrics`) that runs a Flux query with a **field
whitelist** + hex-serial guard (no caller input reaches the query raw). Unit
tests for point construction, query building, and unsafe-input rejection.
- Next: themed SPA (ckpt 5) → Docker/compose + PKI + docs (ckpt 6).