Private/Write-AACFirewallRuleHtml.ps1
|
function Write-AACFirewallRuleHtml { <# .SYNOPSIS Writes Get-AACFirewallRule's -HtmlPath report: tiles for rules, Allow, Deny, DNAT, policies and Allow rules open to any address; charts by action, policy, rule collection and rule type; and every rule in one interactive table, grouped by rule collection. .DESCRIPTION Sources and destinations are shown as one column each (addresses, IP Groups, FQDNs, FQDN tags, web categories, URLs); the separate properties are in the CSV download. The Exposure column flags Allow rules open to any source or any destination ('*', 0.0.0.0/0), and the CSV download says which. #> [CmdletBinding()] [OutputType([System.IO.FileInfo])] param( [Parameter(Mandatory)] [AllowEmptyCollection()] [object[]] $Rule, [Parameter(Mandatory)] [string] $Path, [string] $Title = 'Azure Firewall rules', [System.Collections.IDictionary] $Detail ) $join = { param([string[]] $Values) (@($Values) | Where-Object { $_ }) -join '; ' } $isAny = { param([string] $Value) @(($Value -split ',\s*') | Where-Object { $_ -eq '*' -or $_ -eq '0.0.0.0/0' -or $_ -eq 'Any' }).Count -gt 0 } $rows = @(foreach ($item in $Rule) { $anySource = [string]$item.Action -eq 'Allow' -and (& $isAny $item.SourceAddresses) $anyDestination = [string]$item.Action -eq 'Allow' -and (& $isAny $item.DestinationAddresses) [ordered]@{ FirewallPolicy = $item.FirewallPolicy FirewallPolicyId = $item.FirewallPolicyId RuleCollectionGroup = $item.RuleCollectionGroup RuleCollection = "$($item.RuleCollection) ($($item.RuleCollectionPriority))" Priority = $item.RuleCollectionPriority Action = $item.Action RuleName = $item.RuleName RuleType = ([string]$item.RuleType) -replace 'Rule$', '' Sources = & $join @($item.SourceAddresses, $(if ($item.SourceIpGroups) { "IP Groups: $($item.SourceIpGroupAddresses)" })) Destinations = & $join @($item.DestinationAddresses, $(if ($item.DestinationIpGroups) { "IP Groups: $($item.DestinationIpGroupAddresses)" }), $item.DestinationFqdns, $item.TargetFqdns, $item.TargetUrls, $(if ($item.FqdnTags) { "FQDN tags: $($item.FqdnTags)" }), $(if ($item.WebCategories) { "Web categories: $($item.WebCategories)" })) Protocols = $item.Protocols DestinationPorts = $item.DestinationPorts Translated = & $join @($item.TranslatedAddress, $item.TranslatedFqdn, $(if ($item.TranslatedPort) { "port $($item.TranslatedPort)" })) Exposure = if ($anySource -or $anyDestination) { 'Open to any' } else { '' } ExposureDetail = if ($anySource -and $anyDestination) { 'any source to any destination' } elseif ($anySource) { 'any source' } elseif ($anyDestination) { 'any destination' } else { '' } Description = $item.Description SubscriptionName = $item.SubscriptionName ResourceGroup = $item.ResourceGroup Location = $item.Location BasePolicy = $item.BasePolicy Firewalls = $item.Firewalls TerminateTls = $item.TerminateTls } }) $count = { param([scriptblock] $Where) @($rows | Where-Object $Where).Count } $open = & $count { $_.Exposure } $tiles = @( @{ Value = '{0:N0}' -f $rows.Count; Label = 'rules'; Tone = 'info'; Table = 'rules' } @{ Value = '{0:N0}' -f (& $count { $_.Action -eq 'Allow' }); Label = 'allow'; Tone = 'good'; Table = 'rules'; Filters = @{ Action = 'Allow' } } @{ Value = '{0:N0}' -f (& $count { $_.Action -eq 'Deny' }); Label = 'deny'; Tone = 'bad'; Table = 'rules'; Filters = @{ Action = 'Deny' } } @{ Value = '{0:N0}' -f (& $count { $_.Action -eq 'DNAT' }); Label = 'DNAT'; Tone = 'warn'; Table = 'rules'; Filters = @{ Action = 'DNAT' } } @{ Value = '{0:N0}' -f @($rows | ForEach-Object { $_.FirewallPolicyId } | Select-Object -Unique).Count; Label = 'policies'; Tone = 'violet' } $(if ($open) { @{ Value = '{0:N0}' -f $open; Label = 'allow rules open to any address'; Tone = 'bad'; Table = 'rules'; Filters = @{ Exposure = 'Open to any' } } }) ) | Where-Object { $_ } $top = { param([string] $Property, [int] $First = 10) @($rows | Group-Object -Property { $_[$Property] } | Sort-Object -Property @{ Expression = 'Count'; Descending = $true }, Name | Select-Object -First $First | ForEach-Object { @{ Label = $(if ($_.Name) { $_.Name } else { '(none)' }); Value = $_.Count } }) } $byAction = @(foreach ($action in 'Allow', 'Deny', 'DNAT') { $n = & $count { $_.Action -eq $action } if ($n) { @{ Label = $action; Value = $n; Tone = @{ Allow = 'good'; Deny = 'bad'; DNAT = 'warn' }[$action] } } }) $charts = @( @{ Title = 'By action'; Items = $byAction; Table = 'rules'; Column = 'Action' } @{ Title = 'By policy'; Items = @(& $top 'FirewallPolicy'); Table = 'rules'; Column = 'FirewallPolicy'; Tone = 'violet' } @{ Title = 'Largest rule collections'; Items = @(& $top 'RuleCollection'); Table = 'rules'; Column = 'RuleCollection' } @{ Title = 'By rule type'; Items = @(& $top 'RuleType'); Table = 'rules'; Column = 'RuleType'; Tone = 'neutral' } ) $table = @{ Id = 'rules' Title = 'Rules' Note = 'In priority order. Allow rules open to any source or destination are flagged under Exposure.' Noun = 'rules' File = 'FirewallRules' Rows = $rows Group = 'RuleCollection' GroupBy = @('RuleCollection', 'RuleCollectionGroup', 'FirewallPolicy', 'Action', 'RuleType') Columns = @( @{ Key = 'Action'; Label = 'Action'; Type = 'badge'; Facet = $true; Tones = @{ Allow = 'good'; Deny = 'bad'; DNAT = 'warn' } } @{ Key = 'RuleName'; Label = 'Rule'; Nowrap = $true } @{ Key = 'RuleType'; Label = 'Type'; Facet = $true } @{ Key = 'Sources'; Label = 'Sources'; Type = 'mono' } @{ Key = 'Destinations'; Label = 'Destinations'; Type = 'mono' } @{ Key = 'Protocols'; Label = 'Protocols'; Nowrap = $true } @{ Key = 'DestinationPorts'; Label = 'Ports'; Type = 'mono' } @{ Key = 'Translated'; Label = 'Translated to'; Type = 'mono' } @{ Key = 'Exposure'; Label = 'Exposure'; Type = 'badge'; Facet = $true; Tones = @{ 'Open to any' = 'bad' } } @{ Key = 'ExposureDetail'; Label = 'Open to'; Hidden = $true } @{ Key = 'RuleCollection'; Label = 'Rule collection'; Facet = $true; Nowrap = $true } @{ Key = 'RuleCollectionGroup'; Label = 'Collection group'; Facet = $true } @{ Key = 'FirewallPolicy'; Label = 'Policy'; Type = 'resource'; IdKey = 'FirewallPolicyId'; Facet = $true } @{ Key = 'Description'; Label = 'Description'; Type = 'wide' } @{ Key = 'Priority'; Label = 'Collection priority'; Type = 'number'; Hidden = $true } @{ Key = 'SubscriptionName'; Label = 'Subscription'; Hidden = $true } @{ Key = 'ResourceGroup'; Label = 'Resource group'; Hidden = $true } @{ Key = 'Location'; Label = 'Location'; Hidden = $true } @{ Key = 'BasePolicy'; Label = 'Base policy'; Hidden = $true } @{ Key = 'Firewalls'; Label = 'Firewalls'; Hidden = $true } @{ Key = 'TerminateTls'; Label = 'Terminate TLS'; Hidden = $true } ) } Write-AACHtmlReport -Path $Path -Title $Title -Subtitle "$('{0:N0}' -f $rows.Count) rules in $('{0:N0}' -f @($rows | ForEach-Object { $_.FirewallPolicyId } | Select-Object -Unique).Count) policies" -Fact $Detail -Tile $tiles -Chart $charts -Table @($table) } |