mirror of
https://github.com/Yamato-Security/WELA.git
synced 2026-10-03 13:04:43 +02:00
* Gate conditional stronger-profile IPsec auditing on native evidence * Use supported literal shells in native prerequisite matrix * Retain native IPsec fixture diagnostics and allow inactive rule omission * Expose exact native rule fields when prerequisite classification fails * Recognize native inactive IPsec rules without granting applicability * Restore standalone regression loading and valid owned IPsec auth defaults
110 lines
9.8 KiB
PowerShell
110 lines
9.8 KiB
PowerShell
param([switch]$AllowDisposablePolicyWrite,[switch]$AllowDisposableIpsecRule)
|
|
$ErrorActionPreference='Stop'
|
|
if ($env:OS -ne 'Windows_NT') {throw 'Native Windows fixture required.'}
|
|
if (-not $AllowDisposablePolicyWrite -or -not $AllowDisposableIpsecRule) {throw 'Disposable audit-policy and owned IPsec-rule opt-in are both required.'}
|
|
$repo=Split-Path $PSScriptRoot -Parent;$script:ScriptRoot=$repo
|
|
Import-Module (Join-Path $repo 'modules/AuditProfiles.psm1') -Force
|
|
Import-Module NetSecurity -ErrorAction Stop
|
|
. (Join-Path $repo 'scripts/Configuration.ps1')
|
|
$script:checks=0
|
|
function Assert($Condition,[string]$Message) {if(-not $Condition){throw "FAIL: $Message"};$script:checks++}
|
|
$identity=[Security.Principal.WindowsIdentity]::GetCurrent()
|
|
Assert ([Security.Principal.WindowsPrincipal]::new($identity).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) 'fixture is elevated'
|
|
$root=Join-Path $env:RUNNER_TEMP ('wela-ipsec-'+[guid]::NewGuid().ToString('N'))
|
|
$null=New-Item $root -ItemType Directory
|
|
$name='wela-ipsec-'+[guid]::NewGuid().ToString('N')
|
|
$guid='0CCE9218-69AE-11D9-BED3-505054503030'
|
|
$before=Get-WelaEffectiveAuditPolicy
|
|
$precedencePath='HKLM:\SYSTEM\CurrentControlSet\Control\Lsa'
|
|
$precedence=Get-WelaRegistryState $precedencePath SCENoApplyLegacyAuditPolicy
|
|
$beforeRules=@(Get-NetIPsecRule -PolicyStore ActiveStore -ErrorAction Stop|Select-Object Name,Enabled,InboundSecurity,OutboundSecurity,PrimaryStatus|Sort-Object Name|ConvertTo-Json -Depth 5 -Compress)
|
|
$engine=(Get-Process -Id $PID).Path
|
|
$created=$false;$cleanup=$false
|
|
try {
|
|
$baseline=Get-WelaIpsecPrerequisite
|
|
$baseline|ConvertTo-Json -Depth 10|Set-Content (Join-Path $root 'baseline.json') -Encoding UTF8
|
|
Assert ($baseline.Status -ne 'Unknown') "both native sources are readable: $($baseline.Diagnostic)"
|
|
# Both endpoints are documentation-only addresses; no packets or negotiations are generated.
|
|
$null=New-NetIPsecRule -Name $name -DisplayName $name -PolicyStore PersistentStore -Profile Any -Enabled False -LocalAddress 192.0.2.250 -RemoteAddress 192.0.2.251 -InboundSecurity Request -OutboundSecurity Request -ErrorAction Stop
|
|
$created=$true
|
|
$evidence=Get-WelaIpsecPrerequisite
|
|
$evidence|ConvertTo-Json -Depth 10|Set-Content (Join-Path $root 'disabled.json') -Encoding UTF8
|
|
Get-NetIPsecRule -Name $name -PolicyStore PersistentStore -ErrorAction Stop|Select-Object *|Export-Clixml (Join-Path $root 'disabled-native.xml')
|
|
Get-NetIPsecRule -PolicyStore ActiveStore -ErrorAction Stop|Select-Object *|Export-Clixml (Join-Path $root 'disabled-active-native.xml')
|
|
$owned=@($evidence.Rules|Where-Object Name -eq $name)
|
|
Assert ((Get-NetIPsecRule -Name $name -PolicyStore PersistentStore -ErrorAction Stop).Enabled -eq 'False') 'owned persistent rule is actually disabled'
|
|
Assert ($evidence.Status -ne 'Unknown' -and @($owned|Where-Object Qualifies).Count -eq 0) "disabled rule does not qualify (ActiveStore may omit it): $($evidence.Diagnostic)"
|
|
Set-NetIPsecRule -Name $name -PolicyStore PersistentStore -Enabled True -InboundSecurity None -OutboundSecurity None -ErrorAction Stop
|
|
$evidence=Get-WelaIpsecPrerequisite
|
|
$owned=@($evidence.Rules|Where-Object Name -eq $name)
|
|
$evidence|ConvertTo-Json -Depth 10|Set-Content (Join-Path $root 'exemption.json') -Encoding UTF8
|
|
Get-NetIPsecRule -Name $name -PolicyStore PersistentStore -ErrorAction Stop|Select-Object *|Export-Clixml (Join-Path $root 'exemption-native.xml')
|
|
Assert ($evidence.Status -ne 'Unknown' -and @($owned|Where-Object Qualifies).Count -eq 0) "real exemption-only rule does not qualify: $($evidence.Diagnostic)"
|
|
# Converting to an exemption clears its authentication-set references. Recreate
|
|
# only this owned fixture so New-NetIPsecRule supplies valid native defaults.
|
|
Remove-NetIPsecRule -Name $name -PolicyStore PersistentStore -ErrorAction Stop
|
|
$created=$false
|
|
$null=New-NetIPsecRule -Name $name -DisplayName $name -PolicyStore PersistentStore -Profile Any -Enabled True -LocalAddress 192.0.2.250 -RemoteAddress 192.0.2.251 -InboundSecurity Request -OutboundSecurity Request -ErrorAction Stop
|
|
$created=$true
|
|
$evidence=Get-WelaIpsecPrerequisite
|
|
$evidence|ConvertTo-Json -Depth 10|Set-Content (Join-Path $root 'positive.json') -Encoding UTF8
|
|
Get-NetIPsecRule -Name $name -PolicyStore ActiveStore -ErrorAction Stop|Select-Object *|Export-Clixml (Join-Path $root 'positive-native.xml')
|
|
$owned=@($evidence.Rules|Where-Object Name -eq $name)
|
|
Assert ($evidence.Status -eq 'Applicable' -and $owned.Count -eq 1 -and $owned[0].Qualifies) "real enabled securing ActiveStore rule establishes scoped applicability: $($evidence.Diagnostic)"
|
|
$planPath=Join-Path $root 'plan.json'
|
|
& $engine -NoProfile -File (Join-Path $repo 'WELA.ps1') plan -Profile microsoft-stronger-reviewed-2026-09 -IncludeOptional -SaclMode Skip -PlanPath $planPath
|
|
Assert ($LASTEXITCODE -eq 0) 'public live plan succeeds'
|
|
$plan=Get-Content $planPath -Raw|ConvertFrom-Json
|
|
$row=@($plan.policies|Where-Object id -eq 'IPsec Main Mode')[0]
|
|
Assert ($row.conditionalPrerequisite.Status -eq 'Applicable' -and $row.targetMask -eq 3) 'public plan contains native evidence and selected SF mask'
|
|
$dryPath=Join-Path $root 'dry.json'
|
|
& $engine -NoProfile -File (Join-Path $repo 'WELA.ps1') configure -Profile microsoft-stronger-reviewed-2026-09 -IncludeOptional -SaclMode Skip -DryRun -Auto -ResultsPath $dryPath
|
|
Assert ($LASTEXITCODE -eq 0) 'public configure dry-run succeeds'
|
|
$current=Get-WelaEffectiveAuditPolicy
|
|
Assert (@($before.Keys|Where-Object {$before[$_] -ne $current[$_]}).Count -eq 0) 'dry-run preserves all59 effective masks'
|
|
$dry=Get-Content $dryPath -Raw|ConvertFrom-Json
|
|
$row=@($dry.Results|Where-Object Id -eq 'AuditPolicy/IPsec Main Mode')[0]
|
|
Assert ($row.PrerequisiteObservations.Count -ge 2 -and $row.Status -in @('Skipped','AlreadyCompliant')) 'public dry-run retains native prerequisite evidence'
|
|
|
|
# Actual public configure must produce a write for this control, then read it back.
|
|
Set-WelaEffectiveAuditPolicy -Guid $guid -Mask 0 -Mode exact
|
|
$resultPath=Join-Path $root 'configure.json'
|
|
& $engine -NoProfile -File (Join-Path $repo 'WELA.ps1') configure -Profile microsoft-stronger-reviewed-2026-09 -IncludeOptional -SaclMode Skip -Auto -BackupPath (Join-Path $root 'backup') -ResultsPath $resultPath
|
|
Assert ($LASTEXITCODE -eq 0) 'actual public configure succeeds'
|
|
$result=Get-Content $resultPath -Raw|ConvertFrom-Json
|
|
$row=@($result.Results|Where-Object Id -eq 'AuditPolicy/IPsec Main Mode')[0]
|
|
Assert ($row.Status -eq 'Applied' -and $row.After -eq 3 -and $row.PrerequisiteObservations.Count -eq 5) 'actual gated policy write retains all five native observations'
|
|
Assert (@($row.PrerequisiteObservations|Where-Object Status -ne Applicable).Count -eq 0) 'every configure boundary has positive native evidence'
|
|
$journal=@(Get-Content (Join-Path $root 'backup/before.jsonl')|ConvertFrom-Json)
|
|
Assert (@($journal|Where-Object {$_.Id -eq 'AuditPolicy/IPsec Main Mode' -and $_.Before -eq 0 -and $_.Desired.Mask -eq 3}).Count -eq 1) 'real public recovery journal retains exact policy transition'
|
|
|
|
# Native drift after prompt: exercise the real configuration callback and native reader.
|
|
Set-WelaEffectiveAuditPolicy -Guid $guid -Mask 0 -Mode exact
|
|
$plan.policies=@($plan.policies|Where-Object id -eq 'IPsec Main Mode')
|
|
function Read-Host {param($Prompt) Remove-NetIPsecRule -Name $name -PolicyStore PersistentStore -ErrorAction Stop; $script:created=$false; 'y'}
|
|
$ctx=New-WelaConfigurationContext -BackupPath (Join-Path $root 'drift-backup')
|
|
Set-WelaProfileAuditControls $ctx $plan
|
|
$drift=Complete-WelaConfiguration $ctx -Plan $plan -ResultsPath (Join-Path $root 'drift.json')
|
|
$row=@($drift.Results|Where-Object Id -eq 'AuditPolicy/IPsec Main Mode')[0]
|
|
if($baseline.Status -eq 'NotObservedWithinScope') {
|
|
Assert ($row.Status -eq 'Failed' -and (Get-WelaEffectiveAuditPolicy)[$guid] -eq 0) 'real rule disappearance after prompt blocks auditpol write'
|
|
} else {
|
|
Assert ($row.Status -eq 'Applied') 'independent baseline prerequisite remains applicable after owned-rule removal'
|
|
}
|
|
} finally {
|
|
if(@(Get-NetIPsecRule -Name $name -PolicyStore PersistentStore -ErrorAction SilentlyContinue).Count){Remove-NetIPsecRule -Name $name -PolicyStore PersistentStore -ErrorAction Stop}
|
|
foreach($id in $before.Keys){Set-WelaEffectiveAuditPolicy -Guid $id -Mask $before[$id] -Mode exact}
|
|
if($precedence.ValueExists){Set-ItemProperty -LiteralPath $precedencePath -Name SCENoApplyLegacyAuditPolicy -Type $precedence.Type -Value $precedence.Value -ErrorAction Stop}
|
|
else {Remove-ItemProperty -LiteralPath $precedencePath -Name SCENoApplyLegacyAuditPolicy -ErrorAction SilentlyContinue}
|
|
$after=Get-WelaEffectiveAuditPolicy
|
|
Assert (@($before.Keys|Where-Object {$before[$_] -ne $after[$_]}).Count -eq 0) 'all59 original masks restored'
|
|
$afterPrecedence=Get-WelaRegistryState $precedencePath SCENoApplyLegacyAuditPolicy
|
|
Assert (($precedence|ConvertTo-Json -Compress) -ceq ($afterPrecedence|ConvertTo-Json -Compress)) 'typed precedence/absence restored'
|
|
$afterRules=@(Get-NetIPsecRule -PolicyStore ActiveStore -ErrorAction Stop|Select-Object Name,Enabled,InboundSecurity,OutboundSecurity,PrimaryStatus|Sort-Object Name|ConvertTo-Json -Depth 5 -Compress)
|
|
Assert (($beforeRules -join '') -ceq ($afterRules -join '')) 'native rule inventory restored exactly'
|
|
Assert (@(Get-NetIPsecRule -Name $name -PolicyStore PersistentStore -ErrorAction SilentlyContinue).Count -eq 0) 'owned persistent rule removed'
|
|
$cleanup=$true
|
|
[pscustomobject]@{CleanupVerified=$cleanup;Checks=$script:checks;Engine=$PSVersionTable.PSVersion.ToString();Computer=$env:COMPUTERNAME;NoTrafficGenerated=$true}|ConvertTo-Json|Set-Content (Join-Path $root 'cleanup.json') -Encoding UTF8
|
|
}
|
|
Write-Host "Passed $script:checks native IPsec checks; artifacts: $root"
|