mirror of
https://github.com/Yamato-Security/WELA.git
synced 2026-10-10 08:15:27 +02:00
Exercise descendant drift and use supported workflow shell dispatch
This commit is contained in:
1 parent
ea184e5e95
commit
036f51886a
2 files changed
+115
-6
No files matched your search
@@ -22,12 +22,20 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Existing WMI regression contracts
|
||||
shell: ${{ matrix.shell }}
|
||||
run: ./tests/WmiNamespaceAuditing.Tests.ps1
|
||||
- name: Actual owned namespace tree and cleanup
|
||||
shell: ${{ matrix.shell }}
|
||||
run: ./tests/WmiNamespaceDescendants.Windows.Tests.ps1 -AllowDisposableNamespaceWrite -EvidencePath wmi-descendants-native.json
|
||||
- name: Native tree validation in Windows PowerShell
|
||||
if: matrix.shell == 'powershell'
|
||||
shell: powershell
|
||||
run: |
|
||||
./tests/WmiNamespaceAuditing.Tests.ps1
|
||||
./tests/WmiNamespaceDescendants.Tests.ps1
|
||||
./tests/WmiNamespaceDescendants.Windows.Tests.ps1 -AllowDisposableNamespaceWrite -EvidencePath wmi-descendants-native.json
|
||||
- name: Native tree validation in PowerShell 7
|
||||
if: matrix.shell == 'pwsh'
|
||||
shell: pwsh
|
||||
run: |
|
||||
./tests/WmiNamespaceAuditing.Tests.ps1
|
||||
./tests/WmiNamespaceDescendants.Tests.ps1
|
||||
./tests/WmiNamespaceDescendants.Windows.Tests.ps1 -AllowDisposableNamespaceWrite -EvidencePath wmi-descendants-native.json
|
||||
- name: Retain complete native observations
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
$ErrorActionPreference='Stop'
|
||||
$repo=Split-Path $PSScriptRoot -Parent
|
||||
. (Join-Path $repo 'scripts/Configuration.ps1')
|
||||
. (Join-Path $repo 'scripts/WmiNamespaceAuditing.ps1')
|
||||
$script:assertions=0
|
||||
function Assert($v,[string]$m){if(-not $v){throw $m};$script:assertions++}
|
||||
function Throws([scriptblock]$f,[string]$m){$yes=$false;try{& $f|Out-Null}catch{$yes=$true};Assert $yes $m}
|
||||
function Descriptor([uint32]$flags=32772){[pscustomobject]@{ControlFlags=$flags;Owner='owner';Group='group';DACL=@('a','b');SACL=@();Opaque='preserve'}}
|
||||
function Snapshot([string]$ns,$d){[pscustomobject]@{Namespace=$ns;DescriptorJson=(ConvertTo-WelaWmiJson $d);DescriptorMof='native full descriptor';SaclReadPrivilege='test'}}
|
||||
function Ace([uint32]$flags=82){[pscustomobject]@{AceType=2;AceFlags=$flags;AccessMask=262175;Trustee=[pscustomobject]@{SIDString='S-1-1-0'}}}
|
||||
$script:tree=@{};$script:reads=0;$script:changeAt=0;$script:context='caller/host/source';$script:writes=0
|
||||
function Reset {
|
||||
$script:tree=@{'root\default'=(Descriptor);'root\default\A'=(Descriptor);'root\default\A\B'=(Descriptor);'root\default\Protected'=(Descriptor 40964);'root\default\Protected\B'=(Descriptor)}
|
||||
$script:reads=0;$script:changeAt=0;$script:context='caller/host/source';$script:writes=0;$script:prompt=$null
|
||||
}
|
||||
function Get-WelaWmiChildNames {
|
||||
param($Namespace,$Maximum)
|
||||
@($script:tree.Keys|Where-Object {$_ -clike ($Namespace+'\*') -and $_.Substring($Namespace.Length+1) -notmatch '\\'}|ForEach-Object {$_.Substring($Namespace.Length+1)}|Sort-Object)
|
||||
}
|
||||
function Get-WelaWmiDescendantContext {$script:context}
|
||||
function Get-WelaWmiNamespaceSnapshot {
|
||||
param($Namespace)
|
||||
$script:reads++
|
||||
if($script:changeAt -and $script:reads -eq $script:changeAt){$script:tree['root\default\A'].Owner='racing owner'}
|
||||
if(-not $script:tree.ContainsKey($Namespace)){throw 'Unknown namespace.'}
|
||||
Snapshot $Namespace $script:tree[$Namespace]
|
||||
}
|
||||
function Set-WelaWmiNamespaceDescriptor {
|
||||
param($Namespace,$ExpectedJson,$Definitions)
|
||||
if((ConvertTo-WelaWmiJson $script:tree[$Namespace]) -cne $ExpectedJson){throw 'Immediate parent drift'}
|
||||
$script:writes++
|
||||
foreach($d in $Definitions){$script:tree[$Namespace].SACL+=Ace $d.AceFlags}
|
||||
$script:tree[$Namespace].ControlFlags=$script:tree[$Namespace].ControlFlags -bor 16
|
||||
# Model the provider's potential inherited-only propagation exactly, leaving protected tree unchanged.
|
||||
foreach($ns in @('root\default\A','root\default\A\B')){$script:tree[$ns].SACL+=Ace;$script:tree[$ns].ControlFlags=$script:tree[$ns].ControlFlags -bor 16}
|
||||
}
|
||||
function Read-Host {param($Prompt)if($script:prompt){& $script:prompt};'Y'}
|
||||
$defs=@(Get-WelaWmiAuditDefinitions -Namespace 'root\default' -IncludeChildren)
|
||||
$temp=Join-Path ([IO.Path]::GetTempPath()) ('wela-wmi-desc-test-'+[guid]::NewGuid().ToString('N'))
|
||||
try{
|
||||
Reset
|
||||
$before=Get-WelaWmiStableDescendants 'root\default'
|
||||
Assert ($before.Entries.Count -eq 4) 'Complete multilevel inventory.'
|
||||
Assert (@($before.Entries|Where-Object ProtectedBarrier).Count -eq 2) 'Protected ancestor marks whole subtree.'
|
||||
Assert ((Get-WelaWmiDescendantKey $before) -ceq (Get-WelaWmiDescendantKey (Get-WelaWmiStableDescendants 'root\default'))) 'Stable tree key omits observation clock.'
|
||||
$script:changeAt=$script:reads+7
|
||||
Throws {Get-WelaWmiStableDescendants 'root\default'} 'Second-pass descriptor drift must fail.'
|
||||
Reset;$script:tree['root\default\A\bad-child']=Descriptor
|
||||
Assert ((Get-WelaWmiDescendants 'root\default').Status -eq 'Incomplete') 'Ambiguous child name rejected.'
|
||||
Reset;foreach($i in 1..65){$script:tree['root\default\N'+$i]=Descriptor}
|
||||
Assert ((Get-WelaWmiDescendants 'root\default').Status -eq 'Incomplete') 'Count overflow fails rather than truncates.'
|
||||
Reset;$n='root\default';foreach($i in 1..9){$n+='\Deep';$script:tree[$n]=Descriptor}
|
||||
Assert ((Get-WelaWmiDescendants 'root\default').Status -eq 'Incomplete') 'Depth overflow fails rather than truncates.'
|
||||
Reset;$script:tree['root\default\A'].Opaque='x'*2097153
|
||||
Assert ((Get-WelaWmiDescendants 'root\default').Status -eq 'Incomplete') 'Serialized descriptor budget enforced.'
|
||||
Reset
|
||||
$p=@(Get-WelaWmiAuditPlan -Namespace 'root\default' -IncludeChildren)
|
||||
$c=New-WelaConfigurationContext -Auto -DryRun -BackupPath $temp
|
||||
Set-WelaWmiAuditControls $c $p
|
||||
Assert ($script:writes -eq 0 -and $c.Results[0].Status -eq 'Skipped' -and -not (Test-Path $temp)) 'DryRun no state or journal mutation.'
|
||||
$script:tree['root\default\New']=Descriptor
|
||||
$c=New-WelaConfigurationContext -Auto -BackupPath (Join-Path $temp stale)
|
||||
Set-WelaWmiAuditControls $c $p
|
||||
Assert ($script:writes -eq 0 -and $c.Results[0].Status -eq 'Failed') 'Stale membership blocks before journal or setter.'
|
||||
Reset;$p=@(Get-WelaWmiAuditPlan -Namespace 'root\default' -IncludeChildren)
|
||||
$script:prompt={$script:tree['root\default\A'].DACL=@('changed')}
|
||||
$c=New-WelaConfigurationContext -BackupPath (Join-Path $temp prompt)
|
||||
Set-WelaWmiAuditControls $c $p
|
||||
Assert ($script:writes -eq 0 -and $c.Results[0].Status -eq 'Failed') 'Descendant drift during confirmation blocks parent setter.'
|
||||
Reset;$p=@(Get-WelaWmiAuditPlan -Namespace 'root\default' -IncludeChildren)
|
||||
$c=New-WelaConfigurationContext -Auto -BackupPath (Join-Path $temp successful)
|
||||
Set-WelaWmiAuditControls $c $p
|
||||
$result=Complete-WelaConfiguration $c
|
||||
Assert ($script:writes -eq 1 -and $result.ExitCode -eq 0 -and $result.Results[0].Status -eq 'Applied') 'Exact inherited propagation and protected preservation pass.'
|
||||
$ob=$result.Results[0].DescendantVerification.Observation
|
||||
Assert (@($ob.Outcomes|Where-Object Status -eq InheritedAceObserved).Count -eq 2) 'Two inherited readbacks represented.'
|
||||
Assert (@($ob.Outcomes|Where-Object Status -eq ProtectedUnchanged).Count -eq 2) 'Two protected readbacks represented.'
|
||||
$journal=Get-Content (Join-Path $temp successful/before.jsonl)|ConvertFrom-Json
|
||||
Assert ($journal.Before.Descendants.Entries.Count -eq 4 -and $journal.Before.DescriptorMof -eq 'native full descriptor') 'Original journal fields and full subtree retained.'
|
||||
Assert ((Get-WelaWmiDescendantKey $journal.Before.Descendants) -ceq (Get-WelaWmiDescendantKey $p[0].Descendants)) 'Journal serialization does not truncate original child snapshots.'
|
||||
$script:tree['root\default\A'].Opaque='drift'
|
||||
Assert ((Complete-WelaConfiguration $c).ExitCode -eq 1) 'Final child drift propagates failure.'
|
||||
# Each unrelated mutation invalidates observed propagation, even when required ACE still exists.
|
||||
foreach($kind in @('Owner','Dacl','Control','Unknown','Protected','Removed','Extra','Missing','New')){
|
||||
Reset;$a=Get-WelaWmiStableDescendants 'root\default';$null=Set-WelaWmiNamespaceDescriptor 'root\default' $a.Root.DescriptorJson $defs
|
||||
switch($kind){
|
||||
Owner {$script:tree['root\default\A'].Owner='other'}
|
||||
Dacl {$script:tree['root\default\A'].DACL=@('other')}
|
||||
Control {$script:tree['root\default\A'].ControlFlags=$script:tree['root\default\A'].ControlFlags -bor 256}
|
||||
Unknown {$script:tree['root\default\A'].Opaque='other'}
|
||||
Protected {$script:tree['root\default\Protected\B'].SACL+=Ace}
|
||||
Removed {$script:tree.Remove('root\default\A\B')}
|
||||
Extra {$script:tree['root\default\A'].SACL+=Ace 64}
|
||||
Missing {$script:tree['root\default\A'].SACL=@()}
|
||||
New {$script:tree['root\default\Unreviewed']=Descriptor}
|
||||
}
|
||||
$b=Get-WelaWmiStableDescendants 'root\default'
|
||||
Assert ((Test-WelaWmiDescendantOutcomes $a $b $defs).Status -eq 'Unverified') "$kind child change must fail verification."
|
||||
}
|
||||
Write-Host "PASS: $script:assertions bounded WMI descendant assertions."
|
||||
}finally{if(Test-Path $temp){Remove-Item $temp -Recurse -Force}}
|
||||
Reference in new issue
Block a user