Make rollback, first upgrades and pin changes work as tested on Windows

Found in an end-to-end test on a Windows 11 VM against a self-signed server.

- Rollback: a 1.20 agent re-enrolls within a second of an un-enroll, so an
  older agent pushed afterwards arrived on an enrolled PC and was refused.
  Revoke now takes {"blockreenroll": true, "reason": ...}; a blocked PC is
  never issued a credential and keeps working on the shared key until
  POST .../assets/<assetid>/allow-reenroll. The Enrollment page offers the
  block when un-enrolling and shows blocked PCs with an allow button.
- First upgrade of old agents: agents before 1.20 check the server
  certificate when downloading the upgrade, so a store wave failed on PCs that
  do not trust a self-signed server while the Migration tab said ready. A
  failed download now marks the PC blocked with "server certificate not
  trusted by this PC". New Install-ShopDBServerCertificate.ps1 (GE-Enforce,
  SYSTEM) adds a non-CA server certificate to the trusted roots after checking
  its thumbprint; the server certificate page offers a .cer download and the
  SHA-1 thumbprint.
- The generated installer writes ServerPins (with an optional next pin for a
  planned rotation), can leave the shared key out so the PC waits for
  approval, never touches an existing credential, and skips the trusted-root
  import for a CA certificate.
- The agent re-reads server pins about every 60 seconds and recycles pooled
  connections within 2 minutes, so a pin change needs no restart.
- An alert can only be acknowledged by a PC it targets.
- A wave no longer counts a PC as failed after a later wave upgraded it.
- The scripts report a pin mismatch only when the pin really is missing, and
  otherwise log the real error.
- Runbook: trust step before the first wave, rollback order, pin re-read,
  verifying the pin against the live URL; agent contract updated.
This commit is contained in:
cproudlock
2026-09-15 13:37:37 -04:00
parent 31ed9a5fde
commit da4b224ac1
20 changed files with 1713 additions and 151 deletions

View File

@@ -626,8 +626,10 @@ key authenticated the call, the hostname matches exactly one computer, the
serial on record matches (trimmed, case-insensitive) and the asset has no active
credential. Otherwise the request is pending with one of the reasons `unknown
hostname`, `duplicate hostname`, `no serial on record`, `serial mismatch`,
`already enrolled`, `awaiting approval`, `nonce changed`. The credential is in
the 201 body exactly once.
`already enrolled`, `awaiting approval`, `nonce changed`, `reenroll blocked`.
`reenroll blocked` means an admin revoked the PC with `blockreenroll` (9.7); the
agent keeps retrying on its normal schedule and enrolls once an admin calls
`allow-reenroll`. The credential is in the 201 body exactly once.
### 9.3 Error codes
@@ -646,7 +648,9 @@ as `/api/collector/endpoints`), `GET /api/endpoints/monitors/resolved`,
`POST /api/endpoints/operatorreports`, `GET /api/endpoints/alerts`,
`POST /api/endpoints/alerts/<id>/acknowledge`. With a credential, a missing
hostname is allowed and the asset comes from the credential; an acknowledge with
assetid 0 or absent means the caller. The core collector
assetid 0 or absent means the caller. Acknowledging an alert that does not
target the caller returns 404 `NOT_FOUND`, as for an unknown id.
The core collector
(`/api/collector/computers`, `/api/collector/printers`) accepts the same header
for the SYSTEM reporter scripts.
@@ -673,6 +677,14 @@ for the SYSTEM reporter scripts.
(`spkisha256` on `GET /api/endpoints/settings/server-cert`);
`client/Get-ShopDBServerPin.ps1` computes it from a URL or a file;
`client/Set-ShopDBServerPins.ps1` writes it.
- The agent re-reads the pin set within about 60 seconds of a change; no
service restart is needed.
- The same `GET /api/endpoints/settings/server-cert` carries `sha1thumbprint`
(hex SHA-1 of the certificate), and
`GET /api/endpoints/settings/server-cert/download` returns the public
certificate as a `.cer`. `client/Install-ShopDBServerCertificate.ps1` checks
the file against that thumbprint and adds it to `LocalMachine\Root` on PCs
whose pre-1.20 agent validates the chain when it downloads the upgrade.
- Any pin configured: accept the handshake if and only if the leaf key hash is
in the set (chain and name errors ignored). No pin: require a clean Windows
validation.
@@ -702,7 +714,20 @@ Read needs `endpoints.fleet`; writes need the admin role and are audited.
- `GET /api/endpoints/enrollment` (`page`, `perpage` <= 100, `state`, `search`)
- `POST /api/endpoints/enrollment/requests/<id>/approve` `{"assetid": n}`
- `POST /api/endpoints/enrollment/requests/<id>/reject`
- `POST /api/endpoints/enrollment/assets/<assetid>/revoke` (un-enroll)
- `POST /api/endpoints/enrollment/assets/<assetid>/revoke` (un-enroll). Optional
body `{"blockreenroll": true, "reason": "rollback"}` (`reason` <= 64 chars,
used only with `blockreenroll`) also keeps the PC from enrolling again: its
enroll requests stay pending with `reenroll blocked`, and the Migration tab
shows it blocked with the same blocker. Works on a PC whose credential is
already revoked. Without the block a 1.20 agent re-enrolls within about a
second of a plain revoke.
- `POST /api/endpoints/enrollment/assets/<assetid>/allow-reenroll` lifts the
block; the PC enrolls on its next attempt. 404 when the PC has no credential
record; a PC that was not blocked returns success unchanged.
- `PUT /api/endpoints/enrollment/settings` `{"sharedkeyallowed": bool, "autoenroll": bool}`
- `GET /api/endpoints/enrollment/migration` (`page`, `perpage` <= 100, `state`, `search`)
- `POST /api/endpoints/enrollment/migration/wave` `{"assetids": [...]}` or `{"next": n}` (n <= 100)
- Migration blockers include `server certificate not trusted by this PC`: the
PC's last upgrade download failed on the certificate chain (the server only
knows from the failure the PC reported). Trust the certificate
(`Install-ShopDBServerCertificate.ps1`) and queue it again.