Collectors/Security.ps1
|
function Get-AerSecurityGaps { [CmdletBinding()] param( [Parameter(Mandatory)] [string[]] $SubscriptionIds, $SubscriptionMap ) $subLookup = @{} if ($SubscriptionMap -is [hashtable]) { $subLookup = $SubscriptionMap } elseif ($SubscriptionMap) { $SubscriptionMap.PSObject.Properties | ForEach-Object { $subLookup[$_.Name] = $_.Value } } function SubName($id) { if (-not $id) { return '' }; $n = $subLookup[$id.ToLowerInvariant()]; if ($n) { $n } else { $id } } function ArgRows($q) { Expand-AerRows (Invoke-AerArgQuery -SubscriptionIds $SubscriptionIds -Query $q) } function Get-CheckData($base) { $count = 0; $res = @() try { $c = @(ArgRows "$base | summarize Count=count()"); if ($c -and $null -ne $c[0].Count) { $count = [int]$c[0].Count } } catch { } try { $res = @(ArgRows "$base | project id, name, type=tolower(type), resourceGroup, subscriptionId | limit 200" | ForEach-Object { [pscustomobject]@{ Name = "$($_.name)"; Type = ("$($_.type)" -replace '^microsoft\.', ''); ResourceGroup = "$($_.resourceGroup)"; SubscriptionName = (SubName $_.subscriptionId); Id = "$($_.id)" } }) } catch { } return @{ Count = $count; Resources = $res } } # Each check = an ARG heuristic over the resource graph. Base yields the # affected resources; Count is computed exactly, Resources capped at 200. $checks = @( @{ Severity = 'Critical'; Title = 'Storage accounts with public access enabled'; ResourceType = 'microsoft.storage/storageaccounts' Base = "resources | where type =~ 'microsoft.storage/storageaccounts' | where properties.allowBlobPublicAccess == true" } @{ Severity = 'High'; Title = 'NSGs with inbound Any/Any rules'; ResourceType = 'microsoft.network/networksecuritygroups' Base = "resources | where type =~ 'microsoft.network/networksecuritygroups' | mv-expand rule=properties.securityRules | where rule.properties.direction == 'Inbound' and rule.properties.sourceAddressPrefix == '*' and rule.properties.destinationAddressPrefix == '*' | summarize by id, name, type, resourceGroup, subscriptionId" } @{ Severity = 'Medium'; Title = 'VMs without AMA or MDE extension'; ResourceType = 'microsoft.compute/virtualmachines' Base = "resources | where type =~ 'microsoft.compute/virtualmachines' | extend vmId = tolower(id) | join kind=leftanti (resources | where type =~ 'microsoft.compute/virtualmachines/extensions' | where name startswith 'AzureMonitor' or name startswith 'MDE' | extend vmId = tolower(tostring(split(id, '/extensions/')[0])) | distinct vmId) on vmId" } @{ Severity = 'Info'; Title = 'Key Vaults with soft-delete disabled'; ResourceType = 'microsoft.keyvault/vaults' Base = "resources | where type =~ 'microsoft.keyvault/vaults' | where properties.enableSoftDelete != true" } ) $severityOrder = @{ Critical = 0; High = 1; Medium = 2; Info = 3 } $items = foreach ($check in $checks) { $d = Get-CheckData $check.Base [pscustomobject]@{ Severity = $check.Severity Title = $check.Title ResourceType = $check.ResourceType Count = $d.Count Resources = @($d.Resources) } } $sorted = $items | Sort-Object { $severityOrder[$_.Severity] } return [pscustomobject]@{ TotalGaps = ($items | Measure-Object -Property Count -Sum).Sum Items = @($sorted) } } |