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
This commit is contained in:
田中ザック Isaac Mathis authored and GitHub committed 2026-09-21 17:42:53 +09:00
1 parent 7cd2eb9dbf
commit b7e649185b
15 files changed
+442 -12

No files matched your search

+35 -6
View File
@@ -284,10 +284,17 @@ function Get-WelaAuditPolicyMask {
function Set-WelaAuditPolicyControl {
param($Context, $Policy, [ValidateRange(0, 3)][int]$Mask = 3,
[ValidateSet('exact', 'minimum')][string]$Mode = 'exact', [switch]$RequirePrecedence)
[ValidateSet('exact', 'minimum')][string]$Mode = 'exact', [switch]$RequirePrecedence,
$IpsecObservations, [scriptblock]$ReadIpsec = { Get-WelaIpsecPrerequisite })
$guid = $Policy.GUID
$state = @{ Guid = $guid; Mask = $Mask; Mode = $Mode; RequirePrecedence = [bool]$RequirePrecedence }
$read = { param($state) Get-WelaAuditPolicyMask -Guid $state.Guid }
$state = @{ Guid = $guid; Mask = $Mask; Mode = $Mode; RequirePrecedence = [bool]$RequirePrecedence; IpsecObservations=$IpsecObservations; ReadIpsec=$ReadIpsec }
$read = { param($state)
if ($null -ne $state.IpsecObservations) {
$evidence = & $state.ReadIpsec; $state.IpsecObservations.Add($evidence)
Assert-WelaIpsecPrerequisite $evidence
}
Get-WelaAuditPolicyMask -Guid $state.Guid
}
$test = {
param($value, $state)
if ($state.Mode -eq 'minimum') { return ($value -band $state.Mask) -eq $state.Mask }
@@ -311,6 +318,11 @@ function Set-WelaAuditPolicyControl {
$failure = if ($state.Mask -band 2) { 'enable' } else { 'disable' }
$arguments += "/success:$success", "/failure:$failure"
}
if ($null -ne $state.IpsecObservations) {
# This check runs after the operator prompt and durable recovery journal.
$evidence = & $state.ReadIpsec; $state.IpsecObservations.Add($evidence)
Assert-WelaIpsecPrerequisite $evidence
}
Invoke-WelaNative -FilePath 'auditpol.exe' -Arguments $arguments
}
Invoke-WelaConfigurationControl -Context $Context -Id "AuditPolicy/$($Policy.Name)" -Kind AuditPolicy `
@@ -318,7 +330,7 @@ function Set-WelaAuditPolicyControl {
}
function Set-WelaProfileAuditControls {
param($Context, $Plan)
param($Context, $Plan, [scriptblock]$ReadIpsec = { Get-WelaIpsecPrerequisite })
if ($Plan.PSObject.Properties['CustomProfileSource']) {
$Context | Add-Member NoteProperty CustomProfileGuard ([pscustomobject]@{Source=$Plan.CustomProfileSource;Role=$Plan.role;Build=$Plan.build}) -Force
Assert-WelaConfigurationProfileGuard $Context
@@ -334,9 +346,26 @@ function Set-WelaProfileAuditControls {
$Context.Results.Add([pscustomobject]@{ Id = "AuditPolicy/$($policy.id)"; Kind = 'AuditPolicy'; Target = @{ Guid = $policy.guid }; Desired = $policy.requiredMask; Before = $null; After = $null; Status = 'Skipped'; Diagnostic = 'Audit precedence was not verified; dependent policy was not changed.' })
continue
}
$mode = if ($policy.mode -eq 'minimum') { 'minimum' } else { 'exact' }
Set-WelaAuditPolicyControl -Context $Context -Policy @{ GUID = $policy.guid; Name = $policy.id } -Mask $policy.requiredMask -Mode $mode -RequirePrecedence
$conditional = Test-WelaIpsecConditionalPolicy $Plan $policy
$observations = $null; $blocked = $false
if ($conditional) {
$observations = New-Object 'System.Collections.Generic.List[object]'
try {
$evidence = & $ReadIpsec; $observations.Add($evidence)
$blocked = $evidence.Status -ne 'Applicable'
$status = if ($evidence.Status -eq 'NotObservedWithinScope') { 'Skipped' } else { 'Failed' }
$diagnostic = "IPsec prerequisite $($evidence.Status); policy preserved. $($evidence.Diagnostic)"
} catch { $blocked = $true; $status = 'Failed'; $diagnostic = $_.ToString() }
if ($blocked) {
$Context.Results.Add([pscustomobject]@{Id="AuditPolicy/$($policy.id)";Kind='AuditPolicy';Target=@{Guid=$policy.guid};Desired=@{Mask=$policy.requiredMask;Mode='exact'};Before=$null;After=$null;Status=$status;Diagnostic=$diagnostic})
}
}
if (-not $blocked) {
$mode = if ($policy.mode -eq 'minimum') { 'minimum' } else { 'exact' }
Set-WelaAuditPolicyControl -Context $Context -Policy @{ GUID = $policy.guid; Name = $policy.id } -Mask $policy.requiredMask -Mode $mode -RequirePrecedence -IpsecObservations $observations -ReadIpsec $ReadIpsec
}
$row = $Context.Results[$Context.Results.Count - 1]
if ($conditional) { $row | Add-Member NoteProperty PrerequisiteObservations $observations }
$row | Add-Member NoteProperty Profile $Plan.profile
$row | Add-Member NoteProperty Version $Plan.version
$row | Add-Member NoteProperty SchemaSha256 $Plan.schemaSha256
+73
View File
@@ -0,0 +1,73 @@
# 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)"
}
}