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:
@@ -222,10 +222,11 @@
|
||||
<div class="card form-card">
|
||||
<p class="field-hint">
|
||||
Produce an instance-locked agent package stamped with this server's
|
||||
URL, the endpoint agent key, and the reporting cadence, so it deploys
|
||||
with no manual configuration on the client. The download includes the
|
||||
agent, a non-secret <code>appsettings.json</code>, and an
|
||||
<code>install.ps1</code> that writes the credentials to the registry
|
||||
URL, server pin, optionally the endpoint agent key, and the reporting
|
||||
cadence, so it deploys with no manual configuration on the client. The
|
||||
download includes the agent, a non-secret
|
||||
<code>appsettings.json</code>, and an <code>install.ps1</code> that
|
||||
writes the settings to the registry
|
||||
(<code>HKLM\SOFTWARE\GE\ShopDB</code>) - the key never lands in
|
||||
appsettings.
|
||||
</p>
|
||||
@@ -245,14 +246,46 @@
|
||||
placeholder="https://shopdb.example"
|
||||
/>
|
||||
|
||||
<label class="field-label" for="agent-key">Endpoint agent key</label>
|
||||
<label class="checkbox-row">
|
||||
<input v-model="agentincludesharedkey" type="checkbox" />
|
||||
<span>
|
||||
Include shared key. When off, the package holds no key and each PC
|
||||
requests enrollment for an admin to approve in Settings >
|
||||
Endpoints > Enrollment.
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<template v-if="agentincludesharedkey">
|
||||
<label class="field-label" for="agent-key">Endpoint agent key</label>
|
||||
<input
|
||||
id="agent-key"
|
||||
v-model="agentcollectorkey"
|
||||
class="form-control mono"
|
||||
type="text"
|
||||
placeholder="Paste the endpoint agent key generated above"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<label class="field-label" for="agent-nextpin">
|
||||
Next server pin (optional)
|
||||
</label>
|
||||
<input
|
||||
id="agent-key"
|
||||
v-model="agentcollectorkey"
|
||||
id="agent-nextpin"
|
||||
v-model.trim="agentnextserverpin"
|
||||
class="form-control mono"
|
||||
type="text"
|
||||
placeholder="Paste the endpoint agent key generated above"
|
||||
maxlength="64"
|
||||
placeholder="64 lowercase hex characters"
|
||||
/>
|
||||
<p class="field-hint">
|
||||
Current server pin:
|
||||
<code>{{ agentserverpin || 'not available (no server certificate found)' }}</code>.
|
||||
Add the next certificate's pin only ahead of a planned certificate
|
||||
rotation.
|
||||
</p>
|
||||
<p v-if="agentnextpininvalid()" class="field-error">
|
||||
The next server pin must be exactly 64 lowercase hex characters.
|
||||
</p>
|
||||
|
||||
<label class="field-label" for="agent-cadence">
|
||||
Reporting cadence (seconds)
|
||||
@@ -287,9 +320,10 @@
|
||||
/>
|
||||
<p class="field-hint">
|
||||
For a reverse-proxy or internal-CA server this app cannot see, upload
|
||||
the server's public certificate (or issuing CA) in PEM format. It is
|
||||
validated, bundled with the package, and imported into the endpoint's
|
||||
Trusted Root store by <code>install.ps1</code>. Leave empty when the
|
||||
the server's public certificate in PEM format. It is validated,
|
||||
bundled with the package, pinned, and imported into the endpoint's
|
||||
Trusted Root store by <code>install.ps1</code> (a CA certificate is
|
||||
bundled but never imported or pinned). Leave empty when the
|
||||
server uses a publicly trusted certificate. An upload takes precedence
|
||||
over the bundled server certificate above.
|
||||
</p>
|
||||
@@ -298,7 +332,9 @@
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
type="button"
|
||||
:disabled="agentbusy || !agentbaseurl || !agentcollectorkey"
|
||||
:disabled="agentbusy || !agentbaseurl
|
||||
|| (agentincludesharedkey && !agentcollectorkey)
|
||||
|| agentnextpininvalid()"
|
||||
@click="buildSiteAgent"
|
||||
>
|
||||
{{ agentbusy ? 'Building...' : 'Build site agent' }}
|
||||
@@ -529,6 +565,14 @@ const agentcertfile = ref(null)
|
||||
const agentbusy = ref(false)
|
||||
const agentmessage = ref('')
|
||||
const agenterror = ref('')
|
||||
const agentincludesharedkey = ref(true)
|
||||
const agentnextserverpin = ref('')
|
||||
const agentserverpin = ref('')
|
||||
|
||||
function agentnextpininvalid() {
|
||||
const pin = agentnextserverpin.value
|
||||
return !!pin && !/^[0-9a-f]{64}$/.test(pin)
|
||||
}
|
||||
|
||||
// Versioned agent/tray binaries. Each block owns its own upload form, retained
|
||||
// version list, and busy/message state so the two rows act independently. The
|
||||
@@ -594,6 +638,8 @@ onMounted(async () => {
|
||||
agentstaged.value = !!status.agentstaged
|
||||
certavailable.value = !!status.certavailable
|
||||
agentbundlecert.value = !!status.certavailable
|
||||
agentincludesharedkey.value = status.sharedkeyallowed !== false
|
||||
agentserverpin.value = status.serverpin || ''
|
||||
applyBinaryStatus('agent', status.agentcurrent, status.agentversions)
|
||||
applyBinaryStatus('tray', status.traycurrent, status.trayversions)
|
||||
} catch (statusError) {
|
||||
@@ -717,7 +763,13 @@ async function buildSiteAgent() {
|
||||
try {
|
||||
const form = new FormData()
|
||||
form.append('baseurl', agentbaseurl.value)
|
||||
form.append('collectorkey', agentcollectorkey.value)
|
||||
if (agentincludesharedkey.value) {
|
||||
form.append('collectorkey', agentcollectorkey.value)
|
||||
}
|
||||
form.append('includesharedkey', agentincludesharedkey.value ? 'true' : 'false')
|
||||
if (agentnextserverpin.value) {
|
||||
form.append('nextserverpin', agentnextserverpin.value)
|
||||
}
|
||||
form.append('cadenceseconds', agentcadenceseconds.value)
|
||||
form.append('bundlecert', agentbundlecert.value ? 'true' : 'false')
|
||||
if (agentcertfile.value) {
|
||||
|
||||
Reference in New Issue
Block a user