feat(windows): add recoverable existing-agent credential upgrade
This commit is contained in:
1 parent
3cfb0d1abe
commit
97a0139e3d
25 files changed
+11122
-108
No files matched your search
+124
-3
@@ -74,9 +74,122 @@ credentials in screenshots, chat, tickets, logs, or source control.
|
||||
|
||||
### Existing Agent upgrade
|
||||
|
||||
For an already enrolled/configured Agent, do not run the enrollment
|
||||
bootstrapper. Copy the new canonical Agent files to a temporary directory and
|
||||
run install.ps1 from an elevated Windows PowerShell session.
|
||||
An existing legacy Agent must receive a per-Agent credential before the global
|
||||
shared-secret compatibility path can be retired for its identity. In Agent
|
||||
Management, use **Upgrade credential** on that Agent. Download the resulting
|
||||
Windows package, transfer it only to that Agent computer, and run:
|
||||
|
||||
.\Upgrade-MinerOpsAgentCredential.ps1
|
||||
|
||||
The authorization is bound server-side to the existing Agent ID and its
|
||||
current Site and Agent Zone. The script validates the local Agent ID, upgrades
|
||||
the canonical runtime, stops the main Agent task, generates the permanent
|
||||
secret locally, and recoverably replaces only the credential and durable routing
|
||||
fields in `agent-config.json` using protected same-directory pending and prior
|
||||
names. Existing API3 credentials, polling settings,
|
||||
network ranges, state, logs, and backups are preserved. Before any secret is
|
||||
written, the script creates and verifies a protected recovery file containing
|
||||
the original Scheduled Task state, exact transition token, and exact locally
|
||||
generated credential. The Hub accepts an ambiguous retry only for that same
|
||||
token and credential.
|
||||
|
||||
The Agent directory is a filesystem security boundary. The transition rejects
|
||||
junctions/reparse points, verifies trusted ancestors, denies standard users
|
||||
create/delete/rename rights, and revalidates the boundary before each rename.
|
||||
If an existing Agent directory is discovered insecure, the script first writes
|
||||
`C:\MinerOpsAgent.minerops-filesystem-trust.blocked-<random>.state` under the trusted
|
||||
parent, then hardens the directory only to contain the incident. The blocker is
|
||||
outside the contaminated namespace because changing a DACL does not revoke
|
||||
file or directory handles opened before repair. The same process never redeems
|
||||
a token, creates or activates a permanent credential, runs the installer, or
|
||||
removes that blocker. Ordinary second, third, and later invocations remain
|
||||
blocked even when every current ACL looks correct.
|
||||
|
||||
Recovery, pending, replacement remnants, and late-arriving artifacts are
|
||||
non-authoritative while the external blocker exists. Known transition state is
|
||||
quarantined without parsing. A correct filename, syntax, or ACL cannot promote
|
||||
it on a later run. Invalidate any possibly exposed authorization and retain or
|
||||
remove evidence only under the approved incident procedure.
|
||||
|
||||
### Filesystem trust-epoch recovery
|
||||
|
||||
Use this procedure only after confirming the blocker was created by the
|
||||
containment event and preserving required evidence:
|
||||
|
||||
1. Do not delete or edit the external blocker. Do not run an ordinary retry.
|
||||
2. Use **Restart** to perform a full Windows kernel boot. Shutdown with Fast
|
||||
Startup is not sufficient: Microsoft documents Fast Startup as hibernating
|
||||
and restoring kernel session 0. The script identifies the kernel epoch by
|
||||
the Event Log record anchor of the newest
|
||||
`Microsoft-Windows-Kernel-Boot` event 27 whose `BootType` is exactly `0`.
|
||||
Provider, event ID, record ID, and supported event version come from typed
|
||||
EventRecord properties; namespace-aware XML selection reads only the
|
||||
`BootType` payload. The new full-boot record ID must be strictly greater
|
||||
than the stored record ID. Equality, an older/restored record, log clearing
|
||||
or numbering reset, missing history, duplication, malformed XML, and an
|
||||
unsupported provider schema all fail closed and require safe operator
|
||||
intervention. There is no timestamp fallback. The anchor is unchanged by
|
||||
clock or timezone changes, sleep, hibernation, and Fast Startup. See
|
||||
Microsoft's [system power-state documentation](https://learn.microsoft.com/windows/win32/power/system-power-states)
|
||||
and [Fast Startup support guidance](https://learn.microsoft.com/troubleshoot/windows-client/setup-upgrade-and-drivers/fast-startup-causes-system-hibernation-shutdown-fail).
|
||||
3. For a credential transition, use a fresh transition package if any recovery,
|
||||
pending, remnant, quarantine, or internal blocker existed. Remove those
|
||||
artifacts only under the approved incident procedure after invalidating the
|
||||
old authorization.
|
||||
4. From the reviewed package, run:
|
||||
|
||||
.\Upgrade-MinerOpsAgentCredential.ps1 `
|
||||
-ApproveFilesystemTrustResetAfterReboot
|
||||
|
||||
The script verifies that Windows boot identity changed, rescans the protected
|
||||
namespace, positively verifies and stops the expected Scheduled Task, and asks the
|
||||
installer to replace managed runtime without backing up or restoring any
|
||||
pre-reset runtime. The installer leaves both the Agent task and external
|
||||
blocker untouched after publication; it has no blocker-deletion path. Before
|
||||
its first fallible stop/install step, the parent durably records the strictly
|
||||
validated original task intent as `reset-authorized`; after installer
|
||||
success it advances the blocker to `runtime-published`. It then verifies the
|
||||
config against the package/server identity, commits the credential, and
|
||||
restores the recorded task intent. Only the parent removes the blocker,
|
||||
after active config and final task state have both been positively verified
|
||||
and non-recovery transition cleanup has succeeded. Protected recovery stays
|
||||
until after blocker deletion so an interrupted final cleanup remains safe.
|
||||
5. For a genuinely new enrollment, preserve evidence and use the approved
|
||||
protected-recreation incident procedure to remove the contaminated namespace
|
||||
and its external blocker, then let `Enroll-MinerOpsAgent.ps1` stage a new
|
||||
protected directory. Enrollment has no in-script blocker-deletion path; its
|
||||
explicit compatibility switch cannot override a retained blocker.
|
||||
|
||||
The explicit switch without a strictly newer canonical full-kernel-boot anchor
|
||||
fails.
|
||||
Any suspicious transition artifact, failed runtime publication, failed
|
||||
credential convergence, invalid final task, or interrupted cleanup retains the
|
||||
blocker. Runtime publication alone never authorizes deletion. Protected
|
||||
directory recreation is the alternative when the retained namespace cannot be
|
||||
verified.
|
||||
|
||||
If the script is interrupted after the Hub commits, rerun the same extracted
|
||||
package without deleting or editing `agent-credential-transition.recovery.json`
|
||||
or `agent-config.json.pending`. A protected, matching pending config is resumed;
|
||||
an insecure, malformed, or mismatched artifact fails closed. The original task
|
||||
intent is durable: an Agent which was running before the first attempt ends
|
||||
running, while an intentionally stopped Agent remains stopped. Success is not
|
||||
reported and transition artifacts are not removed until both the active config
|
||||
and expected final task state are verified. The verifier requires exactly one
|
||||
enabled root `MinerOpsAgent` task, the SYSTEM service-account/highest principal,
|
||||
the installed Windows PowerShell action and service-script arguments, and an
|
||||
exact final state of `Running` for running intent or `Ready` for stopped intent.
|
||||
Missing, query failure, access denied, malformed, multiple, disabled, queued,
|
||||
unknown, wrong-action, and wrong-principal results all fail closed and retain
|
||||
recovery.
|
||||
|
||||
After a successful transition, the existing Agent ID, Site, and Zone are
|
||||
unchanged. Explicit Agent ID plus the new per-Agent secret is required, and the
|
||||
legacy shared secret is permanently disabled for that identity.
|
||||
|
||||
For later code-only upgrades of an Agent that already has a per-Agent
|
||||
credential, copy the new canonical Agent files to a temporary directory and run
|
||||
install.ps1 from an elevated Windows PowerShell session.
|
||||
|
||||
The installer copies only managed runtime code to C:\MinerOpsAgent and
|
||||
preserves agent-config.json, agent-state.json, logs\, backups, probe output,
|
||||
@@ -90,6 +203,14 @@ again. A previously stopped Agent remains stopped. Before replacing runtime
|
||||
files, the installer copies them to a timestamped backup under
|
||||
C:\MinerOpsAgent\backups.
|
||||
|
||||
Runtime sources, installed destinations, active config, and rollback copies are
|
||||
rejected when they are reparse points or have more than one hard link. A normal
|
||||
trusted upgrade creates a fresh random backup directory and never adopts an
|
||||
existing backup destination. During an explicit post-reboot trust reset, old
|
||||
runtime is never copied into backup or restored; fresh package content replaces
|
||||
it while the external blocker remains, and the Agent task stays stopped for
|
||||
credential verification.
|
||||
|
||||
A failed deployment restores managed files, removes tasks created by that
|
||||
failed attempt, and restores the main Agent's prior running state.
|
||||
|
||||
|
||||
Reference in new issue
Block a user