modules/normalizers/Normalize-AzureQuotaReports.ps1
|
#Requires -Version 7.4 <# .SYNOPSIS Normalizer for Azure Quota Reports wrapper output. .DESCRIPTION Converts v1 `azure-quota` findings into v2 FindingRows using `New-FindingRow`. Severity mapping follows the team decision ladder: - UsagePercent >= 99 => Critical - UsagePercent >= 95 => High - UsagePercent >= threshold => Medium - UsagePercent < threshold => Info Compliant is locked to: UsagePercent < Threshold (default 80 when missing). #> [CmdletBinding()] param () . "$PSScriptRoot\..\shared\Schema.ps1" . "$PSScriptRoot\..\shared\Canonicalize.ps1" function Get-ImpactFromUsagePercent { param([double] $UsagePercent) if ($UsagePercent -ge 90.0) { return 'High' } if ($UsagePercent -ge 75.0) { return 'Medium' } return 'Low' } function Get-EffortForQuotaType { param( [string] $Service, [string] $MetricName ) $normalizedService = ([string]$Service).Trim().ToLowerInvariant() $normalizedMetric = ([string]$MetricName).Trim().ToLowerInvariant() if ($normalizedService -eq 'network') { return 'Low' } if ($normalizedService -eq 'vm') { if ($normalizedMetric -like '*family*') { return 'Medium' } return 'Medium' } return 'Medium' } function Get-EvidenceUrisForQuotaType { param( [string] $Service, [string] $MetricName ) $normalizedService = ([string]$Service).Trim().ToLowerInvariant() $normalizedMetric = ([string]$MetricName).Trim().ToLowerInvariant() if ($normalizedService -eq 'vm') { return @('https://learn.microsoft.com/azure/virtual-machines/quotas') } if ($normalizedService -eq 'network') { if ($normalizedMetric -like '*publicip*') { return @('https://learn.microsoft.com/azure/azure-resource-manager/management/azure-subscription-service-limits#networking-limits') } return @('https://learn.microsoft.com/azure/networking/networking-quotas') } return @('https://learn.microsoft.com/azure/azure-resource-manager/management/azure-subscription-service-limits') } function Get-QuotaPortalDeepLinkUrl { param( [Parameter(Mandatory)][string] $SubscriptionId, [Parameter(Mandatory)][string] $Location, [Parameter(Mandatory)][string] $Service ) $encodedSub = [uri]::EscapeDataString($SubscriptionId) $encodedLocation = [uri]::EscapeDataString($Location) $encodedService = [uri]::EscapeDataString($Service) return "https://portal.azure.com/#view/Microsoft_Azure_Capacity/QuotaMenuBlade/~/myQuotas/subscriptionId/$encodedSub/regionName/$encodedLocation/serviceId/$encodedService" } function Normalize-AzureQuotaReports { [CmdletBinding()] param ( [Parameter(Mandatory)] [PSCustomObject] $ToolResult ) if ($ToolResult.Status -ne 'Success' -or -not $ToolResult.Findings) { return @() } $runId = [guid]::NewGuid().ToString() $normalized = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($f in $ToolResult.Findings) { $subscriptionId = if ($f.PSObject.Properties['SubscriptionId']) { [string]$f.SubscriptionId } else { '' } if ([string]::IsNullOrWhiteSpace($subscriptionId)) { continue } $location = if ($f.PSObject.Properties['Location']) { [string]$f.Location } else { 'unknown-region' } $provider = if ($f.PSObject.Properties['Service'] -and $f.Service) { [string]$f.Service } else { 'unknown' } $entityArmId = "/subscriptions/$($subscriptionId.ToLowerInvariant())/providers/microsoft.capacity/locations/$($location.ToLowerInvariant())/serviceId/$($provider.ToLowerInvariant())" try { $canonical = ConvertTo-CanonicalEntityId -RawId $entityArmId -EntityType 'AzureResource' $entityId = $canonical.CanonicalId } catch { continue } $threshold = 80.0 if ($f.PSObject.Properties['Threshold'] -and $null -ne $f.Threshold) { $threshold = [double]$f.Threshold } $usagePercent = 0.0 if ($f.PSObject.Properties['UsagePercent'] -and $null -ne $f.UsagePercent) { $usagePercent = [double]$f.UsagePercent } $compliant = ($usagePercent -lt $threshold) $severity = if ($usagePercent -ge 99.0) { 'Critical' } elseif ($usagePercent -ge 95.0) { 'High' } elseif ($usagePercent -ge $threshold) { 'Medium' } else { 'Info' } $skuName = if ($f.PSObject.Properties['SkuName'] -and $f.SkuName) { [string]$f.SkuName } elseif ($f.PSObject.Properties['Sku'] -and $f.Sku) { [string]$f.Sku } elseif ($f.PSObject.Properties['MetricName'] -and $f.MetricName) { [string]$f.MetricName } else { 'unknown-sku' } $currentValue = if ($f.PSObject.Properties['CurrentValue'] -and $null -ne $f.CurrentValue) { [double]$f.CurrentValue } else { 0.0 } $limit = if ($f.PSObject.Properties['Limit'] -and $null -ne $f.Limit) { [double]$f.Limit } else { 0.0 } $quotaId = if ($f.PSObject.Properties['QuotaId'] -and $f.QuotaId) { [string]$f.QuotaId } elseif ($f.PSObject.Properties['MetricName'] -and $f.MetricName) { [string]$f.MetricName } else { $skuName } $findingId = if ($f.PSObject.Properties['Id'] -and $f.Id) { [string]$f.Id } else { [guid]::NewGuid().ToString() } $title = "Quota $skuName in $location is at $usagePercent%" $detail = "CurrentValue=$currentValue; Limit=$limit; Region=$location; SkuName=$skuName." $ruleId = "azure-quota:${provider}:${quotaId}:${location}" $impact = Get-ImpactFromUsagePercent -UsagePercent $usagePercent $effort = Get-EffortForQuotaType -Service $provider -MetricName $quotaId $evidenceUris = @(Get-EvidenceUrisForQuotaType -Service $provider -MetricName $quotaId) $deepLinkUrl = Get-QuotaPortalDeepLinkUrl -SubscriptionId $subscriptionId -Location $location -Service $provider $toolVersion = if ($f.PSObject.Properties['ToolVersion'] -and $f.ToolVersion) { [string]$f.ToolVersion } elseif ($ToolResult.PSObject.Properties['ToolVersion']) { [string]$ToolResult.ToolVersion } else { '' } $row = New-FindingRow -Id $findingId ` -Source 'azure-quota' -EntityId $entityId -EntityType 'AzureResource' ` -Title $title -RuleId $ruleId ` -Compliant ([bool]$compliant) -ProvenanceRunId $runId ` -Platform 'Azure' -Category 'Capacity' -Severity $severity ` -Detail $detail -SubscriptionId $subscriptionId ` -Pillar 'Reliability' -Impact $impact -Effort $effort ` -DeepLinkUrl $deepLinkUrl -ScoreDelta $usagePercent ` -EvidenceUris $evidenceUris -EntityRefs @($subscriptionId, $location) ` -ToolVersion $toolVersion -LearnMoreUrl $(if ($evidenceUris.Count -gt 0) { $evidenceUris[0] } else { '' }) if ($null -eq $row) { continue } foreach ($extra in @( 'Location', 'Service', 'Sku', 'SkuName', 'MetricName', 'CurrentValue', 'Limit', 'UsagePercent', 'Threshold', 'Unit' )) { if ($f.PSObject.Properties[$extra] -and $null -ne $f.$extra) { $row | Add-Member -NotePropertyName $extra -NotePropertyValue $f.$extra -Force } } $normalized.Add($row) } return @($normalized) } |