From d396a864961e65d39bc5623582f0dec65260d258 Mon Sep 17 00:00:00 2001 From: Or Parnes Date: Thu, 18 Jul 2024 16:17:29 -0700 Subject: [PATCH 1/3] Remediate-ConfigAzureDefender: support Containers policies Assign relevant Azure Policies (DINE) associated with MDC Containers plan. Also fixed an issue with RP registration. Before the change, if RP was not registered, there was a call to perform registration but no plan enablement after it (unless running the script twice). Also, logged some other issues found during debugging. --- .../Remediate-ConfigAzureDefender.ps1 | 644 +++++++++++------- 1 file changed, 409 insertions(+), 235 deletions(-) diff --git a/Scripts/RemediationScripts/Remediate-ConfigAzureDefender.ps1 b/Scripts/RemediationScripts/Remediate-ConfigAzureDefender.ps1 index b53743d8..86e3ff8f 100644 --- a/Scripts/RemediationScripts/Remediate-ConfigAzureDefender.ps1 +++ b/Scripts/RemediationScripts/Remediate-ConfigAzureDefender.ps1 @@ -1,7 +1,7 @@ <########################################## # Overview: - This script is used to configure Azure Defender on subscription. + This script is used to configure Microsoft Defender for Cloud on subscription. # ControlId: Azure_Subscription_Config_MDC_Defender_Plans @@ -12,11 +12,14 @@ # Steps performed by the script: 1. Install and validate pre-requisites to run the script for subscription. - 2. Get the list of resource types that do not have Azure Defender plan enabled, from subscription. + 2. Get the list of disabled Microsoft Defender for Cloud plans from subscription. - 3. Take a backup of these non-compliant resource types. + 3. Take a backup of these non-compliant plans. + + 4. Register 'Microsoft.Security' provider and enable all disabled Microsoft Defender for Cloud plans for subscription. + + 5. Verify Azure Policy assignments for MDC features that requires DINE policy in place. - 4. Register 'Microsoft.Security' provider and enable Azure Defender plan for all non-compliant resource types for subscription. # Step to execute script: Download and load remediation script in PowerShell session and execute below command. @@ -24,20 +27,105 @@ # Command to execute: Examples: - 1. Run below command to configure Azure Defender for subscription + 1. Run below command to configure Microsoft Defender for Cloud for subscription - Set-ConfigAzureDefender -SubscriptionId '' -PerformPreReqCheck: $true + Set-ConfigAzureDefender -Environment 'AzureCloud' -SubscriptionId '' -PerformPreReqCheck: $true Note: To rollback changes made by remediation script, execute below command - Remove-ConfigAzureDefender -SubscriptionId '' -Path '' -PerformPreReqCheck: $true + Remove-ConfigAzureDefender -Environment 'AzureCloud' -SubscriptionId '' -Path '' -PerformPreReqCheck: $true To know more about parameter execute: a. Get-Help Set-ConfigAzureDefender -Detailed b. Get-Help Remove-ConfigAzureDefender -Detailed + +# Known issues + + 1. This script does not support plan extension-level, meaning: + a. It is possible that some plan was already enabled but with some specific extension being disabled - no change will be made in this case. + b. There is no extension-level log and the revert capability is limited. + 2. There is no SubPlan feature support for plans that supports it in the API level + 3. Plans 'Servers', 'Databases' are partially supported - it seems like enabling the plan does not enables all extensions within it. + 4. Plan 'API' throws exception when trying to enable, because of (2) above. + 5. Revert does not support Azure Policy definition assigned during the enablement script. + 6. Enable-MdcPlans: need to better manage errors on speicific plan enablement, so issues like (4) will be easier to be found. + ######################################## #> + +function Install-Az-Module +{ + param ( + [string] + [Parameter(Mandatory = $true, HelpMessage="Module name")] + $Name + ) + + if ($null -eq (Get-Module -Name $Name)) + { + Write-Host "Installing module $Name..." -ForegroundColor Yellow + Install-Module -Name $Name -Scope CurrentUser -Repository 'PSGallery' + } + else + { + Write-Host "$Name module is available." -ForegroundColor Green + } +} + +function Register-ResourceProvider +{ + param ( + [string] + [Parameter(Mandatory = $true, HelpMessage="Resource Provider name")] + $Name + ) + + # Checking IsProviderRegister with relevant provider + $registeredProvider = Get-AzResourceProvider -ProviderNamespace $Name | Where-Object { $_.RegistrationState -eq "Registered" } + $isProviderRegister = $true + + if($null -eq $registeredProvider) + { + # capture provider registration state + $isProviderRegister = $false + Write-Host "Found [$($Name)] provider is not registered." + Write-Host "$Name registering [It takes 2-3 min to get registered]..." + # Registering provider with required provider name, it will take 1-2 min for registration + try + { + Register-AzResourceProvider -ProviderNamespace $Name + while((((Get-AzResourceProvider -ProviderNamespace $Name).RegistrationState -ne "Registered") | Measure-Object).Count -gt 0) + { + # Checking threshold time limit to avoid getting into infinite loop + if($thresholdTimeLimit -ge 300) + { + Write-Host "Error occurred while registering [$($Name)] provider. It is taking more time than expected, Aborting process..." -ForegroundColor Red + throw [System.ArgumentException] ($_) + } + Start-Sleep -Seconds 30 + Write-Host "$Name registering..." -ForegroundColor Yellow + + # Incrementing threshold time limit by 30 sec in every iteration + $thresholdTimeLimit = $thresholdTimeLimit + 30 + } + + $isProviderRegister = $true + } + catch + { + Write-Host "Error occurred while registering $Name provider. ErrorMessage [$($_)]" -ForegroundColor Red + } + Write-Host "$Name provider successfully registered." -ForegroundColor Green + } + else + { + Write-Host "Found [$($Name)] provider is already registered." + } + + return $isProviderRegister +} + function Pre_requisites { <# @@ -47,45 +135,245 @@ function Pre_requisites This command would check pre requisites modules to perform remediation. #> - Write-Host "Required modules are: Az.Resources, Az.Security, Az.Accounts" -ForegroundColor Cyan - Write-Host "Checking for required modules..." - $availableModules = $(Get-Module -ListAvailable Az.Resources, Az.Security, Az.Accounts) - - # Checking if 'Az.Accounts' module is available or not. - if($availableModules.Name -notcontains 'Az.Accounts') - { - Write-Host "Installing module Az.Accounts..." -ForegroundColor Yellow - Install-Module -Name Az.Accounts -Scope CurrentUser -Repository 'PSGallery' - } - else + try { - Write-Host "Az.Accounts module is available." -ForegroundColor Green + Write-Host "Checking for pre-requisites..." + Write-Host "Required modules are: Az.Resources, Az.Security, Az.Accounts" -ForegroundColor Cyan + Write-Host "Checking for required modules..." + Install-Az-Module -Name 'Az.Accounts' + Install-Az-Module -Name 'Az.Resources' + Install-Az-Module -Name 'Az.Security' + Write-Host "------------------------------------------------------" + return $true } + catch + { + Write-Host "Error occurred while checking pre-requisites. ErrorMessage [$($_)]" -ForegroundColor Red + return $false + } +} - # Checking if 'Az.Resources' module is available or not. - if($availableModules.Name -notcontains 'Az.Resources') +function Connect-Subscription +{ + param ( + [string] + [Parameter(Mandatory = $true, HelpMessage="Subscription Id")] + $SubscriptionId, + + [string] + [Parameter(Mandatory = $true, HelpMessage="Environment")] + $Environment + ) + + $isContextSet = Get-AzContext + if ([string]::IsNullOrEmpty($isContextSet)) { - Write-Host "Installing module Az.Resources..." -ForegroundColor Yellow - Install-Module -Name Az.Resources -Scope CurrentUser -Repository 'PSGallery' + Write-Host "Connecting to AzAccount..." + Connect-AzAccount -Environment $Environment -ErrorAction Stop + Write-Host "Connected to AzAccount" -ForegroundColor Green } - else + + # Setting context for current subscription. + $currentSub = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop -Force + + Write-Host "Metadata Details: `n SubscriptionId: $($SubscriptionId) `n AccountName: $($currentSub.Account.Id) `n AccountType: $($currentSub.Account.Type)" + Write-Host "------------------------------------------------------" + Write-Host "Starting with Subscription [$($SubscriptionId)]..." + + return $currentSub +} + + +function Validate-Permissions +{ + param ( + [object] + [Parameter(Mandatory = $true, HelpMessage="Subscription object")] + $Subscription + ) + + # Safe Check: Checking whether the current account is of type User + if($Subscription.Account.Type -ne "User") { - Write-Host "Az.Resources module is available." -ForegroundColor Green + Write-Host "Warning: This script can only be run by user account type." -ForegroundColor Yellow + return; } - # Checking if 'Az.Security' module is available or not. - if($availableModules.Name -notcontains 'Az.Security') + # Safe Check: Current user needs to be either Contributor or Owner for the subscription + $currentLoginRoleAssignments = Get-AzRoleAssignment -SignInName $Subscription.Account.Id -Scope "/subscriptions/$($SubscriptionId)"; + + if(($currentLoginRoleAssignments | Where { $_.RoleDefinitionName -eq "Owner" -or $_.RoleDefinitionName -eq 'Contributor' -or $_.RoleDefinitionName -eq "Security Admin" } | Measure-Object).Count -le 0) { - Write-Host "Installing module Az.Security..." -ForegroundColor Yellow - Install-Module -Name Az.Security -Scope CurrentUser -Repository 'PSGallery' + Write-Host "Warning: This script can only be run by an Owner or Contributor of subscription [$($SubscriptionId)] " -ForegroundColor Yellow + return; } - else +} + +function Get-NonCompliantPlans +{ + param ( + [string] + [Parameter(Mandatory = $true, HelpMessage="Expected tier name")] + $ExpectedTierName + ) + + $reqMdcPlans = "VirtualMachines", "SqlServers", "AppServices", "StorageAccounts", "SqlServerVirtualMachines", "KeyVaults", "Arm", "CosmosDbs", "Containers", "CloudPosture", "Api"; #TODO: list it from API instead. + Write-Host "Checking [$($reqMDCTier)] pricing tier for [$($ExpectedTierName -join ", ")] plans..." + $nonCompliantPlans = @() + $nonCompliantPlans = Get-AzSecurityPricing | Where-Object { $_.PricingTier -ne $ExpectedTierName -and $reqMdcPlans.Contains($_.Name) } | select "Name", "PricingTier", "Id" + + return $nonCompliantPlans +} + +function Create-LogFolder +{ + $folderPath = [Environment]::GetFolderPath("MyDocuments") + if (Test-Path -Path $folderPath) { - Write-Host "Az.Security module is available." -ForegroundColor Green + $folderPath += "\AzTS\Remediation\Subscriptions\$($subscriptionid.replace("-","_"))\$((Get-Date).ToString('yyyyMMdd_hhmm'))\ConfigAzureDefender" + New-Item -ItemType Directory -Path $folderPath | Out-Null } + + return $folderPath +} + +function Log-MdcPlanCompliance +{ + param ( + [string] + [Parameter(Mandatory = $true, HelpMessage="Log folder path")] + $FolderPath, + + [string] + [Parameter(Mandatory = $true, HelpMessage="Subscription Id")] + $SubscriptionId, + + [object] + [Parameter(Mandatory = $true, HelpMessage="Non compliant plans")] + $NonCompliantPlans + ) + + # Creating data object for resource types without 'Standard' pricing tier to export into json, it will help while doing rollback operation. + $nonCompliantMDCResource = New-Object psobject -Property @{ + SubscriptionId = $SubscriptionId + IsProviderRegister = $true + } + $nonCompliantMDCResource | Add-Member -Name 'NonCompliantMDCType' -Type NoteProperty -Value $NonCompliantPlans + + Write-Host "Step 3: Taking backup of resource types without [$($reqMDCTier)] tier and [$($mdcResourceProviderName)] provider registration status. Please do not delete this file. Without this file you won't be able to rollback any changes done through Remediation script." -ForegroundColor Cyan + $nonCompliantMDCResource | ConvertTo-json | out-file "$($folderpath)\NonCompliantMDCType.json" + Write-Host "Path: $($folderpath)\NonCompliantMDCType.json" +} + +function Enable-MdcPlans +{ + param ( + [object] + [Parameter(Mandatory = $true, HelpMessage="Plans to enable")] + $PlansToEnable, + + [string] + [Parameter(Mandatory = $true, HelpMessage="Required Pricing tier")] + $PricingTier + ) + + try + { + Write-Host "Setting [$($PricingTier)] pricing tier..." + + # TODO: currnet implementation hide failed operations with 'SilentlyContinue'. Better to change it. + $PlansToEnable | ForEach-Object { + (Set-AzSecurityPricing -Name $_.Name -PricingTier $PricingTier -ErrorAction SilentlyContinue) | Select-Object -Property Id, Name, PricingTier + } + } + catch + { + Write-Host "Error occurred while setting $PricingTier pricing tier. ErrorMessage [$($_)]" -ForegroundColor Red + return + } + Write-Host "Successfully set [$($PricingTier)] pricing tier for non-compliant resource types." -ForegroundColor Green + +} + +function Get-PolicySubscriptionScope +{ + param ( + [string] + [Parameter(Mandatory = $true, HelpMessage="Subscription Id")] + $SubscriptionId + ) + + return "/subscriptions/$SubscriptionId" +} + +function Get-NonCompliantPolicies +{ + param ( + [string] + [Parameter(Mandatory = $true, HelpMessage="Subscription Id")] + $SubscriptionId + ) + + # build expected policies + $expectedPoliciesIds = New-Object System.Collections.ArrayList + + if ('Standard' -eq (Get-AzSecurityPricing -Name 'Containers').PricingTier) + { + $expectedPoliciesIds.Add('64def556-fbad-4622-930e-72d1d5589bf5') # defender agent for AKS + $expectedPoliciesIds.Add('708b60a6-d253-4fe0-9114-4be4c00f012c') # defender agent for Arc + $expectedPoliciesIds.Add('a8eff44f-8c92-45c3-a3fb-9880802d67a7') # policy add-on for AKS + $expectedPoliciesIds.Add('0adc5395-9169-4b9b-8687-af838d69410a') # policy add-on for Arc + } + + # query all policies + $nonCompliantPoliciesIds = New-Object System.Collections.ArrayList + $scope = Get-PolicySubscriptionScope -SubscriptionId $SubscriptionId + + $expectedPoliciesIds | ForEach-Object { + $fullId = "/providers/Microsoft.Authorization/policyDefinitions/$_" + $foundPolicy = (Get-AzPolicyAssignment -Scope $scope -PolicyDefinitionId $fullId) + if ($foundPolicy -eq $null) + { + Write-Host "assignment not found for $fullId" + $nonCompliantPoliciesIds.Add($_) + } + else + { + Write-Host "assignment found for $fullId" + } + } + + Write-Host "non compliant policies (if any): $nonCompliantPoliciesIds" + return $nonCompliantPoliciesIds +} + +function Assign-Policies +{ + param ( + [string] + [Parameter(Mandatory = $true, HelpMessage="Subscription Id")] + $SubscriptionId, + + [object] + [Parameter(Mandatory = $true, HelpMessage="Subscription Id")] + $Policies + ) + $scope = Get-PolicySubscriptionScope -SubscriptionId $SubscriptionId + + $location = (Get-AzLocation)[0].Location + + $Policies | ForEach-Object { + if (($_.Length) -eq 36) # Weird bug of working with arrays in PS, will be solved later + { + $definition = Get-AzPolicyDefinition -SubscriptionId $SubscriptionId -Name $_ + $assignment = New-AzPolicyAssignment -Name $_ -Scope $scope -PolicyDefinition $definition -IdentityType 'SystemAssigned' -Location $location + $remediation = Start-AzPolicyRemediation -Name "creation-$(New-Guid)" -PolicyAssignmentId ($assignment.Id) -ResourceDiscoveryMode ReEvaluateCompliance + Write-Host "Policy assigned and remediation task created for $_" + } + } } -function Set-ConfigAzureDefender +function Set-ConfigAzureDefender # didn't changed this one as I'm not sure how it is being called, but better to align with 'Defender for Cloud' naming { <# .SYNOPSIS @@ -104,166 +392,79 @@ function Set-ConfigAzureDefender $SubscriptionId, [switch] - $PerformPreReqCheck + $PerformPreReqCheck, + + [string] + [Parameter(Mandatory = $false, HelpMessage="Environment")] + $Environment = 'AzureCloud' ) Write-Host "======================================================" - Write-Host "Starting to configure Azure Defender for subscription [$($SubscriptionId)]..." + Write-Host "Starting to configure Microsoft Defender for Cloud for subscription [$($SubscriptionId)]..." Write-Host "------------------------------------------------------" if($PerformPreReqCheck) { - try - { - Write-Host "Checking for pre-requisites..." - Pre_requisites - Write-Host "------------------------------------------------------" - } - catch - { - Write-Host "Error occurred while checking pre-requisites. ErrorMessage [$($_)]" -ForegroundColor Red - break - } - } - - # Connect to AzAccount - $isContextSet = Get-AzContext - if ([string]::IsNullOrEmpty($isContextSet)) - { - Write-Host "Connecting to AzAccount..." - Connect-AzAccount -ErrorAction Stop - Write-Host "Connected to AzAccount" -ForegroundColor Green + if ($false -eq (Pre_requisites)) + { + return + } } # Setting context for current subscription. - $currentSub = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop -Force + $currentSub = Connect-Subscription -Environment $Environment -SubscriptionId $SubscriptionId - Write-Host "Metadata Details: `n SubscriptionId: $($SubscriptionId) `n AccountName: $($currentSub.Account.Id) `n AccountType: $($currentSub.Account.Type)" - Write-Host "------------------------------------------------------" - Write-Host "Starting with Subscription [$($SubscriptionId)]..." - - - Write-Host "Step 1 of 3: Validating whether the current user [$($currentSub.Account.Id)] has the required permissions to run the script for subscription [$($SubscriptionId)]..." - - # Safe Check: Checking whether the current account is of type User - if($currentSub.Account.Type -ne "User") - { - Write-Host "Warning: This script can only be run by user account type." -ForegroundColor Yellow - return; - } - - # Safe Check: Current user needs to be either Contributor or Owner for the subscription - $currentLoginRoleAssignments = Get-AzRoleAssignment -SignInName $currentSub.Account.Id -Scope "/subscriptions/$($SubscriptionId)"; - - if(($currentLoginRoleAssignments | Where { $_.RoleDefinitionName -eq "Owner" -or $_.RoleDefinitionName -eq 'Contributor' -or $_.RoleDefinitionName -eq "Security Admin" } | Measure-Object).Count -le 0) - { - Write-Host "Warning: This script can only be run by an Owner or Contributor of subscription [$($SubscriptionId)] " -ForegroundColor Yellow - return; - } + Write-Host "Step 1: Validating whether the current user [$($currentSub.Account.Id)] has the required permissions to run the script for subscription [$($SubscriptionId)]..." + + if ($false -eq (Validate-Permissions -Subscription $currentSub)) + { + return; + } # Declaring required resource types and pricing tier - $reqMDCTierResourceTypes = "VirtualMachines","SqlServers","AppServices","StorageAccounts","Containers","KeyVaults","SqlServerVirtualMachines","Dns","Arm"; - $reqMDCTier = "Standard"; - $reqProviderName = "Microsoft.Security" - $isProviderRegister = $true - - # Checking IsProviderRegister with 'Microsoft.Security' provider - $registeredProvider = Get-AzResourceProvider -ProviderNamespace $reqProviderName | Where-Object { $_.RegistrationState -eq "Registered" } + $mdcResourceProviderName = "Microsoft.Security" + $isProviderRegister = Register-ResourceProvider -Name $mdcResourceProviderName + $isProviderRegister = $isProviderRegister && Register-ResourceProvider -Name 'Microsoft.PolicyInsights' + + if ($false -eq $isProviderRegister) + { + return; + } - if($null -eq $registeredProvider) - { - # capture provider registration state - $isProviderRegister = $false - Write-Host "Found [$($reqProviderName)] provider is not registered." - Write-Host "$reqProviderName registering [It takes 2-3 min to get registered]..." - # Registering provider with required provider name, it will take 1-2 min for registration - try - { - Register-AzResourceProvider -ProviderNamespace $reqProviderName - while((((Get-AzResourceProvider -ProviderNamespace $reqProviderName).RegistrationState -ne "Registered") | Measure-Object).Count -gt 0) - { - # Checking threshold time limit to avoid getting into infinite loop - if($thresholdTimeLimit -ge 300) - { - Write-Host "Error occurred while registering [$($reqProviderName)] provider. It is taking more time than expected, Aborting process..." -ForegroundColor Red - throw [System.ArgumentException] ($_) - } - Start-Sleep -Seconds 30 - Write-Host "$reqProviderName registering..." -ForegroundColor Yellow - - # Incrementing threshold time limit by 30 sec in every iteration - $thresholdTimeLimit = $thresholdTimeLimit + 30 - } - } - catch - { - Write-Host "Error occurred while registering $reqProviderName provider. ErrorMessage [$($_)]" -ForegroundColor Red - return - } - Write-Host "$reqProviderName provider successfully registered." -ForegroundColor Green - } - - Write-Host "Step 2 of 3: Checking [$($reqMDCTier)] pricing tier for [$($reqMDCTierResourceTypes -join ", ")] resource types..." - $nonCompliantMDCTierResourcetype = @() - $nonCompliantMDCTierResourcetype = Get-AzSecurityPricing | Where-Object { $_.PricingTier -ne $reqMDCTier -and $reqMDCTierResourceTypes.Contains($_.Name) } | select "Name", "PricingTier", "Id" - - $nonCompliantMDCTypeCount = ($nonCompliantMDCTierResourcetype | Measure-Object).Count - - Write-Host "Found [$($nonCompliantMDCTypeCount)] resource types without [$($reqMDCTier)]" - Write-Host "[NonCompliantMDCType]: [$($nonCompliantMDCTierResourcetype.Name -join ", ")]" + $reqMDCTier = "Standard"; + $nonCompliantMDCTierResourcetype = Get-NonCompliantPlans -ExpectedTierName $reqMDCTier # If control is already in Passed state (i.e. 'Microsoft.Security' provider is already registered and no non-compliant resource types are found) then no need to execute below steps. - if($isProviderRegister -and ($nonCompliantMDCTypeCount -eq 0)) + $nonCompliantMDCTypeCount = ($nonCompliantMDCTierResourcetype | Measure-Object).Count + + if($nonCompliantMDCTypeCount -eq 0) { - Write-Host "[$($reqProviderName)] provider is already registered and there are no non-compliant resource types. In this case, remediation is not required." -ForegroundColor Green + Write-Host "[$($mdcResourceProviderName)] provider is already registered and there are no non-compliant resource types. In this case, remediation is not required." -ForegroundColor Green Write-Host "======================================================" return } - - # Creating data object for resource types without 'Standard' pricing tier to export into json, it will help while doing rollback operation. - $nonCompliantMDCResource = New-Object psobject -Property @{ - SubscriptionId = $SubscriptionId - IsProviderRegister = $isProviderRegister - } - $nonCompliantMDCResource | Add-Member -Name 'NonCompliantMDCType' -Type NoteProperty -Value $nonCompliantMDCTierResourcetype - - # Creating the log file - $folderPath = [Environment]::GetFolderPath("MyDocuments") - if (Test-Path -Path $folderPath) - { - $folderPath += "\AzTS\Remediation\Subscriptions\$($subscriptionid.replace("-","_"))\$((Get-Date).ToString('yyyyMMdd_hhmm'))\ConfigAzureDefender" - New-Item -ItemType Directory -Path $folderPath | Out-Null - } - - Write-Host "Step 3 of 3: Taking backup of resource types without [$($reqMDCTier)] tier and [$($reqProviderName)] provider registration status. Please do not delete this file. Without this file you won't be able to rollback any changes done through Remediation script." -ForegroundColor Cyan - $nonCompliantMDCResource | ConvertTo-json | out-file "$($folderpath)\NonCompliantMDCType.json" - Write-Host "Path: $($folderpath)\NonCompliantMDCType.json" - Write-Host "`n" - + + Write-Host "Found [$($nonCompliantMDCTypeCount)] resource types without [$($reqMDCTier)]" + Write-Host "[NonCompliantMDCType]: [$($nonCompliantMDCTierResourcetype.Name -join ", ")]" + + # Creating the log folder + $folderPath = Create-LogFolder + + # Create log file + Log-MdcPlanCompliance -FolderPath $folderPath -SubscriptionId $SubscriptionId -NonCompliantPlans $nonCompliantMDCTierResourcetype + # Performing remediation - if($nonCompliantMDCTypeCount -gt 0) - { - try - { - Write-Host "Setting [$($reqMDCTier)] pricing tier..." - $nonCompliantMDCTierResourcetype | ForEach-Object { - (Set-AzSecurityPricing -Name $_.Name -PricingTier $reqMDCTier -ErrorAction SilentlyContinue) | Select-Object -Property Id, Name, PricingTier - } - } - catch - { - Write-Host "Error occurred while setting $reqMDCTier pricing tier. ErrorMessage [$($_)]" -ForegroundColor Red - return - } - Write-Host "Successfully set [$($reqMDCTier)] pricing tier for non-compliant resource types." -ForegroundColor Green - Write-Host "======================================================" - } - else - { - Write-Host "Required resource types compliant with [$($reqMDCTier)] pricing tier." -ForegroundColor Green - Write-Host "======================================================" - return - } + Enable-MdcPlans -PlansToEnable $nonCompliantMDCTierResourcetype -PricingTier $reqMDCTier + + ## Handle MDC capabilities enabled by Azure policy + + # Get non-assigned policies + $policies = Get-NonCompliantPolicies -SubscriptionId $SubscriptionId + + # Assign the policies + Assign-Policies -SubscriptionId $SubscriptionId -Policies $policies + + Write-Host "======================================================" } @@ -281,10 +482,16 @@ function Remove-ConfigAzureDefender #> param ( + + [string] + [Parameter(Mandatory = $false, HelpMessage="Environment")] + $Environment = 'AzureCloud', + [string] [Parameter(Mandatory = $true, HelpMessage="Enter subscription id to perform rollback operation")] $SubscriptionId, + [string] [Parameter(Mandatory = $true, HelpMessage="Json file path which contain logs generated by remediation script to rollback remediation changes")] $Path, @@ -294,61 +501,28 @@ function Remove-ConfigAzureDefender ) Write-Host "======================================================" - Write-Host "Starting to rollback operation to configure Azure Defender for subscription [$($SubscriptionId)]..." + Write-Host "Starting to rollback operation to configure Microsoft Defender for Cloud for subscription [$($SubscriptionId)]..." Write-Host "------------------------------------------------------" if($PerformPreReqCheck) { - try - { - Write-Host "Checking for pre-requisites..." - Pre_requisites - Write-Host "------------------------------------------------------" - } - catch - { - Write-Host "Error occurred while checking pre-requisites. ErrorMessage [$($_)]" -ForegroundColor Red - break - } + if ($false -eq (Pre_requisites)) + { + return + } } # Connect to AzAccount - $isContextSet = Get-AzContext - if ([string]::IsNullOrEmpty($isContextSet)) - { - Write-Host "Connecting to AzAccount..." - Connect-AzAccount -ErrorAction Stop - Write-Host "Connected to AzAccount" -ForegroundColor Green - } + $currentSub = Connect-Subscription -Environment $Environment -SubscriptionId $SubscriptionId - # Setting context for current subscription. - $currentSub = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop -Force - - - - Write-Host "Metadata Details: `n SubscriptionId: $($SubscriptionId) `n AccountName: $($currentSub.Account.Id) `n AccountType: $($currentSub.Account.Type)" - Write-Host "------------------------------------------------------" - Write-Host "Starting with subscription [$($SubscriptionId)]..." - - - Write-Host "Step 1 of 3: Validating whether the current user [$($currentSub.Account.Id)] has the required permissions to run the script for subscription [$($SubscriptionId)]..." - - # Safe Check: Checking whether the current account is of type User - if($currentSub.Account.Type -ne "User") - { - Write-Host "Warning: This script can only be run by user account type." -ForegroundColor Yellow - break; - } - - # Safe Check: Current user need to be either Contributor or Owner for the subscription - $currentLoginRoleAssignments = Get-AzRoleAssignment -SignInName $currentSub.Account.Id -Scope "/subscriptions/$($SubscriptionId)"; + Write-Host "Step 1: Validating whether the current user [$($currentSub.Account.Id)] has the required permissions to run the script for subscription [$($SubscriptionId)]..." + + if ($false -eq (Validate-Permissions -Subscription $currentSub)) + { + return; + } - if(($currentLoginRoleAssignments | Where { $_.RoleDefinitionName -eq "Owner" -or $_.RoleDefinitionName -eq 'Contributor' } | Measure-Object).Count -le 0) - { - Write-Host "Warning: This script can only be run by an Owner or Contributor of subscription [$($SubscriptionId)] " -ForegroundColor Yellow - break; - } - Write-Host "Step 2 of 3: Fetching remediation log to perform rollback operation to configure Azure Defender for subscription [$($SubscriptionId)]..." + Write-Host "Step 2 of 3: Fetching remediation log to perform rollback operation to configure Microsoft Defender for Cloud for subscription [$($SubscriptionId)]..." # Array to store resource context if (-not (Test-Path -Path $Path)) @@ -359,17 +533,17 @@ function Remove-ConfigAzureDefender # Declaring required resource types and pricing tier $reqMDCTier = "Standard"; - $reqProviderName = "Microsoft.Security" + $mdcResourceProviderName = "Microsoft.Security" $remediatedLog = Get-Content -Raw -Path $Path | ConvertFrom-Json - Write-Host "Step 3 of 3: Performing rollback operation to configure Azure Defender for subscription [$($SubscriptionId)]..." + Write-Host "Step 3 of 3: Performing rollback operation to configure Microsoft Defender for Cloud for subscription [$($SubscriptionId)]..." # Performing rollback operation try { if(($remediatedLog | Measure-Object).Count -gt 0) { - Write-Host "Configuring Azure Defender as per remediation log on subscription [$($SubscriptionId)]..." + Write-Host "Configuring Microsoft Defender for Cloud as per remediation log on subscription [$($SubscriptionId)]..." if($null -ne $remediatedLog.NonCompliantMDCType -and ($remediatedLog.NonCompliantMDCType | Measure-Object).Count -gt 0) { @@ -381,7 +555,7 @@ function Remove-ConfigAzureDefender } catch { - Write-Host "Error occurred while performing rollback operation to configure Azure Defender. ErrorMessage [$($_)]" -ForegroundColor Red + Write-Host "Error occurred while performing rollback operation to configure Microsoft Defender for Cloud. ErrorMessage [$($_)]" -ForegroundColor Red break } } @@ -392,10 +566,10 @@ function Remove-ConfigAzureDefender Write-Host "`n" # Checking current registration state of provider i.e. 'Microsoft.Security' on subscription. - $isProviderRegister = (((Get-AzResourceProvider -ProviderNamespace $reqProviderName).RegistrationState -eq "Registered") | Measure-Object).Count -gt 0 + $isProviderRegister = (((Get-AzResourceProvider -ProviderNamespace $mdcResourceProviderName).RegistrationState -eq "Registered") | Measure-Object).Count -gt 0 if([System.Convert]::ToBoolean($remediatedLog.IsProviderRegister) -eq $isProviderRegister) { - Write-Host "[$($reqProviderName)] provider registration state is same as before executing remediation script." -ForegroundColor Green + Write-Host "[$($mdcResourceProviderName)] provider registration state is same as before executing remediation script." -ForegroundColor Green Write-Host "Rollback operation successfully performed." -ForegroundColor Green Write-Host "======================================================" break; @@ -404,21 +578,21 @@ function Remove-ConfigAzureDefender { # when current provider registration state and before executing remediation script is not same. # That means while doing remediation it got registered, to perform rollback we need to unregister it - Write-Host "$reqProviderName provider name was registered before executing remediation script, performing rollback." - Write-Host "$reqProviderName unregistering...[It takes 2-3 min to get unregistered]..." + Write-Host "$mdcResourceProviderName provider name was registered before executing remediation script, performing rollback." + Write-Host "$mdcResourceProviderName unregistering...[It takes 2-3 min to get unregistered]..." try { - Unregister-AzResourceProvider -ProviderNamespace $reqProviderName - while((((Get-AzResourceProvider -ProviderNamespace $reqProviderName).RegistrationState -ne "Unregistered") | Measure-Object).Count -gt 0) + Unregister-AzResourceProvider -ProviderNamespace $mdcResourceProviderName + while((((Get-AzResourceProvider -ProviderNamespace $mdcResourceProviderName).RegistrationState -ne "Unregistered") | Measure-Object).Count -gt 0) { # Checking threshold time limit to avoid getting into infinite loop if($thresholdTimeLimit -ge 300) { - Write-Host "Error occurred while unregistering [$($reqProviderName)] provider. It is taking more time than expected, Aborting process..." -ForegroundColor Red + Write-Host "Error occurred while unregistering [$($mdcResourceProviderName)] provider. It is taking more time than expected, Aborting process..." -ForegroundColor Red throw [System.ArgumentException] ($_) } Start-Sleep -Seconds 30 - Write-Host "$reqProviderName unregistering..." -ForegroundColor Yellow + Write-Host "$mdcResourceProviderName unregistering..." -ForegroundColor Yellow # Incrementing threshold time limit by 30 sec in every iteration $thresholdTimeLimit = $thresholdTimeLimit + 30 @@ -426,24 +600,24 @@ function Remove-ConfigAzureDefender } catch { - Write-Host "Error occurred while unregistering $reqProviderName provider. ErrorMessage [$($_)]" -ForegroundColor Red + Write-Host "Error occurred while unregistering $mdcResourceProviderName provider. ErrorMessage [$($_)]" -ForegroundColor Red break; } - Write-Host "$reqProviderName provider successfully unregistered." -ForegroundColor Green + Write-Host "$mdcResourceProviderName provider successfully unregistered." -ForegroundColor Green Write-Host "Rollback operation successfully performed." -ForegroundColor Green Write-Host "======================================================" } } else { - Write-Host "Azure Defender details not found to perform rollback operation." + Write-Host "Microsoft Defender for Cloud details not found to perform rollback operation." Write-Host "======================================================" break } } catch { - Write-Host "Error occurred while performing rollback operation to configure Azure Defender. ErrorMessage [$($_)]" -ForegroundColor Red + Write-Host "Error occurred while performing rollback operation to configure Microsoft Defender for Cloud. ErrorMessage [$($_)]" -ForegroundColor Red break } } @@ -451,8 +625,8 @@ function Remove-ConfigAzureDefender <# # ***************************************************** # # Function calling with parameters for remediation. -Set-ConfigAzureDefender -SubscriptionId '' -PerformPreReqCheck: $true +Set-ConfigAzureDefender -Environment 'AzureCloud' -SubscriptionId '' -PerformPreReqCheck: $true # Function calling with parameters to rollback remediation changes. -Remove-ConfigAzureDefender -SubscriptionId '' -Path '' -PerformPreReqCheck: $true +Remove-ConfigAzureDefender -Environment 'AzureCloud' -SubscriptionId '' -Path '' -PerformPreReqCheck: $true #> From 4f317044fc4c8769dd1831f0a51e981ae11cbf64 Mon Sep 17 00:00:00 2001 From: Or Parnes Date: Thu, 18 Jul 2024 16:34:58 -0700 Subject: [PATCH 2/3] Update Remediate-ConfigAzureDefender.ps1 mostly tabs to spaces --- .../Remediate-ConfigAzureDefender.ps1 | 412 +++++++++--------- 1 file changed, 202 insertions(+), 210 deletions(-) diff --git a/Scripts/RemediationScripts/Remediate-ConfigAzureDefender.ps1 b/Scripts/RemediationScripts/Remediate-ConfigAzureDefender.ps1 index 86e3ff8f..db19826e 100644 --- a/Scripts/RemediationScripts/Remediate-ConfigAzureDefender.ps1 +++ b/Scripts/RemediationScripts/Remediate-ConfigAzureDefender.ps1 @@ -11,15 +11,10 @@ # Steps performed by the script: 1. Install and validate pre-requisites to run the script for subscription. - 2. Get the list of disabled Microsoft Defender for Cloud plans from subscription. - 3. Take a backup of these non-compliant plans. - 4. Register 'Microsoft.Security' provider and enable all disabled Microsoft Defender for Cloud plans for subscription. - - 5. Verify Azure Policy assignments for MDC features that requires DINE policy in place. - + 5. Verify Azure Policy assignments for MDC features that requires DINE policy in place. # Step to execute script: Download and load remediation script in PowerShell session and execute below command. @@ -39,17 +34,16 @@ To know more about parameter execute: a. Get-Help Set-ConfigAzureDefender -Detailed b. Get-Help Remove-ConfigAzureDefender -Detailed - + # Known issues - - 1. This script does not support plan extension-level, meaning: - a. It is possible that some plan was already enabled but with some specific extension being disabled - no change will be made in this case. - b. There is no extension-level log and the revert capability is limited. - 2. There is no SubPlan feature support for plans that supports it in the API level - 3. Plans 'Servers', 'Databases' are partially supported - it seems like enabling the plan does not enables all extensions within it. - 4. Plan 'API' throws exception when trying to enable, because of (2) above. - 5. Revert does not support Azure Policy definition assigned during the enablement script. - 6. Enable-MdcPlans: need to better manage errors on speicific plan enablement, so issues like (4) will be easier to be found. + 1. This script does not support plan extension-level, meaning: + a. It is possible that some plan was already enabled but with some specific extension being disabled - no change will be made in this case. + b. There is no extension-level log and the revert capability is limited. + 2. There is no SubPlan feature support for plans that supports it in the API level + 3. Plans 'Servers', 'Databases' are partially supported - it seems like enabling the plan does not enables all extensions within it. + 4. Plan 'API' throws exception when trying to enable, because of (2) above. + 5. Revert does not support Azure Policy definition assigned during the enablement script. + 6. Enable-MdcPlans: need to better manage errors on speicific plan enablement, so issues like (4) will be easier to be found. ######################################## #> @@ -61,13 +55,13 @@ function Install-Az-Module [Parameter(Mandatory = $true, HelpMessage="Module name")] $Name ) - - if ($null -eq (Get-Module -Name $Name)) - { - Write-Host "Installing module $Name..." -ForegroundColor Yellow + + if ($null -eq (Get-Module -Name $Name)) + { + Write-Host "Installing module $Name..." -ForegroundColor Yellow Install-Module -Name $Name -Scope CurrentUser -Repository 'PSGallery' - } - else + } + else { Write-Host "$Name module is available." -ForegroundColor Green } @@ -75,15 +69,15 @@ function Install-Az-Module function Register-ResourceProvider { - param ( + param ( [string] [Parameter(Mandatory = $true, HelpMessage="Resource Provider name")] $Name ) - - # Checking IsProviderRegister with relevant provider + + # Checking IsProviderRegister with relevant provider $registeredProvider = Get-AzResourceProvider -ProviderNamespace $Name | Where-Object { $_.RegistrationState -eq "Registered" } - $isProviderRegister = $true + $isProviderRegister = $true if($null -eq $registeredProvider) { @@ -109,8 +103,8 @@ function Register-ResourceProvider # Incrementing threshold time limit by 30 sec in every iteration $thresholdTimeLimit = $thresholdTimeLimit + 30 } - - $isProviderRegister = $true + + $isProviderRegister = $true } catch { @@ -118,12 +112,12 @@ function Register-ResourceProvider } Write-Host "$Name provider successfully registered." -ForegroundColor Green } - else - { - Write-Host "Found [$($Name)] provider is already registered." - } - - return $isProviderRegister + else + { + Write-Host "Found [$($Name)] provider is already registered." + } + + return $isProviderRegister } function Pre_requisites @@ -140,31 +134,31 @@ function Pre_requisites Write-Host "Checking for pre-requisites..." Write-Host "Required modules are: Az.Resources, Az.Security, Az.Accounts" -ForegroundColor Cyan Write-Host "Checking for required modules..." - Install-Az-Module -Name 'Az.Accounts' - Install-Az-Module -Name 'Az.Resources' - Install-Az-Module -Name 'Az.Security' - Write-Host "------------------------------------------------------" - return $true + Install-Az-Module -Name 'Az.Accounts' + Install-Az-Module -Name 'Az.Resources' + Install-Az-Module -Name 'Az.Security' + Write-Host "------------------------------------------------------" + return $true + } + catch + { + Write-Host "Error occurred while checking pre-requisites. ErrorMessage [$($_)]" -ForegroundColor Red + return $false } - catch - { - Write-Host "Error occurred while checking pre-requisites. ErrorMessage [$($_)]" -ForegroundColor Red - return $false - } } function Connect-Subscription { - param ( - [string] + param ( + [string] [Parameter(Mandatory = $true, HelpMessage="Subscription Id")] $SubscriptionId, - - [string] + + [string] [Parameter(Mandatory = $true, HelpMessage="Environment")] $Environment ) - + $isContextSet = Get-AzContext if ([string]::IsNullOrEmpty($isContextSet)) { @@ -173,26 +167,25 @@ function Connect-Subscription Write-Host "Connected to AzAccount" -ForegroundColor Green } - # Setting context for current subscription. - $currentSub = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop -Force + # Setting context for current subscription. + $currentSub = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop -Force - Write-Host "Metadata Details: `n SubscriptionId: $($SubscriptionId) `n AccountName: $($currentSub.Account.Id) `n AccountType: $($currentSub.Account.Type)" + Write-Host "Metadata Details: `n SubscriptionId: $($SubscriptionId) `n AccountName: $($currentSub.Account.Id) `n AccountType: $($currentSub.Account.Type)" Write-Host "------------------------------------------------------" Write-Host "Starting with Subscription [$($SubscriptionId)]..." return $currentSub } - function Validate-Permissions { - param ( - [object] - [Parameter(Mandatory = $true, HelpMessage="Subscription object")] - $Subscription - ) - - # Safe Check: Checking whether the current account is of type User + param ( + [object] + [Parameter(Mandatory = $true, HelpMessage="Subscription object")] + $Subscription + ) + + # Safe Check: Checking whether the current account is of type User if($Subscription.Account.Type -ne "User") { Write-Host "Warning: This script can only be run by user account type." -ForegroundColor Yellow @@ -211,48 +204,48 @@ function Validate-Permissions function Get-NonCompliantPlans { - param ( + param ( [string] [Parameter(Mandatory = $true, HelpMessage="Expected tier name")] $ExpectedTierName ) - - $reqMdcPlans = "VirtualMachines", "SqlServers", "AppServices", "StorageAccounts", "SqlServerVirtualMachines", "KeyVaults", "Arm", "CosmosDbs", "Containers", "CloudPosture", "Api"; #TODO: list it from API instead. - Write-Host "Checking [$($reqMDCTier)] pricing tier for [$($ExpectedTierName -join ", ")] plans..." - $nonCompliantPlans = @() + + $reqMdcPlans = "VirtualMachines", "SqlServers", "AppServices", "StorageAccounts", "SqlServerVirtualMachines", "KeyVaults", "Arm", "CosmosDbs", "Containers", "CloudPosture", "Api"; #TODO: list it from API instead. + Write-Host "Checking [$($reqMDCTier)] pricing tier for [$($ExpectedTierName -join ", ")] plans..." + $nonCompliantPlans = @() $nonCompliantPlans = Get-AzSecurityPricing | Where-Object { $_.PricingTier -ne $ExpectedTierName -and $reqMdcPlans.Contains($_.Name) } | select "Name", "PricingTier", "Id" - - return $nonCompliantPlans + + return $nonCompliantPlans } function Create-LogFolder { - $folderPath = [Environment]::GetFolderPath("MyDocuments") + $folderPath = [Environment]::GetFolderPath("MyDocuments") if (Test-Path -Path $folderPath) { $folderPath += "\AzTS\Remediation\Subscriptions\$($subscriptionid.replace("-","_"))\$((Get-Date).ToString('yyyyMMdd_hhmm'))\ConfigAzureDefender" New-Item -ItemType Directory -Path $folderPath | Out-Null } - - return $folderPath + + return $folderPath } function Log-MdcPlanCompliance { - param ( + param ( [string] [Parameter(Mandatory = $true, HelpMessage="Log folder path")] $FolderPath, - - [string] + + [string] [Parameter(Mandatory = $true, HelpMessage="Subscription Id")] $SubscriptionId, - - [object] + + [object] [Parameter(Mandatory = $true, HelpMessage="Non compliant plans")] - $NonCompliantPlans + $NonCompliantPlans ) - + # Creating data object for resource types without 'Standard' pricing tier to export into json, it will help while doing rollback operation. $nonCompliantMDCResource = New-Object psobject -Property @{ SubscriptionId = $SubscriptionId @@ -267,110 +260,110 @@ function Log-MdcPlanCompliance function Enable-MdcPlans { - param ( + param ( [object] [Parameter(Mandatory = $true, HelpMessage="Plans to enable")] - $PlansToEnable, - - [string] + $PlansToEnable, + + [string] [Parameter(Mandatory = $true, HelpMessage="Required Pricing tier")] $PricingTier ) - - try - { - Write-Host "Setting [$($PricingTier)] pricing tier..." - - # TODO: currnet implementation hide failed operations with 'SilentlyContinue'. Better to change it. - $PlansToEnable | ForEach-Object { - (Set-AzSecurityPricing -Name $_.Name -PricingTier $PricingTier -ErrorAction SilentlyContinue) | Select-Object -Property Id, Name, PricingTier - } - } - catch - { - Write-Host "Error occurred while setting $PricingTier pricing tier. ErrorMessage [$($_)]" -ForegroundColor Red - return - } - Write-Host "Successfully set [$($PricingTier)] pricing tier for non-compliant resource types." -ForegroundColor Green - + + try + { + Write-Host "Setting [$($PricingTier)] pricing tier..." + + # TODO: current implementation hide failed operations with 'SilentlyContinue'. Better to change it. + $PlansToEnable | ForEach-Object { + (Set-AzSecurityPricing -Name $_.Name -PricingTier $PricingTier -ErrorAction SilentlyContinue) | Select-Object -Property Id, Name, PricingTier + } + } + catch + { + Write-Host "Error occurred while setting $PricingTier pricing tier. ErrorMessage [$($_)]" -ForegroundColor Red + return + } + Write-Host "Successfully set [$($PricingTier)] pricing tier for non-compliant resource types." -ForegroundColor Green + } function Get-PolicySubscriptionScope { - param ( - [string] - [Parameter(Mandatory = $true, HelpMessage="Subscription Id")] - $SubscriptionId + param ( + [string] + [Parameter(Mandatory = $true, HelpMessage="Subscription Id")] + $SubscriptionId ) - return "/subscriptions/$SubscriptionId" + return "/subscriptions/$SubscriptionId" } function Get-NonCompliantPolicies { - param ( - [string] - [Parameter(Mandatory = $true, HelpMessage="Subscription Id")] - $SubscriptionId + param ( + [string] + [Parameter(Mandatory = $true, HelpMessage="Subscription Id")] + $SubscriptionId ) - - # build expected policies - $expectedPoliciesIds = New-Object System.Collections.ArrayList - - if ('Standard' -eq (Get-AzSecurityPricing -Name 'Containers').PricingTier) - { - $expectedPoliciesIds.Add('64def556-fbad-4622-930e-72d1d5589bf5') # defender agent for AKS - $expectedPoliciesIds.Add('708b60a6-d253-4fe0-9114-4be4c00f012c') # defender agent for Arc - $expectedPoliciesIds.Add('a8eff44f-8c92-45c3-a3fb-9880802d67a7') # policy add-on for AKS - $expectedPoliciesIds.Add('0adc5395-9169-4b9b-8687-af838d69410a') # policy add-on for Arc - } - - # query all policies - $nonCompliantPoliciesIds = New-Object System.Collections.ArrayList - $scope = Get-PolicySubscriptionScope -SubscriptionId $SubscriptionId - - $expectedPoliciesIds | ForEach-Object { - $fullId = "/providers/Microsoft.Authorization/policyDefinitions/$_" - $foundPolicy = (Get-AzPolicyAssignment -Scope $scope -PolicyDefinitionId $fullId) - if ($foundPolicy -eq $null) - { - Write-Host "assignment not found for $fullId" - $nonCompliantPoliciesIds.Add($_) - } - else - { - Write-Host "assignment found for $fullId" - } - } - - Write-Host "non compliant policies (if any): $nonCompliantPoliciesIds" - return $nonCompliantPoliciesIds + + # build expected policies list + $expectedPoliciesIds = New-Object System.Collections.ArrayList + + if ('Standard' -eq (Get-AzSecurityPricing -Name 'Containers').PricingTier) + { + $expectedPoliciesIds.Add('64def556-fbad-4622-930e-72d1d5589bf5') # defender sensor for AKS + $expectedPoliciesIds.Add('708b60a6-d253-4fe0-9114-4be4c00f012c') # defender sensor for Arc + $expectedPoliciesIds.Add('a8eff44f-8c92-45c3-a3fb-9880802d67a7') # policy add-on for AKS + $expectedPoliciesIds.Add('0adc5395-9169-4b9b-8687-af838d69410a') # policy add-on for Arc + } + + # query all policies + $nonCompliantPoliciesIds = New-Object System.Collections.ArrayList + $scope = Get-PolicySubscriptionScope -SubscriptionId $SubscriptionId + + $expectedPoliciesIds | ForEach-Object { + $fullId = "/providers/Microsoft.Authorization/policyDefinitions/$_" + $foundPolicy = (Get-AzPolicyAssignment -Scope $scope -PolicyDefinitionId $fullId) + if ($foundPolicy -eq $null) + { + Write-Host "assignment not found for $fullId" + $nonCompliantPoliciesIds.Add($_) + } + else + { + Write-Host "assignment found for $fullId" + } + } + + Write-Host "non compliant policies (if any): $nonCompliantPoliciesIds" + return $nonCompliantPoliciesIds } function Assign-Policies { - param ( - [string] - [Parameter(Mandatory = $true, HelpMessage="Subscription Id")] - $SubscriptionId, - - [object] - [Parameter(Mandatory = $true, HelpMessage="Subscription Id")] - $Policies + param ( + [string] + [Parameter(Mandatory = $true, HelpMessage="Subscription Id")] + $SubscriptionId, + + [object] + [Parameter(Mandatory = $true, HelpMessage="Subscription Id")] + $Policies ) - $scope = Get-PolicySubscriptionScope -SubscriptionId $SubscriptionId - - $location = (Get-AzLocation)[0].Location - - $Policies | ForEach-Object { - if (($_.Length) -eq 36) # Weird bug of working with arrays in PS, will be solved later - { - $definition = Get-AzPolicyDefinition -SubscriptionId $SubscriptionId -Name $_ - $assignment = New-AzPolicyAssignment -Name $_ -Scope $scope -PolicyDefinition $definition -IdentityType 'SystemAssigned' -Location $location - $remediation = Start-AzPolicyRemediation -Name "creation-$(New-Guid)" -PolicyAssignmentId ($assignment.Id) -ResourceDiscoveryMode ReEvaluateCompliance - Write-Host "Policy assigned and remediation task created for $_" - } - } + $scope = Get-PolicySubscriptionScope -SubscriptionId $SubscriptionId + + $location = (Get-AzLocation)[0].Location + + $Policies | ForEach-Object { + if (($_.Length) -eq 36) # Weird bug of working with arrays in PS, will be solved later + { + $definition = Get-AzPolicyDefinition -SubscriptionId $SubscriptionId -Name $_ + $assignment = New-AzPolicyAssignment -Name $_ -Scope $scope -PolicyDefinition $definition -IdentityType 'SystemAssigned' -Location $location + $remediation = Start-AzPolicyRemediation -Name "creation-$(New-Guid)" -PolicyAssignmentId ($assignment.Id) -ResourceDiscoveryMode ReEvaluateCompliance + Write-Host "Policy assigned and remediation task created for $_" + } + } } function Set-ConfigAzureDefender # didn't changed this one as I'm not sure how it is being called, but better to align with 'Defender for Cloud' naming @@ -393,8 +386,8 @@ function Set-ConfigAzureDefender # didn't changed this one as I'm not sure how i [switch] $PerformPreReqCheck, - - [string] + + [string] [Parameter(Mandatory = $false, HelpMessage="Environment")] $Environment = 'AzureCloud' ) @@ -405,66 +398,66 @@ function Set-ConfigAzureDefender # didn't changed this one as I'm not sure how i if($PerformPreReqCheck) { - if ($false -eq (Pre_requisites)) - { - return - } + if ($false -eq (Pre_requisites)) + { + return + } } # Setting context for current subscription. $currentSub = Connect-Subscription -Environment $Environment -SubscriptionId $SubscriptionId Write-Host "Step 1: Validating whether the current user [$($currentSub.Account.Id)] has the required permissions to run the script for subscription [$($SubscriptionId)]..." - - if ($false -eq (Validate-Permissions -Subscription $currentSub)) - { - return; - } + + if ($false -eq (Validate-Permissions -Subscription $currentSub)) + { + return; + } # Declaring required resource types and pricing tier $mdcResourceProviderName = "Microsoft.Security" $isProviderRegister = Register-ResourceProvider -Name $mdcResourceProviderName - $isProviderRegister = $isProviderRegister && Register-ResourceProvider -Name 'Microsoft.PolicyInsights' - - if ($false -eq $isProviderRegister) - { - return; - } + $isProviderRegister = $isProviderRegister && Register-ResourceProvider -Name 'Microsoft.PolicyInsights' + + if ($false -eq $isProviderRegister) + { + return; + } $reqMDCTier = "Standard"; $nonCompliantMDCTierResourcetype = Get-NonCompliantPlans -ExpectedTierName $reqMDCTier # If control is already in Passed state (i.e. 'Microsoft.Security' provider is already registered and no non-compliant resource types are found) then no need to execute below steps. $nonCompliantMDCTypeCount = ($nonCompliantMDCTierResourcetype | Measure-Object).Count - - if($nonCompliantMDCTypeCount -eq 0) + + if($nonCompliantMDCTypeCount -eq 0) { Write-Host "[$($mdcResourceProviderName)] provider is already registered and there are no non-compliant resource types. In this case, remediation is not required." -ForegroundColor Green Write-Host "======================================================" return } - - Write-Host "Found [$($nonCompliantMDCTypeCount)] resource types without [$($reqMDCTier)]" - Write-Host "[NonCompliantMDCType]: [$($nonCompliantMDCTierResourcetype.Name -join ", ")]" - - # Creating the log folder + + Write-Host "Found [$($nonCompliantMDCTypeCount)] resource types without [$($reqMDCTier)]" + Write-Host "[NonCompliantMDCType]: [$($nonCompliantMDCTierResourcetype.Name -join ", ")]" + + # Creating the log folder $folderPath = Create-LogFolder - - # Create log file - Log-MdcPlanCompliance -FolderPath $folderPath -SubscriptionId $SubscriptionId -NonCompliantPlans $nonCompliantMDCTierResourcetype - + + # Create log file + Log-MdcPlanCompliance -FolderPath $folderPath -SubscriptionId $SubscriptionId -NonCompliantPlans $nonCompliantMDCTierResourcetype + # Performing remediation - Enable-MdcPlans -PlansToEnable $nonCompliantMDCTierResourcetype -PricingTier $reqMDCTier - - ## Handle MDC capabilities enabled by Azure policy - - # Get non-assigned policies - $policies = Get-NonCompliantPolicies -SubscriptionId $SubscriptionId - - # Assign the policies - Assign-Policies -SubscriptionId $SubscriptionId -Policies $policies - - Write-Host "======================================================" + Enable-MdcPlans -PlansToEnable $nonCompliantMDCTierResourcetype -PricingTier $reqMDCTier + + ## Handle MDC capabilities enabled by Azure policy + + # Get non-assigned policies + $policies = Get-NonCompliantPolicies -SubscriptionId $SubscriptionId + + # Assign the policies + Assign-Policies -SubscriptionId $SubscriptionId -Policies $policies + + Write-Host "======================================================" } @@ -482,11 +475,10 @@ function Remove-ConfigAzureDefender #> param ( - [string] [Parameter(Mandatory = $false, HelpMessage="Environment")] $Environment = 'AzureCloud', - + [string] [Parameter(Mandatory = $true, HelpMessage="Enter subscription id to perform rollback operation")] $SubscriptionId, @@ -506,21 +498,21 @@ function Remove-ConfigAzureDefender if($PerformPreReqCheck) { - if ($false -eq (Pre_requisites)) - { - return - } + if ($false -eq (Pre_requisites)) + { + return + } } # Connect to AzAccount $currentSub = Connect-Subscription -Environment $Environment -SubscriptionId $SubscriptionId Write-Host "Step 1: Validating whether the current user [$($currentSub.Account.Id)] has the required permissions to run the script for subscription [$($SubscriptionId)]..." - - if ($false -eq (Validate-Permissions -Subscription $currentSub)) - { - return; - } + + if ($false -eq (Validate-Permissions -Subscription $currentSub)) + { + return; + } Write-Host "Step 2 of 3: Fetching remediation log to perform rollback operation to configure Microsoft Defender for Cloud for subscription [$($SubscriptionId)]..." From c61783c6757074dca315802b0c31ebd549ddb2ef Mon Sep 17 00:00:00 2001 From: Or Parnes Date: Sun, 21 Jul 2024 08:43:19 -0700 Subject: [PATCH 3/3] Update Remediate-ConfigAzureDefender.ps1 policy role assignment --- .../Remediate-ConfigAzureDefender.ps1 | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Scripts/RemediationScripts/Remediate-ConfigAzureDefender.ps1 b/Scripts/RemediationScripts/Remediate-ConfigAzureDefender.ps1 index db19826e..ec826162 100644 --- a/Scripts/RemediationScripts/Remediate-ConfigAzureDefender.ps1 +++ b/Scripts/RemediationScripts/Remediate-ConfigAzureDefender.ps1 @@ -360,6 +360,20 @@ function Assign-Policies { $definition = Get-AzPolicyDefinition -SubscriptionId $SubscriptionId -Name $_ $assignment = New-AzPolicyAssignment -Name $_ -Scope $scope -PolicyDefinition $definition -IdentityType 'SystemAssigned' -Location $location + + $roleDefinitionIds = $definition.policyRule.then.details.roleDefinitionIds + + if ($roleDefinitionIds.Count -gt 0) + { + Write-Host "Setting role assignments for $_ and $roleDefinitionIds" + + $roleDefinitionIds | ForEach-Object { + $roleDefId = $_.Split("/") | Select-Object -Last 1 + $principalId = $assignment.IdentityPrincipalId + New-AzRoleAssignment -Scope $scope -ObjectId $principalId -RoleDefinitionId $roleDefId -principalType 'ServicePrincipal' + } + } + $remediation = Start-AzPolicyRemediation -Name "creation-$(New-Guid)" -PolicyAssignmentId ($assignment.Id) -ResourceDiscoveryMode ReEvaluateCompliance Write-Host "Policy assigned and remediation task created for $_" }