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 8eee6f19d8
commit 70d5531b4d
20 changed files with 1713 additions and 151 deletions

View File

@@ -9,7 +9,9 @@ and, when the route names a PC, binds the caller through bind_hostname().
X-API-Key The shared fleet key (or a collector-scoped managed
token). Accepted only while
plugin.endpoints.sharedkeyallowed is on, and never for a
PC that holds an active credential.
PC that holds an active credential. A PC blocked
from re-enrolling has no active credential, so an
older agent on it keeps working on the shared key.
Only the sha256 of a credential is stored. The full secret exists once, in the
enroll response.
@@ -79,6 +81,18 @@ def active_credential_for(assetid):
.first())
def reenroll_blocked(assetid):
"""True when an admin blocked this asset from enrolling again."""
from ..models import EndpointCredential
if assetid is None:
return False
return (EndpointCredential.query
.filter(EndpointCredential.assetid == assetid,
EndpointCredential.reenrollblocked.is_(True))
.first()) is not None
def resolve_credential(secret, touch=True):
"""Asset id for an active credential, else None."""
from ..models import EndpointCredential