Skip to content

Commit

Permalink
Merge pull request #260 from JulianHayward/6.6.0
Browse files Browse the repository at this point in the history
6.6.0
  • Loading branch information
JulianHayward authored Oct 26, 2024
2 parents a6d51a5 + 47d15e0 commit 7fd176e
Show file tree
Hide file tree
Showing 11 changed files with 621 additions and 14 deletions.
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,17 @@ The [Azure Governance Visualizer accelerator](https://github.com/Azure/Azure-Gov

## Release history

**Changes** (2024-October-26 / 6.6.0 Minor)

- Microsoft Defender for Cloud Coverage (Tenant Summary and CSV export). Example html:
![MicrosoftDefenderForCloudCoverage_preview](img/MicrosoftDefenderForCloudCoverage_preview.png)
- CostOptimization add `microsoft.network/privateendpoints` for intent=cost savings
- extend ResourcesAll.csv output with sku and kind information
- update [API reference](#api-reference) '/subscriptions/`subscriptionId`/resources' use API version 2024-03-01 (previous 2023-07-01)

**Changes** (2024-October-9 / 6.5.5 Patch)

- introduce a new optional [parameter](#parameters) `-SubscriptionIdWhitelist`, which defines the subscriptions that must match in order to be processed.

**Changes** (2024-September-19 / 6.5.4 Patch)

- minor PSScriptAnalyzer finding resolved

[Full release history](history.md)

Expand Down Expand Up @@ -609,7 +613,7 @@ Azure Governance Visualizer polls the following APIs
| ARM | 2020-01-01-preview | /subscriptions/`subscriptionId`/providers/Microsoft.Security/securityContacts |
| ARM | 2019-10-01 | /subscriptions/`subscriptionId`/providers |
| ARM | 2021-04-01 | /subscriptions/`subscriptionId`/resourcegroups |
| ARM | 2023-07-01 | /subscriptions/`subscriptionId`/resources |
| ARM | 2024-03-01 | /subscriptions/`subscriptionId`/resources |
| ARM | 2020-01-01 | /subscriptions |
| ARM | 2020-01-01 | /tenants |

Expand Down
8 changes: 8 additions & 0 deletions history.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@

### Azure Governance Visualizer version 6

**Changes** (2024-October-26 / 6.6.0 Minor)

- Microsoft Defender for Cloud Coverage (Tenant Summary and CSV export)
![MicrosoftDefenderForCloudCoverage_preview](img/MicrosoftDefenderForCloudCoverage_preview.png)
- CostOptimization add `microsoft.network/privateendpoints` for intent=cost savings
- extend ResourcesAll.csv output with sku and kind information
- update [API reference](#api-reference) '/subscriptions/`subscriptionId`/resources' use API version 2024-03-01 (previous 2023-07-01)

**Changes** (2024-October-9 / 6.5.5 Patch)

- introduce a new optional parameter `-SubscriptionIdWhitelist`, which defines the subscriptions that must match in order to be processed.
Expand Down
Binary file added img/MicrosoftDefenderForCloudCoverage_preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
305 changes: 301 additions & 4 deletions pwsh/AzGovVizParallel.ps1

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion pwsh/dev/devAzGovVizParallel.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ Param
$Product = 'AzGovViz',

[string]
$ProductVersion = '6.5.5',
$ProductVersion = '6.6.0',

[string]
$GithubRepository = 'aka.ms/AzGovViz',
Expand Down Expand Up @@ -679,6 +679,7 @@ if ($ManagementGroupId -match ' ') {
}

#region Functions
. ".\$($ScriptPath)\functions\processMDfCCoverage.ps1"
. ".\$($ScriptPath)\functions\getPrivateEndpointCapableResourceTypes.ps1"
. ".\$($ScriptPath)\functions\validateLeastPrivilegeForUser.ps1"
. ".\$($ScriptPath)\functions\getPolicyRemediation.ps1"
Expand Down Expand Up @@ -1024,6 +1025,7 @@ if (-not $HierarchyMapOnly) {
$htDailySummary = @{}
$arrayDefenderPlans = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList))
$arrayDefenderPlansSubscriptionsSkipped = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList))
$htSecuritySettings = [System.Collections.Hashtable]::Synchronized(@{})
$arrayUserAssignedIdentities4Resources = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList))
$htSubscriptionsRoleAssignmentLimit = [System.Collections.Hashtable]::Synchronized(@{})
if ($azAPICallConf['htParameters'].NoMDfCSecureScore -eq $false) {
Expand Down
35 changes: 34 additions & 1 deletion pwsh/dev/functions/dataCollection/dataCollectionFunctions.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,29 @@ function dataCollectionDefenderPlans {
subscriptionMgPath = $childMgMgPath
defenderPlan = $defenderPlanResult.name
defenderPlanTier = $defenderPlanResult.properties.pricingTier
defenderPlanFull = $defenderPlanResult
})
}
}
}

