Private/Get-NSPFortiGatePolicyAnalysis.ps1
|
function Get-NSPFortiGatePolicyAnalysis { <# .SYNOPSIS Resolves every firewall policy's references into report rows. .DESCRIPTION Returns Rows (one per policy, see Get-NSPFortiGatePolicyAccess) and Used (object keys of everything the policies reach, directly or through groups, tunnels, and user groups). A phase2 counts as used when its phase1 does. PolicyCount is 0 when no policy was loaded. #> [CmdletBinding()] param( [Parameter(Mandatory)][AllowEmptyCollection()][object[]]$Tree, [string]$Delimiter = '; ' ) $index = Get-NSPFortiGateIndex -Tree $Tree $used = New-Object 'System.Collections.Generic.HashSet[string]' ([StringComparer]::Ordinal) $policies = @(Find-NSPFortiGateSection -Section $Tree -Pattern 'firewall policy') $showVdom = @($policies | Where-Object { $_.Vdom }).Count -gt 0 $label = { param($Leaf) if ($Leaf.Missing) { return "$($Leaf.Name) (not in input)" } $text = $Leaf.Name if ($Leaf.Entry) { $summary = Get-NSPFortiGateObjectSummary -Path $Leaf.Path -Settings $Leaf.Entry.Settings if ($summary) { $text = "$text ($summary)" } } if ($Leaf.Excluded) { $text = "NOT $text" } $text } $peers = { param($Leaves) foreach ($leaf in $Leaves) { if ($leaf.Path -eq 'user peer') { & $label $leaf } if ($leaf.Related) { & $peers $leaf.Related } } } $missing = { param($Leaves) foreach ($leaf in $Leaves) { if ($leaf.Missing) { "$($leaf.Kind) $($leaf.Name)" } if ($leaf.Related) { & $missing $leaf.Related } } } $rows = foreach ($section in $policies) { $sequence = 0 foreach ($policy in $section.Entries) { $sequence++ $settings = $policy.Settings $get = { param([string]$Key) if ($settings.Contains($Key)) { @($settings[$Key]) } else { @() } } $one = { param([string]$Key, [string]$Default = '') $v = & $get $Key; if ($v.Count) { $v -join $Delimiter } else { $Default } } $resolve = { param([string]$Kind, [string[]]$Names) foreach ($name in $Names) { Resolve-NSPFortiGateReference -Index $index -Kind $Kind -Name $name -Vdom $policy.Vdom -Used $used } } $detail = { param($Leaves, [string]$NegateKey) $text = @($Leaves | ForEach-Object { & $label $_ } | Select-Object -Unique) -join $Delimiter if ($text -and (& $one $NegateKey) -eq 'enable') { $text = "NOT: $text" } $text } $interfaces = @(& $resolve Interface (@(& $get 'srcintf') + @(& $get 'dstintf'))) $source = @(& $resolve Address (@(& $get 'srcaddr') + @(& $get 'srcaddr6'))) $destination = @(& $resolve Address (@(& $get 'dstaddr') + @(& $get 'dstaddr6'))) $services = @(& $resolve Service (& $get 'service')) $groups = @(& $resolve User (& $get 'groups')) $users = @(& $resolve User (& $get 'users')) $tunnels = @($interfaces | Where-Object Path -eq 'vpn ipsec phase1-interface') $vpn = @($tunnels | ForEach-Object { & $label $_ } | Select-Object -Unique) $vpnPeers = @(& $peers $tunnels | Select-Object -Unique) $groupMatch = foreach ($group in $groups) { if (-not $group.Entry) { continue } foreach ($match in @($group.Entry.Sections | Where-Object Path -eq 'match')) { foreach ($rule in $match.Entries) { $text = (@('server-name', 'group-name') | Where-Object { $rule.Settings.Contains($_) } | ForEach-Object { $rule.Settings[$_] -join ' ' }) -join ': ' if ($groups.Count -gt 1) { "$($group.Name) -> $text" } else { $text } } } } $groupMembers = foreach ($group in $groups) { if ($group.Entry -and $group.Entry.Settings.Contains('member')) { $members = $group.Entry.Settings['member'] -join ', ' if ($groups.Count -gt 1) { "$($group.Name) -> $members" } else { $members } } } $row = [ordered]@{} if ($showVdom) { $row.Vdom = $policy.Vdom } $row.Sequence = $sequence $row.PolicyId = $policy.Name $row.Name = & $one 'name' $row.Status = & $one 'status' 'enable' $row.Action = & $one 'action' 'deny' $row.SrcIntf = & $one 'srcintf' $row.DstIntf = & $one 'dstintf' $row.Vpn = $vpn -join $Delimiter $row.VpnPeers = $vpnPeers -join $Delimiter $row.Groups = & $one 'groups' $row.GroupMatch = @($groupMatch) -join $Delimiter $row.GroupMembers = @($groupMembers) -join $Delimiter $row.Users = & $one 'users' $row.SrcAddr = & $one 'srcaddr' $row.SrcAddrDetail = & $detail $source 'srcaddr-negate' $row.DstAddr = & $one 'dstaddr' $row.DstAddrDetail = & $detail $destination 'dstaddr-negate' $row.Service = & $one 'service' $row.ServiceDetail = & $detail $services 'service-negate' $row.Schedule = & $one 'schedule' $row.Nat = & $one 'nat' 'disable' $row.Comments = & $one 'comments' $row.Unresolved = @(& $missing ($interfaces + $source + $destination + $services + $groups + $users) | Select-Object -Unique) -join $Delimiter [pscustomobject]$row } } foreach ($phase2 in (Find-NSPFortiGateSection -Section $Tree -Pattern 'vpn ipsec phase2-interface')) { foreach ($entry in $phase2.Entries) { if (-not $entry.Settings.Contains('phase1name')) { continue } $phase1 = $entry.Settings['phase1name'][0] $isUsed = @($entry.Vdom, 'global', '') | Where-Object { $used.Contains((Get-NSPFortiGateObjectKey -Vdom $_ -Path 'vpn ipsec phase1-interface' -Name $phase1)) } if ($isUsed) { [void]$used.Add((Get-NSPFortiGateObjectKey -Vdom $entry.Vdom -Path $phase2.Path -Name $entry.Name)) } } } @{ Rows = @($rows); Used = $used; PolicyCount = @($policies | ForEach-Object { $_.Entries }).Count } } |