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
@@ -32,6 +32,9 @@ function Assert-SourceContains {
|
||||
Assert-SourceContains 'enrollment\.json' `
|
||||
"Bootstrap enrollment file is not explicit."
|
||||
|
||||
Assert-SourceContains 'workflow\s*-ne\s*"new_agent"' `
|
||||
"New-Agent bootstrap does not reject transition packages."
|
||||
|
||||
Assert-SourceContains '/api/agent/enroll' `
|
||||
"Bootstrap does not use the secure enrollment endpoint."
|
||||
|
||||
@@ -44,8 +47,20 @@ Assert-SourceContains 'agent-config\.json\.pending' `
|
||||
Assert-SourceContains 'agent-config\.recovery\.json' `
|
||||
"Post-redemption credential recovery path is missing."
|
||||
|
||||
Assert-SourceContains 'Protect-MinerOpsCredentialFile' `
|
||||
"Credential file ACL protection is missing."
|
||||
Assert-SourceContains 'Open-MinerOpsEnrollmentCredentialFileCreateNew' `
|
||||
"Handle-based credential creation is missing."
|
||||
|
||||
Assert-SourceContains 'FileMode\]::CreateNew' `
|
||||
"Credential files are not created with fail-if-present semantics."
|
||||
|
||||
Assert-SourceContains 'FileShare\]::None' `
|
||||
"Credential files are not held exclusively while being created."
|
||||
|
||||
Assert-SourceContains 'Flush\(\$true\)' `
|
||||
"Credential writes are not durably flushed through the trusted handle."
|
||||
|
||||
Assert-SourceContains 'GetNumberOfLinks' `
|
||||
"Enrollment does not reject hard-linked credential targets."
|
||||
|
||||
Assert-SourceContains 'S-1-5-18' `
|
||||
"SYSTEM ACL entry is missing."
|
||||
@@ -53,12 +68,73 @@ Assert-SourceContains 'S-1-5-18' `
|
||||
Assert-SourceContains 'S-1-5-32-544' `
|
||||
"Administrators ACL entry is missing."
|
||||
|
||||
Assert-SourceContains 'WindowsIdentity\]::GetCurrent\(\)\.User' `
|
||||
"Installing user ACL entry is missing."
|
||||
Assert-SourceContains 'SetOwner\(\$administrators\)' `
|
||||
"Stable Administrators ownership is missing from credential ACLs."
|
||||
|
||||
Assert-SourceContains 'Move-Item[\s\S]*\$ConfigPath' `
|
||||
Assert-SourceContains '\[IO\.File\]::Move\(\$PendingConfigPath, \$ConfigPath\)' `
|
||||
"Atomic final config move is missing."
|
||||
|
||||
Assert-SourceContains 'Assert-MinerOpsEnrollmentDirectory' `
|
||||
"Enrollment does not revalidate the directory trust boundary."
|
||||
|
||||
Assert-SourceContains 'minerops-filesystem-trust\.blocked-' `
|
||||
"Enrollment directory repair lacks an external durable trust blocker."
|
||||
|
||||
Assert-SourceContains 'ApproveFilesystemTrustResetAfterReboot' `
|
||||
"Enrollment lacks an explicit post-reboot trust-reset gate."
|
||||
|
||||
Assert-SourceContains 'windows-full-boot-event-v1:' `
|
||||
"Enrollment blockers do not use canonical full-kernel-boot records."
|
||||
|
||||
if ($source -match 'LastBootUpTime') {
|
||||
throw "Enrollment must not use a wall-clock boot timestamp."
|
||||
}
|
||||
|
||||
if ($source -match 'Remove-MinerOpsEnrollmentTrustBlocker') {
|
||||
throw "Enrollment must not have independent trust-blocker deletion authority."
|
||||
}
|
||||
|
||||
Assert-SourceContains 'Enrollment cannot clear an existing filesystem trust blocker' `
|
||||
"Enrollment does not fail closed on retained filesystem trust blockers."
|
||||
|
||||
Assert-SourceContains 'Assert-MinerOpsEnrollmentTrustedAncestors' `
|
||||
"Enrollment does not validate ancestor namespace protection."
|
||||
|
||||
foreach ($functionName in @(
|
||||
"Assert-MinerOpsEnrollmentNoReparsePath",
|
||||
"Assert-MinerOpsEnrollmentTrustedAncestors"
|
||||
)) {
|
||||
$functionSource = [regex]::Match(
|
||||
$source,
|
||||
"(?s)function $functionName \{.*?\n\}"
|
||||
).Value
|
||||
if ([string]::IsNullOrWhiteSpace($functionSource)) {
|
||||
throw "$functionName source was not found."
|
||||
}
|
||||
if ($functionSource -notmatch '\$current\s*=\s*\$root') {
|
||||
throw "$functionName does not preserve the filesystem root."
|
||||
}
|
||||
if ($functionSource -notmatch '\$fullPath\.Equals\(\s*\$root') {
|
||||
throw "$functionName does not protect a root-valued full path from trimming."
|
||||
}
|
||||
if ($functionSource -match '\$root\.TrimEnd\(') {
|
||||
throw "$functionName converts a root-qualified path to drive-relative form."
|
||||
}
|
||||
if ($functionSource -match 'GetFullPath\(\$Path\)\.TrimEnd\(') {
|
||||
throw "$functionName trims the filesystem root before identifying it."
|
||||
}
|
||||
if ($functionName -eq "Assert-MinerOpsEnrollmentTrustedAncestors" -and
|
||||
$functionSource -notmatch '\$parent\.Equals\(\s*\$root') {
|
||||
throw "$functionName does not protect a root-valued parent from trimming."
|
||||
}
|
||||
}
|
||||
|
||||
Assert-SourceContains 'Open-MinerOpsEnrollmentDirectoryLock' `
|
||||
"Enrollment does not lock an insecure target during repair."
|
||||
|
||||
Assert-SourceContains 'DirectoryInfo\(\$staging\)' `
|
||||
"Enrollment does not securely stage a new directory."
|
||||
|
||||
Assert-SourceContains '& \$Installer' `
|
||||
"Bootstrap does not hand off to the safe installer."
|
||||
|
||||
@@ -68,6 +144,78 @@ Assert-SourceContains 'Start-ScheduledTask -TaskName "MinerOpsAgent"' `
|
||||
Assert-SourceContains 'Remove-Item -LiteralPath \$EnrollmentPath' `
|
||||
"Consumed extracted enrollment token is not removed."
|
||||
|
||||
if ($source -match '\[IO\.File\]::WriteAllText\(\s*\$(Pending|Recovery)ConfigPath') {
|
||||
throw "Enrollment must not truncate or adopt a credential pathname."
|
||||
}
|
||||
|
||||
$createFunction = [regex]::Match(
|
||||
$source,
|
||||
'(?s)function Open-MinerOpsEnrollmentCredentialFileCreateNew \{.*?\n\}'
|
||||
).Value
|
||||
$createIndex = $createFunction.IndexOf(
|
||||
'New-MinerOpsEnrollmentProtectedFileCreateNewStream'
|
||||
)
|
||||
$verifyIndex = $createFunction.IndexOf('Test-MinerOpsEnrollmentCredentialAcl')
|
||||
$linkIndex = $createFunction.IndexOf('Assert-MinerOpsEnrollmentSingleLinkStream')
|
||||
if (
|
||||
$createIndex -lt 0 -or $verifyIndex -le $createIndex -or
|
||||
$linkIndex -le $verifyIndex -or
|
||||
$createFunction -match 'Set-MinerOpsEnrollmentStreamAcl'
|
||||
) {
|
||||
throw "Enrollment credential creation must apply FileSecurity at CreateNew, verify it, then reject links before content."
|
||||
}
|
||||
|
||||
$createStreamFunction = [regex]::Match(
|
||||
$source,
|
||||
'(?s)function New-MinerOpsEnrollmentProtectedFileCreateNewStream \{.*?\n\}'
|
||||
).Value
|
||||
foreach ($requiredCreateContract in @(
|
||||
'\[IO\.FileStream\]::new',
|
||||
'FileMode\]::CreateNew',
|
||||
'FileSystemRights\]::WriteData',
|
||||
'FileSystemRights\]::ReadPermissions',
|
||||
'FileSystemRights\]::Synchronize',
|
||||
'FileShare\]::None',
|
||||
'\$Acl'
|
||||
)) {
|
||||
if ($createStreamFunction -notmatch $requiredCreateContract) {
|
||||
throw "Enrollment protected CreateNew stream contract is incomplete."
|
||||
}
|
||||
}
|
||||
if ($createStreamFunction -match 'FullControl|SetAccessControl|Set-Acl') {
|
||||
throw "Enrollment protected creation requests excess rights or repairs by path."
|
||||
}
|
||||
|
||||
$repairFunction = [regex]::Match(
|
||||
$source,
|
||||
'(?s)function Protect-MinerOpsEnrollmentExistingConfig \{.*?\n\}'
|
||||
).Value
|
||||
foreach ($requiredRepairRight in @(
|
||||
'ReadPermissions',
|
||||
'ChangePermissions',
|
||||
'TakeOwnership',
|
||||
'Synchronize'
|
||||
)) {
|
||||
if ($repairFunction -notmatch $requiredRepairRight) {
|
||||
throw "Enrollment existing-config repair is missing $requiredRepairRight."
|
||||
}
|
||||
}
|
||||
if ($repairFunction -match 'Set-Acl|FileAccess') {
|
||||
throw "Enrollment existing-config repair must remain handle-bound."
|
||||
}
|
||||
|
||||
$writeFunction = [regex]::Match(
|
||||
$source,
|
||||
'(?s)function Write-MinerOpsEnrollmentCredentialJson \{.*?\n\}'
|
||||
).Value
|
||||
if (
|
||||
$writeFunction -notmatch 'Assert-MinerOpsEnrollmentSingleLinkStream' -or
|
||||
$writeFunction -notmatch '\$Stream\.Write' -or
|
||||
$writeFunction -notmatch '\$Stream\.Flush\(\$true\)'
|
||||
) {
|
||||
throw "Enrollment credential content is not written and flushed through the verified handle."
|
||||
}
|
||||
|
||||
$existingConfigGuard = $source.IndexOf(
|
||||
'Test-Path -LiteralPath $ConfigPath'
|
||||
)
|
||||
@@ -85,7 +233,7 @@ if (
|
||||
}
|
||||
|
||||
$configMove = $source.IndexOf(
|
||||
'-Destination $ConfigPath'
|
||||
'[IO.File]::Move($PendingConfigPath, $ConfigPath)'
|
||||
)
|
||||
|
||||
$installerCall = $source.IndexOf(
|
||||
|
||||
Reference in new issue
Block a user