$currentTask = "Getting Microsoft Defender for Cloud settings for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$SubscriptionQuotaId']"
$uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Security/settings?api-version=2022-05-01"
$method = 'GET'
$securitySettingsResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask

if ($securitySettingsResult -eq 'SubScriptionNotRegistered' -or $securitySettingsResult -eq 'DisallowedProvider') {
Write-Host "Subscription $scopeId skipped for SecuritySettings ($securitySettingsResult)"
}
else {
foreach ($setting in $securitySettingsResult) {
if (-not $htSecuritySettings.$scopeId) {
$script:htSecuritySettings.$scopeId = @{}
}
$script:htSecuritySettings.($scopeId).($setting.name) = $setting
}
}

}
$funcDataCollectionDefenderPlans = $function:dataCollectionDefenderPlans.ToString()

Expand Down Expand Up @@ -508,7 +527,7 @@ function dataCollectionResources {

#region resources LIST
$currentTask = "Getting Resources for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
$uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/resources?`$expand=createdTime,changedTime,properties&api-version=2023-07-01"
$uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/resources?`$expand=createdTime,changedTime,properties&api-version=2024-03-01"
$method = 'GET'
$resourcesSubscriptionResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
#endregion resources LIST
Expand Down Expand Up @@ -1263,10 +1282,24 @@ function dataCollectionResources {
$cafResourceNamingCheck = 'n/a'
$applicableNaming = 'n/a'
}

$sku = $null
if ($resource.sku) {
$sku = $resource.sku
}

$kind = $null
if ($resource.kind) {
$kind = $resource.kind
}

$null = $script:resourcesIdsAll.Add([PSCustomObject]@{
subscriptionId = $scopeId
subscriptionName = $scopeDisplayName
mgPath = $childMgMgPath
type = ($resource.type).ToLower()
sku = $sku
kind = $kind
id = ($resource.Id).ToLower()
name = ($resource.name).ToLower()
location = ($resource.location).ToLower()
Expand Down
2 changes: 1 addition & 1 deletion pwsh/dev/functions/getOrphanedResources.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ resources
intent = $intent
})

$intent = 'misconfiguration'
$intent = 'cost savings'
$null = $queries.Add([PSCustomObject]@{
queryName = 'microsoft.network/privateendpoints'
query = @"
Expand Down
28 changes: 27 additions & 1 deletion pwsh/dev/functions/processDataCollection.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@
$htUserTypesGuest = $using:htUserTypesGuest
$arrayDefenderPlans = $using:arrayDefenderPlans
$arrayDefenderPlansSubscriptionsSkipped = $using:arrayDefenderPlansSubscriptionsSkipped
$htSecuritySettings = $using:htSecuritySettings
$arrayUserAssignedIdentities4Resources = $using:arrayUserAssignedIdentities4Resources
$htSubscriptionsRoleAssignmentLimit = $using:htSubscriptionsRoleAssignmentLimit
$arrayPsRule = $using:arrayPsRule
Expand Down Expand Up @@ -887,8 +888,33 @@
#DataCollection Export of All Resources
if ($resourcesIdsAll.Count -gt 0) {
if (-not $NoCsvExport) {
$startExportingResourcesAllCSV = Get-Date
Write-Host "Exporting ResourcesAll CSV '$($outputPath)$($DirectorySeparatorChar)$($fileName)_ResourcesAll.csv'"
$resourcesIdsAll | Sort-Object -Property id | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_ResourcesAll.csv" -Delimiter "$csvDelimiter" -NoTypeInformation
$arrListSKUKeys = [System.Collections.ArrayList]@()
foreach ($entry in $resourcesIdsAll.where({ $_.sku }).sku) {
foreach ($noteProperty in ($entry | Get-Member).where({ $_.MemberType -eq 'NoteProperty' })) {
if ($arrListSKUKeys -notcontains $noteProperty.Name) {
$null = $arrListSKUKeys.Add($noteProperty.Name)
}
}
}
Write-Host " SKU keys: $(($arrListSKUKeys | Sort-Object) -join ', ')"

Write-Host ' Enriching resources with SKU keys'
foreach ($entry in $resourcesIdsAll) {
foreach ($key in $arrListSKUKeys | Sort-Object) {
if ($entry.sku.$key) {
$entry | Add-Member -MemberType NoteProperty -Name "sku_$($key)" -Value $entry.sku.$key
}
else {
$entry | Add-Member -MemberType NoteProperty -Name "sku_$($key)" -Value $null
}
}
}

$resourcesIdsAll | Sort-Object -Property id | Select-Object -Property subscriptionId, subscriptionName, mgPath, type, sku_*, kind, id, name, location, tags, createdTime, changedTime, cafResourceNamingResult, cafResourceNaming, cafResourceNamingFriendlyName | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_ResourcesAll.csv" -Delimiter "$csvDelimiter" -NoTypeInformation
$endExportingResourcesAllCSV = Get-Date
Write-Host " Exporting ResourcesAll CSV duration: $((New-TimeSpan -Start $startExportingResourcesAllCSV -End $endExportingResourcesAllCSV).TotalMinutes) minutes ($((New-TimeSpan -Start $startExportingResourcesAllCSV -End $endExportingResourcesAllCSV).TotalSeconds) seconds)"
}
}
else {
Expand Down
125 changes: 125 additions & 0 deletions pwsh/dev/functions/processMDfCCoverage.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
function processMDfCCoverage {
Write-Host ' Processing Defender Coverage'
$start = Get-Date

$htDefenderProps = @{}
$htDefenderExtensions = @{}
foreach ($x in $arrayDefenderPlans) {
if (-not $htDefenderProps.($x.defenderPlan)) {
$htDefenderProps.($x.defenderPlan) = [System.Collections.ArrayList]@()
}
if (-not $htDefenderExtensions.($x.defenderPlan)) {
$htDefenderExtensions.($x.defenderPlan) = [System.Collections.ArrayList]@()
}
foreach ($noteprop in ($x.defenderPlanFull.properties | Get-Member).where({ $_.MemberType -eq 'NoteProperty' })) {
if ($htDefenderProps.($x.defenderPlan) -notcontains $noteprop.Name) {
$null = $htDefenderProps.($x.defenderPlan).Add($noteprop.Name)
}
if ($noteprop.Name -eq 'extensions') {
foreach ($extension in $x.defenderPlanFull.properties.($noteprop.Name)) {
if ($htDefenderExtensions.($x.defenderPlan) -notcontains $extension.name) {
$null = $htDefenderExtensions.($x.defenderPlan).Add($extension.name)
}
}
}
}
}

$arrayDefenderPlansNamesUnique = $arrayDefenderPlans.defenderPlan | Sort-Object -Unique
$script:arrayDefenderPlansCoverage = [System.Collections.ArrayList]@()
foreach ($defenderPlanName in $arrayDefenderPlansNamesUnique) {
foreach ($defenderPlanEntry in $arrayDefenderPlans.where({ $_.defenderPlan -eq $defenderPlanName })) {
$objDefenderPlan = [ordered]@{
plan = $defenderPlanEntry.defenderPlan
subscriptionId = $defenderPlanEntry.subscriptionId
subscriptionName = $defenderPlanEntry.subscriptionName
subscriptionMgPath = $defenderPlanEntry.subscriptionMgPath
}
foreach ($prop in $htDefenderProps.($defenderPlanName)) {
if ($prop -eq 'extensions') {
foreach ($extension in $htDefenderExtensions.($defenderPlanName)) {
$extensionObject = $defenderPlanEntry.defenderPlanFull.properties.extensions.where({ $_.name -eq $extension })
if ($extensionObject.count -gt 0) {
$objDefenderPlan.("ext_$($extension)") = $extensionObject.isEnabled
if ($defenderPlanName -eq 'StorageAccounts' -and $extension -eq 'OnUploadMalwareScanning') {
if ($extensionObject.additionalExtensionProperties.CapGBPerMonthPerStorageAccount) {
$objDefenderPlan.("ext_$("$($extension)_CapGBPerMonthPerStorageAccount")") = $extensionObject.additionalExtensionProperties.CapGBPerMonthPerStorageAccount
}
else {
$objDefenderPlan.("ext_$("$($extension)_CapGBPerMonthPerStorageAccount")") = $null
}
}
}
else {
$objDefenderPlan.("ext_$($extension)") = $null
if ($defenderPlanName -eq 'StorageAccounts' -and $extension -eq 'OnUploadMalwareScanning') {
$objDefenderPlan.("ext_$("$($extension)_CapGBPerMonthPerStorageAccount")") = $null
}
}
}
}
elseif ($prop -eq 'replacedBy') {
$objDefenderPlan.($prop) = $defenderPlanEntry.defenderPlanFull.properties.($prop) -join ';'
}
else {
$objDefenderPlan.($prop) = $defenderPlanEntry.defenderPlanFull.properties.($prop)
}

if ($defenderPlanName -eq 'VirtualMachines' -and $prop -eq 'subPlan') {
if ($defenderPlanEntry.defenderPlanFull.properties.($prop)) {
if ($htSecuritySettings.($defenderPlanEntry.subscriptionId).WDATP) {
$objDefenderPlan.('ext_MicrosoftDefenderforEndpoint') = ($htSecuritySettings.($defenderPlanEntry.subscriptionId).WDATP.properties.enabled).ToString()
}
else {
$objDefenderPlan.('ext_MicrosoftDefenderforEndpoint') = 'unknown'
}
}
else {
$objDefenderPlan.('ext_MicrosoftDefenderforEndpoint') = 'n/a'
}

}
}
$null = $script:arrayDefenderPlansCoverage.Add($objDefenderPlan)
}
}

# $tstsmp = Get-Date -Format 'yyyyMMdd_HHmmss'
# $arrayDefenderPlansCoverage | ConvertTo-Json -Depth 99 > "c:\temp\defenderCoverage_Final_$($tstsmp).json"

$arrayDefenderPlanSpecificProperties = [System.Collections.ArrayList]@()
$arrayDefenderPlanCommonProperties = @('plan', 'subscriptionId', 'subscriptionName', 'subscriptionMgPath', 'pricingTier', 'freeTrialRemainingTime')
foreach ($plan in $arrayDefenderPlansCoverage) {
$plan.Keys | ForEach-Object {
if ($_ -notin $arrayDefenderPlanCommonProperties) {
$null = $arrayDefenderPlanSpecificProperties.Add("$($plan.plan)_$($_)")
}
}
}
$arrayDefenderPlanSpecificPropertiesUnique = $arrayDefenderPlanSpecificProperties | Sort-Object -Unique

$arrayDefenderPlansCoverageAll = [System.Collections.ArrayList]@()
foreach ($entry in $arrayDefenderPlansCoverage) {
$obj = [PSCustomObject]@{}
foreach ($cprop in $arrayDefenderPlanCommonProperties) {
$obj | Add-Member -MemberType NoteProperty -Name $cprop -Value $entry.($cprop)
}
foreach ($sprop in $arrayDefenderPlanSpecificPropertiesUnique) {
if ($sprop -like "$($entry.plan)_*") {
$obj | Add-Member -MemberType NoteProperty -Name $sprop -Value $entry.($sprop -replace "$($entry.plan)_", '' )
}
else {
$obj | Add-Member -MemberType NoteProperty -Name $sprop -Value $null
}
}
$null = $arrayDefenderPlansCoverageAll.Add($obj)
}

if (-not $NoCsvExport) {
Write-Host " Exporting MDfCCoverage CSV '$($outputPath)$($DirectorySeparatorChar)$($fileName)_MDfCCoverage.csv'"
$arrayDefenderPlansCoverageAll | Sort-Object -Property plan, subscriptionName | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_MDfCCoverage.csv" -Delimiter "$csvDelimiter" -NoTypeInformation
}

$end = Get-Date
Write-Host " Defender Coverage processing duration: $((New-TimeSpan -Start $start -End $end).TotalMinutes) minutes ($((New-TimeSpan -Start $start -End $end).TotalSeconds) seconds)"
}
Loading

0 comments on commit 7fd176e

Please sign in to comment.