Files
WELA/scripts/IpsecPrerequisites.ps1
T
田中ザック Isaac Mathis b7e649185b Gate conditional IPsec auditing on native prerequisite evidence (#439)
* 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
2026-09-21 17:42:53 +09:00

74 lines
5.7 KiB
PowerShell

# Read-only local NetSecurity evidence. No policy, service or traffic changes.
function Test-WelaIpsecConditionalPolicy {
param($Plan, $Policy)
return (-not $Plan.PSObject.Properties['CustomProfileSource'] -and
$Plan.profile -ceq 'microsoft-stronger-reviewed-2026-09' -and
$Policy.guid -ieq '0CCE9218-69AE-11D9-BED3-505054503030' -and $Policy.mode -eq 'optional')
}
function Get-WelaIpsecPrerequisite {
[CmdletBinding()]
param([switch]$Offline,
[scriptblock]$ReadRules = { NetSecurity\Get-NetIPsecRule -PolicyStore ActiveStore -ErrorAction Stop },
[scriptblock]$ReadAssociations = { NetSecurity\Get-NetIPsecMainModeSA -ErrorAction Stop })
$started = [DateTime]::UtcNow.ToString('o')
$rules = @(); $associations = @(); $reads = @(); $diagnostics = @()
if ($Offline) { $diagnostics += 'Offline scenario; this host was not queried.' }
else {
foreach ($source in @('ActiveStoreRules', 'MainModeAssociations')) {
$status = 'Complete'; $errorText = ''; $items = @()
try {
$reader = if ($source -eq 'ActiveStoreRules') { $ReadRules } else { $ReadAssociations }
# Keep at most 4096 observations per native source. A cap is not an empty/successful inventory.
$items = @(& $reader | Select-Object -First 4097)
if ($items.Count -gt 4096) { throw 'Observation cap exceeded (4096 records).' }
$seen = @{}
foreach ($item in $items) {
if ($source -eq 'ActiveStoreRules') {
foreach ($property in @('Name', 'Enabled', 'InboundSecurity', 'OutboundSecurity', 'PrimaryStatus')) {
if ($null -eq $item -or -not $item.PSObject.Properties[$property] -or $null -eq $item.$property) { throw "Missing native rule property: $property." }
}
$name = [string]$item.Name
$enabled = [string]$item.Enabled; $inbound = [string]$item.InboundSecurity; $outbound = [string]$item.OutboundSecurity; $health = [string]$item.PrimaryStatus
if (-not $name -or $name.Length -gt 1024 -or $seen.ContainsKey($name) -or $enabled -cnotin @('True','False') -or
$inbound -cnotin @('None','Request','Require') -or $outbound -cnotin @('None','Request','Require') -or
$health -cnotin @('OK','Inactive','Error','Unknown')) { throw "Unrecognized or duplicate native IPsec rule observation: Name='$name', Enabled='$enabled', InboundSecurity='$inbound', OutboundSecurity='$outbound', PrimaryStatus='$health'." }
$seen[$name] = $true
$qualifies = $enabled -ceq 'True' -and ($inbound -cne 'None' -or $outbound -cne 'None') -and $health -ceq 'OK'
$rules += [pscustomobject]@{ Name=$name; Enabled=$enabled; InboundSecurity=$inbound; OutboundSecurity=$outbound; PrimaryStatus=$health; Qualifies=$qualifies }
if ($enabled -ceq 'True' -and ($inbound -cne 'None' -or $outbound -cne 'None') -and $health -cne 'OK') { throw 'Enabled non-exemption rule has uncertain effective health.' }
} else {
foreach ($property in @('Name','LocalEndpoint','RemoteEndpoint')) {
if ($null -eq $item -or -not $item.PSObject.Properties[$property] -or -not [string]$item.$property) { throw "Missing native association property: $property." }
}
$name = [string]$item.Name; $local = [string]$item.LocalEndpoint; $remote = [string]$item.RemoteEndpoint
$address = $null
if ($name.Length -gt 1024 -or $seen.ContainsKey($name) -or -not [Net.IPAddress]::TryParse($local,[ref]$address) -or -not [Net.IPAddress]::TryParse($remote,[ref]$address)) { throw 'Unrecognized or duplicate main-mode association.' }
$seen[$name] = $true
$associations += [pscustomobject]@{ Name=$name; LocalEndpoint=$local; RemoteEndpoint=$remote }
}
}
} catch { $status = 'Unknown'; $errorText = $_.Exception.Message; $diagnostics += "$source`: $errorText" }
$reads += [pscustomobject]@{ Source=$source; Status=$status; ObservedCount=$items.Count; Diagnostic=$errorText }
}
}
$status = if ($Offline -or @($reads | Where-Object Status -ne Complete).Count) { 'Unknown' }
elseif (@($rules | Where-Object Qualifies).Count -or $associations.Count) { 'Applicable' }
else { 'NotObservedWithinScope' }
[pscustomobject][ordered]@{
SchemaVersion=1; Status=$status; Scope='Local NetSecurity ActiveStore rules and current main-mode SAs'
StartedUtc=$started; CompletedUtc=[DateTime]::UtcNow.ToString('o'); ComputerName=$env:COMPUTERNAME
Basis=$(if ($status -eq 'Applicable') { 'Enabled healthy non-exemption effective rule or current main-mode SA observed.' } else { 'No complete positive prerequisite evidence.' })
Reads=$reads; Rules=$rules; MainModeAssociations=$associations; Diagnostic=($diagnostics -join ' ')
Limitations='Point-in-time local scope. Configured rules do not prove matching traffic, successful negotiation or audit events. Absence does not exclude legacy IPsec, VPN or other providers. No event-volume, failure-outcome or Sigma credit.'
}
}
function Assert-WelaIpsecPrerequisite {
param($Evidence)
if ($null -eq $Evidence -or $Evidence.Status -cne 'Applicable') {
$status = if ($Evidence) { $Evidence.Status } else { 'Unknown' }
throw "IPsec Main Mode prerequisite is $status; this conditional audit setting was not changed. $($Evidence.Diagnostic)"
}
}