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:
@@ -133,6 +133,11 @@ public static class ShopDbServerPinValidator
|
||||
return Pins.Contains(LastPresented);
|
||||
}
|
||||
|
||||
public static bool IsPinned(string pin)
|
||||
{
|
||||
return !String.IsNullOrEmpty(pin) && Pins.Contains(pin);
|
||||
}
|
||||
|
||||
// Walks the DER: Certificate > TBSCertificate > [0] version (optional),
|
||||
// serialNumber, signature, issuer, validity, subject, subjectPublicKeyInfo.
|
||||
public static string SpkiSha256Hex(byte[] der)
|
||||
@@ -271,8 +276,20 @@ function Get-ShopDbHttpFailure($ErrorRecord) {
|
||||
try { $code = [string](($text | ConvertFrom-Json).data.error.code) } catch {}
|
||||
}
|
||||
$presented = ''
|
||||
if ('ShopDbServerPinValidator' -as [type]) { $presented = [string][ShopDbServerPinValidator]::LastPresented }
|
||||
return @{ Status = $status; Code = $code; PresentedPin = $presented }
|
||||
$matched = $false
|
||||
if ('ShopDbServerPinValidator' -as [type]) {
|
||||
$presented = [string][ShopDbServerPinValidator]::LastPresented
|
||||
$matched = [ShopDbServerPinValidator]::IsPinned($presented)
|
||||
}
|
||||
# Innermost exception text: the outer WebException hides the real cause
|
||||
# (unexpected EOF, connection reset, trust failure).
|
||||
$message = ''
|
||||
$exception = $ErrorRecord.Exception
|
||||
while ($exception) {
|
||||
if ($exception.Message) { $message = $exception.Message }
|
||||
$exception = $exception.InnerException
|
||||
}
|
||||
return @{ Status = $status; Code = $code; PresentedPin = $presented; PinMatched = $matched; Message = $message }
|
||||
}
|
||||
|
||||
function Write-ShopDbAuthFailure($Failure) {
|
||||
@@ -292,7 +309,14 @@ function Write-ShopDbAuthFailure($Failure) {
|
||||
}
|
||||
default {
|
||||
if ($Failure.Status -eq 0 -and $Failure.PresentedPin) {
|
||||
Log "ERROR TLS refused: server key pin $($Failure.PresentedPin) is not in ServerPins"
|
||||
# A pin was seen, but only an absent pin is a pin failure. A
|
||||
# matched pin means the handshake passed and something later
|
||||
# failed (server dropped mid-transfer, timeout).
|
||||
if ($Failure.PinMatched) {
|
||||
Log "ERROR request failed after the server key pin matched ($($Failure.PresentedPin)): $($Failure.Message)"
|
||||
} else {
|
||||
Log "ERROR TLS refused: server key pin $($Failure.PresentedPin) is not in ServerPins"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user