Private/New-AzureUtilsPublicResourceQuery.ps1
|
function New-AzureUtilsPublicResourceQuery { <# .SYNOPSIS Builds the Resource Graph (KQL) query for publicly exposed resources. .DESCRIPTION Pure, side-effect-free. Returns a union of the selected exposure categories, each projecting the same columns plus an 'exposure' reason. .PARAMETER Type Which exposure categories to include. Defaults to all. .OUTPUTS System.String (the KQL query) #> [CmdletBinding()] [OutputType([string])] param( [ValidateSet('PublicIp', 'PublicNetworkAccess', 'StorageOpen', 'NsgInternetInbound')] [string[]] $Type = @('PublicIp', 'PublicNetworkAccess', 'StorageOpen', 'NsgInternetInbound') ) $cols = 'id, name, type, resourceGroup, location, subscriptionId, tags, exposure' $subQueries = [System.Collections.Generic.List[string]]::new() if ($Type -contains 'PublicIp') { $subQueries.Add(@" resources | where type =~ 'microsoft.network/publicipaddresses' and isnotnull(properties.ipConfiguration) | extend exposure = strcat('Public IP in use: ', tostring(properties.ipAddress)) | project $cols "@) } if ($Type -contains 'PublicNetworkAccess') { # Managed disks expose publicNetworkAccess = Enabled by default, but that # is not a real public exposure (controlled by networkAccessPolicy / SAS), # so they are excluded to avoid noise. $subQueries.Add(@" resources | where tostring(properties.publicNetworkAccess) =~ 'Enabled' and type !~ 'microsoft.compute/disks' | extend exposure = strcat(type, ' publicNetworkAccess=Enabled') | project $cols "@) } if ($Type -contains 'StorageOpen') { $subQueries.Add(@" resources | where type =~ 'microsoft.storage/storageaccounts' and (properties.allowBlobPublicAccess == true or tostring(properties.networkAcls.defaultAction) =~ 'Allow') | extend exposure = case( properties.allowBlobPublicAccess == true and tostring(properties.networkAcls.defaultAction) =~ 'Allow', 'Storage: anonymous blob access and open network default', properties.allowBlobPublicAccess == true, 'Storage: anonymous blob access allowed', 'Storage: network default action is Allow') | project $cols "@) } if ($Type -contains 'NsgInternetInbound') { $subQueries.Add(@" resources | where type =~ 'microsoft.network/networksecuritygroups' | mv-expand rule = properties.securityRules | extend src = tolower(tostring(rule.properties.sourceAddressPrefix)) | where tolower(tostring(rule.properties.direction)) == 'inbound' and tolower(tostring(rule.properties.access)) == 'allow' and (src == '*' or src == '0.0.0.0/0' or src == 'internet' or src == 'any') | extend exposure = strcat('NSG inbound from Internet on port(s) ', tostring(rule.properties.destinationPortRange)) | project $cols "@) } if ($subQueries.Count -eq 1) { return $subQueries[0] } # Azure Resource Graph requires the query to start with a table, then chain # '| union (subquery)' for the remaining categories. $query = $subQueries[0] for ($i = 1; $i -lt $subQueries.Count; $i++) { $query += "`n| union (`n$($subQueries[$i])`n)" } return $query } |