MCSAssessment.psm1
|
function Invoke-MCSAssessment { Param( [Alias("Tenant","Tenants")] [string]$TenantID, [Alias("Subscription","Subscriptions","Subs")] [String[]]$SubscriptionID, [Alias("Workload","Workloads")] [String]$WorkloadName = "Default_Workload", [switch]$SkipCompress, [switch]$SkipARI, [switch]$SkipWARA, [switch]$SkipMDC, [switch]$SkipMCSB, [switch]$SkipPSRule ) # WARA Files $RecommendationResourceTypesUri = 'https://azure.github.io/WARA-Build/objects/WARAinScopeResTypes.csv' $RecommendationDataUri = 'https://azure.github.io/WARA-Build/objects/recommendations.json' Write-Host "Setting Variables.." $workingDirectory = (Get-Location).Path Write-Host "Working Directory: $workingDirectory" if ($workingDirectory -eq "C:\") { Write-Host "Error: Working Directory cannot be the root of C:\" -ForegroundColor Red exit 1 } Write-Host "Validating Parameters..." if (-Not $TenantID) { Write-Host "Error: No TenantID" -ForegroundColor Red exit 1 } if (-Not $SubscriptionID) { Write-Host "Error: No SubscriptionID" -ForegroundColor Red exit 1 } <# Write-Host "Creating Folders..." $subjson = "$workingDirectory/JSON" $subcsv = "$workingDirectory/CSV" if (-Not (Test-Path $subjson)) { New-Item -ItemType Directory -Force -Path $subjson | Out-Null } if (-Not (Test-Path $subcsv)) { New-Item -ItemType Directory -Force -Path $subcsv | Out-Null } #> if ($SkipARI.IsPresent) { Write-Host "Skipping ARI Data Collection as per user request." -ForegroundColor Yellow } else { Write-Host "Starting ARI Data Collection for Workload: $WorkloadName" Invoke-ARI -ReportDir $workingDirectory -ReportName $WorkloadName -NoAutoUpdate -DiagramFullEnvironment -TenantId $TenantID -SubscriptionId $SubscriptionID -Debug -IncludeCosts -Lite } if ($SkipWARA.IsPresent) { Write-Host "Skipping WARA Data Collection as per user request." -ForegroundColor Yellow } else { Write-Host "Starting WARA Data Collection for Workload: $WorkloadName" Start-WARACollector -tenantid $TenantID -subscriptionid @($SubscriptionID) -Debug $WARAFile = Get-ChildItem -Path $workingDirectory -Filter "WARA-File*.json" $JSONResources = Get-Item -Path $WARAFile $JSONResources = $JSONResources.FullName $JSONContent = Get-Content -Path $JSONResources | ConvertFrom-Json $RootTypes = Invoke-RestMethod $RecommendationResourceTypesUri | ConvertFrom-Csv $RootTypes = $RootTypes | Where-Object { $_.InAprlAndOrAdvisor -eq 'yes' } $RecommendationObject = Invoke-RestMethod $RecommendationDataUri Write-Host "Count of WARA Recommendations: $($RecommendationObject.Count)" $ResourceRecommendations = $RecommendationObject | Where-Object { [string]::IsNullOrEmpty($_.tags) } $ResourceCollection = @() # First loop through the recommendations to get the impacted resources foreach ($Recom in $ResourceRecommendations) { $Resources = $JSONContent.ImpactedResources| Where-Object { ($_.recommendationId -eq $Recom.aprlGuid) } # If the recommendation is not a Custom Recommendation, we need to validate if the resources are not already in the tmp array (from a previous loop of a Custom Recommendation) if ([string]::IsNullOrEmpty($Resources) -and $Recom.aprlGuid -notin $tmp.Guid -and -not $Recom.checkName) { $Resources = $JSONContent.ImpactedResources | Where-Object { ($_.recommendationId -eq $Recom.aprlGuid) } } foreach ($Resource in $Resources) { $ResObj = [PSCustomObject]@{ 'Recommendation Guid' = $Recom.aprlGuid 'Recommendation Title' = $Recom.description 'Description' = $Recom.longDescription 'Priority' = $Recom.recommendationImpact 'Customer-facing annotation' = "" 'Internal-facing notes' = ($Recom.learnMoreLink.url -join " `n") 'Potential Benefit' = $Recommendation.'Potential Benefit' 'Resource Type' = $Resource.type 'Resource ID' = $Resource.id } $ResourceCollection += $ResObj } } } Write-Host "Gethering Resource IDs" $ResourceList = @() $ResourceIDsQuery = "resources | project id" foreach ($Subscription in $SubscriptionID) { Write-Host "ResourceID loops: $Subscription" try { $QueryResult = Search-AzGraph -Query $ResourceIDsQuery -first 1000 -Subscription $Subscription -Debug:$false } catch { $QueryResult = Search-AzGraph -Query $ResourceIDsQuery -first 200 -Subscription $Subscription -Debug:$false } $ResourceList += $QueryResult while ($QueryResult.SkipToken) { try { $QueryResult = Search-AzGraph -Query $ResourceIDsQuery -SkipToken $QueryResult.SkipToken -Subscription $Subscription -first 1000 -Debug:$false } catch { $QueryResult = Search-AzGraph -Query $ResourceIDsQuery -SkipToken $QueryResult.SkipToken -Subscription $Subscription -first 200 -Debug:$false } $ResourceList += $QueryResult } } Write-Host "Starting Security Data Collection for Workload: $WorkloadName" $MDCQuery = Get-Content -Path ("$PSScriptRoot\MDC.kql") -Raw -Encoding UTF8 $MCSBQuery = Get-Content -Path ("$PSScriptRoot\MCSB.kql") -Raw -Encoding UTF8 if ($SkipMDC.IsPresent) { Write-Host "Skipping Defender for Cloud Data Collection as per user request." -ForegroundColor Yellow } else { $MDCLocalResults = @() foreach ($Subscription in $SubscriptionID) { Write-Host "Processing Security loops: $Subscription" try { $QueryResult = Search-AzGraph -Query $MDCQuery -first 1000 -Subscription $Subscription -Debug:$false } catch { $QueryResult = Search-AzGraph -Query $MDCQuery -first 200 -Subscription $Subscription -Debug:$false } $MDCLocalResults += $QueryResult while ($QueryResult.SkipToken) { try { $QueryResult = Search-AzGraph -Query $MDCQuery -SkipToken $QueryResult.SkipToken -Subscription $Subscription -first 1000 -Debug:$false } catch { $QueryResult = Search-AzGraph -Query $MDCQuery -SkipToken $QueryResult.SkipToken -Subscription $Subscription -first 200 -Debug:$false } $MDCLocalResults += $QueryResult } } Write-Host "Exporting Defender for Cloud Recommendations to CSV" $MDCLocalResults | Export-Csv -Path ($workingDirectory + "\DefenderForCloudRecommendations.csv") -NoTypeInformation -Encoding UTF8 Foreach ($MDC in $MDCLocalResults) { if ($MDC.state -eq 'Unhealthy') { $MDCResourceType = $MDC.resourceId.Split('/')[6]+ '/' + $MDC.resourceId.Split('/')[7] $Resourceid = $ResourceList | where-object { $_.id -eq $MDC.resourceId } if ([string]::IsNullOrEmpty($Resourceid.id) -eq $false) { $ResObj = [PSCustomObject]@{ 'Recommendation Guid' = $MDC.recommendationId 'Recommendation Title' = $MDC.recommendationDisplayName 'Description' = $MDC.description 'Priority' = $MDC.severity 'Customer-facing annotation' = "" 'Internal-facing notes' = $MDC.azurePortalRecommendationLink 'Potential Benefit' = "" 'Resource Type' = $MDCResourceType 'Resource ID' = $Resourceid.id } $ResourceCollection += $ResObj } } } } if ($SkipMCSB.IsPresent) { Write-Host "Skipping Security Benchmark Data Collection as per user request." -ForegroundColor Yellow } else { $MCSBLocalResults = @() foreach ($Subscription in $SubscriptionID) { Write-Host "Processing Security loops: $Subscription" try { $QueryResult = Search-AzGraph -Query $MCSBQuery -first 1000 -Subscription $Subscription -Debug:$false } catch { $QueryResult = Search-AzGraph -Query $MCSBQuery -first 200 -Subscription $Subscription -Debug:$false } $MCSBLocalResults += $QueryResult while ($QueryResult.SkipToken) { try { $QueryResult = Search-AzGraph -Query $MCSBQuery -SkipToken $QueryResult.SkipToken -Subscription $Subscription -first 1000 -Debug:$false } catch { $QueryResult = Search-AzGraph -Query $MCSBQuery -SkipToken $QueryResult.SkipToken -Subscription $Subscription -first 200 -Debug:$false } $MCSBLocalResults += $QueryResult } } Write-Host "Exporting Security Benchmark Recommendations to CSV" $MCSBLocalResults | Export-Csv -Path ($workingDirectory + "\SecurityBenchmarkRecommendations.csv") -NoTypeInformation -Encoding UTF8 Foreach ($MCSB in $MCSBLocalResults) { if ($MCSB.state -eq 'Unhealthy' -and $MCSB.recommendationMetadataState -eq 'failed') { $MCSBResourceType = $MCSB.resourceId.Split('/')[6]+ '/' + $MCSB.resourceId.Split('/')[7] $Resourceid = $ResourceList | where-object { $_.id -eq $MCSB.resourceId } if ([string]::IsNullOrEmpty($Resourceid.id) -eq $false) { $ResObj = [PSCustomObject]@{ 'Recommendation Guid' = $MCSB.recommendationId 'Recommendation Title' = $MCSB.recommendationDisplayName 'Description' = $MCSB.description 'Priority' = $MCSB.severity 'Customer-facing annotation' = "" 'Internal-facing notes' = $MCSB.azurePortalRecommendationLink 'Potential Benefit' = "" 'Resource Type' = $MCSBResourceType 'Resource ID' = $Resourceid.id } $ResourceCollection += $ResObj } } } } Write-Host "Total Recommendation Lines to Export: $($ResourceCollection.Count)" if ($ResourceCollection.Count -gt 500) { $Loop = $ResourceCollection.Count / 500 $Loop = [math]::ceiling($Loop) $Looper = 0 $Limit = 0 while ($Looper -lt $Loop) { $Looper ++ Write-Host "Exporting Partial CSV File: $Looper" $ResourceCollection[($Limit - 500)..($Limit - 1)] | Export-Csv -Path "$workingDirectory\Consolidated_Assessment_${WorkloadName}_Part${Looper}.csv" -NoTypeInformation -Encoding UTF8 $Limit += 500 } } else { Write-Host "Exporting Complete CSV File" $ResourceCollection | Export-Csv -Path "$workingDirectory\Consolidated_Assessment_$WorkloadName.csv" -NoTypeInformation -Encoding UTF8 } if ($SkipPSRule.IsPresent) { Write-Host "Skipping PSRule Data Collection as per user request." -ForegroundColor Yellow } else { Write-Host "Starting PSRule Data Collection for Workload: $WorkloadName" # PSRule Export $RuleObjects = @() $RuleQuery = "resources" foreach ($Subscription in $SubscriptionID) { # PSRule Resources Export try { $QueryResult = Search-AzGraph -Query $RuleQuery -first 1000 -Subscription $Subscription -Debug:$false } catch { $QueryResult = Search-AzGraph -Query $RuleQuery -first 200 -Subscription $Subscription -Debug:$false } $RuleObjects += $QueryResult while ($QueryResult.SkipToken) { try { $QueryResult = Search-AzGraph -Query $RuleQuery -SkipToken $QueryResult.SkipToken -Subscription $Subscription -first 1000 -Debug:$false } catch { $QueryResult = Search-AzGraph -Query $RuleQuery -SkipToken $QueryResult.SkipToken -Subscription $Subscription -first 200 -Debug:$false } $RuleObjects += $QueryResult } } Write-Host " - Generating CSV results..." -ForegroundColor Yellow $RuleObjects | Invoke-PSRule -Outcome Fail -Module PSRule.Rules.Azure -OutputPath "$workingDirectory\PSRuleResults.csv" -OutputFormat Csv $RulesResult = $RuleObjects | Invoke-PSRule -Outcome Fail -Module PSRule.Rules.Azure foreach ($Rule in $RulesResult) { $ResObj = [PSCustomObject]@{ 'Recommendation Guid' = $Rule.Ref 'Recommendation Title' = $Rule.Recommendation 'Description' = $Rule.Reason 'Priority' = "Medium" 'Customer-facing annotation' = "" 'Internal-facing notes' = ("PSRuleID: "+$Rule.RuleId) 'Potential Benefit' = "" 'Resource Type' = $Rule.TargetType 'Resource ID' = $Rule.TargetObject.id } $ResourceCollection += $ResObj } } if ($SkipCompress) { Write-Host "Skipping compression as per user request." -ForegroundColor Yellow } else { try { # Create a temporary folder to gather all contents $tempFolder = "$workingDirectory\TempForZip" New-Item -ItemType Directory -Path $tempFolder -Force | Out-Null $FilesToMove = Get-ChildItem -Path $workingDirectory -Include ("*.json","*.csv","*.xml","*.xlsx") -Recurse # Move all files into the temp folder foreach ($file in $FilesToMove) { Move-Item -Path $file.FullName -Destination $tempFolder } # Compress the temp folder into a ZIP file Compress-Archive -Path "$tempFolder\*" -DestinationPath ("$workingDirectory\Consolidated_Assessment.zip") -Force } catch { Write-Host "Error during compression: " + $_.Exception.Message -ForegroundColor Red exit 1 } # Clean up the temporary folder Remove-Item -Path $tempFolder -Recurse -Force # Output path of the created ZIP file $zipFilePath = Join-Path -Path $workingDirectory -ChildPath "Consolidated_Assessment.zip" Write-Host "The Assessment Data Collection ZIP file is stored at the location : $zipFilePath" -ForegroundColor Green # End of the script } # CleanUp <# Write-Host "Cleaning up WARA and ARI generated files..." if (Test-Path -PathType Leaf -Path $WARAFile) { Remove-Item $WARAFile -Force } #> if (Test-Path -PathType Container -Path "$workingDirectory\DiagramCache") { Remove-Item "$workingDirectory\DiagramCache" -Force -Recurse } if (Test-Path -PathType Container -Path "$workingDirectory\ReportCache") { Remove-Item "$workingDirectory\ReportCache" -Force -Recurse } if (Test-Path -PathType Leaf -Path "$workingDirectory\DiagramLogFile.log") { Remove-Item "$workingDirectory\DiagramLogFile.log" -Force } } |