WIP: Windows existing-agent credential transition and trust-reset recovery #5
No files matched your search
@@ -197,6 +197,20 @@ and other runtime data. Existing Scheduled Task definitions are authoritative
|
|||||||
and are not rewritten. Missing tasks are created for a fresh or incomplete
|
and are not rewritten. Missing tasks are created for a fresh or incomplete
|
||||||
installation without hard-coded user names.
|
installation without hard-coded user names.
|
||||||
|
|
||||||
|
The per-user `MinerOpsAgentTray` task is optional UI and is not credential or
|
||||||
|
core Agent authority. During an explicit filesystem trust reset, runtime
|
||||||
|
publication may run noninteractively as `NT AUTHORITY\SYSTEM`, where no safe
|
||||||
|
interactive user principal can be inferred. The installer still publishes
|
||||||
|
`MinerOpsAgentTray.ps1`, but deliberately defers creation of a missing tray task
|
||||||
|
until a later interactive maintenance install. It never creates the tray task
|
||||||
|
as SYSTEM or invents a username. An existing tray task remains authoritative
|
||||||
|
and is preserved unchanged rather than silently repaired under SYSTEM; inspect
|
||||||
|
and remediate a suspected malformed definition separately. The core
|
||||||
|
`MinerOpsAgent` task remains mandatory and strictly verified by credential
|
||||||
|
recovery. `MinerOpsApi3Enrichment` and `MinerOpsPoolPoller` are non-interactive
|
||||||
|
background tasks with explicit SYSTEM principals, so missing instances continue
|
||||||
|
to be created during trust-reset publication.
|
||||||
|
|
||||||
If MinerOpsAgent was running before an upgrade, the installer stops only that
|
If MinerOpsAgent was running before an upgrade, the installer stops only that
|
||||||
Scheduled Task, waits for it to stop, replaces the managed files, and starts it
|
Scheduled Task, waits for it to stop, replaces the managed files, and starts it
|
||||||
again. A previously stopped Agent remains stopped. Before replacing runtime
|
again. A previously stopped Agent remains stopped. Before replacing runtime
|
||||||
|
|||||||
@@ -953,6 +953,19 @@ function Get-MinerOpsScheduledTask {
|
|||||||
return $tasks[0]
|
return $tasks[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Test-MinerOpsInstallerShouldCreateMissingTask {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]$Definition,
|
||||||
|
[Parameter(Mandatory)][bool]$TrustResetMode
|
||||||
|
)
|
||||||
|
|
||||||
|
if (-not $TrustResetMode) { return $true }
|
||||||
|
if ($null -eq $Definition.CreateWhenMissingDuringTrustReset) {
|
||||||
|
throw "Scheduled Task trust-reset creation policy is missing."
|
||||||
|
}
|
||||||
|
return [bool]$Definition.CreateWhenMissingDuringTrustReset
|
||||||
|
}
|
||||||
|
|
||||||
function Stop-MinerOpsScheduledTaskForUpgrade {
|
function Stop-MinerOpsScheduledTaskForUpgrade {
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)][string]$Name,
|
[Parameter(Mandatory)][string]$Name,
|
||||||
@@ -1101,22 +1114,26 @@ try {
|
|||||||
Name = "MinerOpsAgent"; Script = "MinerOpsAgentService.ps1"
|
Name = "MinerOpsAgent"; Script = "MinerOpsAgentService.ps1"
|
||||||
Trigger = $startup; Principal = $system; Settings = $agentSettings
|
Trigger = $startup; Principal = $system; Settings = $agentSettings
|
||||||
ExtraArguments = ""
|
ExtraArguments = ""
|
||||||
|
CreateWhenMissingDuringTrustReset = $true
|
||||||
},
|
},
|
||||||
@{
|
@{
|
||||||
Name = "MinerOpsAgentTray"; Script = "MinerOpsAgentTray.ps1"
|
Name = "MinerOpsAgentTray"; Script = "MinerOpsAgentTray.ps1"
|
||||||
Trigger = (New-ScheduledTaskTrigger -AtLogOn); Principal = $null
|
Trigger = (New-ScheduledTaskTrigger -AtLogOn); Principal = $null
|
||||||
Settings = $commonSettings; ExtraArguments = "-WindowStyle Hidden"
|
Settings = $commonSettings; ExtraArguments = "-WindowStyle Hidden"
|
||||||
|
CreateWhenMissingDuringTrustReset = $false
|
||||||
},
|
},
|
||||||
@{
|
@{
|
||||||
Name = "MinerOpsApi3Enrichment"
|
Name = "MinerOpsApi3Enrichment"
|
||||||
Script = "MinerOpsApi3EnrichmentPoller.ps1"
|
Script = "MinerOpsApi3EnrichmentPoller.ps1"
|
||||||
Trigger = $fiveMinutes; Principal = $system; Settings = $commonSettings
|
Trigger = $fiveMinutes; Principal = $system; Settings = $commonSettings
|
||||||
ExtraArguments = "-Limit 0 -TimeoutMs 8000"
|
ExtraArguments = "-Limit 0 -TimeoutMs 8000"
|
||||||
|
CreateWhenMissingDuringTrustReset = $true
|
||||||
},
|
},
|
||||||
@{
|
@{
|
||||||
Name = "MinerOpsPoolPoller"; Script = "MinerOpsPoolPollerTask.ps1"
|
Name = "MinerOpsPoolPoller"; Script = "MinerOpsPoolPollerTask.ps1"
|
||||||
Trigger = $fiveMinutes; Principal = $system; Settings = $commonSettings
|
Trigger = $fiveMinutes; Principal = $system; Settings = $commonSettings
|
||||||
ExtraArguments = ""
|
ExtraArguments = ""
|
||||||
|
CreateWhenMissingDuringTrustReset = $true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
foreach ($definition in $definitions) {
|
foreach ($definition in $definitions) {
|
||||||
@@ -1129,6 +1146,16 @@ try {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (-not (Test-MinerOpsInstallerShouldCreateMissingTask `
|
||||||
|
-Definition $definition -TrustResetMode $trustResetMode)) {
|
||||||
|
# Trust-reset publication can run noninteractively as SYSTEM. A
|
||||||
|
# missing per-user tray has no safe interactive principal in that
|
||||||
|
# context. Its runtime file is already published; task creation is
|
||||||
|
# intentionally deferred to a later interactive maintenance run.
|
||||||
|
Write-Host "Deferring missing per-user Scheduled Task $($definition.Name) until an interactive install."
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
$arguments = '-NoProfile -ExecutionPolicy Bypass -File "{0}"' -f `
|
$arguments = '-NoProfile -ExecutionPolicy Bypass -File "{0}"' -f `
|
||||||
(Join-Path $Target $definition.Script)
|
(Join-Path $Target $definition.Script)
|
||||||
if ($definition.ExtraArguments) {
|
if ($definition.ExtraArguments) {
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ Assert-SourceContains '\$existingTask\s*=\s*Get-MinerOpsScheduledTask' `
|
|||||||
"Existing task preservation guard is missing."
|
"Existing task preservation guard is missing."
|
||||||
Assert-SourceContains 'Preserving existing Scheduled Task' `
|
Assert-SourceContains 'Preserving existing Scheduled Task' `
|
||||||
"Existing task preservation is not explicit."
|
"Existing task preservation is not explicit."
|
||||||
|
Assert-SourceContains 'Test-MinerOpsInstallerShouldCreateMissingTask' `
|
||||||
|
"Missing-task trust-reset policy helper is absent."
|
||||||
|
Assert-SourceContains 'Deferring missing per-user Scheduled Task' `
|
||||||
|
"Missing per-user task deferral is not explicit."
|
||||||
Assert-SourceContains 'ExtraArguments\s*=\s*"-Limit 0 -TimeoutMs 8000"' `
|
Assert-SourceContains 'ExtraArguments\s*=\s*"-Limit 0 -TimeoutMs 8000"' `
|
||||||
"Fresh enrichment task arguments are missing."
|
"Fresh enrichment task arguments are missing."
|
||||||
Assert-SourceContains 'Stop-MinerOpsScheduledTaskForUpgrade' `
|
Assert-SourceContains 'Stop-MinerOpsScheduledTaskForUpgrade' `
|
||||||
@@ -52,6 +56,98 @@ Assert-SourceContains 'could not restart restored MinerOpsAgent' `
|
|||||||
Assert-SourceContains 'Assert-MinerOpsInstallerTrustedAncestors' `
|
Assert-SourceContains 'Assert-MinerOpsInstallerTrustedAncestors' `
|
||||||
"Installer does not validate ancestor namespace protection."
|
"Installer does not validate ancestor namespace protection."
|
||||||
|
|
||||||
|
$taskDefinitionsStart = $source.IndexOf('$definitions = @(')
|
||||||
|
$taskLoopStart = $source.IndexOf(
|
||||||
|
'foreach ($definition in $definitions)', $taskDefinitionsStart
|
||||||
|
)
|
||||||
|
if ($taskDefinitionsStart -lt 0 -or $taskLoopStart -le $taskDefinitionsStart) {
|
||||||
|
throw "Scheduled Task definitions or reconciliation loop were not found."
|
||||||
|
}
|
||||||
|
$taskDefinitions = $source.Substring(
|
||||||
|
$taskDefinitionsStart, $taskLoopStart - $taskDefinitionsStart
|
||||||
|
)
|
||||||
|
foreach ($policy in @(
|
||||||
|
@{ Name = "MinerOpsAgent"; Expected = '\$true' },
|
||||||
|
@{ Name = "MinerOpsAgentTray"; Expected = '\$false' },
|
||||||
|
@{ Name = "MinerOpsApi3Enrichment"; Expected = '\$true' },
|
||||||
|
@{ Name = "MinerOpsPoolPoller"; Expected = '\$true' }
|
||||||
|
)) {
|
||||||
|
$definition = [regex]::Match(
|
||||||
|
$taskDefinitions,
|
||||||
|
('(?s)@\{{.*?Name\s*=\s*"{0}".*?\}}' -f
|
||||||
|
[regex]::Escape($policy.Name))
|
||||||
|
).Value
|
||||||
|
if ([string]::IsNullOrWhiteSpace($definition) -or
|
||||||
|
$definition -notmatch (
|
||||||
|
'CreateWhenMissingDuringTrustReset\s*=\s*{0}' -f
|
||||||
|
$policy.Expected
|
||||||
|
)) {
|
||||||
|
throw "Scheduled Task $($policy.Name) has the wrong trust-reset creation policy."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$taskLoopEnd = $source.IndexOf(
|
||||||
|
'if ($mainTaskWasRunning', $taskLoopStart
|
||||||
|
)
|
||||||
|
if ($taskLoopEnd -le $taskLoopStart) {
|
||||||
|
throw "Scheduled Task reconciliation loop end was not found."
|
||||||
|
}
|
||||||
|
$taskLoop = $source.Substring($taskLoopStart, $taskLoopEnd - $taskLoopStart)
|
||||||
|
$preserveIndex = $taskLoop.IndexOf('$null -ne $existingTask')
|
||||||
|
$deferIndex = $taskLoop.IndexOf(
|
||||||
|
'Test-MinerOpsInstallerShouldCreateMissingTask'
|
||||||
|
)
|
||||||
|
$actionIndex = $taskLoop.IndexOf('New-ScheduledTaskAction')
|
||||||
|
if (
|
||||||
|
$preserveIndex -lt 0 -or
|
||||||
|
$deferIndex -le $preserveIndex -or
|
||||||
|
$actionIndex -le $deferIndex
|
||||||
|
) {
|
||||||
|
throw "Existing-task preservation and missing-task deferral are ordered unsafely."
|
||||||
|
}
|
||||||
|
if ($taskLoop -match '80070534|No mapping between account names') {
|
||||||
|
throw "Missing per-user task handling must be policy-based, not HRESULT-based."
|
||||||
|
}
|
||||||
|
|
||||||
|
$taskPolicyFunction = [regex]::Match(
|
||||||
|
$source,
|
||||||
|
'(?s)function Test-MinerOpsInstallerShouldCreateMissingTask \{.*?\n\}'
|
||||||
|
).Value
|
||||||
|
if ([string]::IsNullOrWhiteSpace($taskPolicyFunction)) {
|
||||||
|
throw "Missing-task trust-reset policy function source was not found."
|
||||||
|
}
|
||||||
|
Invoke-Expression $taskPolicyFunction
|
||||||
|
$trayPolicy = [pscustomobject]@{
|
||||||
|
CreateWhenMissingDuringTrustReset = $false
|
||||||
|
}
|
||||||
|
$systemTaskPolicy = [pscustomobject]@{
|
||||||
|
CreateWhenMissingDuringTrustReset = $true
|
||||||
|
}
|
||||||
|
if (Test-MinerOpsInstallerShouldCreateMissingTask `
|
||||||
|
-Definition $trayPolicy -TrustResetMode $true) {
|
||||||
|
throw "Trust reset would create a missing per-user tray task."
|
||||||
|
}
|
||||||
|
if (-not (Test-MinerOpsInstallerShouldCreateMissingTask `
|
||||||
|
-Definition $trayPolicy -TrustResetMode $false)) {
|
||||||
|
throw "Normal interactive installation would not repair a missing tray task."
|
||||||
|
}
|
||||||
|
if (-not (Test-MinerOpsInstallerShouldCreateMissingTask `
|
||||||
|
-Definition $systemTaskPolicy -TrustResetMode $true)) {
|
||||||
|
throw "Trust reset would skip a missing explicit-SYSTEM task."
|
||||||
|
}
|
||||||
|
$missingPolicyRejected = $false
|
||||||
|
try {
|
||||||
|
[void](Test-MinerOpsInstallerShouldCreateMissingTask `
|
||||||
|
-Definition ([pscustomobject]@{}) -TrustResetMode $true)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
$missingPolicyRejected = $_.Exception.Message -match
|
||||||
|
'trust-reset creation policy is missing'
|
||||||
|
}
|
||||||
|
if (-not $missingPolicyRejected) {
|
||||||
|
throw "Trust reset accepted a task definition without explicit policy."
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($functionName in @(
|
foreach ($functionName in @(
|
||||||
"Assert-MinerOpsInstallerNoReparsePath",
|
"Assert-MinerOpsInstallerNoReparsePath",
|
||||||
"Assert-MinerOpsInstallerTrustedAncestors"
|
"Assert-MinerOpsInstallerTrustedAncestors"
|
||||||
|
|||||||
Reference in new issue
Block a user