feat(fleet-sync): anchor replicas to a control fingerprint (#968)

A replica now binds to the first control that pushes to it. Subsequent
pushes from a different control are rejected with 409
CONTROL_IDENTITY_MISMATCH until an admin explicitly reanchors. Closes
the cross-control hijack window where any node_proxy bearer signed
against the replica's secret could overwrite security policies.

Wire protocol:
- Sender includes a stable 16-hex-char `controlIdentity` derived by
  SHA-256-truncating `system_state.instance_id` (the local UUID written
  once by LicenseService.initialize on first boot). Hostname rotations
  do not flag drift; only a SQLite reset or explicit reanchor breaks
  the binding.
- Receiver caches the fingerprint inside the same transaction that
  applies the row replacement and watermark write. Three states:
  null (fresh install), '' (post-reanchor), '<fingerprint>' (anchored).
- Empty `controlIdentity` is treated as legacy and accepted, so older
  controls keep working during rollout.

New endpoint:
- POST /api/fleet/role/reanchor (admin, requires `{override: true}`):
  clears the cached fingerprint, both `received_pushed_at:*` watermarks,
  and replicated rows of both resources, all in one transaction. The
  static cached fingerprint is also flushed defensively.

Public surface additions:
- `FleetSyncService.getControlIdentity()`: stable fingerprint for the
  outgoing push body.
- `FleetSyncService.reanchor()`: admin-driven anchor reset.
- `ControlIdentityMismatchError`: typed sentinel the route translates
  to 409 with structured body `{error, code, expected, got}`.

Tests:
- 9 new vitest cases covering first-sync persistence, mismatch
  rejection, matching acceptance, empty-incoming back-compat,
  post-reanchor un-anchored state, fingerprint stability, missing
  instance_id fallback, route-level mismatch, route-level reanchor
  with override gating.
- One ordered route-level scenario instead of cross-dependent it()
  blocks so test reordering cannot silently break the suite.
- Full backend suite: 1769 pass / 5 skipped.
This commit is contained in:
Anso
2026-05-07 13:00:21 -04:00
committed by GitHub
parent 27660f622b
commit f3757b43c6
6 changed files with 341 additions and 14 deletions
+1
View File
@@ -87,6 +87,7 @@ export const AUDIT_ROUTE_SUMMARIES: Record<string, string> = {
'POST /fleet/snapshots/*/restore': 'Restored fleet backup',
'POST /fleet/nodes/*/update': 'Triggered fleet node update',
'POST /fleet/update-all': 'Triggered fleet-wide update',
'POST /fleet/role/reanchor': 'Re-anchored fleet replica',
// Cloud backup
'PUT /cloud-backup/config': 'Updated cloud backup config',