From 5e8927ea8935a26fa022897804ed401dec91a67b Mon Sep 17 00:00:00 2001 From: Abhishek P Date: Wed, 30 Nov 2022 15:39:06 +0530 Subject: [PATCH 01/26] Files --- ...iate-ConfigureWAFPolicyForFrontDoorCDN.ps1 | 1490 +++++++++++++++++ ...mediate-EnableWAFPolicyForFrontDoorCDN.ps1 | 1342 +++++++++++++++ ...WAFPolicyPreventionModeForFrontDoorCDN.ps1 | 1083 ++++++++++++ 3 files changed, 3915 insertions(+) create mode 100644 Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 create mode 100644 Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 create mode 100644 Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 diff --git a/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 new file mode 100644 index 00000000..d28be8e2 --- /dev/null +++ b/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 @@ -0,0 +1,1490 @@ +<### +# Overview: + This script is used to configure WAF Policy on All endpoints of Front Door CDNs in a Subscription. + +# Control ID: + Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial + +# Display Name: + Front Door should have Web Application Firewall configured + +# Prerequisites: + 1. Contributor or higher privileges on the Front Door CDNs in a Subscription. + 2. Must be connected to Azure with an authenticated account. + +# Steps performed by the script: + To remediate: + 1. Validate and install the modules required to run the script. + 2. Get the list of all Front Door CDNs Endpoints in a Subscription that do not have WAF Policy Configured + 3. Back up details of Front Door CDN Endpoint(s) that are to be remediated. + 4. Configure WAF Policy for all endpoints in the Frontdoors. + + To roll back: + 1. Validate and install the modules required to run the script. + 2. Get the list of Frontdoors' Endpoint(s) in a Subscription, the changes made to which previously, are to be rolled back. + 3. Revert by removing WAF Policies from endpoints in all the Frontdoors. + +# Instructions to execute the script: + To remediate: + 1. Download the script. + 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. + 3. Execute the script to configure WAF Policy on all endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. + + To roll back: + 1. Download the script. + 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. + 3. Execute the script to remove the configured WAF Policy on all endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. + +# Examples: + To remediate: + 1. To review the Front Door CDNs in a Subscription that will be remediated: + Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun + + 2. To configure WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription: + Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck + + 3. To configure WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription, from a previously taken snapshot: + Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\ConfigureFrontDoorCDNWAFPolicy\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv + + To know more about the options supported by the remediation command, execute: + Get-Help Configure-WAFPolicyForFrontDoorCDN -Detailed + + To roll back: + 1. To remove configured WAF Policy Mode for Endpoint(s) all Front Door CDNs in a Subscription, from a previously taken snapshot: + Remove-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\ConfigureFrontDoorCDNWAFPolicy\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv + + To know more about the options supported by the roll back command, execute: + Get-Help Remove-WAFPolicyForFrontDoorCDN -Detailed +###> + +function Setup-Prerequisites +{ + <# + .SYNOPSIS + Checks if the prerequisites are met, else, sets them up. + + .DESCRIPTION + Checks if the prerequisites are met, else, sets them up. + Includes installing any required Azure modules. + + .INPUTS + None. You cannot pipe objects to Setup-Prerequisites. + + .OUTPUTS + None. Setup-Prerequisites does not return anything that can be piped and used as an input to another command. + + .EXAMPLE + PS> Setup-Prerequisites + + .LINK + None + #> + + # List of required modules + $requiredModules = @("Az.Accounts", "Az.FrontDoor", "Az.Cdn") + + Write-Host "Required modules: $($requiredModules -join ', ')" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Checking if the required modules are present..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + + $availableModules = $(Get-Module -ListAvailable $requiredModules -ErrorAction Stop) + + # Check if the required modules are installed. + $requiredModules | ForEach-Object { + if ($availableModules.Name -notcontains $_) + { + Write-Host "[$($_)] module is not present." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host "Installing [$($_)] module..." -ForegroundColor $([Constants]::MessageType.Info) + Install-Module -Name $_ -Scope CurrentUser -Repository 'PSGallery' -ErrorAction Stop + Write-Host "[$($_)] module installed." -ForegroundColor $([Constants]::MessageType.Update) + } + else + { + Write-Host "[$($_)] module is present." -ForegroundColor $([Constants]::MessageType.Update) + } + } +} + +function Configure-WAFPolicyForFrontDoorCDN +{ + <# + .SYNOPSIS + Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + + .DESCRIPTION + Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + WAF Policy must be configured for Front Door CDN Endpoint(s). + + .PARAMETER SubscriptionId + Specifies the ID of the Subscription to be remediated. + + .PARAMETER Force + Specifies a forceful remediation without any prompts. + + .Parameter PerformPreReqCheck + Specifies validation of prerequisites for the command. + + .PARAMETER DryRun + Specifies a dry run of the actual remediation. + + .PARAMETER FilePath + Specifies the path to the file to be used as input for the remediation. + + .PARAMETER Path + Specifies the path to the file to be used as input for the remediation when AutoRemediation switch is used. + + .PARAMETER AutoRemediation + Specifies script is run as a subroutine of AutoRemediation Script. + + .PARAMETER TimeStamp + Specifies the time of creation of file to be used for logging remediation details when AutoRemediation switch is used. + + .INPUTS + None. You cannot pipe objects to Enable-WAFPolicyForFrontDoors. + + .OUTPUTS + None. Configure-WAFPolicyForFrontDoorCDN does not return anything that can be piped and used as an input to another command. + + .EXAMPLE + PS> Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun + + .EXAMPLE + PS> Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck + + .EXAMPLE + PS> Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202201011212\ConfigureFrontDoorCDNWAFPolicy\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv + + .LINK + None + #> + + param ( + [String] + [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] + [Parameter(ParameterSetName = "WetRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] + $SubscriptionId, + + [Switch] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies a forceful remediation without any prompts")] + $Force, + + [Switch] + [Parameter(ParameterSetName = "DryRun", HelpMessage="Specifies validation of prerequisites for the command")] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies validation of prerequisites for the command")] + $PerformPreReqCheck, + + [Switch] + [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies a dry run of the actual remediation")] + $DryRun, + + [String] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation")] + $FilePath, + + [String] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation when AutoRemediation switch is used")] + $Path, + + [Switch] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies script is run as a subroutine of AutoRemediation Script")] + $AutoRemediation, + + [String] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the time of creation of file to be used for logging remediation details when AutoRemediation switch is used")] + $TimeStamp + ) + + Write-Host $([Constants]::DoubleDashLine) + if ($PerformPreReqCheck) + { + try + { + Write-Host "[Step 1 of 5] Validate and install the modules required to run the script and validate the user" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Setup-Prerequisites + Write-Host "Completed setting up prerequisites." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + catch + { + Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + return + } + } + else + { + Write-Host "[Step 1 of 5] Validate the user" + Write-Host $([Constants]::SingleDashLine) + } + + # Connect to Azure account + $context = Get-AzContext + + if ([String]::IsNullOrWhiteSpace($context)) + { + Write-Host "Connecting to Azure account..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null + Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + # Setting up context for the current Subscription. + $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop + + + if(-not($AutoRemediation)) + { + Write-Host "Subscription Name: [$($context.Subscription.Name)]" + Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" + Write-Host "Account Name: [$($context.Account.Id)]" + Write-Host "Account Type: [$($context.Account.Type)]" + Write-Host $([Constants]::SingleDashLine) + } + + Write-Host " To configure WAF Policy for Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Write-Host "[Step 2 of 5] Preparing to fetch all Front Door CDNs" + Write-Host $([Constants]::SingleDashLine) + + $frontDoorCDNs = @() + $frontDoorEndPoints = @() + $resourceAppIdURI = "https://management.azure.com/" + $apiResponse =@() + $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token + $endPointPolicies = New-Object System.Collections.ArrayList + + # To keep track of remediated and skipped resources + $logRemediatedResources = @() + $logSkippedResources=@() + + # Control Id + $controlIds = "Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial" + + if($AutoRemediation) + { + if(-not (Test-Path -Path $Path)) + { + Write-Host "File containing failing controls details [$($Path)] not found. Skipping remediation..." -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + return + } + Write-Host "Fetching all Front Doors CDN failing for the [$($controlIds)] control from [$($Path)]..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + $controlForRemediation = Get-content -path $Path | ConvertFrom-Json + $controls = $controlForRemediation.ControlRemediationList + $resourceDetails = $controls | Where-Object { $controlIds -eq $_.ControlId }; + $validResources = $resourceDetails.FailedResourceList | Where-Object {![String]::IsNullOrWhiteSpace($_.ResourceId)} + + if(($resourceDetails | Measure-Object).Count -eq 0 -or ($validResources | Measure-Object).Count -eq 0) + { + Write-Host "No Front Door CDN(s) found in input json file for remediation." -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::SingleDashLine) + return + } + + $validResources | ForEach-Object { + try + { + $name = $_.ResourceName + $resourceGroupName = $_.ResourceGroupName + + if($null -ne $classicAccessToken) + { + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$name) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + { + if($null -ne $apiResponse.Content) + { + $content = $apiResponse.Content | ConvertFrom-Json + $apiResponse.Content | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200 + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) + { + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) + { + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) + { + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } + } + } + + # Get all Frontendpoint(s) for this Front Door. + $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $name -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }}, + @{N='IsPreventionMode';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + if($WAFPolicy.PolicyMode -eq 'Prevention') + { + $true + } + else + { + $false + + } + } + }}, + @{N='IsWAFEnabled';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + + if($WAFPolicy.PolicyEnabledState -eq 'Enabled') + { + $true + } + else + { + $false + } + } + }} + } + catch + { + Write-Host "Valid resource id(s) not found in input json file. Error: [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host "Skipping the Resource: [$($_.ResourceName)]..." -ForegroundColor $([Constants]::MessageType.Warning) + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.ResourceName)) + $logResource.Add("Reason","Valid resource id(s) not found in input json file.") + $logSkippedResources += $logResource + Write-Host $([Constants]::SingleDashLine) + } + } + } + else + { + # No file path provided as input to the script. Fetch all Front Door CDNs in the Subscription. + if ([String]::IsNullOrWhiteSpace($FilePath)) + { + Write-Host "Fetching all Front Door CDNs in Subscription: [$($context.Subscription.SubscriptionId)]..." -ForegroundColor $([Constants]::MessageType.Info) + + # Get all Front Door CDNs in the Subscription + $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction Stop + $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count + + if($totalfrontDoors -gt 0) + { + $frontDoorCDNs | ForEach-Object { + $frontDoor = $_ + $frontDoorId = $_.Id + $resourceGroupName = $_.Id.Split('/')[4] + $frontDoorName = $_.Name + + if($null -ne $classicAccessToken) + { + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + { + if($null -ne $apiResponse.Content) + { + $content = $apiResponse.Content | ConvertFrom-Json + $apiResponse.Content | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200 + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) + { + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) + { + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) + { + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } + } + } + + + # Get all Endpoint(s) for this Front Door CDN. + $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }}, + @{N='IsPreventionMode';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + if($WAFPolicy.PolicyMode -eq 'Prevention') + { + $true + } + else + { + $false + + } + } + }}, + @{N='IsWAFEnabled';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + + if($WAFPolicy.PolicyEnabledState -eq 'Enabled') + { + $true + } + else + { + $false + } + } + }} + } + } + } + else + { + if (-not (Test-Path -Path $FilePath)) + { + Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + break + } + + Write-Host "Fetching all Front Door CDN Endpoint(s) from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) + + $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath + $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } + + $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName + + foreach($frontdoor in $uniquefrontDoors) + { + $resourceGroupName = $frontdoor.ResourceGroupName + $frontDoorName = $frontdoor.FrontDoorName + + if($null -ne $classicAccessToken) + { + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + { + if($null -ne $apiResponse.Content) + { + $content = $apiResponse.Content | ConvertFrom-Json + $apiResponse.Content | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200 + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) + { + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) + { + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) + { + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } + } + } + } + + $validfrontDoorEndpointsDetails | ForEach-Object { + $frontdoorEndpointName = $_.EndPointName + $resourceGroupName = $_.ResourceGroupName + $frontDoorName = $_.FrontDoorName + + try + { + $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }}, + @{N='IsPreventionMode';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + if($WAFPolicy.PolicyMode -eq 'Prevention') + { + $true + } + else + { + $false + + } + } + }}, + @{N='IsWAFEnabled';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + + if($WAFPolicy.PolicyEnabledState -eq 'Enabled') + { + $true + } + else + { + $false + } + } + }} + } + catch + { + Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) + } + } + } + } + + + if(-not($AutoRemediation)) + { + if ([String]::IsNullOrWhiteSpace($FilePath)) + { + $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count + + if ($totalfrontDoors -eq 0) + { + Write-Host "No Front Door CDNs found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::DoubleDashLine) + break + } + + Write-Host "Found [$($totalfrontDoors)] Front Door CDN(s)." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + } + + + $totalfrontDoorEndPoints = ($frontDoorEndPoints | Measure-Object).Count + + if ($totalfrontDoorEndPoints -eq 0) + { + Write-Host "No Front Door CDN Endpoint(s) having WAF Policy not configured. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::DoubleDashLine) + break + } + + Write-Host "Found [$($totalfrontDoorEndPoints)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + + # Includes Front Door CDN Endpoint(s) where WAF Policy is not configured + $frontDoorEndpointsWithWAFPolicyNotConfigured = @() + + # Includes Front Door CDN Endpoint(s) that were skipped during remediation. There were errors remediating them. + $frontDoorEndpointsSkipped = @() + + + + Write-Host "[Step 3 of 5] Fetching Endpoint(s)" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is not configured..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + $frontDoorEndPoints | ForEach-Object { + $endPoint = $_ + if($_.IsWAFConfigured -eq $false) + { + $frontDoorEndpointsWithWAFPolicyNotConfigured += $endPoint + } + } + + $totalfrontDoorEndpointsWithWAFPolicyNotConfigured = ($frontDoorEndpointsWithWAFPolicyNotConfigured | Measure-Object).Count + + if ($totalfrontDoorEndpointsWithWAFPolicyNotConfigured -eq 0) + { + Write-Host "No Front Door CDN endpoints(s) found where WAF Policy is not configured.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + + if($AutoRemediation -and ($frontDoorFrontendPoints |Measure-Object).Count -gt 0) + { + $logFile = "LogFiles\"+ $($TimeStamp) + "\log_" + $($SubscriptionId) +".json" + $log = Get-content -Raw -path $logFile | ConvertFrom-Json + foreach($logControl in $log.ControlList){ + if($logControl.ControlId -eq $controlIds){ + $logControl.RemediatedResources=$logRemediatedResources + $logControl.SkippedResources=$logSkippedResources + } + } + $log | ConvertTo-json -depth 10 | Out-File $logFile + } + return + } + + Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door CDN Endpoints(s) found where WAF Policy is not configured ." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + + if(-not($AutoRemediation)) + { + Write-Host "Following Front Door CDN Endpoints(s) are having WAF Policies not configured:" -ForegroundColor $([Constants]::MessageType.Info) + $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, + @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, + @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, + @{Expression={$_.IsPreventionMode};Label="Is Prevention Mode on ";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} + $frontDoorEndpointsWithWAFPolicyNotConfigured | Format-Table -Property $colsProperty -Wrap + } + # Back up snapshots to `%LocalApplicationData%'. + $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\ConfigureFrontDoorCDNWAFPolicy" + + if (-not (Test-Path -Path $backupFolderPath)) + { + New-Item -ItemType Directory -Path $backupFolderPath | Out-Null + } + + + Write-Host "[Step 4 of 5] Backing up Front Door CDN Endpoint(s) details" + Write-Host $([Constants]::SingleDashLine) + if ([String]::IsNullOrWhiteSpace($FilePath)) + { + + # Backing up Front Door CDN Endpoints details. + $backupFile = "$($backupFolderPath)\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv" + $frontDoorEndpointsWithWAFPolicyNotConfigured | Export-CSV -Path $backupFile -NoTypeInformation + Write-Host "Front Door Endpoint(s) details have been successful backed up to [$($backupFolderPath)]" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + else + { + Write-Host "Skipped as -FilePath is provided" -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + + if (-not $DryRun) + { + if(-not $AutoRemediation) + { + Write-Host "WAF Policy will be configured for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Warning) + + if (-not $Force) + { + Write-Host "Do you want to configure WAF Policy associated with Front Door CDN Endpoint(s)? " -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline + Write-Host $([Constants]::SingleDashLine) + $userInput = Read-Host -Prompt "(Y|N)" + Write-Host $([Constants]::SingleDashLine) + if($userInput -ne "Y") + { + Write-Host "WAF Policy will not be configured for any Front Door CDN Endpoint(s). Exiting." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::DoubleDashLine) + break + } + else + { + Write-Host "WAF Policy will be configured for all Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + } + else + { + Write-Host "'Force' flag is provided. WAF Policy will be configured on all Front Door CDN Endoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + } + + + Write-Host "[Step 5 of 5] Configuring WAF Policy for Front Door CDN Endpoint(s)" + Write-Host $([Constants]::SingleDashLine) + # To hold results from the remediation. + $frontDoorEndpointsRemediated = @() + $endpointsSkipped = @() + $otherPolicyEndpointsAssociations = @() + Do + { + $wafPolicyName = Read-Host -Prompt "Enter WAF Policy Name" + $policyResourceGroup = Read-Host -Prompt "Enter WAF Policy Resource Group" + $policy = Get-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $policyResourceGroup -ErrorAction SilentlyContinue + + if($policy -eq $null) + { + Write-Host "WAF Policy name or WAF Policy Resource Group Name is not correct. Please enter correct details." + Write-Host $([Constants]::SingleDashLine) + } + + if($policy -ne $null -and $policy.Sku -ne 'Standard_AzureFrontDoor') + { + Write-Host "WAF Policy is not of type Front door tier Standard . Please enter correct details." + Write-Host $([Constants]::SingleDashLine) + } + } + while($policy.Sku -ne 'Standard_AzureFrontDoor' -or $policy -eq $null) + $wafPolicyId = $policy.Id + + # Remidiate Controls by configuring WAF Policy + $frontDoorEndpointsWithWAFPolicyNotConfigured | ForEach-Object { + $frontDoorEndPoint = $_ + $endpointName = $_.EndPointName + $frontdoorName = $_.FrontDoorName + $resourceGroupName = $_.ResourceGroupName + $i= 0 + + try + { + $endpoint = Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontdoorName -EndpointName $endpointName + $updateAssociation = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$($endpoint.Id)}) + $updateAssociations = @() + $updateAssociations += $updateAssociation + $otherPolicyEndpointsAssociations = $endPointPolicies | where wafPolicyName -eq ($wafPolicyName) + $otherPolicyEndpointsAssociations = $otherPolicyEndpointsAssociations | where frontDoorName -eq $frontdoorName + + $otherPolicyEndpointsAssociations | ForEach-Object { + $association = $_ + $associatedEndpoint = $_.endpointName + + New-Variable -Force -Name "endpoint$i" -Value (Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontdoorName -EndpointName $associatedEndpoint) + New-Variable -Force -Name "updateAssociation$i" -Value (New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$(Get-Variable -Name "endpoint$i" -ValueOnly).Id})) + $updateAssociations += (Get-Variable -Name "updateAssociation$i" -ValueOnly) + $i++ + } + + $updateWafParameter = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallParametersObject -Association @($updateAssociations) -WafPolicyId $wafPolicyId + $policySecurity = New-AzFrontDoorCdnSecurityPolicy -ResourceGroupName v-abprasadTestRG -ProfileName testFrontdoorCDN -Name Policy -Parameter $updateWafParameter + + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $policyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontdoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + + if ($policySecurity.Name -eq $null) + { + $endpointsSkipped += $frontDoorEndPoint + + } + else + { $frontDoorEndPoint.IsWAFConfigured = $true + $frontDoorEndPoint.WAFPolicyName = $wafPolicyName + $frontDoorEndPoint.WAFPolicyResourceGroup = $policyResourceGroup + $frontDoorEndpointsRemediated += $frontDoorEndPoint + } + } + catch + { + $endpointsSkipped += $frontDoorEndPoint + } + } + + $totalRemediated = ($frontDoorEndpointsRemediated | Measure-Object).Count + + Write-Host $([Constants]::SingleDashLine) + + if ($totalRemediated -eq $totalfrontDoorEndpointsWithWAFPolicyNotConfigured) + { + Write-Host "WAF Policy configured for all [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + else + { + Write-Host "WAF Policy configured for [$totalRemediated] out of [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + + $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, + @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, + @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, + @{Expression={$_.IsPreventionMode};Label="Is Prevention Mode on ";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} + + + + Write-Host "Remediation Summary:" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + + if($AutoRemediation) + { + if($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) + { + # Write this to a file. + $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv" + $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation + Write-Host "The information related to Front door CDN Endpoints(s) where WAF Policy is configured has been saved to [$($frontDoorEndpointsRemediatedFile)]. Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + + if ($($endpointsSkipped | Measure-Object).Count -gt 0) + { + # Write this to a file. + $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv" + $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation + Write-Host "The information related to Front door CDN Endpoints(s) where WAF Policy is not configured has been saved to [$($endpointsSkippedFile)]." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + } + else + { + if ($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) + { + Write-Host "Successfully configured WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + $frontDoorEndpointsRemediated | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv" + $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation + Write-Host "This information has been saved to [$($frontDoorEndpointsRemediatedFile)]" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + } + + if ($($endpointsSkipped | Measure-Object).Count -gt 0) + { + Write-Host "Error performing remediation steps for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::SingleDashLine) + $endpointsSkipped | Format-Table -Property $colsProperty -Wrap + Write-Host $([Constants]::SingleDashLine) + # Write this to a file. + $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv" + $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation + Write-Host "This information has been saved to [$($endpointsSkippedFile)]" + Write-Host $([Constants]::SingleDashLine) + } + } + + if($AutoRemediation) + { + $logFile = "LogFiles\"+ $($TimeStamp) + "\log_" + $($SubscriptionId) +".json" + $log = Get-content -Raw -path $logFile | ConvertFrom-Json + foreach($logControl in $log.ControlList){ + if($logControl.ControlId -eq $controlIds){ + $logControl.RemediatedResources=$logRemediatedResources + $logControl.SkippedResources=$logSkippedResources + $logControl.RollbackFile = $frontDoorEndpointsRemediatedFile + } + } + $log | ConvertTo-json -depth 10 | Out-File $logFile + } + } + else + { + + Write-Host "[Step 5 of 5] Configuring WAF Policy for Endpoint(s)" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Skipped as -DryRun switch is provided." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + + Write-Host "Next steps:" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host "Run the same command with -FilePath $($backupFile) and without -DryRun, to configure WAF Policy Mode for all Front Door CDN Endpoint(s) listed in the file." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + } +} + +function Remove-WAFPolicyForFrontDoorCDN +{ + <# + .SYNOPSIS + Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + + .DESCRIPTION + Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + Removes configured WAF Policy for all WAF Policies in all Front Door CDN s in the Subscription. + + .PARAMETER SubscriptionId + Specifies the ID of the Subscription that was previously remediated. + + .PARAMETER Force + Specifies a forceful roll back without any prompts. + + .Parameter PerformPreReqCheck + Specifies validation of prerequisites for the command. + + .PARAMETER FilePath + Specifies the path to the file to be used as input for the roll back. + + .INPUTS + None. You cannot pipe objects to Remove-WAFPolicyForFrontDoorCDN. + + .OUTPUTS + None. Remove-WAFPolicyForFrontDoorCDN does not return anything that can be piped and used as an input to another command. + + .EXAMPLE + PS> Remove-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202211190719\ConfigureFrontDoorCDNWAFPolicy\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv + + .LINK + None + #> + + param ( + [String] + [Parameter(Mandatory = $true, HelpMessage="Specifies the ID of the Subscription that was previously remediated.")] + $SubscriptionId, + + [Switch] + [Parameter(HelpMessage="Specifies a forceful roll back without any prompts")] + $Force, + + [Switch] + [Parameter(HelpMessage="Specifies validation of prerequisites for the command")] + $PerformPreReqCheck, + + [String] + [Parameter(Mandatory = $true, HelpMessage="Specifies the path to the file to be used as input for the roll back")] + $FilePath + ) + + Write-Host $([Constants]::DoubleDashLine) + if ($PerformPreReqCheck) + { + try + { + Write-Host "[Step 1 of 4] Validate and install the modules required to run the script and validate the user" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Setup-Prerequisites + Write-Host "Completed setting up prerequisites" + Write-Host $([Constants]::SingleDashLine) + } + catch + { + Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + return + } + } + else + { + Write-Host "[Step 1 of 4] Validate the user" + Write-Host $([Constants]::SingleDashLine) + } + + # Connect to Azure account + $context = Get-AzContext + + if ([String]::IsNullOrWhiteSpace($context)) + { + + Write-Host "Connecting to Azure account..." + Write-Host $([Constants]::SingleDashLine) + Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null + Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + else + { + # Setting up context for the current Subscription. + $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop + } + + Write-Host "Subscription Name: [$($context.Subscription.Name)]" + Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" + Write-Host "Account Name: [$($context.Account.Id)]" + Write-Host "Account Type: [$($context.Account.Type)]" + Write-Host $([Constants]::SingleDashLine) + + # Note about the required access required for remediation + + Write-Host "To remove Configured WAF Policy for all Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Write-Host "[Step 2 of 4] Preparing to fetch all Front Door CDN Endpoints" + Write-Host $([Constants]::SingleDashLine) + $frontDoorEndPoints = @() + $resourceAppIdURI = "https://management.azure.com/" + $apiResponse =@() + $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token + $endPointPolicies = New-Object System.Collections.ArrayList + + if (-not (Test-Path -Path $FilePath)) + { + Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + break + } + + Write-Host "Fetching all Front Door CDN Endpoints from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) + + $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath + $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } + + $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName + + foreach($frontdoor in $uniquefrontDoors) + { + $resourceGroupName = $frontdoor.ResourceGroupName + $frontDoorName = $frontdoor.FrontDoorName + + if($null -ne $classicAccessToken) + { + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + { + if($null -ne $apiResponse.Content) + { + $content = $apiResponse.Content | ConvertFrom-Json + $apiResponse.Content | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200 + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) + { + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) + { + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) + { + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } + } + } + } + + + $validfrontDoorEndpointsDetails | ForEach-Object { + $frontdoorEndpointName = $_.EndPointName + $resourceGroupName = $_.ResourceGroupName + $frontDoorName = $_.FrontDoorName + + try + { + $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }}, + @{N='IsPreventionMode';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + if($WAFPolicy.PolicyMode -eq 'Prevention') + { + $true + } + else + { + $false + + } + } + }}, + @{N='IsWAFEnabled';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + + if($WAFPolicy.PolicyEnabledState -eq 'Enabled') + { + $true + } + else + { + $false + } + } + }} + } + catch + { + Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::SingleDashLine) + Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + } + + + + # Includes Front Door CDN Endpoint(s) where WAF Policy is configured + $frontDoorEndpointsWithWAFPolicyConfigured = @() + + + + Write-Host "[Step 3 of 4] Fetching Endpoint(s) Endpoint(s) where WAF Policy is not configured" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is configured..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + $frontDoorEndPoints | ForEach-Object { + $endPoint = $_ + if($_.IsWAFConfigured -eq $true) + { + $frontDoorEndpointsWithWAFPolicyConfigured += $endPoint + } + else + { + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.EndPointName)) + $logResource.Add("Reason","WAF Policy is already configured on Frontdoor CDN Endpoint") + $logSkippedResources += $logResource + + } + } + + $totalfrontDoorEndpointsWithWAFPolicyConfigured = ($frontDoorEndpointsWithWAFPolicyConfigured | Measure-Object).Count + + if ($totalfrontDoorEndpointsWithWAFPolicyConfigured -eq 0) + { + Write-Host "No Front Door CDN Endpoints(s) found where WAF Policy is configured.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + return + } + + + Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyConfigured)] Front Door CDN Endpoints(s) found in file where WAF Policy is configured to Rollback." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + + # Back up snapshots to `%LocalApplicationData%'. + $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\ConfigureFrontDoorCDNWAFPolicy" + + if (-not (Test-Path -Path $backupFolderPath)) + { + New-Item -ItemType Directory -Path $backupFolderPath | Out-Null + } + + if (-not $Force) + { + Write-Host "Do you want remove configured WAF Policy Mode for all Front Door CDN Endpoint(s)?" -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline + Write-Host $([Constants]::SingleDashLine) + $userInput = Read-Host -Prompt "(Y|N)" + Write-Host $([Constants]::SingleDashLine) + if($userInput -ne "Y") + { + Write-Host "Configured WAF Policy will not be removed for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::DoubleDashLine) + break + } + else + { + Write-Host "Configured WAF Policy will be removed for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + } + else + { + Write-Host "'Force' flag is provided. Configured WAF Policy will be removed for all the Front Door CDN Endpoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline + Write-Host $([Constants]::SingleDashLine) + } + + + + + + Write-Host "[Step 4 of 4] Switching WAF Policy mode to Detection for Front Door CDNs Endpoint(s)" + Write-Host $([Constants]::SingleDashLine) + # Includes Front Door CDN s, to which, previously made changes were successfully rolled back. + $frontDoorEndpointsRolledBack = @() + + # Includes Front Door CDN s that were skipped during roll back. There were errors rolling back the changes made previously. + $frontDoorEndpointsSkipped = @() + + + + # Roll back by removing configured WAF Policy on Endpoints + $frontDoorEndpointsWithWAFPolicyConfigured | ForEach-Object { + $frontDoorEndPoint = $_ + $endpointName = $_.EndPointName + $frontdoorName = $_.FrontDoorName + $resourceGroupName = $_.ResourceGroupName + $wafPolicyName = $_.WAFPolicyName + $policyResourceGroup = $_.WAFPolicyResourceGroup + $i = 0 + + $policy = Get-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $policyResourceGroup -ErrorAction SilentlyContinue + $wafPolicyId = $policy.Id + try + { + $updateAssociations = @() + $otherPolicyEndpointsAssociations = @() + + # Remove the endpoint to be rolledback from endpointPolicies List + foreach($policy in $endPointPolicies.Clone()) + { + if($policy.endpointName -eq $endpointName) + { + $endPointPolicies.Remove($policy) + } + + } + + $otherPolicyEndpointsAssociations = $endPointPolicies | where wafPolicyName -eq ($wafPolicyName) + $otherPolicyEndpointsAssociations = $otherPolicyEndpointsAssociations | where frontDoorName -eq $frontdoorName + + $otherPolicyEndpointsAssociations | ForEach-Object { + $association = $_ + $associatedEndpoint = $_.endpointName + + New-Variable -Force -Name "endpoint$i" -Value (Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontdoorName -EndpointName $associatedEndpoint) + New-Variable -Force -Name "updateAssociation$i" -Value (New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$(Get-Variable -Name "endpoint$i" -ValueOnly).Id})) + $updateAssociations += (Get-Variable -Name "updateAssociation$i" -ValueOnly) + $i++ + } + + $updateWafParameter = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallParametersObject -Association @($updateAssociations) -WafPolicyId $wafPolicyId + $policySecurity = New-AzFrontDoorCdnSecurityPolicy -ResourceGroupName v-abprasadTestRG -ProfileName testFrontdoorCDN -Name Policy -Parameter $updateWafParameter + + if ($policySecurity.Name -eq $null) + { + $frontDoorEndpointsSkipped += $frontDoorEndPoint + + } + else + { + $frontDoorEndPoint.IsWAFConfigured = $false + $frontDoorEndPoint.WAFPolicyName = "" + $frontDoorEndPoint.WAFPolicyResourceGroup = "" + $frontDoorEndpointsRolledBack += $frontDoorEndPoint + } + } + catch + { + Write-Host $_ + $frontDoorEndpointsSkipped += $frontDoorEndPoint + } + } + + + $totalfrontDoorEndpointsRolledBack = ($frontDoorEndpointsRolledBack | Measure-Object).Count + + Write-Host $([Constants]::SingleDashLine) + + if ($totalfrontDoorEndpointsRolledBack -eq $totalfrontDoorEndpointsWithWAFPolicyConfigured) + { + Write-Host "Configured WAF Policy removed for all [$($totalfrontDoorEndpointsWithWAFPolicyConfigured)] Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + else + { + Write-Host "Configured WAF Policy removed for [$totalfrontDoorEndpointsRolledBack] out of [$($totalfrontDoorEndpointsWithWAFPolicyConfigured)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + + Write-Host "Rollback Summary:" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, + @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, + @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, + @{Expression={$_.IsPreventionMode};Label="Is Prevention Mode on ";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} + + + if ($($frontDoorEndpointsRolledBack | Measure-Object).Count -gt 0) + { + Write-Host "Successfully removed WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + $frontDoorEndpointsRolledBack | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $frontDoorEndpointsRolledBackFile = "$($backupFolderPath)\RolledBackFrontDoorCDNEndpointsForConfigureWAFPolicy.csv" + $frontDoorEndpointsRolledBack | Export-CSV -Path $frontDoorEndpointsRolledBackFile -NoTypeInformation + Write-Host "This information has been saved to [$($frontDoorEndpointsRolledBackFile)]" + Write-Host $([Constants]::SingleDashLine) + } + + if ($($frontDoorEndpointsSkipped | Measure-Object).Count -gt 0) + { + Write-Host "Error removing configured WAF Policy for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::SingleDashLine) + $frontDoorEndpointsSkipped | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $frontDoorEndpointsSkippedFile = "$($backupFolderPath)\RollbackSkippedFrontDoorCDNEndpointsForConfigureWAFPolicy.csv" + $frontDoorEndpointsSkipped | Export-CSV -Path $frontDoorEndpointsSkippedFile -NoTypeInformation + Write-Host "This information has been saved to [$($frontDoorEndpointsSkippedFile)]" + } +} + +# Defines commonly used constants. +class Constants +{ + # Defines commonly used colour codes, corresponding to the severity of the log. + static [Hashtable] $MessageType = @{ + Error = [System.ConsoleColor]::Red + Warning = [System.ConsoleColor]::Yellow + Info = [System.ConsoleColor]::Cyan + Update = [System.ConsoleColor]::Green + Default = [System.ConsoleColor]::White + } + + static [String] $DoubleDashLine = "========================================================================================================================" + static [String] $SingleDashLine = "------------------------------------------------------------------------------------------------------------------------" +} \ No newline at end of file diff --git a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 new file mode 100644 index 00000000..273be2fb --- /dev/null +++ b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 @@ -0,0 +1,1342 @@ +<### +# Overview: + This script is used to enable WAF Policy on All endpoints of Front Door CDNs in a Subscription. + +# Control ID: + Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial + +# Display Name: + Front Door should have Web Application Firewall configured + +# Prerequisites: + 1. Contributor or higher privileges on the Front Door CDNs in a Subscription. + 2. Must be connected to Azure with an authenticated account. + +# Steps performed by the script: + To remediate: + 1. Validate and install the modules required to run the script. + 2. Get the list of all Front Door CDNs Endpoints in a Subscription that do not have WAF Policy Enabled + 3. Back up details of Front Door CDN Endpoint(s) that are to be remediated. + 4. Enable Policy for all endpoints in the Frontdoors. + + To roll back: + 1. Validate and install the modules required to run the script. + 2. Get the list of Frontdoors' Endpoint(s) in a Subscription, the changes made to which previously, are to be rolled back. + 3. Revert back Policy State to Disabled for all endpoints in all the Frontdoors. + +# Instructions to execute the script: + To remediate: + 1. Download the script. + 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. + 3. Execute the script to enable WAF Policy on All endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. + + To roll back: + 1. Download the script. + 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. + 3. Execute the script to disable WAF Policy on All endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. + +# Examples: + To remediate: + 1. To review the Front Door CDNs in a Subscription that will be remediated: + Enable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun + + 2. To Enable WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription: + Enable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck + + 3. To Enable WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription, from a previously taken snapshot: + Enable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\EnableFrontDoorCDNWAFPolicy\frontdoorCDNEndpointsWithoutPolicyEnabled.csv + + To know more about the options supported by the remediation command, execute: + Get-Help Enable-WAFPolicyForFrontDoorCDN -Detailed + + To roll back: + 1. To Disable WAF Policy for Endpoint(s) all Front Door CDNs in a Subscription, from a previously taken snapshot: + Disable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\EnableFrontDoorCDNWAFPolicy\RemediatedfrontDoorCDNEndpointsForEnabledState.csv + + To know more about the options supported by the roll back command, execute: + Get-Help Disable-WAFPolicyForFrontDoorCDN -Detailed +###> + +function Setup-Prerequisites +{ + <# + .SYNOPSIS + Checks if the prerequisites are met, else, sets them up. + + .DESCRIPTION + Checks if the prerequisites are met, else, sets them up. + Includes installing any required Azure modules. + + .INPUTS + None. You cannot pipe objects to Setup-Prerequisites. + + .OUTPUTS + None. Setup-Prerequisites does not return anything that can be piped and used as an input to another command. + + .EXAMPLE + PS> Setup-Prerequisites + + .LINK + None + #> + + # List of required modules + $requiredModules = @("Az.Accounts", "Az.FrontDoor", "Az.Cdn") + + Write-Host "Required modules: $($requiredModules -join ', ')" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Checking if the required modules are present..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + + $availableModules = $(Get-Module -ListAvailable $requiredModules -ErrorAction Stop) + + # Check if the required modules are installed. + $requiredModules | ForEach-Object { + if ($availableModules.Name -notcontains $_) + { + Write-Host "[$($_)] module is not present." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host "Installing [$($_)] module..." -ForegroundColor $([Constants]::MessageType.Info) + Install-Module -Name $_ -Scope CurrentUser -Repository 'PSGallery' -ErrorAction Stop + Write-Host "[$($_)] module installed." -ForegroundColor $([Constants]::MessageType.Update) + } + else + { + Write-Host "[$($_)] module is present." -ForegroundColor $([Constants]::MessageType.Update) + } + } +} + +function Enable-WAFPolicyForFrontDoorCDN +{ + <# + .SYNOPSIS + Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + + .DESCRIPTION + Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + WAF Policy must be Enabled for Front Door CDN Endpoint(s). + + .PARAMETER SubscriptionId + Specifies the ID of the Subscription to be remediated. + + .PARAMETER Force + Specifies a forceful remediation without any prompts. + + .Parameter PerformPreReqCheck + Specifies validation of prerequisites for the command. + + .PARAMETER DryRun + Specifies a dry run of the actual remediation. + + .PARAMETER FilePath + Specifies the path to the file to be used as input for the remediation. + + .PARAMETER Path + Specifies the path to the file to be used as input for the remediation when AutoRemediation switch is used. + + .PARAMETER AutoRemediation + Specifies script is run as a subroutine of AutoRemediation Script. + + .PARAMETER TimeStamp + Specifies the time of creation of file to be used for logging remediation details when AutoRemediation switch is used. + + .INPUTS + None. You cannot pipe objects to Enable-WAFPolicyForFrontDoors. + + .OUTPUTS + None. Enable-WAFPolicyForFrontDoorCDN does not return anything that can be piped and used as an input to another command. + + .EXAMPLE + PS> Enable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun + + .EXAMPLE + PS> Enable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck + + .EXAMPLE + PS> Enable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202201011212\EnableFrontDoorCDNWAFPolicy\frontdoorCDNEndpointsWithoutPolicyEnabled.csv + + .LINK + None + #> + + param ( + [String] + [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] + [Parameter(ParameterSetName = "WetRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] + $SubscriptionId, + + [Switch] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies a forceful remediation without any prompts")] + $Force, + + [Switch] + [Parameter(ParameterSetName = "DryRun", HelpMessage="Specifies validation of prerequisites for the command")] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies validation of prerequisites for the command")] + $PerformPreReqCheck, + + [Switch] + [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies a dry run of the actual remediation")] + $DryRun, + + [String] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation")] + $FilePath, + + [String] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation when AutoRemediation switch is used")] + $Path, + + [Switch] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies script is run as a subroutine of AutoRemediation Script")] + $AutoRemediation, + + [String] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the time of creation of file to be used for logging remediation details when AutoRemediation switch is used")] + $TimeStamp + ) + + Write-Host $([Constants]::DoubleDashLine) + if ($PerformPreReqCheck) + { + try + { + Write-Host "[Step 1 of 5] Validate and install the modules required to run the script and validate the user" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Setup-Prerequisites + Write-Host "Completed setting up prerequisites." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + catch + { + Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + return + } + } + else + { + Write-Host "[Step 1 of 5] Validate the user" + Write-Host $([Constants]::SingleDashLine) + } + + # Connect to Azure account + $context = Get-AzContext + + if ([String]::IsNullOrWhiteSpace($context)) + { + Write-Host "Connecting to Azure account..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null + Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + # Setting up context for the current Subscription. + $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop + + + if(-not($AutoRemediation)) + { + Write-Host "Subscription Name: [$($context.Subscription.Name)]" + Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" + Write-Host "Account Name: [$($context.Account.Id)]" + Write-Host "Account Type: [$($context.Account.Type)]" + Write-Host $([Constants]::SingleDashLine) + } + + Write-Host "To enable WAF Policy for Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::DoubleDashLine) + Write-Host "[Step 2 of 5] Preparing to fetch all Front Door CDNs..." + + $frontDoorCDNs = @() + $frontDoorEndPoints = @() + $resourceAppIdURI = "https://management.azure.com/" + $apiResponse =@() + $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token + $endPointPolicies = New-Object System.Collections.ArrayList + + # To keep track of remediated and skipped resources + $logRemediatedResources = @() + $logSkippedResources=@() + + # Control Id + $controlIds = "Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial" + + if($AutoRemediation) + { + if(-not (Test-Path -Path $Path)) + { + Write-Host "File containing failing controls details [$($Path)] not found. Skipping remediation..." -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + return + } + Write-Host "Fetching all Front Doors CDN failing for the [$($controlIds)] control from [$($Path)]..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + $controlForRemediation = Get-content -path $Path | ConvertFrom-Json + $controls = $controlForRemediation.ControlRemediationList + $resourceDetails = $controls | Where-Object { $controlIds -eq $_.ControlId }; + $validResources = $resourceDetails.FailedResourceList | Where-Object {![String]::IsNullOrWhiteSpace($_.ResourceId)} + + if(($resourceDetails | Measure-Object).Count -eq 0 -or ($validResources | Measure-Object).Count -eq 0) + { + Write-Host "No Front Door CDN(s) found in input json file for remediation." -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::SingleDashLine) + return + } + + $validResources | ForEach-Object { + try + { + $name = $_.ResourceName + $resourceGroupName = $_.ResourceGroupName + + if($null -ne $classicAccessToken) + { + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$name) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + { + if($null -ne $apiResponse.Content) + { + $content = $apiResponse.Content | ConvertFrom-Json + $apiResponse.Content | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200 + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) + { + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) + { + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) + { + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } + } + } + + # Get all Frontendpoint(s) for this Front Door. + $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $name -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }}, + @{N='IsPreventionMode';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + if($WAFPolicy.PolicyMode -eq 'Prevention') + { + $true + } + else + { + $false + + } + } + }}, + @{N='IsWAFEnabled';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + + if($WAFPolicy.PolicyEnabledState -eq 'Enabled') + { + $true + } + else + { + $false + } + } + }} + } + catch + { + Write-Host "Valid resource id(s) not found in input json file. Error: [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host "Skipping the Resource: [$($_.ResourceName)]..." -ForegroundColor $([Constants]::MessageType.Warning) + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.ResourceName)) + $logResource.Add("Reason","Valid resource id(s) not found in input json file.") + $logSkippedResources += $logResource + Write-Host $([Constants]::SingleDashLine) + } + } + } + else + { + # No file path provided as input to the script. Fetch all Front Door CDNs in the Subscription. + if ([String]::IsNullOrWhiteSpace($FilePath)) + { + Write-Host "Fetching all Front Door CDNs in Subscription: $($context.Subscription.SubscriptionId)" -ForegroundColor $([Constants]::MessageType.Info) + + # Get all Front Door CDNs in the Subscription + $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction Stop + $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count + + if($totalfrontDoors -gt 0) + { + $frontDoorCDNs | ForEach-Object { + $frontDoor = $_ + $frontDoorId = $_.Id + $resourceGroupName = $_.Id.Split('/')[4] + $frontDoorName = $_.Name + + if($null -ne $classicAccessToken) + { + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + { + if($null -ne $apiResponse.Content) + { + $content = $apiResponse.Content | ConvertFrom-Json + $apiResponse.Content | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200 + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) + { + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) + { + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) + { + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } + } + } + + + # Get all Endpoint(s) for this Front Door CDN. + $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }}, + @{N='IsPreventionMode';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + if($WAFPolicy.PolicyMode -eq 'Prevention') + { + $true + } + else + { + $false + + } + } + }}, + @{N='IsWAFEnabled';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + + if($WAFPolicy.PolicyEnabledState -eq 'Enabled') + { + $true + } + else + { + $false + } + } + }} + + } + } + } + else + { + if (-not (Test-Path -Path $FilePath)) + { + Write-Host "ERROR: Input file - $($FilePath) not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + break + } + + Write-Host "Fetching all Front Door CDN Endpoint(s) from $($FilePath)" -ForegroundColor $([Constants]::MessageType.Info) + + $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath + $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } + + $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName + + foreach($frontdoor in $uniquefrontDoors) + { + $resourceGroupName = $frontdoor.ResourceGroupName + $frontDoorName = $frontdoor.FrontDoorName + if($null -ne $classicAccessToken) + { + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + { + if($null -ne $apiResponse.Content) + { + $content = $apiResponse.Content | ConvertFrom-Json + $apiResponse.Content | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200 + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) + { + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) + { + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) + { + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } + } + } + + } + + $validfrontDoorEndpointsDetails | ForEach-Object { + $frontdoorEndpointName = $_.EndPointName + $resourceGroupName = $_.ResourceGroupName + $frontDoorName = $_.FrontDoorName + + try + { + + + $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }}, + @{N='IsPreventionMode';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + if($WAFPolicy.PolicyMode -eq 'Prevention') + { + $true + } + else + { + $false + + } + } + }}, + @{N='IsWAFEnabled';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + + if($WAFPolicy.PolicyEnabledState -eq 'Enabled') + { + $true + } + else + { + $false + } + } + }} + } + catch + { + Write-Host "Error fetching Front Door CDN Endpoint: ID - $($frontdoorEndpointId). Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) + } + } + } + } + + if(-not($AutoRemediation)) + { + if ([String]::IsNullOrWhiteSpace($FilePath)) + { + $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count + + if ($totalfrontDoors -eq 0) + { + Write-Host "No Front Door CDNs found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + break + } + + Write-Host "Found $($totalfrontDoors) Front Door CDN(s)." -ForegroundColor $([Constants]::MessageType.Update) + } + } + + + $totalfrontDoorEndPoints = ($frontDoorEndPoints | Measure-Object).Count + + if ($totalfrontDoorEndPoints -eq 0) + { + Write-Host "No Front Door CDN Endpoint(s) found having WAF Policy not Enabled. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + break + } + + Write-Host "Found $($totalfrontDoorEndPoints) Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) + + + # Includes Front Door CDN Endpoint(s) where WAF Policy is in not Enabled + $frontDoorEndpointsWithWAFPolicyNotEnabled = @() + + # Includes Front Door CDN Endpoint(s) that were skipped during remediation. There were errors remediating them. + $frontDoorEndpointsSkipped = @() + + Write-Host $([Constants]::DoubleDashLine) + Write-Host "[Step 3 of 5] Fetching Endpoint(s)..." + Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is not Enabled..." -ForegroundColor $([Constants]::MessageType.Info) + + $frontDoorEndPoints | ForEach-Object { + $endPoint = $_ + if(($_.IsWAFEnabled -eq $false) -and ($_.IsWAFConfigured -eq $true)) + { + $frontDoorEndpointsWithWAFPolicyNotEnabled += $endPoint + } + } + + $totalfrontDoorEndpointsWithWAFPolicyNotEnabled = ($frontDoorEndpointsWithWAFPolicyNotEnabled | Measure-Object).Count + + if ($totalfrontDoorEndpointsWithWAFPolicyNotEnabled -eq 0) + { + Write-Host "No Front Door CDN endpoints(s) found where WAF Policy is not Enabled.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + + if($AutoRemediation -and ($frontDoorFrontendPoints |Measure-Object).Count -gt 0) + { + $logFile = "LogFiles\"+ $($TimeStamp) + "\log_" + $($SubscriptionId) +".json" + $log = Get-content -Raw -path $logFile | ConvertFrom-Json + foreach($logControl in $log.ControlList){ + if($logControl.ControlId -eq $controlIds){ + $logControl.RemediatedResources=$logRemediatedResources + $logControl.SkippedResources=$logSkippedResources + } + } + $log | ConvertTo-json -depth 10 | Out-File $logFile + } + return + } + + Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyNotEnabled)] Front Door CDN Endpoints(s) found where WAF Policy is not Enabled ." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + + if(-not($AutoRemediation)) + { + Write-Host "Following Front Door CDN Endpoints(s) are having WAF Policies not Enabled:" -ForegroundColor $([Constants]::MessageType.Info) + $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, + @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, + @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, + @{Expression={$_.IsPreventionMode};Label="Is Prevention Mode on ";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} + $frontDoorEndpointsWithWAFPolicyNotEnabled | Format-Table -Property $colsProperty -Wrap + } + + # Back up snapshots to `%LocalApplicationData%'. + $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\EnableFrontDoorCDNWAFPolicy" + + if (-not (Test-Path -Path $backupFolderPath)) + { + New-Item -ItemType Directory -Path $backupFolderPath | Out-Null + } + + Write-Host $([Constants]::DoubleDashLine) + Write-Host "[Step 4 of 5] Backing up Front Door CDN Endpoint(s) details..." + if ([String]::IsNullOrWhiteSpace($FilePath)) + { + + # Backing up Front Door CDN Endpoints details. + $backupFileForWAFNotEnabled = "$($backupFolderPath)\frontdoorCDNEndpointsWithoutPolicyEnabled.csv" + $frontDoorEndpointsWithWAFPolicyNotEnabled | Export-CSV -Path $backupFileForWAFNotEnabled -NoTypeInformation + Write-Host "Front Door Endpoint(s) details have been successful backed up to $($backupFolderPath)" -ForegroundColor $([Constants]::MessageType.Update) + } + else + { + Write-Host "Skipped as -FilePath is provided" -ForegroundColor $([Constants]::MessageType.Warning) + } + + if (-not $DryRun) + { + if(-not $AutoRemediation) + { + Write-Host "WAF Policy will be Enabled for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Warning) + + if (-not $Force) + { + Write-Host "Do you want to Enable WAF Policies associated with Front Door CDN Endpoint(s)? " -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline + + $userInput = Read-Host -Prompt "(Y|N)" + + if($userInput -ne "Y") + { + Write-Host " WAF Policy will not be Enabled for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + break + } + } + else + { + Write-Host "'Force' flag is provided. WAF Policy will be enabled on all Front Door CDN Endoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) + } + } + + Write-Host $([Constants]::DoubleDashLine) + Write-Host "[Step 5 of 5] Enabling WAF Policy for Front Door CDN Endpoint(s)..." + + # To hold results from the remediation. + $frontDoorEndpointsRemediated = @() + + + # Remidiate Controls by Eanbling WAF Policy + $frontDoorEndpointsWithWAFPolicyNotEnabled | ForEach-Object { + $frontDoorEndPoint = $_ + $wafPolicyName = $_.WAFPolicyName + $wafPolicyRG = $_.WAFPolicyResourceGroup + $endpointsSkipped = @() + + + try + { + $updatedPolicy = Update-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $wafPolicyRG -EnabledState Enabled + + if ($updatedPolicy.PolicyEnabledState -ne 'Enabled') + { + $endpointsSkipped += $frontDoorEndPoint + + } + else + { + $frontDoorEndPoint.IsWAFEnabled = $true + $frontDoorEndpointsRemediated += $frontDoorEndPoint + } + } + catch + { + $endpointsSkipped += $frontDoorEndPoint + } + } + + $totalRemediated = ($frontDoorEndpointsRemediated | Measure-Object).Count + + Write-Host $([Constants]::SingleDashLine) + + if ($totalRemediated -eq $totalfrontDoorEndpointsWithWAFPolicyNotEnabled) + { + Write-Host "WAF Policy Enabled for all $($totalfrontDoorEndpointsWithWAFPolicyNotEnabled) Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) + } + else + { + Write-Host "WAF Policy Enabled for $totalRemediated out of $($totalfrontDoorEndpointsWithWAFPolicyNotEnabled) Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) + } + + $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, + @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, + @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, + @{Expression={$_.IsPreventionMode};Label="Is Prevention Mode on ";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} + + + Write-Host $([Constants]::DoubleDashLine) + Write-Host "Remediation Summary:" -ForegroundColor $([Constants]::MessageType.Info) + + if($AutoRemediation) + { + if($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) + { + # Write this to a file. + $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForEnabledState.csv" + $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation + Write-Host "The information related to Front door CDN Endpoints(s) where WAF Policy is enabled has been saved to [$($frontDoorEndpointsRemediatedFile)]. Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + + if ($($endpointsSkipped | Measure-Object).Count -gt 0) + { + # Write this to a file. + $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForEnabledState.csv" + $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation + Write-Host "The information related to Front door CDN Endpoints(s) where WAF Policy is not enabled has been saved to [$($endpointsSkippedFile)]." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + } + else + { + if ($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) + { + $frontDoorEndpointsRemediated | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForEnabledState.csv" + $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation + Write-Host "This information has been saved to $($frontDoorEndpointsRemediatedFile)" + Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) + } + + if ($($endpointsSkipped | Measure-Object).Count -gt 0) + { + Write-Host "Error performing remediation steps for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) + $endpointsSkipped | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForEnabledState.csv" + $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation + Write-Host "This information has been saved to $($endpointsSkippedFile)" + } + } + } + else + { + Write-Host $([Constants]::DoubleDashLine) + Write-Host "[Step 5 of 5] Enabling WAF Policy for Endpoint(s)..." + Write-Host $([Constants]::SingleDashLine) + Write-Host "Skipped as -DryRun switch is provided." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + + Write-Host "**Next steps:**" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host "Run the same command with -FilePath $($backupFile) and without -DryRun, to Enable WAF Policy for all Front Door CDN Endpoint(s) listed in the file." -ForegroundColor $([Constants]::MessageType.Info) + } +} + +function Disable-WAFPolicyForFrontDoorCDN +{ + <# + .SYNOPSIS + Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + + .DESCRIPTION + Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + Disables back all WAF Policies in all Front Door CDNs in the Subscription. + + .PARAMETER SubscriptionId + Specifies the ID of the Subscription that was previously remediated. + + .PARAMETER Force + Specifies a forceful roll back without any prompts. + + .Parameter PerformPreReqCheck + Specifies validation of prerequisites for the command. + + .PARAMETER FilePath + Specifies the path to the file to be used as input for the roll back. + + .INPUTS + None. You cannot pipe objects to Disable-WAFPolicyForFrontDoorCDN. + + .OUTPUTS + None. Disable-WAFPolicyForFrontDoorCDN does not return anything that can be piped and used as an input to another command. + + .EXAMPLE + PS> Disable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202211190719\EnableFrontDoorCDNWAFPolicy\RemediatedfrontDoorCDNEndpointsForEnabledState.csv + + .LINK + None + #> + + param ( + [String] + [Parameter(Mandatory = $true, HelpMessage="Specifies the ID of the Subscription that was previously remediated.")] + $SubscriptionId, + + [Switch] + [Parameter(HelpMessage="Specifies a forceful roll back without any prompts")] + $Force, + + [Switch] + [Parameter(HelpMessage="Specifies validation of prerequisites for the command")] + $PerformPreReqCheck, + + [String] + [Parameter(Mandatory = $true, HelpMessage="Specifies the path to the file to be used as input for the roll back")] + $FilePath + ) + + Write-Host $([Constants]::DoubleDashLine) + if ($PerformPreReqCheck) + { + try + { + Write-Host "[Step 1 of 4] Validate and install the modules required to run the script and validate the user" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Setup-Prerequisites + Write-Host "Completed setting up prerequisites" + Write-Host $([Constants]::SingleDashLine) + } + catch + { + Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + return + } + } + else + { + Write-Host "[Step 1 of 4] Validate the user" + Write-Host $([Constants]::SingleDashLine) + } + + # Connect to Azure account + $context = Get-AzContext + + if ([String]::IsNullOrWhiteSpace($context)) + { + + Write-Host "Connecting to Azure account..." + Write-Host $([Constants]::SingleDashLine) + Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null + Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + else + { + # Setting up context for the current Subscription. + $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop + } + + Write-Host "Subscription Name: [$($context.Subscription.Name)]" + Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" + Write-Host "Account Name: [$($context.Account.Id)]" + Write-Host "Account Type: [$($context.Account.Type)]" + Write-Host $([Constants]::SingleDashLine) + + # Note about the required access required for remediation + + Write-Host "*** To Disable WAF Policy for all Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required. ***" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::DoubleDashLine) + Write-Host "[Step 2 of 4] Preparing to fetch all Front Door CDN Endpoints..." + + if (-not (Test-Path -Path $FilePath)) + { + Write-Host "ERROR: Input file - $($FilePath) not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + break + } + + Write-Host "Fetching all Front Door CDN Endpoints from $($FilePath)" -ForegroundColor $([Constants]::MessageType.Info) + + Write-Host "Fetching all Front Door CDN Endpoint(s) from $($FilePath)" -ForegroundColor $([Constants]::MessageType.Info) + + $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath + $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } + + $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName + + foreach($frontdoor in $uniquefrontDoors) + { + $resourceGroupName = $frontdoor.ResourceGroupName + $frontDoorName = $frontdoor.FrontDoorName + + if($null -ne $classicAccessToken) + { + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + { + if($null -ne $apiResponse.Content) + { + $content = $apiResponse.Content | ConvertFrom-Json + $apiResponse.Content | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200 + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) + { + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) + { + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) + { + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } + } + } + } + + $frontDoorEndPoints = @() + $resourceAppIdURI = "https://management.azure.com/" + $apiResponse =@() + $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token + $endPointPolicies = New-Object System.Collections.ArrayList + + $validfrontDoorEndpointsDetails | ForEach-Object { + $frontdoorEndpointName = $_.EndPointName + $resourceGroupName = $_.ResourceGroupName + $frontDoorName = $_.FrontDoorName + + try + { + + $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }}, + @{N='IsPreventionMode';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + if($WAFPolicy.PolicyMode -eq 'Prevention') + { + $true + } + else + { + $false + + } + } + }}, + @{N='IsWAFEnabled';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + + if($WAFPolicy.PolicyEnabledState -eq 'Enabled') + { + $true + } + else + { + $false + } + } + }} + } + catch + { + Write-Host "Error fetching Front Door CDN Endpoint: ID - $($frontdoorEndpointId). Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) + } + } + + + + # Includes Front Door CDN Endpoint(s) where WAF Policy is in Enabled + $frontDoorEndpointsWithWAFPolicyEnabled = @() + + + Write-Host $([Constants]::DoubleDashLine) + Write-Host "[Step 3 of 4] Fetching Endpoint(s)..." + Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is Enabled..." -ForegroundColor $([Constants]::MessageType.Info) + + $frontDoorEndPoints | ForEach-Object { + $endPoint = $_ + if(($_.IsWAFEnabled -eq $true) -and ($_.IsWAFConfigured -eq $true)) + { + $frontDoorEndpointsWithWAFPolicyEnabled += $endPoint + } + } + + $totalfrontDoorEndpointsWithWAFPolicyEnabled = ($frontDoorEndpointsWithWAFPolicyEnabled | Measure-Object).Count + + if ($totalfrontDoorEndpointsWithWAFPolicyEnabled -eq 0) + { + Write-Host "No Front Door CDN Endpoints(s) found where WAF Policy is Enabled.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + return + } + + + Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyEnabled)] Front Door CDN Endpoints(s) found where WAF Policy is Enabled in file to Rollback." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + + # Back up snapshots to `%LocalApplicationData%'. + $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\EnableFrontDoorCDNWAFPolicy" + + if (-not (Test-Path -Path $backupFolderPath)) + { + New-Item -ItemType Directory -Path $backupFolderPath | Out-Null + } + + if (-not $Force) + { + Write-Host "Do you want to Disable WAF Policy for all Front Door CDN Endpoint(s)?" -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline + + $userInput = Read-Host -Prompt "(Y|N)" + + if($userInput -ne "Y") + { + Write-Host "WAF Policy will not be Disabled for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + break + } + } + else + { + Write-Host "'Force' flag is provided. WAF Policy will be Disabled for all the Front Door CDN Endpoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline + } + + + Write-Host $([Constants]::DoubleDashLine) + Write-Host "[Step 3 of 4] Disabling WAF Policy for Front Door CDNs Endpoint(s) ..." + + # Includes Front Door CDN s, to which, previously made changes were successfully rolled back. + $frontDoorEndpointsRolledBack = @() + + # Includes Front Door CDN s that were skipped during roll back. There were errors rolling back the changes made previously. + $frontDoorEndpointsSkipped = @() + + + # Roll back by disabling WAF Policy + $frontDoorEndpointsWithWAFPolicyEnabled | ForEach-Object { + $frontDoorEndPoint = $_ + $wafPolicyName = $_.WAFPolicyName + $wafPolicyRG = $_.WAFPolicyResourceGroup + + try + { + $endpointResource = Update-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $wafPolicyRG -EnabledState Disabled + + if ($endpointResource.PolicyEnabledState -ne 'Disabled') + { + $frontDoorEndpointsSkipped += $frontDoorEndPoint + + } + else + { + $frontDoorEndPoint.IsWAFEnabled = $false + $frontDoorEndpointsRolledBack += $frontDoorEndPoint + } + } + catch + { + $frontDoorEndpointsSkipped += $frontDoorEndPoint + } + } + + + $totalfrontDoorEndpointsRolledBack = ($frontDoorEndpointsRolledBack | Measure-Object).Count + + Write-Host $([Constants]::SingleDashLine) + + if ($totalfrontDoorEndpointsRolledBack -eq $totalfrontDoorEndpointsWithWAFPolicyEnabled) + { + Write-Host "WAF Policy Disabled for all $($totalfrontDoorEndpointsWithWAFPolicyEnabled) Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) + } + else + { + Write-Host "WAF Policy disabled for $totalfrontDoorEndpointsRolledBack out of $($totalfrontDoorEndpointsWithWAFPolicyEnabled) Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) + } + Write-Host $([Constants]::DoubleDashLine) + Write-Host "Rollback Summary:" -ForegroundColor $([Constants]::MessageType.Info) + + $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, + @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, + @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, + @{Expression={$_.IsPreventionMode};Label="Is Prevention Mode on ";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} + + + if ($($frontDoorEndpointsRolledBack | Measure-Object).Count -gt 0) + { + $frontDoorEndpointsRolledBack | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $frontDoorEndpointsRolledBackFile = "$($backupFolderPath)\RolledBackfrontDoorCDNEndpointsForWAFPolicyState.csv" + $frontDoorEndpointsRolledBack | Export-CSV -Path $frontDoorEndpointsRolledBackFile -NoTypeInformation + Write-Host "This information has been saved to $($frontDoorEndpointsRolledBackFile)" + } + + if ($($frontDoorEndpointsSkipped | Measure-Object).Count -gt 0) + { + Write-Host "Error Disabling WAF Policy for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) + $frontDoorEndpointsSkipped | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $frontDoorEndpointsSkippedFile = "$($backupFolderPath)\RolledBackSkippedfrontDoorCDNEndpointsForWAFPolicyState.csv" + $frontDoorEndpointsSkipped | Export-CSV -Path $frontDoorEndpointsSkippedFile -NoTypeInformation + Write-Host "This information has been saved to $($frontDoorEndpointsSkippedFile)" + } + +} + +# Defines commonly used constants. +class Constants +{ + # Defines commonly used colour codes, corresponding to the severity of the log. + static [Hashtable] $MessageType = @{ + Error = [System.ConsoleColor]::Red + Warning = [System.ConsoleColor]::Yellow + Info = [System.ConsoleColor]::Cyan + Update = [System.ConsoleColor]::Green + Default = [System.ConsoleColor]::White + } + + static [String] $DoubleDashLine = "========================================================================================================================" + static [String] $SingleDashLine = "------------------------------------------------------------------------------------------------------------------------" +} \ No newline at end of file diff --git a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 new file mode 100644 index 00000000..0ee73660 --- /dev/null +++ b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 @@ -0,0 +1,1083 @@ +<### +# Overview: + This script is used to enable WAF Policy Mode to Prevention on All endpoints of Front Door CDNs in a Subscription. + +# Control ID: + Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial + +# Display Name: + Front Door should have Web Application Firewall configured + +# Prerequisites: + 1. Contributor or higher privileges on the Front Door CDNs in a Subscription. + 2. Must be connected to Azure with an authenticated account. + +# Steps performed by the script: + To remediate: + 1. Validate and install the modules required to run the script. + 2. Get the list of all Front Door CDNs Endpoints in a Subscription that do not have WAF Policy in Prevention Mode + 3. Back up details of Front Door CDN Endpoint(s) that are to be remediated. + 4. Set Policy mode to Prevention for all endpoints in the Frontdoors. + + To roll back: + 1. Validate and install the modules required to run the script. + 2. Get the list of Frontdoors' Endpoint(s) in a Subscription, the changes made to which previously, are to be rolled back. + 3. Revert Policy mode to Detection all endpoints in all the Frontdoors. + +# Instructions to execute the script: + To remediate: + 1. Download the script. + 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. + 3. Execute the script to enable Prevention Mode for WAF Policy on All endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. + + To roll back: + 1. Download the script. + 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. + 3. Execute the script to enable Prevention Mode WAF Policy on All endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. + +# Examples: + To remediate: + 1. To review the Front Door CDNs in a Subscription that will be remediated: + Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun + + 2. To Switch WAF Policy Mode to Prvention for Endpoint(s) of all Front Door CDNs in a Subscription: + Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck + + 3. To Switch WAF Policy Mode to Prvention for Endpoint(s) of all Front Door CDNs in a Subscription, from a previously taken snapshot: + Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\SetFrontDoorCDNPolicyModeToPrevention\frontdoorCDNEndpointsWithoutPolicyInPreventionMode.csv + + To know more about the options supported by the remediation command, execute: + Get-Help Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints -Detailed + + To roll back: + 1. To Switch WAF Policy Mode to Prvention for Endpoint(s) all Front Door CDNs in a Subscription, from a previously taken snapshot: + Disable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\SetFrontDoorCDNPolicyModeToPrevention\RemediatedfrontDoorCDNEndpointsForPreventionMode.csv + + To know more about the options supported by the roll back command, execute: + Get-Help Disable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints -Detailed +###> + +function Setup-Prerequisites +{ + <# + .SYNOPSIS + Checks if the prerequisites are met, else, sets them up. + + .DESCRIPTION + Checks if the prerequisites are met, else, sets them up. + Includes installing any required Azure modules. + + .INPUTS + None. You cannot pipe objects to Setup-Prerequisites. + + .OUTPUTS + None. Setup-Prerequisites does not return anything that can be piped and used as an input to another command. + + .EXAMPLE + PS> Setup-Prerequisites + + .LINK + None + #> + + # List of required modules + $requiredModules = @("Az.Accounts", "Az.Websites", "Az.Resources", "Azure") + + Write-Host "Required modules: $($requiredModules -join ', ')" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host "Checking if the required modules are present..." + + $availableModules = $(Get-Module -ListAvailable $requiredModules -ErrorAction Stop) + + # Check if the required modules are installed. + $requiredModules | ForEach-Object { + if ($availableModules.Name -notcontains $_) + { + Write-Host "Installing $($_) module..." -ForegroundColor $([Constants]::MessageType.Info) + Install-Module -Name $_ -Scope CurrentUser -Repository 'PSGallery' -ErrorAction Stop + } + else + { + Write-Host "$($_) module is present." -ForegroundColor $([Constants]::MessageType.Update) + } + } +} + +function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints +{ + <# + .SYNOPSIS + Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + + .DESCRIPTION + Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + WAF Policy Mode must be in Prevention Mode for Front Door CDN Endpoint(s). + + .PARAMETER SubscriptionId + Specifies the ID of the Subscription to be remediated. + + .PARAMETER Force + Specifies a forceful remediation without any prompts. + + .Parameter PerformPreReqCheck + Specifies validation of prerequisites for the command. + + .PARAMETER DryRun + Specifies a dry run of the actual remediation. + + .PARAMETER FilePath + Specifies the path to the file to be used as input for the remediation. + + .INPUTS + None. You cannot pipe objects to Enable-WAFPolicyForFrontDoors. + + .OUTPUTS + None. Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints does not return anything that can be piped and used as an input to another command. + + .EXAMPLE + PS> Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun + + .EXAMPLE + PS> Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck + + .EXAMPLE + PS> Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202201011212\SetFrontDoorCDNPolicyModeToPrevention\frontdoorCDNEndpointsWithoutPolicyInPreventionMode.csv + + .LINK + None + #> + + param ( + [String] + [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] + [Parameter(ParameterSetName = "WetRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] + $SubscriptionId, + + [Switch] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies a forceful remediation without any prompts")] + $Force, + + [Switch] + [Parameter(ParameterSetName = "DryRun", HelpMessage="Specifies validation of prerequisites for the command")] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies validation of prerequisites for the command")] + $PerformPreReqCheck, + + [Switch] + [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies a dry run of the actual remediation")] + $DryRun, + + [String] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation")] + $FilePath + ) + + Write-Host $([Constants]::DoubleDashLine) + Write-Host "`n[Step 1 of 5] Preparing to configure WAF Policy with Prevention Mode for Front Door CDN Endpoint(s) in Subscription: $($SubscriptionId)" + + if ($PerformPreReqCheck) + { + try + { + Write-Host "Setting up prerequisites..." + Setup-Prerequisites + } + catch + { + Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + break + } + } + + # Connect to Azure account + $context = Get-AzContext + + if ([String]::IsNullOrWhiteSpace($context)) + { + Write-Host "No active Azure login session found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + break + } + + + # Setting up context for the current Subscription. + $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop + + Write-Host $([Constants]::SingleDashLine) + Write-Host "Subscription Name: $($context.Subscription.Name)" + Write-Host "Subscription ID: $($context.Subscription.SubscriptionId)" + Write-Host "Account Name: $($context.Account.Id)" + Write-Host "Account Type: $($context.Account.Type)" + Write-Host $([Constants]::SingleDashLine) + + Write-Host "*** To enable Prevention Mode on WAF Policy for Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required. ***" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::DoubleDashLine) + Write-Host "`n[Step 2 of 5] Preparing to fetch all Front Door CDNs..." + + $frontDoorCDNs = @() + $frontDoorEndPoints = @() + $resourceAppIdURI = "https://management.azure.com/" + $apiResponse =@() + $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token + $endPointPolicies = New-Object System.Collections.ArrayList + + + # No file path provided as input to the script. Fetch all Front Door CDNs in the Subscription. + if ([String]::IsNullOrWhiteSpace($FilePath)) + { + Write-Host "`nFetching all Front Door CDNs in Subscription: $($context.Subscription.SubscriptionId)" -ForegroundColor $([Constants]::MessageType.Info) + + # Get all Front Door CDNs in the Subscription + $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction Stop + $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count + + if($totalfrontDoors -gt 0) + { + $frontDoorCDNs | ForEach-Object { + $frontDoor = $_ + $frontDoorId = $_.Id + $resourceGroupName = $_.Id.Split('/')[4] + $frontDoorName = $_.Name + + if($null -ne $classicAccessToken) + { + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + { + if($null -ne $apiResponse.Content) + { + $content = $apiResponse.Content | ConvertFrom-Json + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) + { + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $domains = $value[$i].properties.parameters.associations[0].domains + $totalDomains = ($domains | Measure-Object).Count + for($j=0; $j -lt $totalDomains; $j++) + { + $domain = $domains[$j] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $d = [ordered]@{wafPolicyName= $wafPolicyName ;wafPolicyId= $wafPolicyId ;endpointName= $endpointName } + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } + } + + + # Get all Endpoint(s) for this Front Door CDN. + $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }}, + @{N='IsPreventionMode';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + if($WAFPolicy.PolicyMode -eq 'Prevention') + { + $true + } + else + { + $false + + } + } + }}, + @{N='IsWAFEnabled';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + + if($WAFPolicy.PolicyEnabledState -eq 'Enabled') + { + $true + } + else + { + $false + } + } + }} + + } + } + } + else + { + if (-not (Test-Path -Path $FilePath)) + { + Write-Host "ERROR: Input file - $($FilePath) not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + break + } + + Write-Host "Fetching all Front Door CDN Endpoint(s) from $($FilePath)" -ForegroundColor $([Constants]::MessageType.Info) + + $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath + $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } + + $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName + + foreach($frontdoor in $uniquefrontDoors) + { + $resourceGroupName = $frontdoor.ResourceGroupName + $frontDoorName = $frontdoor.FrontDoorName + + if($null -ne $classicAccessToken) + { + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + { + if($null -ne $apiResponse.Content) + { + $content = $apiResponse.Content | ConvertFrom-Json + $apiResponse.Content | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200 + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) + { + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) + { + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) + { + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } + } + } + } + + $validfrontDoorEndpointsDetails | ForEach-Object { + $frontdoorEndpointName = $_.EndPointName + $resourceGroupName = $_.ResourceGroupName + $frontDoorName = $_.FrontDoorName + + try + { + + $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }}, + @{N='IsPreventionMode';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + if($WAFPolicy.PolicyMode -eq 'Prevention') + { + $true + } + else + { + $false + + } + } + }}, + @{N='IsWAFEnabled';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + + if($WAFPolicy.PolicyEnabledState -eq 'Enabled') + { + $true + } + else + { + $false + } + } + }} + } + catch + { + Write-Host "Error fetching Front Door CDN Endpoint: ID - $($frontdoorEndpointId). Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) + } + } + } + + if ([String]::IsNullOrWhiteSpace($FilePath)) + { + $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count + + if ($totalfrontDoors -eq 0) + { + Write-Host "No Front Door CDNs found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + break + } + + Write-Host "Found $($totalfrontDoors) Front Door CDN(s)." -ForegroundColor $([Constants]::MessageType.Update) + } + + + $totalfrontDoorEndPoints = ($frontDoorEndPoints | Measure-Object).Count + + if ($totalfrontDoorEndPoints -eq 0) + { + Write-Host "No Front Door CDN Endpoint(s) having WAF Policy not in Prevention Mode. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + break + } + + Write-Host "Found $($totalfrontDoorEndPoints) Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) + + + # Includes Front Door CDN Endpoint(s) where WAF Policy is in Prevention Mode + $frontDoorEndpointsWithWAFPolicyNotInPrevention = @() + + # Includes Front Door CDN Endpoint(s) that were skipped during remediation. There were errors remediating them. + $frontDoorEndpointsSkipped = @() + + + Write-Host $([Constants]::DoubleDashLine) + Write-Host "`n[Step 3 of 5] Fetching Endpoint(s)..." + Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is not in Prevention Mode..." -ForegroundColor $([Constants]::MessageType.Info) + + $frontDoorEndPoints | ForEach-Object { + $endPoint = $_ + if(($_.IsPreventionMode -eq $false) -and ($_.IsWAFConfigured -eq $true)) + { + $frontDoorEndpointsWithWAFPolicyNotInPrevention += $endPoint + } + } + + $totalfrontDoorEndpointsWithWAFPolicyNotInPrevention = ($frontDoorEndpointsWithWAFPolicyNotInPrevention | Measure-Object).Count + + if ($totalfrontDoorEndpointsWithWAFPolicyNotInPrevention -eq 0) + { + Write-Host "No Front Door CDN endpoints(s) found where WAF Policy is not in Prevention Mode.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + return + } + + Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyNotInPrevention)] Front Door CDN Endpoints(s) found where WAF Policy is not in Prevention Mode ." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + + Write-Host "`nFollowing Front Door CDN Endpoints(s) are having WAF Policies with Mode not in Prevention:" -ForegroundColor $([Constants]::MessageType.Info) + $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, + @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, + @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, + @{Expression={$_.IsPreventionMode};Label="Is Prevention Mode on ";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} + $frontDoorEndpointsWithWAFPolicyNotInPrevention | Format-Table -Property $colsProperty -Wrap + + # Back up snapshots to `%LocalApplicationData%'. + $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\SetFrontDoorCDNPolicyModeToPrevention" + + if (-not (Test-Path -Path $backupFolderPath)) + { + New-Item -ItemType Directory -Path $backupFolderPath | Out-Null + } + + Write-Host $([Constants]::DoubleDashLine) + Write-Host "`n[Step 4 of 5] Backing up Front Door CDN Endpoint(s) details..." + if ([String]::IsNullOrWhiteSpace($FilePath)) + { + + # Backing up Front Door CDN Endpoints details. + $backupFileForWAFNotInPreventionMode = "$($backupFolderPath)\frontdoorCDNEndpointsWithoutPolicyInPreventionMode.csv" + $frontDoorEndpointsWithWAFPolicyNotInPrevention | Export-CSV -Path $backupFileForWAFNotInPreventionMode -NoTypeInformation + Write-Host "Front Door Endpoint(s) details have been successful backed up to $($backupFolderPath)" -ForegroundColor $([Constants]::MessageType.Update) + } + else + { + Write-Host "Skipped as -FilePath is provided" -ForegroundColor $([Constants]::MessageType.Warning) + } + + if (-not $DryRun) + { + Write-Host "WAF Policy mode will be switched to Prevention for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Warning) + + if (-not $Force) + { + Write-Host "Do you want to switch WAF Policy Mode to Prevention from Detection associated with Front Door CDN Endpoint(s)? " -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline + + $userInput = Read-Host -Prompt "(Y|N)" + + if($userInput -ne "Y") + { + Write-Host " WAF Policy Mode will not be switched to Prevention from Detection for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + break + } + } + else + { + Write-Host "'Force' flag is provided. WAF Policy Mode will be switched to Prevention from Detection on all Front Door CDN Endoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) + } + + Write-Host $([Constants]::DoubleDashLine) + Write-Host "`n[Step 5 of 5] Switching Mode to Prevention from Detection for Front Door CDN Endpoint(s)..." + + # To hold results from the remediation. + $frontDoorEndpointsRemediated = @() + + + # Remidiate Controls by Switching Policy Mode from Detection to Prevention + $frontDoorEndpointsWithWAFPolicyNotInPrevention | ForEach-Object { + $frontDoorEndPoint = $_ + $wafPolicyName = $_.WAFPolicyName + $wafPolicyRG = $_.WAFPolicyResourceGroup + $endpointsSkipped = @() + + + try + { + $endpointResource = Update-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $wafPolicyRG -Mode Prevention + + if ($endpointResource.PolicyMode -ne 'Prevention') + { + $endpointsSkipped += $frontDoorEndPoint + + } + else + { + $frontDoorEndPoint.IsPreventionMode = $true + $frontDoorEndpointsRemediated += $frontDoorEndPoint + } + } + catch + { + $endpointsSkipped += $frontDoorEndPoint + } + } + + $totalRemediated = ($frontDoorEndpointsRemediated | Measure-Object).Count + + Write-Host $([Constants]::SingleDashLine) + + if ($totalRemediated -eq $totalfrontDoorEndpointsWithWAFPolicyNotInPrevention) + { + Write-Host "WAF Policy Mode changed to Prevention for all $($totalfrontDoorEndpointsWithWAFPolicyNotInPrevention) Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) + } + else + { + Write-Host "WAF Policy Mode changed to Prevention for $totalRemediated out of $($totalfrontDoorEndpointsWithWAFPolicyNotInPrevention) Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) + } + + $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, + @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, + @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, + @{Expression={$_.IsPreventionMode};Label="Is Prevention Mode on ";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} + + + Write-Host $([Constants]::DoubleDashLine) + Write-Host "`nRemediation Summary:`n" -ForegroundColor $([Constants]::MessageType.Info) + + if ($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) + { + $frontDoorEndpointsRemediated | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForPreventionMode.csv" + $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation + Write-Host "This information has been saved to $($frontDoorEndpointsRemediatedFile)" + Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) + } + + if ($($endpointsSkipped | Measure-Object).Count -gt 0) + { + Write-Host "`nError performing remediation steps for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) + $endpointsSkipped | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForPreventionMode.csv" + $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation + Write-Host "This information has been saved to $($endpointsSkippedFile)" + } + } + else + { + Write-Host $([Constants]::DoubleDashLine) + Write-Host "`n[Step 5 of 5] Switching WAF Policy mode to Prevention for Endpoint(s)..." + Write-Host $([Constants]::SingleDashLine) + Write-Host "Skipped as -DryRun switch is provided." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + + Write-Host "`n**Next steps:**" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host "Run the same command with -FilePath $($backupFile) and without -DryRun, to switch WAF Policy Mode to Prvention for all Front Door CDN Endpoint(s) listed in the file." -ForegroundColor $([Constants]::MessageType.Info) + } +} + +function Disable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints +{ + <# + .SYNOPSIS + Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + + .DESCRIPTION + Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + Switches the Policy Mode to Detection from Prevention for all WAF Policies in all Front Door CDN s in the Subscription. + + .PARAMETER SubscriptionId + Specifies the ID of the Subscription that was previously remediated. + + .PARAMETER Force + Specifies a forceful roll back without any prompts. + + .Parameter PerformPreReqCheck + Specifies validation of prerequisites for the command. + + .PARAMETER FilePath + Specifies the path to the file to be used as input for the roll back. + + .INPUTS + None. You cannot pipe objects to Disable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints. + + .OUTPUTS + None. Disable-WAFPolicyPreventionModeForFrontDoorEndPoints does not return anything that can be piped and used as an input to another command. + + .EXAMPLE + PS> Disable-WAFPolicyPreventionModeForFrontDoorEndPoints -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202211190719\SetFrontDoorCDNPolicyModeToPrevention\RemediatedfrontDoorCDNEndpointsForPreventionMode.csv + + .LINK + None + #> + + param ( + [String] + [Parameter(Mandatory = $true, HelpMessage="Specifies the ID of the Subscription that was previously remediated.")] + $SubscriptionId, + + [Switch] + [Parameter(HelpMessage="Specifies a forceful roll back without any prompts")] + $Force, + + [Switch] + [Parameter(HelpMessage="Specifies validation of prerequisites for the command")] + $PerformPreReqCheck, + + [String] + [Parameter(Mandatory = $true, HelpMessage="Specifies the path to the file to be used as input for the roll back")] + $FilePath + ) + + Write-Host $([Constants]::DoubleDashLine) + Write-Host "`n[Step 1 of 4] Preparing to switch Mode to Detection on WAF Policy for Front Door CDN Endpoint(s) in Subscription: $($SubscriptionId)" + + if ($PerformPreReqCheck) + { + try + { + Write-Host "Setting up prerequisites..." + Setup-Prerequisites + } + catch + { + Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + break + } + } + + # Connect to Azure account + $context = Get-AzContext + + if ([String]::IsNullOrWhiteSpace($context)) + { + Write-Host "No active Azure login session found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + break + } + + # Setting up context for the current Subscription. + $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop + + Write-Host $([Constants]::SingleDashLine) + Write-Host "Subscription Name: $($context.Subscription.Name)" + Write-Host "Subscription ID: $($context.Subscription.SubscriptionId)" + Write-Host "Account Name: $($context.Account.Id)" + Write-Host "Account Type: $($context.Account.Type)" + Write-Host $([Constants]::SingleDashLine) + + Write-Host "*** To Switich WAF Policy Mode to Detection for all Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required. ***" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::DoubleDashLine) + Write-Host "`n[Step 2 of 4] Preparing to fetch all Front Door CDN Endpoints..." + + if (-not (Test-Path -Path $FilePath)) + { + Write-Host "ERROR: Input file - $($FilePath) not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + break + } + + Write-Host "Fetching all Front Door CDN Endpoints from $($FilePath)" -ForegroundColor $([Constants]::MessageType.Info) + + $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath + $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } + + $frontDoorEndPoints = @() + $resourceAppIdURI = "https://management.azure.com/" + $apiResponse =@() + $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token + $endPointPolicies = New-Object System.Collections.ArrayList + + $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName + + foreach($frontdoor in $uniquefrontDoors) + { + $resourceGroupName = $frontdoor.ResourceGroupName + $frontDoorName = $frontdoor.FrontDoorName + + if($null -ne $classicAccessToken) + { + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + { + if($null -ne $apiResponse.Content) + { + $content = $apiResponse.Content | ConvertFrom-Json + $apiResponse.Content | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200 + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) + { + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) + { + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) + { + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } + } + } + } + + $validfrontDoorEndpointsDetails | ForEach-Object { + $frontdoorEndpointName = $_.EndPointName + $resourceGroupName = $_.ResourceGroupName + $frontDoorName = $_.FrontDoorName + + try + { + + $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }}, + @{N='IsPreventionMode';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + if($WAFPolicy.PolicyMode -eq 'Prevention') + { + $true + } + else + { + $false + + } + } + }}, + @{N='IsWAFEnabled';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + + if($WAFPolicy.PolicyEnabledState -eq 'Enabled') + { + $true + } + else + { + $false + } + } + }} + } + catch + { + Write-Host "Error fetching Front Door CDN Endpoint: ID - $($frontdoorEndpointId). Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) + } + } + + + + # Includes Front Door CDN Endpoint(s) where WAF Policy is in Prevention Mode + $frontDoorEndpointsWithWAFPolicyInPrevention = @() + + + Write-Host $([Constants]::DoubleDashLine) + Write-Host "`n[Step 3 of 4] Fetching Endpoint(s)..." + Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is in Prevention Mode..." -ForegroundColor $([Constants]::MessageType.Info) + + $frontDoorEndPoints | ForEach-Object { + $endPoint = $_ + if(($_.IsPreventionMode -eq $true) -and ($_.IsWAFConfigured -eq $true)) + { + $frontDoorEndpointsWithWAFPolicyInPrevention += $endPoint + } + } + + $totalfrontDoorEndpointsWithWAFPolicyInPrevention = ($frontDoorEndpointsWithWAFPolicyInPrevention | Measure-Object).Count + + if ($totalfrontDoorEndpointsWithWAFPolicyInPrevention -eq 0) + { + Write-Host "No Front Door CDN Endpoints(s) found where WAF Policy is in Prevention Mode.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + return + } + + + Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyInPrevention)] Front Door CDN Endpoints(s) found where WAF Policy is in Prevention Mode in file to Rollback." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + + # Back up snapshots to `%LocalApplicationData%'. + $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\SetFrontDoorCDNPolicyModeToPrevention" + + if (-not (Test-Path -Path $backupFolderPath)) + { + New-Item -ItemType Directory -Path $backupFolderPath | Out-Null + } + + if (-not $Force) + { + Write-Host "Do you want to switch WAF Policy Mode to Detection for all Front Door CDN Endpoint(s)?" -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline + + $userInput = Read-Host -Prompt "(Y|N)" + + if($userInput -ne "Y") + { + Write-Host "WAF Policy Mode will not be switched to Detection for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + break + } + } + else + { + Write-Host "'Force' flag is provided. WAF Policy Mode will be switched to Detection for all the Front Door CDN Endpoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline + } + + + + + Write-Host $([Constants]::DoubleDashLine) + Write-Host "`n[Step 3 of 4] Switching WAF Policy mode to Detection for Front Door CDNs Endpoint(s) ..." + + # Includes Front Door CDN s, to which, previously made changes were successfully rolled back. + $frontDoorEndpointsRolledBack = @() + + # Includes Front Door CDN s that were skipped during roll back. There were errors rolling back the changes made previously. + $frontDoorEndpointsSkipped = @() + + + # Roll back by switching Policy Mode from to Detection from Prevention + $frontDoorEndpointsWithWAFPolicyInPrevention | ForEach-Object { + $frontDoorEndPoint = $_ + $wafPolicyName = $_.WAFPolicyName + $wafPolicyRG = $_.WAFPolicyResourceGroup + + try + { + $endpointResource = Update-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $wafPolicyRG -Mode Detection + + if ($endpointResource.PolicyMode -ne 'Detection') + { + $frontDoorEndpointsSkipped += $frontDoorEndPoint + + } + else + { + $frontDoorEndPoint.IsPreventionMode = $false + $frontDoorEndpointsRolledBack += $frontDoorEndPoint + } + } + catch + { + $frontDoorEndpointsSkipped += $frontDoorEndPoint + } + } + + + $totalfrontDoorEndpointsRolledBack = ($frontDoorEndpointsRolledBack | Measure-Object).Count + + Write-Host $([Constants]::SingleDashLine) + + if ($totalfrontDoorEndpointsRolledBack -eq $totalfrontDoorEndpointsWithWAFPolicyInPrevention) + { + Write-Host "WAF Policy Mode changed to Prevention for all $($totalfrontDoorEndpointsWithWAFPolicyInPrevention) Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) + } + else + { + Write-Host "WAF Policy Mode changed to Detection for $totalfrontDoorEndpointsRolledBack out of $($totalfrontDoorEndpointsWithWAFPolicyInPrevention) Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) + } + Write-Host $([Constants]::DoubleDashLine) + Write-Host "`nRollback Summary:" -ForegroundColor $([Constants]::MessageType.Info) + + $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, + @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, + @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, + @{Expression={$_.IsPreventionMode};Label="Is Prevention Mode on ";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} + + + if ($($frontDoorEndpointsRolledBack | Measure-Object).Count -gt 0) + { + $frontDoorEndpointsRolledBack | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $frontDoorEndpointsRolledBackFile = "$($backupFolderPath)\RolledBackfrontDoorEndpointsForWAFPolicyMode.csv" + $frontDoorEndpointsRolledBack | Export-CSV -Path $frontDoorEndpointsRolledBackFile -NoTypeInformation + Write-Host "This information has been saved to $($frontDoorEndpointsRolledBackFile)" + } + + if ($($frontDoorEndpointsSkipped | Measure-Object).Count -gt 0) + { + Write-Host "`nError Switching WAF Policy Mode to Detection from Prevention for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) + $frontDoorEndpointsSkipped | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $frontDoorEndpointsSkippedFile = "$($backupFolderPath)\RollbackSkippedEndpointsForWAFPolicyMode.csv" + $frontDoorEndpointsSkipped | Export-CSV -Path $frontDoorEndpointsSkippedFile -NoTypeInformation + Write-Host "This information has been saved to $($frontDoorEndpointsSkippedFile)" + } + +} + +# Defines commonly used constants. +class Constants +{ + # Defines commonly used colour codes, corresponding to the severity of the log. + static [Hashtable] $MessageType = @{ + Error = [System.ConsoleColor]::Red + Warning = [System.ConsoleColor]::Yellow + Info = [System.ConsoleColor]::Cyan + Update = [System.ConsoleColor]::Green + Default = [System.ConsoleColor]::White + } + + static [String] $DoubleDashLine = "========================================================================================================================" + static [String] $SingleDashLine = "------------------------------------------------------------------------------------------------------------------------" +} \ No newline at end of file From b6b7ecda6a0358949e03fd02daab4a4c0d21d89b Mon Sep 17 00:00:00 2001 From: Abhishek P Date: Tue, 6 Dec 2022 10:14:42 +0530 Subject: [PATCH 02/26] Changes --- ...iate-ConfigureWAFPolicyForFrontDoorCDN.ps1 | 22 +- ...mediate-EnableWAFPolicyForFrontDoorCDN.ps1 | 134 ++- ...WAFPolicyPreventionModeForFrontDoorCDN.ps1 | 902 ++++++++++++------ 3 files changed, 692 insertions(+), 366 deletions(-) diff --git a/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 index d28be8e2..32d9394e 100644 --- a/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 +++ b/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 @@ -304,7 +304,7 @@ function Configure-WAFPolicyForFrontDoorCDN if($null -ne $apiResponse.Content) { $content = $apiResponse.Content | ConvertFrom-Json - $apiResponse.Content | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200 + $value = $content.value $totalValues = ($value | Measure-Object).Count for($i=0; $i -lt $totalValues; $i++) @@ -439,7 +439,7 @@ function Configure-WAFPolicyForFrontDoorCDN if($null -ne $apiResponse.Content) { $content = $apiResponse.Content | ConvertFrom-Json - $apiResponse.Content | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200 + $value = $content.value $totalValues = ($value | Measure-Object).Count for($i=0; $i -lt $totalValues; $i++) @@ -566,7 +566,7 @@ function Configure-WAFPolicyForFrontDoorCDN if($null -ne $apiResponse.Content) { $content = $apiResponse.Content | ConvertFrom-Json - $apiResponse.Content | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200 + $value = $content.value $totalValues = ($value | Measure-Object).Count for($i=0; $i -lt $totalValues; $i++) @@ -713,7 +713,7 @@ function Configure-WAFPolicyForFrontDoorCDN - Write-Host "[Step 3 of 5] Fetching Endpoint(s)" + Write-Host "[Step 3 of 5] Fetching Endpoint(s) for which WAF Policy is not configured" Write-Host $([Constants]::SingleDashLine) Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is not configured..." -ForegroundColor $([Constants]::MessageType.Info) Write-Host $([Constants]::SingleDashLine) @@ -723,13 +723,23 @@ function Configure-WAFPolicyForFrontDoorCDN { $frontDoorEndpointsWithWAFPolicyNotConfigured += $endPoint } + else + { + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.FrontDoorName)) + $logResource.Add("EndPointName",($_.EndPointName)) + $logResource.Add("Reason","WAF Policy already Configured on endpoint") + $logSkippedResources += $logResource + + } } $totalfrontDoorEndpointsWithWAFPolicyNotConfigured = ($frontDoorEndpointsWithWAFPolicyNotConfigured | Measure-Object).Count if ($totalfrontDoorEndpointsWithWAFPolicyNotConfigured -eq 0) { - Write-Host "No Front Door CDN endpoints(s) found where WAF Policy is not configured.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host "No Front Door CDN endpoints(s) found where WAF Policy is not configured. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) Write-Host $([Constants]::DoubleDashLine) if($AutoRemediation -and ($frontDoorFrontendPoints |Measure-Object).Count -gt 0) @@ -1160,7 +1170,7 @@ function Remove-WAFPolicyForFrontDoorCDN if($null -ne $apiResponse.Content) { $content = $apiResponse.Content | ConvertFrom-Json - $apiResponse.Content | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200 + $value = $content.value $totalValues = ($value | Measure-Object).Count for($i=0; $i -lt $totalValues; $i++) diff --git a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 index 273be2fb..445f8c1c 100644 --- a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 +++ b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 @@ -246,9 +246,9 @@ function Enable-WAFPolicyForFrontDoorCDN } Write-Host "To enable WAF Policy for Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::DoubleDashLine) - Write-Host "[Step 2 of 5] Preparing to fetch all Front Door CDNs..." - + Write-Host $([Constants]::SingleDashLine) + Write-Host "[Step 2 of 5] Preparing to fetch all Front Door CDNs" + Write-Host $([Constants]::SingleDashLine) $frontDoorCDNs = @() $frontDoorEndPoints = @() $resourceAppIdURI = "https://management.azure.com/" @@ -263,8 +263,8 @@ function Enable-WAFPolicyForFrontDoorCDN # Control Id $controlIds = "Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial" - if($AutoRemediation) - { + if($AutoRemediation) + { if(-not (Test-Path -Path $Path)) { Write-Host "File containing failing controls details [$($Path)] not found. Skipping remediation..." -ForegroundColor $([Constants]::MessageType.Error) @@ -303,7 +303,6 @@ function Enable-WAFPolicyForFrontDoorCDN if($null -ne $apiResponse.Content) { $content = $apiResponse.Content | ConvertFrom-Json - $apiResponse.Content | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200 $value = $content.value $totalValues = ($value | Measure-Object).Count for($i=0; $i -lt $totalValues; $i++) @@ -412,7 +411,7 @@ function Enable-WAFPolicyForFrontDoorCDN # No file path provided as input to the script. Fetch all Front Door CDNs in the Subscription. if ([String]::IsNullOrWhiteSpace($FilePath)) { - Write-Host "Fetching all Front Door CDNs in Subscription: $($context.Subscription.SubscriptionId)" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host "Fetching all Front Door CDNs in Subscription: [$($context.Subscription.SubscriptionId)]..." -ForegroundColor $([Constants]::MessageType.Info) # Get all Front Door CDNs in the Subscription $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction Stop @@ -438,7 +437,6 @@ function Enable-WAFPolicyForFrontDoorCDN if($null -ne $apiResponse.Content) { $content = $apiResponse.Content | ConvertFrom-Json - $apiResponse.Content | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200 $value = $content.value $totalValues = ($value | Measure-Object).Count for($i=0; $i -lt $totalValues; $i++) @@ -537,11 +535,11 @@ function Enable-WAFPolicyForFrontDoorCDN { if (-not (Test-Path -Path $FilePath)) { - Write-Host "ERROR: Input file - $($FilePath) not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) break } - Write-Host "Fetching all Front Door CDN Endpoint(s) from $($FilePath)" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host "Fetching all Front Door CDN Endpoint(s) from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } @@ -564,7 +562,6 @@ function Enable-WAFPolicyForFrontDoorCDN if($null -ne $apiResponse.Content) { $content = $apiResponse.Content | ConvertFrom-Json - $apiResponse.Content | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200 $value = $content.value $totalValues = ($value | Measure-Object).Count for($i=0; $i -lt $totalValues; $i++) @@ -667,7 +664,7 @@ function Enable-WAFPolicyForFrontDoorCDN } catch { - Write-Host "Error fetching Front Door CDN Endpoint: ID - $($frontdoorEndpointId). Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) } } @@ -699,7 +696,7 @@ function Enable-WAFPolicyForFrontDoorCDN break } - Write-Host "Found $($totalfrontDoorEndPoints) Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host "Found [$($totalfrontDoorEndPoints)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) # Includes Front Door CDN Endpoint(s) where WAF Policy is in not Enabled @@ -708,16 +705,27 @@ function Enable-WAFPolicyForFrontDoorCDN # Includes Front Door CDN Endpoint(s) that were skipped during remediation. There were errors remediating them. $frontDoorEndpointsSkipped = @() - Write-Host $([Constants]::DoubleDashLine) - Write-Host "[Step 3 of 5] Fetching Endpoint(s)..." - Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is not Enabled..." -ForegroundColor $([Constants]::MessageType.Info) - + + Write-Host "[Step 3 of 5] Fetching Endpoint(s) for which WAF Policy is not enabled" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is not enabled..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) $frontDoorEndPoints | ForEach-Object { $endPoint = $_ if(($_.IsWAFEnabled -eq $false) -and ($_.IsWAFConfigured -eq $true)) { $frontDoorEndpointsWithWAFPolicyNotEnabled += $endPoint } + else + { + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.FrontDoorName)) + $logResource.Add("EndPointName",($_.EndPointName)) + $logResource.Add("Reason","WAF Policy already enabled on endpoint") + $logSkippedResources += $logResource + + } } $totalfrontDoorEndpointsWithWAFPolicyNotEnabled = ($frontDoorEndpointsWithWAFPolicyNotEnabled | Measure-Object).Count @@ -768,8 +776,9 @@ function Enable-WAFPolicyForFrontDoorCDN New-Item -ItemType Directory -Path $backupFolderPath | Out-Null } - Write-Host $([Constants]::DoubleDashLine) - Write-Host "[Step 4 of 5] Backing up Front Door CDN Endpoint(s) details..." + + Write-Host "[Step 4 of 5] Backing up Front Door CDN Endpoint(s) details" + Write-Host $([Constants]::SingleDashLine) if ([String]::IsNullOrWhiteSpace($FilePath)) { @@ -797,9 +806,14 @@ function Enable-WAFPolicyForFrontDoorCDN if($userInput -ne "Y") { - Write-Host " WAF Policy will not be Enabled for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host " WAF Policy will not be enabled for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) break } + else + { + Write-Host "WAF Policy will be enabled for all Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } } else { @@ -807,8 +821,9 @@ function Enable-WAFPolicyForFrontDoorCDN } } - Write-Host $([Constants]::DoubleDashLine) - Write-Host "[Step 5 of 5] Enabling WAF Policy for Front Door CDN Endpoint(s)..." + + Write-Host "[Step 5 of 5] Enabling WAF Policy for Front Door CDN Endpoint(s)" + Write-Host $([Constants]::SingleDashLine) # To hold results from the remediation. $frontDoorEndpointsRemediated = @() @@ -849,11 +864,11 @@ function Enable-WAFPolicyForFrontDoorCDN if ($totalRemediated -eq $totalfrontDoorEndpointsWithWAFPolicyNotEnabled) { - Write-Host "WAF Policy Enabled for all $($totalfrontDoorEndpointsWithWAFPolicyNotEnabled) Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host "WAF Policy Enabled for all [$($totalfrontDoorEndpointsWithWAFPolicyNotEnabled)] Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) } else { - Write-Host "WAF Policy Enabled for $totalRemediated out of $($totalfrontDoorEndpointsWithWAFPolicyNotEnabled) Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host "WAF Policy Enabled for [$totalRemediated] out of [$($totalfrontDoorEndpointsWithWAFPolicyNotEnabled)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) } $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, @@ -867,8 +882,9 @@ function Enable-WAFPolicyForFrontDoorCDN @{Expression={$_.IsWAFEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} - Write-Host $([Constants]::DoubleDashLine) + Write-Host "Remediation Summary:" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) if($AutoRemediation) { @@ -894,12 +910,13 @@ function Enable-WAFPolicyForFrontDoorCDN { if ($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) { + Write-Host "Successfully enabled WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) $frontDoorEndpointsRemediated | Format-Table -Property $colsProperty -Wrap # Write this to a file. $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForEnabledState.csv" $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation - Write-Host "This information has been saved to $($frontDoorEndpointsRemediatedFile)" + Write-Host "This information has been saved to [$($frontDoorEndpointsRemediatedFile)]" Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) } @@ -911,19 +928,33 @@ function Enable-WAFPolicyForFrontDoorCDN # Write this to a file. $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForEnabledState.csv" $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation - Write-Host "This information has been saved to $($endpointsSkippedFile)" + Write-Host "This information has been saved to [$($endpointsSkippedFile)]" } } + + if($AutoRemediation) + { + $logFile = "LogFiles\"+ $($TimeStamp) + "\log_" + $($SubscriptionId) +".json" + $log = Get-content -Raw -path $logFile | ConvertFrom-Json + foreach($logControl in $log.ControlList){ + if($logControl.ControlId -eq $controlIds){ + $logControl.RemediatedResources=$logRemediatedResources + $logControl.SkippedResources=$logSkippedResources + $logControl.RollbackFile = $frontDoorEndpointsRemediatedFile + } + } + $log | ConvertTo-json -depth 10 | Out-File $logFile + } } else { - Write-Host $([Constants]::DoubleDashLine) - Write-Host "[Step 5 of 5] Enabling WAF Policy for Endpoint(s)..." + + Write-Host "[Step 5 of 5] Enabling WAF Policy for Endpoint(s)" Write-Host $([Constants]::SingleDashLine) Write-Host "Skipped as -DryRun switch is provided." -ForegroundColor $([Constants]::MessageType.Warning) Write-Host $([Constants]::DoubleDashLine) - Write-Host "**Next steps:**" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host "Next steps:" -ForegroundColor $([Constants]::MessageType.Info) Write-Host "Run the same command with -FilePath $($backupFile) and without -DryRun, to Enable WAF Policy for all Front Door CDN Endpoint(s) listed in the file." -ForegroundColor $([Constants]::MessageType.Info) } } @@ -1033,20 +1064,19 @@ function Disable-WAFPolicyForFrontDoorCDN # Note about the required access required for remediation - Write-Host "*** To Disable WAF Policy for all Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required. ***" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::DoubleDashLine) - Write-Host "[Step 2 of 4] Preparing to fetch all Front Door CDN Endpoints..." + Write-Host "To Disable WAF Policy for all Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Write-Host "[Step 2 of 4] Preparing to fetch all Front Door CDN Endpoints" + Write-Host $([Constants]::SingleDashLine) if (-not (Test-Path -Path $FilePath)) { - Write-Host "ERROR: Input file - $($FilePath) not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) break } - Write-Host "Fetching all Front Door CDN Endpoints from $($FilePath)" -ForegroundColor $([Constants]::MessageType.Info) - - Write-Host "Fetching all Front Door CDN Endpoint(s) from $($FilePath)" -ForegroundColor $([Constants]::MessageType.Info) - + Write-Host "Fetching all Front Door CDN Endpoints from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) + $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } @@ -1069,7 +1099,6 @@ function Disable-WAFPolicyForFrontDoorCDN if($null -ne $apiResponse.Content) { $content = $apiResponse.Content | ConvertFrom-Json - $apiResponse.Content | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200 $value = $content.value $totalValues = ($value | Measure-Object).Count for($i=0; $i -lt $totalValues; $i++) @@ -1176,20 +1205,22 @@ function Disable-WAFPolicyForFrontDoorCDN } catch { - Write-Host "Error fetching Front Door CDN Endpoint: ID - $($frontdoorEndpointId). Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]... Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) } } - # Includes Front Door CDN Endpoint(s) where WAF Policy is in Enabled + # Includes Front Door CDN Endpoint(s) where WAF Policy is in Enabled $frontDoorEndpointsWithWAFPolicyEnabled = @() - Write-Host $([Constants]::DoubleDashLine) - Write-Host "[Step 3 of 4] Fetching Endpoint(s)..." - Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is Enabled..." -ForegroundColor $([Constants]::MessageType.Info) + + Write-Host "[Step 3 of 4] Fetching Endpoint(s) for which WAF Policy is enabled" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is enabled..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) $frontDoorEndPoints | ForEach-Object { $endPoint = $_ @@ -1238,8 +1269,9 @@ function Disable-WAFPolicyForFrontDoorCDN } - Write-Host $([Constants]::DoubleDashLine) - Write-Host "[Step 3 of 4] Disabling WAF Policy for Front Door CDNs Endpoint(s) ..." + + Write-Host "[Step 4 of 4] Disabling WAF Policy for Front Door CDNs Endpoint(s)" + Write-Host $([Constants]::SingleDashLine) # Includes Front Door CDN s, to which, previously made changes were successfully rolled back. $frontDoorEndpointsRolledBack = @() @@ -1282,14 +1314,15 @@ function Disable-WAFPolicyForFrontDoorCDN if ($totalfrontDoorEndpointsRolledBack -eq $totalfrontDoorEndpointsWithWAFPolicyEnabled) { - Write-Host "WAF Policy Disabled for all $($totalfrontDoorEndpointsWithWAFPolicyEnabled) Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host "WAF Policy Disabled for all [$($totalfrontDoorEndpointsWithWAFPolicyEnabled)] Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) } else { - Write-Host "WAF Policy disabled for $totalfrontDoorEndpointsRolledBack out of $($totalfrontDoorEndpointsWithWAFPolicyEnabled) Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host "WAF Policy disabled for [$totalfrontDoorEndpointsRolledBack] out of [$($totalfrontDoorEndpointsWithWAFPolicyEnabled)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) } - Write-Host $([Constants]::DoubleDashLine) + Write-Host "Rollback Summary:" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, @@ -1304,6 +1337,7 @@ function Disable-WAFPolicyForFrontDoorCDN if ($($frontDoorEndpointsRolledBack | Measure-Object).Count -gt 0) { + Write-Host "Successfully disabled WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) $frontDoorEndpointsRolledBack | Format-Table -Property $colsProperty -Wrap # Write this to a file. diff --git a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 index 0ee73660..cd4df1c6 100644 --- a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 +++ b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 @@ -66,7 +66,7 @@ function Setup-Prerequisites .DESCRIPTION Checks if the prerequisites are met, else, sets them up. Includes installing any required Azure modules. - + .INPUTS None. You cannot pipe objects to Setup-Prerequisites. @@ -81,10 +81,12 @@ function Setup-Prerequisites #> # List of required modules - $requiredModules = @("Az.Accounts", "Az.Websites", "Az.Resources", "Azure") + $requiredModules = @("Az.Accounts", "Az.FrontDoor", "Az.Cdn") - Write-Host "Required modules: $($requiredModules -join ', ')" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host "Checking if the required modules are present..." + Write-Host "Required modules: $($requiredModules -join ', ')" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Checking if the required modules are present..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) $availableModules = $(Get-Module -ListAvailable $requiredModules -ErrorAction Stop) @@ -92,12 +94,14 @@ function Setup-Prerequisites $requiredModules | ForEach-Object { if ($availableModules.Name -notcontains $_) { - Write-Host "Installing $($_) module..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host "[$($_)] module is not present." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host "Installing [$($_)] module..." -ForegroundColor $([Constants]::MessageType.Info) Install-Module -Name $_ -Scope CurrentUser -Repository 'PSGallery' -ErrorAction Stop + Write-Host "[$($_)] module installed." -ForegroundColor $([Constants]::MessageType.Update) } else { - Write-Host "$($_) module is present." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host "[$($_)] module is present." -ForegroundColor $([Constants]::MessageType.Update) } } } @@ -127,6 +131,15 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints .PARAMETER FilePath Specifies the path to the file to be used as input for the remediation. + .PARAMETER Path + Specifies the path to the file to be used as input for the remediation when AutoRemediation switch is used. + + .PARAMETER AutoRemediation + Specifies script is run as a subroutine of AutoRemediation Script. + + .PARAMETER TimeStamp + Specifies the time of creation of file to be used for logging remediation details when AutoRemediation switch is used. + .INPUTS None. You cannot pipe objects to Enable-WAFPolicyForFrontDoors. @@ -168,49 +181,75 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints [String] [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation")] $FilePath + + [String] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation when AutoRemediation switch is used")] + $Path, + + [Switch] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies script is run as a subroutine of AutoRemediation Script")] + $AutoRemediation, + + [String] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the time of creation of file to be used for logging remediation details when AutoRemediation switch is used")] + $TimeStamp ) Write-Host $([Constants]::DoubleDashLine) - Write-Host "`n[Step 1 of 5] Preparing to configure WAF Policy with Prevention Mode for Front Door CDN Endpoint(s) in Subscription: $($SubscriptionId)" - if ($PerformPreReqCheck) { try { - Write-Host "Setting up prerequisites..." + Write-Host "[Step 1 of 5] Validate and install the modules required to run the script and validate the user" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) Setup-Prerequisites + Write-Host "Completed setting up prerequisites." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) } catch { - Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - break + Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + return } } + else + { + Write-Host "[Step 1 of 5] Validate the user" + Write-Host $([Constants]::SingleDashLine) + } # Connect to Azure account $context = Get-AzContext if ([String]::IsNullOrWhiteSpace($context)) { - Write-Host "No active Azure login session found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) - break + Write-Host "Connecting to Azure account..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null + Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) } - + # Setting up context for the current Subscription. + $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop - # Setting up context for the current Subscription. - $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop + + if(-not($AutoRemediation)) + { + Write-Host "Subscription Name: [$($context.Subscription.Name)]" + Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" + Write-Host "Account Name: [$($context.Account.Id)]" + Write-Host "Account Type: [$($context.Account.Type)]" + Write-Host $([Constants]::SingleDashLine) + } + + Write-Host "To enable Prevention Mode on WAF Policy for Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Write-Host "Subscription Name: $($context.Subscription.Name)" - Write-Host "Subscription ID: $($context.Subscription.SubscriptionId)" - Write-Host "Account Name: $($context.Account.Id)" - Write-Host "Account Type: $($context.Account.Type)" + Write-Host "[Step 2 of 5] Preparing to fetch all Front Door CDNs" Write-Host $([Constants]::SingleDashLine) - Write-Host "*** To enable Prevention Mode on WAF Policy for Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required. ***" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::DoubleDashLine) - Write-Host "`n[Step 2 of 5] Preparing to fetch all Front Door CDNs..." - $frontDoorCDNs = @() $frontDoorEndPoints = @() $resourceAppIdURI = "https://management.azure.com/" @@ -218,102 +257,234 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token $endPointPolicies = New-Object System.Collections.ArrayList + # To keep track of remediated and skipped resources + $logRemediatedResources = @() + $logSkippedResources=@() + + # Control Id + $controlIds = "Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial" - # No file path provided as input to the script. Fetch all Front Door CDNs in the Subscription. - if ([String]::IsNullOrWhiteSpace($FilePath)) + if($AutoRemediation) { - Write-Host "`nFetching all Front Door CDNs in Subscription: $($context.Subscription.SubscriptionId)" -ForegroundColor $([Constants]::MessageType.Info) - - # Get all Front Door CDNs in the Subscription - $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction Stop - $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - - if($totalfrontDoors -gt 0) + if(-not (Test-Path -Path $Path)) + { + Write-Host "File containing failing controls details [$($Path)] not found. Skipping remediation..." -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + return + } + Write-Host "Fetching all Front Doors CDN failing for the [$($controlIds)] control from [$($Path)]..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + $controlForRemediation = Get-content -path $Path | ConvertFrom-Json + $controls = $controlForRemediation.ControlRemediationList + $resourceDetails = $controls | Where-Object { $controlIds -eq $_.ControlId }; + $validResources = $resourceDetails.FailedResourceList | Where-Object {![String]::IsNullOrWhiteSpace($_.ResourceId)} + + if(($resourceDetails | Measure-Object).Count -eq 0 -or ($validResources | Measure-Object).Count -eq 0) + { + Write-Host "No Front Door CDN(s) found in input json file for remediation." -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::SingleDashLine) + return + } + + $validResources | ForEach-Object { + try + { + $name = $_.ResourceName + $resourceGroupName = $_.ResourceGroupName + + if($null -ne $classicAccessToken) + { + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$name) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + { + if($null -ne $apiResponse.Content) + { + $content = $apiResponse.Content | ConvertFrom-Json + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) + { + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) + { + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) + { + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } + } + } + + # Get all Frontendpoint(s) for this Front Door. + $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $name -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }}, + @{N='IsPreventionMode';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + if($WAFPolicy.PolicyMode -eq 'Prevention') + { + $true + } + else + { + $false + + } + } + }}, + @{N='IsWAFEnabled';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + + if($WAFPolicy.PolicyEnabledState -eq 'Enabled') + { + $true + } + else + { + $false + } + } + }} + } + catch + { + Write-Host "Valid resource id(s) not found in input json file. Error: [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host "Skipping the Resource: [$($_.ResourceName)]..." -ForegroundColor $([Constants]::MessageType.Warning) + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.ResourceName)) + $logResource.Add("Reason","Valid resource id(s) not found in input json file.") + $logSkippedResources += $logResource + Write-Host $([Constants]::SingleDashLine) + } + } + } + else + { + # No file path provided as input to the script. Fetch all Front Door CDNs in the Subscription. + if ([String]::IsNullOrWhiteSpace($FilePath)) { - $frontDoorCDNs | ForEach-Object { - $frontDoor = $_ - $frontDoorId = $_.Id - $resourceGroupName = $_.Id.Split('/')[4] - $frontDoorName = $_.Name - - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + Write-Host "Fetching all Front Door CDNs in Subscription: $($context.Subscription.SubscriptionId)" -ForegroundColor $([Constants]::MessageType.Info) + + # Get all Front Door CDNs in the Subscription + $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction Stop + $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count + + if($totalfrontDoors -gt 0) + { + $frontDoorCDNs | ForEach-Object { + $frontDoor = $_ + $frontDoorId = $_.Id + $resourceGroupName = $_.Id.Split('/')[4] + $frontDoorName = $_.Name - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + if($null -ne $classicAccessToken) { - if($null -ne $apiResponse.Content) + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) { - $content = $apiResponse.Content | ConvertFrom-Json - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) + if($null -ne $apiResponse.Content) { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $domains = $value[$i].properties.parameters.associations[0].domains - $totalDomains = ($domains | Measure-Object).Count - for($j=0; $j -lt $totalDomains; $j++) - { - $domain = $domains[$j] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $d = [ordered]@{wafPolicyName= $wafPolicyName ;wafPolicyId= $wafPolicyId ;endpointName= $endpointName } - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } + $content = $apiResponse.Content | ConvertFrom-Json + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) + { + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $domains = $value[$i].properties.parameters.associations[0].domains + $totalDomains = ($domains | Measure-Object).Count + for($j=0; $j -lt $totalDomains; $j++) + { + $domain = $domains[$j] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $d = [ordered]@{wafPolicyName= $wafPolicyName ;wafPolicyId= $wafPolicyId ;endpointName= $endpointName } + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } } - } - # Get all Endpoint(s) for this Front Door CDN. - $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }}, - @{N='IsPreventionMode';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - if($WAFPolicy.PolicyMode -eq 'Prevention') + # Get all Endpoint(s) for this Front Door CDN. + $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) { - $true + $false } else { - $false - + $true } - } - }}, - @{N='IsWAFEnabled';E={ + }}, + @{N='IsPreventionMode';E={ if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) { $false @@ -321,172 +492,194 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints else { $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - - if($WAFPolicy.PolicyEnabledState -eq 'Enabled') + if($WAFPolicy.PolicyMode -eq 'Prevention') { $true } else { $false + } } - }} - + }}, + @{N='IsWAFEnabled';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + + if($WAFPolicy.PolicyEnabledState -eq 'Enabled') + { + $true + } + else + { + $false + } + } + }} + + } } } - } - else - { - if (-not (Test-Path -Path $FilePath)) + else { - Write-Host "ERROR: Input file - $($FilePath) not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) - break - } - - Write-Host "Fetching all Front Door CDN Endpoint(s) from $($FilePath)" -ForegroundColor $([Constants]::MessageType.Info) + if (-not (Test-Path -Path $FilePath)) + { + Write-Host "ERROR: Input file - $($FilePath) not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + break + } - $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath - $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } - - $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName + Write-Host "Fetching all Front Door CDN Endpoint(s) from $($FilePath)" -ForegroundColor $([Constants]::MessageType.Info) - foreach($frontdoor in $uniquefrontDoors) - { - $resourceGroupName = $frontdoor.ResourceGroupName - $frontDoorName = $frontdoor.FrontDoorName + $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath + $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } + + $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName - if($null -ne $classicAccessToken) + foreach($frontdoor in $uniquefrontDoors) { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + $resourceGroupName = $frontdoor.ResourceGroupName + $frontDoorName = $frontdoor.FrontDoorName - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + if($null -ne $classicAccessToken) { - if($null -ne $apiResponse.Content) + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) { - $content = $apiResponse.Content | ConvertFrom-Json - $apiResponse.Content | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200 - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) + if($null -ne $apiResponse.Content) { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) - { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) + $content = $apiResponse.Content | ConvertFrom-Json + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) + { + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) + { + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } + } } } - } - $validfrontDoorEndpointsDetails | ForEach-Object { - $frontdoorEndpointName = $_.EndPointName - $resourceGroupName = $_.ResourceGroupName - $frontDoorName = $_.FrontDoorName + $validfrontDoorEndpointsDetails | ForEach-Object { + $frontdoorEndpointName = $_.EndPointName + $resourceGroupName = $_.ResourceGroupName + $frontDoorName = $_.FrontDoorName - try - { - - $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }}, - @{N='IsPreventionMode';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - if($WAFPolicy.PolicyMode -eq 'Prevention') + try + { + + $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) { - $true + $false } else { - $false - + $true } - } - }}, - @{N='IsWAFEnabled';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + }}, + @{N='IsPreventionMode';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) { $false } else { $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - - if($WAFPolicy.PolicyEnabledState -eq 'Enabled') + if($WAFPolicy.PolicyMode -eq 'Prevention') { $true } else { $false + } } - }} - } - catch - { - Write-Host "Error fetching Front Door CDN Endpoint: ID - $($frontdoorEndpointId). Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) + }}, + @{N='IsWAFEnabled';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + + if($WAFPolicy.PolicyEnabledState -eq 'Enabled') + { + $true + } + else + { + $false + } + } + }} + } + catch + { + Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) + } } } } - if ([String]::IsNullOrWhiteSpace($FilePath)) + if(-not($AutoRemediation)) { - $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - - if ($totalfrontDoors -eq 0) + if ([String]::IsNullOrWhiteSpace($FilePath)) { - Write-Host "No Front Door CDNs found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - break - } + $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - Write-Host "Found $($totalfrontDoors) Front Door CDN(s)." -ForegroundColor $([Constants]::MessageType.Update) - } + if ($totalfrontDoors -eq 0) + { + Write-Host "No Front Door CDNs found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + break + } + + Write-Host "Found [$($totalfrontDoors)] Front Door CDN(s)." -ForegroundColor $([Constants]::MessageType.Update) + } + } $totalfrontDoorEndPoints = ($frontDoorEndPoints | Measure-Object).Count @@ -497,7 +690,7 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints break } - Write-Host "Found $($totalfrontDoorEndPoints) Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host "Found [$($totalfrontDoorEndPoints)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) # Includes Front Door CDN Endpoint(s) where WAF Policy is in Prevention Mode @@ -507,8 +700,9 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints $frontDoorEndpointsSkipped = @() - Write-Host $([Constants]::DoubleDashLine) - Write-Host "`n[Step 3 of 5] Fetching Endpoint(s)..." + + Write-Host "[Step 3 of 5] Fetching Endpoint(s)" + Write-Host $([Constants]::SingleDashLine) Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is not in Prevention Mode..." -ForegroundColor $([Constants]::MessageType.Info) $frontDoorEndPoints | ForEach-Object { @@ -517,6 +711,16 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints { $frontDoorEndpointsWithWAFPolicyNotInPrevention += $endPoint } + else + { + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.FrontDoorName)) + $logResource.Add("EndPointName",($_.EndPointName)) + $logResource.Add("Reason","WAF Policy already in Prevention Mode on endpoint") + $logSkippedResources += $logResource + + } } $totalfrontDoorEndpointsWithWAFPolicyNotInPrevention = ($frontDoorEndpointsWithWAFPolicyNotInPrevention | Measure-Object).Count @@ -524,24 +728,40 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints if ($totalfrontDoorEndpointsWithWAFPolicyNotInPrevention -eq 0) { Write-Host "No Front Door CDN endpoints(s) found where WAF Policy is not in Prevention Mode.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) + Write-Host $([Constants]::DoubleDashLine) + + if($AutoRemediation -and ($frontDoorFrontendPoints |Measure-Object).Count -gt 0) + { + $logFile = "LogFiles\"+ $($TimeStamp) + "\log_" + $($SubscriptionId) +".json" + $log = Get-content -Raw -path $logFile | ConvertFrom-Json + foreach($logControl in $log.ControlList){ + if($logControl.ControlId -eq $controlIds){ + $logControl.RemediatedResources=$logRemediatedResources + $logControl.SkippedResources=$logSkippedResources + } + } + $log | ConvertTo-json -depth 10 | Out-File $logFile + } return } Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyNotInPrevention)] Front Door CDN Endpoints(s) found where WAF Policy is not in Prevention Mode ." -ForegroundColor $([Constants]::MessageType.Update) Write-Host $([Constants]::SingleDashLine) - - Write-Host "`nFollowing Front Door CDN Endpoints(s) are having WAF Policies with Mode not in Prevention:" -ForegroundColor $([Constants]::MessageType.Info) - $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, - @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, - @{Expression={$_.IsPreventionMode};Label="Is Prevention Mode on ";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} - $frontDoorEndpointsWithWAFPolicyNotInPrevention | Format-Table -Property $colsProperty -Wrap + + if(-not($AutoRemediation)) + { + Write-Host "Following Front Door CDN Endpoints(s) are having WAF Policies with Mode not in Prevention:" -ForegroundColor $([Constants]::MessageType.Info) + $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, + @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, + @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, + @{Expression={$_.IsPreventionMode};Label="Is Prevention Mode on ";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} + $frontDoorEndpointsWithWAFPolicyNotInPrevention | Format-Table -Property $colsProperty -Wrap + } # Back up snapshots to `%LocalApplicationData%'. $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\SetFrontDoorCDNPolicyModeToPrevention" @@ -551,15 +771,16 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints New-Item -ItemType Directory -Path $backupFolderPath | Out-Null } - Write-Host $([Constants]::DoubleDashLine) - Write-Host "`n[Step 4 of 5] Backing up Front Door CDN Endpoint(s) details..." + + Write-Host "[Step 4 of 5] Backing up Front Door CDN Endpoint(s) details" + Write-Host $([Constants]::SingleDashLine) if ([String]::IsNullOrWhiteSpace($FilePath)) { # Backing up Front Door CDN Endpoints details. $backupFileForWAFNotInPreventionMode = "$($backupFolderPath)\frontdoorCDNEndpointsWithoutPolicyInPreventionMode.csv" $frontDoorEndpointsWithWAFPolicyNotInPrevention | Export-CSV -Path $backupFileForWAFNotInPreventionMode -NoTypeInformation - Write-Host "Front Door Endpoint(s) details have been successful backed up to $($backupFolderPath)" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host "Front Door Endpoint(s) details have been successful backed up to [$($backupFolderPath)]" -ForegroundColor $([Constants]::MessageType.Update) } else { @@ -568,27 +789,31 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints if (-not $DryRun) { - Write-Host "WAF Policy mode will be switched to Prevention for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Warning) - - if (-not $Force) + if(-not $AutoRemediation) { - Write-Host "Do you want to switch WAF Policy Mode to Prevention from Detection associated with Front Door CDN Endpoint(s)? " -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline - - $userInput = Read-Host -Prompt "(Y|N)" + Write-Host "WAF Policy mode will be switched to Prevention for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Warning) - if($userInput -ne "Y") + if (-not $Force) { - Write-Host " WAF Policy Mode will not be switched to Prevention from Detection for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - break + Write-Host "Do you want to switch WAF Policy Mode to Prevention from Detection associated with Front Door CDN Endpoint(s)? " -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline + + $userInput = Read-Host -Prompt "(Y|N)" + + if($userInput -ne "Y") + { + Write-Host " WAF Policy Mode will not be switched to Prevention from Detection for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + break + } + } + else + { + Write-Host "'Force' flag is provided. WAF Policy Mode will be switched to Prevention from Detection on all Front Door CDN Endoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) } - } - else - { - Write-Host "'Force' flag is provided. WAF Policy Mode will be switched to Prevention from Detection on all Front Door CDN Endoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) } - Write-Host $([Constants]::DoubleDashLine) - Write-Host "`n[Step 5 of 5] Switching Mode to Prevention from Detection for Front Door CDN Endpoint(s)..." + + Write-Host "[Step 5 of 5] Switching Mode to Prevention from Detection for Front Door CDN Endpoint(s)" + Write-Host $([Constants]::SingleDashLine) # To hold results from the remediation. $frontDoorEndpointsRemediated = @() @@ -629,11 +854,11 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints if ($totalRemediated -eq $totalfrontDoorEndpointsWithWAFPolicyNotInPrevention) { - Write-Host "WAF Policy Mode changed to Prevention for all $($totalfrontDoorEndpointsWithWAFPolicyNotInPrevention) Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host "WAF Policy Mode changed to Prevention for all [$($totalfrontDoorEndpointsWithWAFPolicyNotInPrevention)] Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) } else { - Write-Host "WAF Policy Mode changed to Prevention for $totalRemediated out of $($totalfrontDoorEndpointsWithWAFPolicyNotInPrevention) Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host "WAF Policy Mode changed to Prevention for [$totalRemediated] out of [$($totalfrontDoorEndpointsWithWAFPolicyNotInPrevention)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) } $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, @@ -648,39 +873,76 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints Write-Host $([Constants]::DoubleDashLine) - Write-Host "`nRemediation Summary:`n" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host "Remediation Summary:" -ForegroundColor $([Constants]::MessageType.Info) - if ($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) + if($AutoRemediation) { - $frontDoorEndpointsRemediated | Format-Table -Property $colsProperty -Wrap + if($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) + { + # Write this to a file. + $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForPreventionMode.csv" + $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation + Write-Host "The information related to Front door CDN Endpoints(s) where WAF Policy is changed to Prevention Mode has been saved to [$($frontDoorEndpointsRemediatedFile)]. Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } - # Write this to a file. - $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForPreventionMode.csv" - $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation - Write-Host "This information has been saved to $($frontDoorEndpointsRemediatedFile)" - Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) + if ($($endpointsSkipped | Measure-Object).Count -gt 0) + { + # Write this to a file. + $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForPreventionMode.csv" + $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation + Write-Host "The information related to Front door CDN Endpoints(s) where WAF Policy is not changed to Prevention Mode has been saved to [$($endpointsSkippedFile)]." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + } + else + { + if ($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) + { + $frontDoorEndpointsRemediated | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForPreventionMode.csv" + $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation + Write-Host "This information has been saved to [$($frontDoorEndpointsRemediatedFile)]" + Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) + } + + if ($($endpointsSkipped | Measure-Object).Count -gt 0) + { + Write-Host "Error performing remediation steps for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) + $endpointsSkipped | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForPreventionMode.csv" + $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation + Write-Host "This information has been saved to [$($endpointsSkippedFile)]" + } } - if ($($endpointsSkipped | Measure-Object).Count -gt 0) + if($AutoRemediation) { - Write-Host "`nError performing remediation steps for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) - $endpointsSkipped | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForPreventionMode.csv" - $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation - Write-Host "This information has been saved to $($endpointsSkippedFile)" + $logFile = "LogFiles\"+ $($TimeStamp) + "\log_" + $($SubscriptionId) +".json" + $log = Get-content -Raw -path $logFile | ConvertFrom-Json + foreach($logControl in $log.ControlList){ + if($logControl.ControlId -eq $controlIds){ + $logControl.RemediatedResources=$logRemediatedResources + $logControl.SkippedResources=$logSkippedResources + $logControl.RollbackFile = $frontDoorEndpointsRemediatedFile + } + } + $log | ConvertTo-json -depth 10 | Out-File $logFile } } else { - Write-Host $([Constants]::DoubleDashLine) - Write-Host "`n[Step 5 of 5] Switching WAF Policy mode to Prevention for Endpoint(s)..." + + Write-Host "[Step 5 of 5] Switching WAF Policy mode to Prevention for Endpoint(s)" Write-Host $([Constants]::SingleDashLine) Write-Host "Skipped as -DryRun switch is provided." -ForegroundColor $([Constants]::MessageType.Warning) Write-Host $([Constants]::DoubleDashLine) - Write-Host "`n**Next steps:**" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host "Next steps:" -ForegroundColor $([Constants]::MessageType.Info) Write-Host "Run the same command with -FilePath $($backupFile) and without -DryRun, to switch WAF Policy Mode to Prvention for all Front Door CDN Endpoint(s) listed in the file." -ForegroundColor $([Constants]::MessageType.Info) } } @@ -738,49 +1000,67 @@ function Disable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints $FilePath ) + Write-Host $([Constants]::DoubleDashLine) - Write-Host "`n[Step 1 of 4] Preparing to switch Mode to Detection on WAF Policy for Front Door CDN Endpoint(s) in Subscription: $($SubscriptionId)" - if ($PerformPreReqCheck) { try { - Write-Host "Setting up prerequisites..." + Write-Host "[Step 1 of 4] Validate and install the modules required to run the script and validate the user" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) Setup-Prerequisites + Write-Host "Completed setting up prerequisites" + Write-Host $([Constants]::SingleDashLine) } catch { Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - break + Write-Host $([Constants]::DoubleDashLine) + return } } + else + { + Write-Host "[Step 1 of 4] Validate the user" + Write-Host $([Constants]::SingleDashLine) + } # Connect to Azure account $context = Get-AzContext if ([String]::IsNullOrWhiteSpace($context)) { - Write-Host "No active Azure login session found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) - break + + Write-Host "Connecting to Azure account..." + Write-Host $([Constants]::SingleDashLine) + Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null + Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + else + { + # Setting up context for the current Subscription. + $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop } - # Setting up context for the current Subscription. - $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop - - Write-Host $([Constants]::SingleDashLine) - Write-Host "Subscription Name: $($context.Subscription.Name)" - Write-Host "Subscription ID: $($context.Subscription.SubscriptionId)" - Write-Host "Account Name: $($context.Account.Id)" - Write-Host "Account Type: $($context.Account.Type)" + Write-Host "Subscription Name: [$($context.Subscription.Name)]" + Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" + Write-Host "Account Name: [$($context.Account.Id)]" + Write-Host "Account Type: [$($context.Account.Type)]" Write-Host $([Constants]::SingleDashLine) - Write-Host "*** To Switich WAF Policy Mode to Detection for all Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required. ***" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::DoubleDashLine) - Write-Host "`n[Step 2 of 4] Preparing to fetch all Front Door CDN Endpoints..." + # Note about the required access required for remediation + + Write-Host "To Switich WAF Policy Mode to Detection for all Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) + + Write-Host "[Step 2 of 4] Preparing to fetch all Front Door CDN Endpoints" + Write-Host $([Constants]::SingleDashLine) if (-not (Test-Path -Path $FilePath)) { - Write-Host "ERROR: Input file - $($FilePath) not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + Write-Host "ERROR: Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) break } @@ -814,7 +1094,7 @@ function Disable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints if($null -ne $apiResponse.Content) { $content = $apiResponse.Content | ConvertFrom-Json - $apiResponse.Content | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200 + $value = $content.value $totalValues = ($value | Measure-Object).Count for($i=0; $i -lt $totalValues; $i++) @@ -915,8 +1195,8 @@ function Disable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints } catch { - Write-Host "Error fetching Front Door CDN Endpoint: ID - $($frontdoorEndpointId). Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) } } @@ -926,9 +1206,10 @@ function Disable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints $frontDoorEndpointsWithWAFPolicyInPrevention = @() - Write-Host $([Constants]::DoubleDashLine) - Write-Host "`n[Step 3 of 4] Fetching Endpoint(s)..." - Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is in Prevention Mode..." -ForegroundColor $([Constants]::MessageType.Info) + + Write-Host "[Step 3 of 4] Fetching Endpoint(s)" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is in Prevention Mode..." -ForegroundColor $([Constants]::MessageType.Info) $frontDoorEndPoints | ForEach-Object { $endPoint = $_ @@ -979,13 +1260,14 @@ function Disable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints - Write-Host $([Constants]::DoubleDashLine) - Write-Host "`n[Step 3 of 4] Switching WAF Policy mode to Detection for Front Door CDNs Endpoint(s) ..." + + Write-Host "[Step 3 of 4] Switching WAF Policy mode to Detection for Front Door CDNs Endpoint(s)" + Write-Host $([Constants]::SingleDashLine) - # Includes Front Door CDN s, to which, previously made changes were successfully rolled back. + # Includes Front Door CDNs, to which, previously made changes were successfully rolled back. $frontDoorEndpointsRolledBack = @() - # Includes Front Door CDN s that were skipped during roll back. There were errors rolling back the changes made previously. + # Includes Front Door CDNs that were skipped during roll back. There were errors rolling back the changes made previously. $frontDoorEndpointsSkipped = @() @@ -1023,14 +1305,14 @@ function Disable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints if ($totalfrontDoorEndpointsRolledBack -eq $totalfrontDoorEndpointsWithWAFPolicyInPrevention) { - Write-Host "WAF Policy Mode changed to Prevention for all $($totalfrontDoorEndpointsWithWAFPolicyInPrevention) Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host "WAF Policy Mode changed to Prevention for all [$($totalfrontDoorEndpointsWithWAFPolicyInPrevention)] Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) } else { - Write-Host "WAF Policy Mode changed to Detection for $totalfrontDoorEndpointsRolledBack out of $($totalfrontDoorEndpointsWithWAFPolicyInPrevention) Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host "WAF Policy Mode changed to Detection for [$totalfrontDoorEndpointsRolledBack] out of [$($totalfrontDoorEndpointsWithWAFPolicyInPrevention)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) } Write-Host $([Constants]::DoubleDashLine) - Write-Host "`nRollback Summary:" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host "Rollback Summary:" -ForegroundColor $([Constants]::MessageType.Info) $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, @@ -1055,13 +1337,13 @@ function Disable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints if ($($frontDoorEndpointsSkipped | Measure-Object).Count -gt 0) { - Write-Host "`nError Switching WAF Policy Mode to Detection from Prevention for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host "Error Switching WAF Policy Mode to Detection from Prevention for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) $frontDoorEndpointsSkipped | Format-Table -Property $colsProperty -Wrap # Write this to a file. $frontDoorEndpointsSkippedFile = "$($backupFolderPath)\RollbackSkippedEndpointsForWAFPolicyMode.csv" $frontDoorEndpointsSkipped | Export-CSV -Path $frontDoorEndpointsSkippedFile -NoTypeInformation - Write-Host "This information has been saved to $($frontDoorEndpointsSkippedFile)" + Write-Host "This information has been saved to [$($frontDoorEndpointsSkippedFile)]" } } From 59e55c6d9c8b9b18b6f5d2974c734345774f4d52 Mon Sep 17 00:00:00 2001 From: Abhishek P Date: Wed, 14 Dec 2022 02:51:11 +0530 Subject: [PATCH 03/26] Changes --- ...iate-ConfigureWAFPolicyForFrontDoorCDN.ps1 | 785 +++++++----------- 1 file changed, 284 insertions(+), 501 deletions(-) diff --git a/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 index 32d9394e..e3951fc0 100644 --- a/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 +++ b/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 @@ -131,15 +131,6 @@ function Configure-WAFPolicyForFrontDoorCDN .PARAMETER FilePath Specifies the path to the file to be used as input for the remediation. - .PARAMETER Path - Specifies the path to the file to be used as input for the remediation when AutoRemediation switch is used. - - .PARAMETER AutoRemediation - Specifies script is run as a subroutine of AutoRemediation Script. - - .PARAMETER TimeStamp - Specifies the time of creation of file to be used for logging remediation details when AutoRemediation switch is used. - .INPUTS None. You cannot pipe objects to Enable-WAFPolicyForFrontDoors. @@ -180,19 +171,7 @@ function Configure-WAFPolicyForFrontDoorCDN [String] [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation")] - $FilePath, - - [String] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation when AutoRemediation switch is used")] - $Path, - - [Switch] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies script is run as a subroutine of AutoRemediation Script")] - $AutoRemediation, - - [String] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the time of creation of file to be used for logging remediation details when AutoRemediation switch is used")] - $TimeStamp + $FilePath ) Write-Host $([Constants]::DoubleDashLine) @@ -234,16 +213,13 @@ function Configure-WAFPolicyForFrontDoorCDN } # Setting up context for the current Subscription. $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop - - - if(-not($AutoRemediation)) - { + Write-Host "Subscription Name: [$($context.Subscription.Name)]" Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" Write-Host "Account Name: [$($context.Account.Id)]" Write-Host "Account Type: [$($context.Account.Type)]" Write-Host $([Constants]::SingleDashLine) - } + Write-Host " To configure WAF Policy for Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) Write-Host $([Constants]::SingleDashLine) @@ -264,99 +240,109 @@ function Configure-WAFPolicyForFrontDoorCDN # Control Id $controlIds = "Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial" - if($AutoRemediation) + + # No file path provided as input to the script. Fetch all Front Door CDNs in the Subscription. + if ([String]::IsNullOrWhiteSpace($FilePath)) { - if(-not (Test-Path -Path $Path)) + Write-Host "Fetching all Front Door CDNs in Subscription: [$($context.Subscription.SubscriptionId)]..." -ForegroundColor $([Constants]::MessageType.Info) + + # Get all Front Door CDNs in the Subscription + $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction Stop + $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count + + if($totalfrontDoors -gt 0) { - Write-Host "File containing failing controls details [$($Path)] not found. Skipping remediation..." -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - return - } - Write-Host "Fetching all Front Doors CDN failing for the [$($controlIds)] control from [$($Path)]..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - $controlForRemediation = Get-content -path $Path | ConvertFrom-Json - $controls = $controlForRemediation.ControlRemediationList - $resourceDetails = $controls | Where-Object { $controlIds -eq $_.ControlId }; - $validResources = $resourceDetails.FailedResourceList | Where-Object {![String]::IsNullOrWhiteSpace($_.ResourceId)} + $frontDoorCDNs | ForEach-Object { + $frontDoor = $_ + $frontDoorId = $_.Id + $resourceGroupName = $_.Id.Split('/')[4] + $frontDoorName = $_.Name - if(($resourceDetails | Measure-Object).Count -eq 0 -or ($validResources | Measure-Object).Count -eq 0) - { - Write-Host "No Front Door CDN(s) found in input json file for remediation." -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::SingleDashLine) - return - } - - $validResources | ForEach-Object { - try + if($null -ne $classicAccessToken) { - $name = $_.ResourceName - $resourceGroupName = $_.ResourceGroupName + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - if($null -ne $classicAccessToken) + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$name) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + if($null -ne $apiResponse.Content) { - if($null -ne $apiResponse.Content) + $content = $apiResponse.Content | ConvertFrom-Json + + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) { - $content = $apiResponse.Content | ConvertFrom-Json - - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) + { + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } } - - # Get all Frontendpoint(s) for this Front Door. - $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $name -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + } + + + # Get all Endpoint(s) for this Front Door CDN. + $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }}, + @{N='IsPreventionMode';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + if($WAFPolicy.PolicyMode -eq 'Prevention') { - $false + $true } else { - $true + $false + } - }}, - @{N='IsPreventionMode';E={ + } + }}, + @{N='IsWAFEnabled';E={ if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) { $false @@ -364,333 +350,177 @@ function Configure-WAFPolicyForFrontDoorCDN else { $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - if($WAFPolicy.PolicyMode -eq 'Prevention') + + if($WAFPolicy.PolicyEnabledState -eq 'Enabled') { $true } else { $false - } } - }}, - @{N='IsWAFEnabled';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - - if($WAFPolicy.PolicyEnabledState -eq 'Enabled') - { - $true - } - else - { - $false - } - } - }} - } - catch - { - Write-Host "Valid resource id(s) not found in input json file. Error: [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host "Skipping the Resource: [$($_.ResourceName)]..." -ForegroundColor $([Constants]::MessageType.Warning) - $logResource = @{} - $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) - $logResource.Add("ResourceName",($_.ResourceName)) - $logResource.Add("Reason","Valid resource id(s) not found in input json file.") - $logSkippedResources += $logResource - Write-Host $([Constants]::SingleDashLine) + }} } } } else - { - # No file path provided as input to the script. Fetch all Front Door CDNs in the Subscription. - if ([String]::IsNullOrWhiteSpace($FilePath)) + { + if (-not (Test-Path -Path $FilePath)) { - Write-Host "Fetching all Front Door CDNs in Subscription: [$($context.Subscription.SubscriptionId)]..." -ForegroundColor $([Constants]::MessageType.Info) - - # Get all Front Door CDNs in the Subscription - $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction Stop - $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - - if($totalfrontDoors -gt 0) - { - $frontDoorCDNs | ForEach-Object { - $frontDoor = $_ - $frontDoorId = $_.Id - $resourceGroupName = $_.Id.Split('/')[4] - $frontDoorName = $_.Name + Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + break + } - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + Write-Host "Fetching all Front Door CDN Endpoint(s) from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) - { - if($null -ne $apiResponse.Content) - { - $content = $apiResponse.Content | ConvertFrom-Json - - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) - { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } - } + $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath + $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } + $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName - # Get all Endpoint(s) for this Front Door CDN. - $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }}, - @{N='IsPreventionMode';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - if($WAFPolicy.PolicyMode -eq 'Prevention') - { - $true - } - else - { - $false - - } - } - }}, - @{N='IsWAFEnabled';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - - if($WAFPolicy.PolicyEnabledState -eq 'Enabled') - { - $true - } - else - { - $false - } - } - }} - } - } - } - else + foreach($frontdoor in $uniquefrontDoors) { - if (-not (Test-Path -Path $FilePath)) - { - Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - break - } - - Write-Host "Fetching all Front Door CDN Endpoint(s) from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) - - $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath - $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } - - $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName + $resourceGroupName = $frontdoor.ResourceGroupName + $frontDoorName = $frontdoor.FrontDoorName - foreach($frontdoor in $uniquefrontDoors) + if($null -ne $classicAccessToken) { - $resourceGroupName = $frontdoor.ResourceGroupName - $frontDoorName = $frontdoor.FrontDoorName + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - if($null -ne $classicAccessToken) + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + if($null -ne $apiResponse.Content) { - if($null -ne $apiResponse.Content) + $content = $apiResponse.Content | ConvertFrom-Json + + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) { - $content = $apiResponse.Content | ConvertFrom-Json - - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) + { + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } } } + } - $validfrontDoorEndpointsDetails | ForEach-Object { - $frontdoorEndpointName = $_.EndPointName - $resourceGroupName = $_.ResourceGroupName - $frontDoorName = $_.FrontDoorName + $validfrontDoorEndpointsDetails | ForEach-Object { + $frontdoorEndpointName = $_.EndPointName + $resourceGroupName = $_.ResourceGroupName + $frontDoorName = $_.FrontDoorName - try - { - $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + try + { + $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }}, + @{N='IsPreventionMode';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + if($WAFPolicy.PolicyMode -eq 'Prevention') { - $false + $true } else { - $true + $false + } - }}, - @{N='IsPreventionMode';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + } + }}, + @{N='IsWAFEnabled';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) { $false } else { $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - if($WAFPolicy.PolicyMode -eq 'Prevention') + + if($WAFPolicy.PolicyEnabledState -eq 'Enabled') { $true } else { $false - } } - }}, - @{N='IsWAFEnabled';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - - if($WAFPolicy.PolicyEnabledState -eq 'Enabled') - { - $true - } - else - { - $false - } - } - }} - } - catch - { - Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) - } + }} + } + catch + { + Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) } } } + - if(-not($AutoRemediation)) + + if ([String]::IsNullOrWhiteSpace($FilePath)) { - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count + $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - if ($totalfrontDoors -eq 0) - { - Write-Host "No Front Door CDNs found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::DoubleDashLine) - break - } - - Write-Host "Found [$($totalfrontDoors)] Front Door CDN(s)." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) + if ($totalfrontDoors -eq 0) + { + Write-Host "No Front Door CDNs found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::DoubleDashLine) + break } + + Write-Host "Found [$($totalfrontDoors)] Front Door CDN(s)." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) } + $totalfrontDoorEndPoints = ($frontDoorEndPoints | Measure-Object).Count @@ -740,40 +570,26 @@ function Configure-WAFPolicyForFrontDoorCDN if ($totalfrontDoorEndpointsWithWAFPolicyNotConfigured -eq 0) { Write-Host "No Front Door CDN endpoints(s) found where WAF Policy is not configured. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - - if($AutoRemediation -and ($frontDoorFrontendPoints |Measure-Object).Count -gt 0) - { - $logFile = "LogFiles\"+ $($TimeStamp) + "\log_" + $($SubscriptionId) +".json" - $log = Get-content -Raw -path $logFile | ConvertFrom-Json - foreach($logControl in $log.ControlList){ - if($logControl.ControlId -eq $controlIds){ - $logControl.RemediatedResources=$logRemediatedResources - $logControl.SkippedResources=$logSkippedResources - } - } - $log | ConvertTo-json -depth 10 | Out-File $logFile - } + Write-Host $([Constants]::DoubleDashLine) return } Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door CDN Endpoints(s) found where WAF Policy is not configured ." -ForegroundColor $([Constants]::MessageType.Update) Write-Host $([Constants]::SingleDashLine) - if(-not($AutoRemediation)) - { - Write-Host "Following Front Door CDN Endpoints(s) are having WAF Policies not configured:" -ForegroundColor $([Constants]::MessageType.Info) - $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, - @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, - @{Expression={$_.IsPreventionMode};Label="Is Prevention Mode on ";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} - $frontDoorEndpointsWithWAFPolicyNotConfigured | Format-Table -Property $colsProperty -Wrap - } + + Write-Host "Following Front Door CDN Endpoints(s) are having WAF Policies not configured:" -ForegroundColor $([Constants]::MessageType.Info) + $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, + @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, + @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, + @{Expression={$_.IsPreventionMode};Label="Is Prevention Mode on ";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} + $frontDoorEndpointsWithWAFPolicyNotConfigured | Format-Table -Property $colsProperty -Wrap + # Back up snapshots to `%LocalApplicationData%'. $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\ConfigureFrontDoorCDNWAFPolicy" @@ -802,34 +618,33 @@ function Configure-WAFPolicyForFrontDoorCDN if (-not $DryRun) { - if(-not $AutoRemediation) - { - Write-Host "WAF Policy will be configured for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Warning) + + Write-Host "WAF Policy will be configured for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Warning) - if (-not $Force) + if (-not $Force) + { + Write-Host "Do you want to configure WAF Policy associated with Front Door CDN Endpoint(s)? " -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline + Write-Host $([Constants]::SingleDashLine) + $userInput = Read-Host -Prompt "(Y|N)" + Write-Host $([Constants]::SingleDashLine) + if($userInput -ne "Y") { - Write-Host "Do you want to configure WAF Policy associated with Front Door CDN Endpoint(s)? " -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline - Write-Host $([Constants]::SingleDashLine) - $userInput = Read-Host -Prompt "(Y|N)" - Write-Host $([Constants]::SingleDashLine) - if($userInput -ne "Y") - { - Write-Host "WAF Policy will not be configured for any Front Door CDN Endpoint(s). Exiting." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::DoubleDashLine) - break - } - else - { - Write-Host "WAF Policy will be configured for all Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } + Write-Host "WAF Policy will not be configured for any Front Door CDN Endpoint(s). Exiting." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::DoubleDashLine) + break } else { - Write-Host "'Force' flag is provided. WAF Policy will be configured on all Front Door CDN Endoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host "WAF Policy will be configured for all Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) Write-Host $([Constants]::SingleDashLine) } } + else + { + Write-Host "'Force' flag is provided. WAF Policy will be configured on all Front Door CDN Endoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + Write-Host "[Step 5 of 5] Configuring WAF Policy for Front Door CDN Endpoint(s)" @@ -838,26 +653,7 @@ function Configure-WAFPolicyForFrontDoorCDN $frontDoorEndpointsRemediated = @() $endpointsSkipped = @() $otherPolicyEndpointsAssociations = @() - Do - { - $wafPolicyName = Read-Host -Prompt "Enter WAF Policy Name" - $policyResourceGroup = Read-Host -Prompt "Enter WAF Policy Resource Group" - $policy = Get-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $policyResourceGroup -ErrorAction SilentlyContinue - - if($policy -eq $null) - { - Write-Host "WAF Policy name or WAF Policy Resource Group Name is not correct. Please enter correct details." - Write-Host $([Constants]::SingleDashLine) - } - - if($policy -ne $null -and $policy.Sku -ne 'Standard_AzureFrontDoor') - { - Write-Host "WAF Policy is not of type Front door tier Standard . Please enter correct details." - Write-Host $([Constants]::SingleDashLine) - } - } - while($policy.Sku -ne 'Standard_AzureFrontDoor' -or $policy -eq $null) - $wafPolicyId = $policy.Id + # Remidiate Controls by configuring WAF Policy $frontDoorEndpointsWithWAFPolicyNotConfigured | ForEach-Object { @@ -869,6 +665,28 @@ function Configure-WAFPolicyForFrontDoorCDN try { + Do + { + $wafPolicyName = Read-Host -Prompt "Enter WAF Policy Name for Endpoint: [$($_.EndPointName)] of Frontdoor [$($frontdoorName)] " + $policyResourceGroup = Read-Host -Prompt "Enter WAF Policy Resource Group for Endpoint: [$($_.EndPointName)] of Frontdoor [$($frontdoorName)] " + $policy = Get-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $policyResourceGroup -ErrorAction SilentlyContinue + + if($policy -eq $null) + { + Write-Host "WAF Policy name or WAF Policy Resource Group Name is not correct. Please enter correct details." + Write-Host $([Constants]::SingleDashLine) + } + + if($policy -ne $null -and $policy.Sku -ne 'Standard_AzureFrontDoor') + { + Write-Host "WAF Policy is not of type Front door tier Standard . Please enter correct details." + Write-Host $([Constants]::SingleDashLine) + } + + } + while($policy.Sku -ne 'Standard_AzureFrontDoor' -or $policy -eq $null) + $wafPolicyId = $policy.Id + $endpoint = Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontdoorName -EndpointName $endpointName $updateAssociation = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$($endpoint.Id)}) $updateAssociations = @() @@ -945,70 +763,35 @@ function Configure-WAFPolicyForFrontDoorCDN Write-Host "Remediation Summary:" -ForegroundColor $([Constants]::MessageType.Info) Write-Host $([Constants]::SingleDashLine) - if($AutoRemediation) + + if ($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) { - if($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) - { - # Write this to a file. - $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv" - $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation - Write-Host "The information related to Front door CDN Endpoints(s) where WAF Policy is configured has been saved to [$($frontDoorEndpointsRemediatedFile)]. Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - - if ($($endpointsSkipped | Measure-Object).Count -gt 0) - { - # Write this to a file. - $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv" - $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation - Write-Host "The information related to Front door CDN Endpoints(s) where WAF Policy is not configured has been saved to [$($endpointsSkippedFile)]." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - } - else - { - if ($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) - { - Write-Host "Successfully configured WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndpointsRemediated | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv" - $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation - Write-Host "This information has been saved to [$($frontDoorEndpointsRemediatedFile)]" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - } + Write-Host "Successfully configured WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + $frontDoorEndpointsRemediated | Format-Table -Property $colsProperty -Wrap - if ($($endpointsSkipped | Measure-Object).Count -gt 0) - { - Write-Host "Error performing remediation steps for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::SingleDashLine) - $endpointsSkipped | Format-Table -Property $colsProperty -Wrap - Write-Host $([Constants]::SingleDashLine) - # Write this to a file. - $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv" - $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation - Write-Host "This information has been saved to [$($endpointsSkippedFile)]" - Write-Host $([Constants]::SingleDashLine) - } + # Write this to a file. + $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv" + $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation + Write-Host "This information has been saved to [$($frontDoorEndpointsRemediatedFile)]" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) } - if($AutoRemediation) + if ($($endpointsSkipped | Measure-Object).Count -gt 0) { - $logFile = "LogFiles\"+ $($TimeStamp) + "\log_" + $($SubscriptionId) +".json" - $log = Get-content -Raw -path $logFile | ConvertFrom-Json - foreach($logControl in $log.ControlList){ - if($logControl.ControlId -eq $controlIds){ - $logControl.RemediatedResources=$logRemediatedResources - $logControl.SkippedResources=$logSkippedResources - $logControl.RollbackFile = $frontDoorEndpointsRemediatedFile - } - } - $log | ConvertTo-json -depth 10 | Out-File $logFile + Write-Host "Error performing remediation steps for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::SingleDashLine) + $endpointsSkipped | Format-Table -Property $colsProperty -Wrap + Write-Host $([Constants]::SingleDashLine) + # Write this to a file. + $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv" + $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation + Write-Host "This information has been saved to [$($endpointsSkippedFile)]" + Write-Host $([Constants]::SingleDashLine) } + } else { From e0eb276f10495ee7684b73ea9414270658b6c1b4 Mon Sep 17 00:00:00 2001 From: Abhishek P Date: Wed, 14 Dec 2022 10:09:40 +0530 Subject: [PATCH 04/26] Changes --- .../Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 | 2 +- .../Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 | 2 +- .../Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 index e3951fc0..02aa3896 100644 --- a/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 +++ b/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 @@ -247,7 +247,7 @@ function Configure-WAFPolicyForFrontDoorCDN Write-Host "Fetching all Front Door CDNs in Subscription: [$($context.Subscription.SubscriptionId)]..." -ForegroundColor $([Constants]::MessageType.Info) # Get all Front Door CDNs in the Subscription - $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction Stop + $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction SilentlyContinue $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count if($totalfrontDoors -gt 0) diff --git a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 index 445f8c1c..85f9e3c9 100644 --- a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 +++ b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 @@ -414,7 +414,7 @@ function Enable-WAFPolicyForFrontDoorCDN Write-Host "Fetching all Front Door CDNs in Subscription: [$($context.Subscription.SubscriptionId)]..." -ForegroundColor $([Constants]::MessageType.Info) # Get all Front Door CDNs in the Subscription - $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction Stop + $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction SilentlyContinue $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count if($totalfrontDoors -gt 0) diff --git a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 index cd4df1c6..9507f572 100644 --- a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 +++ b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 @@ -415,7 +415,7 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints Write-Host "Fetching all Front Door CDNs in Subscription: $($context.Subscription.SubscriptionId)" -ForegroundColor $([Constants]::MessageType.Info) # Get all Front Door CDNs in the Subscription - $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction Stop + $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction SilentlyContinue $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count if($totalfrontDoors -gt 0) From b599c5c0ab6c95c04d7ced864b8a1a1d4eb508b1 Mon Sep 17 00:00:00 2001 From: Abhishek P Date: Wed, 14 Dec 2022 13:09:46 +0530 Subject: [PATCH 05/26] changes --- ...iate-ConfigureWAFPolicyForFrontDoorCDN.ps1 | 153 +--- ...mediate-EnableWAFPolicyForFrontDoorCDN.ps1 | 759 +++++++----------- ...WAFPolicyPreventionModeForFrontDoorCDN.ps1 | 742 ++++++----------- 3 files changed, 548 insertions(+), 1106 deletions(-) diff --git a/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 index 02aa3896..489a8dd8 100644 --- a/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 +++ b/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 @@ -1,9 +1,9 @@ <### # Overview: - This script is used to configure WAF Policy on All endpoints of Front Door CDNs in a Subscription. + This script is used to configure WAF Policy on all endpoints of Front Door CDNs in a Subscription. # Control ID: - Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial + Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration # Display Name: Front Door should have Web Application Firewall configured @@ -110,10 +110,10 @@ function Configure-WAFPolicyForFrontDoorCDN { <# .SYNOPSIS - Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. .DESCRIPTION - Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. WAF Policy must be configured for Front Door CDN Endpoint(s). .PARAMETER SubscriptionId @@ -238,7 +238,7 @@ function Configure-WAFPolicyForFrontDoorCDN $logSkippedResources=@() # Control Id - $controlIds = "Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial" + $controlIds = "Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration" # No file path provided as input to the script. Fetch all Front Door CDNs in the Subscription. @@ -322,45 +322,7 @@ function Configure-WAFPolicyForFrontDoorCDN { $true } - }}, - @{N='IsPreventionMode';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - if($WAFPolicy.PolicyMode -eq 'Prevention') - { - $true - } - else - { - $false - - } - } - }}, - @{N='IsWAFEnabled';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - - if($WAFPolicy.PolicyEnabledState -eq 'Enabled') - { - $true - } - else - { - $false - } - } - }} + }} } } } @@ -455,45 +417,7 @@ function Configure-WAFPolicyForFrontDoorCDN { $true } - }}, - @{N='IsPreventionMode';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - if($WAFPolicy.PolicyMode -eq 'Prevention') - { - $true - } - else - { - $false - - } - } - }}, - @{N='IsWAFEnabled';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - - if($WAFPolicy.PolicyEnabledState -eq 'Enabled') - { - $true - } - else - { - $false - } - } - }} + }} } catch { @@ -585,9 +509,8 @@ function Configure-WAFPolicyForFrontDoorCDN @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, - @{Expression={$_.IsPreventionMode};Label="Is Prevention Mode on ";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} + @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"} + $frontDoorEndpointsWithWAFPolicyNotConfigured | Format-Table -Property $colsProperty -Wrap # Back up snapshots to `%LocalApplicationData%'. @@ -668,7 +591,7 @@ function Configure-WAFPolicyForFrontDoorCDN Do { $wafPolicyName = Read-Host -Prompt "Enter WAF Policy Name for Endpoint: [$($_.EndPointName)] of Frontdoor [$($frontdoorName)] " - $policyResourceGroup = Read-Host -Prompt "Enter WAF Policy Resource Group for Endpoint: [$($_.EndPointName)] of Frontdoor [$($frontdoorName)] " + $policyResourceGroup = Read-Host -Prompt "Enter WAF Policy Resource Group Name for Endpoint: [$($_.EndPointName)] of Frontdoor [$($frontdoorName)] " $policy = Get-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $policyResourceGroup -ErrorAction SilentlyContinue if($policy -eq $null) @@ -754,10 +677,8 @@ function Configure-WAFPolicyForFrontDoorCDN @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, - @{Expression={$_.IsPreventionMode};Label="Is Prevention Mode on ";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} - + @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"} + Write-Host "Remediation Summary:" -ForegroundColor $([Constants]::MessageType.Info) @@ -811,10 +732,10 @@ function Remove-WAFPolicyForFrontDoorCDN { <# .SYNOPSIS - Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. .DESCRIPTION - Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. Removes configured WAF Policy for all WAF Policies in all Front Door CDN s in the Subscription. .PARAMETER SubscriptionId @@ -1012,45 +933,7 @@ function Remove-WAFPolicyForFrontDoorCDN { $true } - }}, - @{N='IsPreventionMode';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - if($WAFPolicy.PolicyMode -eq 'Prevention') - { - $true - } - else - { - $false - - } - } - }}, - @{N='IsWAFEnabled';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - - if($WAFPolicy.PolicyEnabledState -eq 'Enabled') - { - $true - } - else - { - $false - } - } - }} + }} } catch { @@ -1235,10 +1118,8 @@ function Remove-WAFPolicyForFrontDoorCDN @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, - @{Expression={$_.IsPreventionMode};Label="Is Prevention Mode on ";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} - + @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"} + if ($($frontDoorEndpointsRolledBack | Measure-Object).Count -gt 0) { diff --git a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 index 85f9e3c9..0e691909 100644 --- a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 +++ b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 @@ -1,9 +1,9 @@ <### # Overview: - This script is used to enable WAF Policy on All endpoints of Front Door CDNs in a Subscription. + This script is used to enable state of the WAF Policy configured on endpoints of Front Door CDNs in a Subscription. # Control ID: - Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial + Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration # Display Name: Front Door should have Web Application Firewall configured @@ -110,10 +110,10 @@ function Enable-WAFPolicyForFrontDoorCDN { <# .SYNOPSIS - Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. .DESCRIPTION - Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. WAF Policy must be Enabled for Front Door CDN Endpoint(s). .PARAMETER SubscriptionId @@ -130,15 +130,6 @@ function Enable-WAFPolicyForFrontDoorCDN .PARAMETER FilePath Specifies the path to the file to be used as input for the remediation. - - .PARAMETER Path - Specifies the path to the file to be used as input for the remediation when AutoRemediation switch is used. - - .PARAMETER AutoRemediation - Specifies script is run as a subroutine of AutoRemediation Script. - - .PARAMETER TimeStamp - Specifies the time of creation of file to be used for logging remediation details when AutoRemediation switch is used. .INPUTS None. You cannot pipe objects to Enable-WAFPolicyForFrontDoors. @@ -180,19 +171,7 @@ function Enable-WAFPolicyForFrontDoorCDN [String] [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation")] - $FilePath, - - [String] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation when AutoRemediation switch is used")] - $Path, - - [Switch] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies script is run as a subroutine of AutoRemediation Script")] - $AutoRemediation, - - [String] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the time of creation of file to be used for logging remediation details when AutoRemediation switch is used")] - $TimeStamp + $FilePath ) Write-Host $([Constants]::DoubleDashLine) @@ -235,15 +214,12 @@ function Enable-WAFPolicyForFrontDoorCDN # Setting up context for the current Subscription. $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop - - if(-not($AutoRemediation)) - { Write-Host "Subscription Name: [$($context.Subscription.Name)]" Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" Write-Host "Account Name: [$($context.Account.Id)]" Write-Host "Account Type: [$($context.Account.Type)]" Write-Host $([Constants]::SingleDashLine) - } + Write-Host "To enable WAF Policy for Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) Write-Host $([Constants]::SingleDashLine) @@ -261,235 +237,110 @@ function Enable-WAFPolicyForFrontDoorCDN $logSkippedResources=@() # Control Id - $controlIds = "Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial" - - if($AutoRemediation) - { - if(-not (Test-Path -Path $Path)) - { - Write-Host "File containing failing controls details [$($Path)] not found. Skipping remediation..." -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - return - } - Write-Host "Fetching all Front Doors CDN failing for the [$($controlIds)] control from [$($Path)]..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - $controlForRemediation = Get-content -path $Path | ConvertFrom-Json - $controls = $controlForRemediation.ControlRemediationList - $resourceDetails = $controls | Where-Object { $controlIds -eq $_.ControlId }; - $validResources = $resourceDetails.FailedResourceList | Where-Object {![String]::IsNullOrWhiteSpace($_.ResourceId)} - - if(($resourceDetails | Measure-Object).Count -eq 0 -or ($validResources | Measure-Object).Count -eq 0) - { - Write-Host "No Front Door CDN(s) found in input json file for remediation." -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::SingleDashLine) - return - } - - $validResources | ForEach-Object { - try - { - $name = $_.ResourceName - $resourceGroupName = $_.ResourceGroupName + $controlIds = "Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration" - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$name) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) - { - if($null -ne $apiResponse.Content) - { - $content = $apiResponse.Content | ConvertFrom-Json - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) - { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } - } - - # Get all Frontendpoint(s) for this Front Door. - $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $name -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }}, - @{N='IsPreventionMode';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - if($WAFPolicy.PolicyMode -eq 'Prevention') - { - $true - } - else - { - $false - - } - } - }}, - @{N='IsWAFEnabled';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - - if($WAFPolicy.PolicyEnabledState -eq 'Enabled') - { - $true - } - else - { - $false - } - } - }} - } - catch - { - Write-Host "Valid resource id(s) not found in input json file. Error: [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host "Skipping the Resource: [$($_.ResourceName)]..." -ForegroundColor $([Constants]::MessageType.Warning) - $logResource = @{} - $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) - $logResource.Add("ResourceName",($_.ResourceName)) - $logResource.Add("Reason","Valid resource id(s) not found in input json file.") - $logSkippedResources += $logResource - Write-Host $([Constants]::SingleDashLine) - } - } - } - else + + # No file path provided as input to the script. Fetch all Front Door CDNs in the Subscription. + if ([String]::IsNullOrWhiteSpace($FilePath)) { - # No file path provided as input to the script. Fetch all Front Door CDNs in the Subscription. - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - Write-Host "Fetching all Front Door CDNs in Subscription: [$($context.Subscription.SubscriptionId)]..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host "Fetching all Front Door CDNs in Subscription: [$($context.Subscription.SubscriptionId)]..." -ForegroundColor $([Constants]::MessageType.Info) + + # Get all Front Door CDNs in the Subscription + $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction SilentlyContinue + $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - # Get all Front Door CDNs in the Subscription - $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction SilentlyContinue - $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - - if($totalfrontDoors -gt 0) + if($totalfrontDoors -gt 0) + { + $frontDoorCDNs | ForEach-Object { + $frontDoor = $_ + $frontDoorId = $_.Id + $resourceGroupName = $_.Id.Split('/')[4] + $frontDoorName = $_.Name + + if($null -ne $classicAccessToken) { - $frontDoorCDNs | ForEach-Object { - $frontDoor = $_ - $frontDoorId = $_.Id - $resourceGroupName = $_.Id.Split('/')[4] - $frontDoorName = $_.Name + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - if($null -ne $classicAccessToken) + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + if($null -ne $apiResponse.Content) { - if($null -ne $apiResponse.Content) + $content = $apiResponse.Content | ConvertFrom-Json + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) { - $content = $apiResponse.Content | ConvertFrom-Json - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) + { + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } } + } - # Get all Endpoint(s) for this Front Door CDN. - $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + # Get all Endpoint(s) for this Front Door CDN. + $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }}, + @{N='IsPreventionMode';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + if($WAFPolicy.PolicyMode -eq 'Prevention') { - $false + $true } else { - $true + $false + } - }}, - @{N='IsPreventionMode';E={ + } + }}, + @{N='IsWAFPolicyStateEnabled';E={ if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) { $false @@ -497,195 +348,175 @@ function Enable-WAFPolicyForFrontDoorCDN else { $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - if($WAFPolicy.PolicyMode -eq 'Prevention') + + if($WAFPolicy.PolicyEnabledState -eq 'Enabled') { $true } else { $false - } } - }}, - @{N='IsWAFEnabled';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - - if($WAFPolicy.PolicyEnabledState -eq 'Enabled') - { - $true - } - else - { - $false - } - } - }} - - } + }} + } } - else + } + else + { + if (-not (Test-Path -Path $FilePath)) { - if (-not (Test-Path -Path $FilePath)) - { - Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) - break - } + Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + break + } - Write-Host "Fetching all Front Door CDN Endpoint(s) from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host "Fetching all Front Door CDN Endpoint(s) from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) - $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath - $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } - - $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName + $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath + $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } + + $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName - foreach($frontdoor in $uniquefrontDoors) + foreach($frontdoor in $uniquefrontDoors) + { + $resourceGroupName = $frontdoor.ResourceGroupName + $frontDoorName = $frontdoor.FrontDoorName + if($null -ne $classicAccessToken) { - $resourceGroupName = $frontdoor.ResourceGroupName - $frontDoorName = $frontdoor.FrontDoorName - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + { + if($null -ne $apiResponse.Content) { - if($null -ne $apiResponse.Content) + $content = $apiResponse.Content | ConvertFrom-Json + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) { - $content = $apiResponse.Content | ConvertFrom-Json - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) + { + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } } - } - $validfrontDoorEndpointsDetails | ForEach-Object { - $frontdoorEndpointName = $_.EndPointName - $resourceGroupName = $_.ResourceGroupName - $frontDoorName = $_.FrontDoorName + } - try - { - - - $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + $validfrontDoorEndpointsDetails | ForEach-Object { + $frontdoorEndpointName = $_.EndPointName + $resourceGroupName = $_.ResourceGroupName + $frontDoorName = $_.FrontDoorName + + try + { + + + $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }}, + @{N='IsPreventionMode';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + if($WAFPolicy.PolicyMode -eq 'Prevention') { - $false + $true } else { - $true + $false + } - }}, - @{N='IsPreventionMode';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + } + }}, + @{N='IsWAFPolicyStateEnabled';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) { $false } else { $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - if($WAFPolicy.PolicyMode -eq 'Prevention') + + if($WAFPolicy.PolicyEnabledState -eq 'Enabled') { $true } else { $false - } } - }}, - @{N='IsWAFEnabled';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - - if($WAFPolicy.PolicyEnabledState -eq 'Enabled') - { - $true - } - else - { - $false - } - } - }} - } - catch - { - Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) - } + }} + } + catch + { + Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) } } } + - if(-not($AutoRemediation)) + + if ([String]::IsNullOrWhiteSpace($FilePath)) { - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count + $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - if ($totalfrontDoors -eq 0) - { - Write-Host "No Front Door CDNs found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - break - } - - Write-Host "Found $($totalfrontDoors) Front Door CDN(s)." -ForegroundColor $([Constants]::MessageType.Update) + if ($totalfrontDoors -eq 0) + { + Write-Host "No Front Door CDNs found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + break } + + Write-Host "Found $($totalfrontDoors) Front Door CDN(s)." -ForegroundColor $([Constants]::MessageType.Update) } + $totalfrontDoorEndPoints = ($frontDoorEndPoints | Measure-Object).Count @@ -712,7 +543,7 @@ function Enable-WAFPolicyForFrontDoorCDN Write-Host $([Constants]::SingleDashLine) $frontDoorEndPoints | ForEach-Object { $endPoint = $_ - if(($_.IsWAFEnabled -eq $false) -and ($_.IsWAFConfigured -eq $true)) + if(($_.IsWAFPolicyStateEnabled -eq $false) -and ($_.IsWAFConfigured -eq $true)) { $frontDoorEndpointsWithWAFPolicyNotEnabled += $endPoint } @@ -734,39 +565,24 @@ function Enable-WAFPolicyForFrontDoorCDN { Write-Host "No Front Door CDN endpoints(s) found where WAF Policy is not Enabled.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) Write-Host $([Constants]::DoubleDashLine) - - if($AutoRemediation -and ($frontDoorFrontendPoints |Measure-Object).Count -gt 0) - { - $logFile = "LogFiles\"+ $($TimeStamp) + "\log_" + $($SubscriptionId) +".json" - $log = Get-content -Raw -path $logFile | ConvertFrom-Json - foreach($logControl in $log.ControlList){ - if($logControl.ControlId -eq $controlIds){ - $logControl.RemediatedResources=$logRemediatedResources - $logControl.SkippedResources=$logSkippedResources - } - } - $log | ConvertTo-json -depth 10 | Out-File $logFile - } return } Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyNotEnabled)] Front Door CDN Endpoints(s) found where WAF Policy is not Enabled ." -ForegroundColor $([Constants]::MessageType.Update) Write-Host $([Constants]::SingleDashLine) - if(-not($AutoRemediation)) - { - Write-Host "Following Front Door CDN Endpoints(s) are having WAF Policies not Enabled:" -ForegroundColor $([Constants]::MessageType.Info) - $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, - @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, - @{Expression={$_.IsPreventionMode};Label="Is Prevention Mode on ";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} - $frontDoorEndpointsWithWAFPolicyNotEnabled | Format-Table -Property $colsProperty -Wrap - } + + Write-Host "Following Front Door CDN Endpoints(s) are having WAF Policies not Enabled:" -ForegroundColor $([Constants]::MessageType.Info) + $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, + @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, + @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFPolicyStateEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} + $frontDoorEndpointsWithWAFPolicyNotEnabled | Format-Table -Property $colsProperty -Wrap + # Back up snapshots to `%LocalApplicationData%'. $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\EnableFrontDoorCDNWAFPolicy" @@ -794,32 +610,31 @@ function Enable-WAFPolicyForFrontDoorCDN if (-not $DryRun) { - if(-not $AutoRemediation) + + Write-Host "WAF Policy will be Enabled for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Warning) + + if (-not $Force) { - Write-Host "WAF Policy will be Enabled for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host "Do you want to Enable WAF Policies associated with Front Door CDN Endpoint(s)? " -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline + + $userInput = Read-Host -Prompt "(Y|N)" - if (-not $Force) + if($userInput -ne "Y") { - Write-Host "Do you want to Enable WAF Policies associated with Front Door CDN Endpoint(s)? " -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline - - $userInput = Read-Host -Prompt "(Y|N)" - - if($userInput -ne "Y") - { - Write-Host " WAF Policy will not be enabled for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - break - } - else - { - Write-Host "WAF Policy will be enabled for all Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } + Write-Host " WAF Policy will not be enabled for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + break } else { - Write-Host "'Force' flag is provided. WAF Policy will be enabled on all Front Door CDN Endoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host "WAF Policy will be enabled for all Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) } } + else + { + Write-Host "'Force' flag is provided. WAF Policy will be enabled on all Front Door CDN Endoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) + } + Write-Host "[Step 5 of 5] Enabling WAF Policy for Front Door CDN Endpoint(s)" @@ -848,7 +663,7 @@ function Enable-WAFPolicyForFrontDoorCDN } else { - $frontDoorEndPoint.IsWAFEnabled = $true + $frontDoorEndPoint.IsWAFPolicyStateEnabled = $true $frontDoorEndpointsRemediated += $frontDoorEndPoint } } @@ -878,73 +693,38 @@ function Enable-WAFPolicyForFrontDoorCDN @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, - @{Expression={$_.IsPreventionMode};Label="Is Prevention Mode on ";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} + @{Expression={$_.IsWAFPolicyStateEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} Write-Host "Remediation Summary:" -ForegroundColor $([Constants]::MessageType.Info) Write-Host $([Constants]::SingleDashLine) - if($AutoRemediation) + + + if ($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) { - if($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) - { - # Write this to a file. - $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForEnabledState.csv" - $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation - Write-Host "The information related to Front door CDN Endpoints(s) where WAF Policy is enabled has been saved to [$($frontDoorEndpointsRemediatedFile)]. Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - - if ($($endpointsSkipped | Measure-Object).Count -gt 0) - { - # Write this to a file. - $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForEnabledState.csv" - $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation - Write-Host "The information related to Front door CDN Endpoints(s) where WAF Policy is not enabled has been saved to [$($endpointsSkippedFile)]." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - } - else - { - if ($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) - { - Write-Host "Successfully enabled WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) - $frontDoorEndpointsRemediated | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForEnabledState.csv" - $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation - Write-Host "This information has been saved to [$($frontDoorEndpointsRemediatedFile)]" - Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) - } + Write-Host "Successfully enabled WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) + $frontDoorEndpointsRemediated | Format-Table -Property $colsProperty -Wrap - if ($($endpointsSkipped | Measure-Object).Count -gt 0) - { - Write-Host "Error performing remediation steps for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) - $endpointsSkipped | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForEnabledState.csv" - $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation - Write-Host "This information has been saved to [$($endpointsSkippedFile)]" - } + # Write this to a file. + $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForEnabledState.csv" + $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation + Write-Host "This information has been saved to [$($frontDoorEndpointsRemediatedFile)]" + Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) } - if($AutoRemediation) + if ($($endpointsSkipped | Measure-Object).Count -gt 0) { - $logFile = "LogFiles\"+ $($TimeStamp) + "\log_" + $($SubscriptionId) +".json" - $log = Get-content -Raw -path $logFile | ConvertFrom-Json - foreach($logControl in $log.ControlList){ - if($logControl.ControlId -eq $controlIds){ - $logControl.RemediatedResources=$logRemediatedResources - $logControl.SkippedResources=$logSkippedResources - $logControl.RollbackFile = $frontDoorEndpointsRemediatedFile - } - } - $log | ConvertTo-json -depth 10 | Out-File $logFile + Write-Host "Error performing remediation steps for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) + $endpointsSkipped | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForEnabledState.csv" + $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation + Write-Host "This information has been saved to [$($endpointsSkippedFile)]" } + } else { @@ -963,10 +743,10 @@ function Disable-WAFPolicyForFrontDoorCDN { <# .SYNOPSIS - Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. .DESCRIPTION - Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. Disables back all WAF Policies in all Front Door CDNs in the Subscription. .PARAMETER SubscriptionId @@ -1183,7 +963,7 @@ function Disable-WAFPolicyForFrontDoorCDN } } }}, - @{N='IsWAFEnabled';E={ + @{N='IsWAFPolicyStateEnabled';E={ if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) { $false @@ -1224,7 +1004,7 @@ function Disable-WAFPolicyForFrontDoorCDN $frontDoorEndPoints | ForEach-Object { $endPoint = $_ - if(($_.IsWAFEnabled -eq $true) -and ($_.IsWAFConfigured -eq $true)) + if(($_.IsWAFPolicyStateEnabled -eq $true) -and ($_.IsWAFConfigured -eq $true)) { $frontDoorEndpointsWithWAFPolicyEnabled += $endPoint } @@ -1297,7 +1077,7 @@ function Disable-WAFPolicyForFrontDoorCDN } else { - $frontDoorEndPoint.IsWAFEnabled = $false + $frontDoorEndPoint.IsWAFPolicyStateEnabled = $false $frontDoorEndpointsRolledBack += $frontDoorEndPoint } } @@ -1331,8 +1111,7 @@ function Disable-WAFPolicyForFrontDoorCDN @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, - @{Expression={$_.IsPreventionMode};Label="Is Prevention Mode on ";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} + @{Expression={$_.IsWAFPolicyStateEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} if ($($frontDoorEndpointsRolledBack | Measure-Object).Count -gt 0) diff --git a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 index 9507f572..47c1b2ee 100644 --- a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 +++ b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 @@ -1,9 +1,9 @@ <### # Overview: - This script is used to enable WAF Policy Mode to Prevention on All endpoints of Front Door CDNs in a Subscription. + This script is used to enable WAF Policy Mode to Prevention on all endpoints of Front Door CDNs in a Subscription. # Control ID: - Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial + Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration # Display Name: Front Door should have Web Application Firewall configured @@ -110,10 +110,10 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints { <# .SYNOPSIS - Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. .DESCRIPTION - Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. WAF Policy Mode must be in Prevention Mode for Front Door CDN Endpoint(s). .PARAMETER SubscriptionId @@ -131,15 +131,6 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints .PARAMETER FilePath Specifies the path to the file to be used as input for the remediation. - .PARAMETER Path - Specifies the path to the file to be used as input for the remediation when AutoRemediation switch is used. - - .PARAMETER AutoRemediation - Specifies script is run as a subroutine of AutoRemediation Script. - - .PARAMETER TimeStamp - Specifies the time of creation of file to be used for logging remediation details when AutoRemediation switch is used. - .INPUTS None. You cannot pipe objects to Enable-WAFPolicyForFrontDoors. @@ -181,18 +172,6 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints [String] [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation")] $FilePath - - [String] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation when AutoRemediation switch is used")] - $Path, - - [Switch] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies script is run as a subroutine of AutoRemediation Script")] - $AutoRemediation, - - [String] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the time of creation of file to be used for logging remediation details when AutoRemediation switch is used")] - $TimeStamp ) Write-Host $([Constants]::DoubleDashLine) @@ -236,14 +215,13 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop - if(-not($AutoRemediation)) - { - Write-Host "Subscription Name: [$($context.Subscription.Name)]" - Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" - Write-Host "Account Name: [$($context.Account.Id)]" - Write-Host "Account Type: [$($context.Account.Type)]" - Write-Host $([Constants]::SingleDashLine) - } + + Write-Host "Subscription Name: [$($context.Subscription.Name)]" + Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" + Write-Host "Account Name: [$($context.Account.Id)]" + Write-Host "Account Type: [$($context.Account.Type)]" + Write-Host $([Constants]::SingleDashLine) + Write-Host "To enable Prevention Mode on WAF Policy for Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) @@ -257,234 +235,108 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token $endPointPolicies = New-Object System.Collections.ArrayList - # To keep track of remediated and skipped resources - $logRemediatedResources = @() - $logSkippedResources=@() - - # Control Id - $controlIds = "Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial" - - if($AutoRemediation) - { - if(-not (Test-Path -Path $Path)) - { - Write-Host "File containing failing controls details [$($Path)] not found. Skipping remediation..." -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - return - } - Write-Host "Fetching all Front Doors CDN failing for the [$($controlIds)] control from [$($Path)]..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - $controlForRemediation = Get-content -path $Path | ConvertFrom-Json - $controls = $controlForRemediation.ControlRemediationList - $resourceDetails = $controls | Where-Object { $controlIds -eq $_.ControlId }; - $validResources = $resourceDetails.FailedResourceList | Where-Object {![String]::IsNullOrWhiteSpace($_.ResourceId)} + # To keep track of remediated and skipped resources + $logRemediatedResources = @() + $logSkippedResources=@() - if(($resourceDetails | Measure-Object).Count -eq 0 -or ($validResources | Measure-Object).Count -eq 0) - { - Write-Host "No Front Door CDN(s) found in input json file for remediation." -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::SingleDashLine) - return - } - - $validResources | ForEach-Object { - try - { - $name = $_.ResourceName - $resourceGroupName = $_.ResourceGroupName + # Control Id + $controlIds = "Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration" - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$name) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) - { - if($null -ne $apiResponse.Content) - { - $content = $apiResponse.Content | ConvertFrom-Json - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) - { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } - } - - # Get all Frontendpoint(s) for this Front Door. - $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $name -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }}, - @{N='IsPreventionMode';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - if($WAFPolicy.PolicyMode -eq 'Prevention') - { - $true - } - else - { - $false - - } - } - }}, - @{N='IsWAFEnabled';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - - if($WAFPolicy.PolicyEnabledState -eq 'Enabled') - { - $true - } - else - { - $false - } - } - }} - } - catch - { - Write-Host "Valid resource id(s) not found in input json file. Error: [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host "Skipping the Resource: [$($_.ResourceName)]..." -ForegroundColor $([Constants]::MessageType.Warning) - $logResource = @{} - $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) - $logResource.Add("ResourceName",($_.ResourceName)) - $logResource.Add("Reason","Valid resource id(s) not found in input json file.") - $logSkippedResources += $logResource - Write-Host $([Constants]::SingleDashLine) - } - } - } - else - { - # No file path provided as input to the script. Fetch all Front Door CDNs in the Subscription. - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - Write-Host "Fetching all Front Door CDNs in Subscription: $($context.Subscription.SubscriptionId)" -ForegroundColor $([Constants]::MessageType.Info) + # No file path provided as input to the script. Fetch all Front Door CDNs in the Subscription. + if ([String]::IsNullOrWhiteSpace($FilePath)) + { + Write-Host "Fetching all Front Door CDNs in Subscription: $($context.Subscription.SubscriptionId)" -ForegroundColor $([Constants]::MessageType.Info) + + # Get all Front Door CDNs in the Subscription + $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction SilentlyContinue + $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - # Get all Front Door CDNs in the Subscription - $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction SilentlyContinue - $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - - if($totalfrontDoors -gt 0) - { - $frontDoorCDNs | ForEach-Object { - $frontDoor = $_ - $frontDoorId = $_.Id - $resourceGroupName = $_.Id.Split('/')[4] - $frontDoorName = $_.Name + if($totalfrontDoors -gt 0) + { + $frontDoorCDNs | ForEach-Object { + $frontDoor = $_ + $frontDoorId = $_.Id + $resourceGroupName = $_.Id.Split('/')[4] + $frontDoorName = $_.Name - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + if($null -ne $classicAccessToken) + { + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + { + if($null -ne $apiResponse.Content) { - if($null -ne $apiResponse.Content) + $content = $apiResponse.Content | ConvertFrom-Json + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) { - $content = $apiResponse.Content | ConvertFrom-Json - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $domains = $value[$i].properties.parameters.associations[0].domains + $totalDomains = ($domains | Measure-Object).Count + for($j=0; $j -lt $totalDomains; $j++) { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $domains = $value[$i].properties.parameters.associations[0].domains - $totalDomains = ($domains | Measure-Object).Count - for($j=0; $j -lt $totalDomains; $j++) - { - $domain = $domains[$j] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $d = [ordered]@{wafPolicyName= $wafPolicyName ;wafPolicyId= $wafPolicyId ;endpointName= $endpointName } - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } + $domain = $domains[$j] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $d = [ordered]@{wafPolicyName= $wafPolicyName ;wafPolicyId= $wafPolicyId ;endpointName= $endpointName } + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } } + } - # Get all Endpoint(s) for this Front Door CDN. - $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + # Get all Endpoint(s) for this Front Door CDN. + $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }}, + @{N='IsPreventionMode';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + if($WAFPolicy.PolicyMode -eq 'Prevention') { - $false + $true } else { - $true + $false + } - }}, - @{N='IsPreventionMode';E={ + } + }}, + @{N='IsWAFEnabled';E={ if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) { $false @@ -492,194 +344,174 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints else { $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - if($WAFPolicy.PolicyMode -eq 'Prevention') + + if($WAFPolicy.PolicyEnabledState -eq 'Enabled') { $true } else { $false - } } - }}, - @{N='IsWAFEnabled';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - - if($WAFPolicy.PolicyEnabledState -eq 'Enabled') - { - $true - } - else - { - $false - } - } - }} - - } + }} + } } - else + } + else + { + if (-not (Test-Path -Path $FilePath)) { - if (-not (Test-Path -Path $FilePath)) - { - Write-Host "ERROR: Input file - $($FilePath) not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) - break - } + Write-Host "ERROR: Input file - $($FilePath) not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + break + } - Write-Host "Fetching all Front Door CDN Endpoint(s) from $($FilePath)" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host "Fetching all Front Door CDN Endpoint(s) from $($FilePath)" -ForegroundColor $([Constants]::MessageType.Info) - $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath - $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } - - $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName + $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath + $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } + + $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName - foreach($frontdoor in $uniquefrontDoors) + foreach($frontdoor in $uniquefrontDoors) + { + $resourceGroupName = $frontdoor.ResourceGroupName + $frontDoorName = $frontdoor.FrontDoorName + + if($null -ne $classicAccessToken) { - $resourceGroupName = $frontdoor.ResourceGroupName - $frontDoorName = $frontdoor.FrontDoorName + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - if($null -ne $classicAccessToken) + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + if($null -ne $apiResponse.Content) { - if($null -ne $apiResponse.Content) + $content = $apiResponse.Content | ConvertFrom-Json + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) { - $content = $apiResponse.Content | ConvertFrom-Json - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) + { + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } } } + } - $validfrontDoorEndpointsDetails | ForEach-Object { - $frontdoorEndpointName = $_.EndPointName - $resourceGroupName = $_.ResourceGroupName - $frontDoorName = $_.FrontDoorName + $validfrontDoorEndpointsDetails | ForEach-Object { + $frontdoorEndpointName = $_.EndPointName + $resourceGroupName = $_.ResourceGroupName + $frontDoorName = $_.FrontDoorName - try - { - - $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + try + { + + $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }}, + @{N='IsPreventionMode';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) + if($WAFPolicy.PolicyMode -eq 'Prevention') { - $false + $true } else { - $true + $false + } - }}, - @{N='IsPreventionMode';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + } + }}, + @{N='IsWAFEnabled';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) { $false } else { $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - if($WAFPolicy.PolicyMode -eq 'Prevention') + + if($WAFPolicy.PolicyEnabledState -eq 'Enabled') { $true } else { $false - } } - }}, - @{N='IsWAFEnabled';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - - if($WAFPolicy.PolicyEnabledState -eq 'Enabled') - { - $true - } - else - { - $false - } - } - }} - } - catch - { - Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) - } + }} + } + catch + { + Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) } } } + - if(-not($AutoRemediation)) + + if ([String]::IsNullOrWhiteSpace($FilePath)) { - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - - if ($totalfrontDoors -eq 0) - { - Write-Host "No Front Door CDNs found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - break - } + $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - Write-Host "Found [$($totalfrontDoors)] Front Door CDN(s)." -ForegroundColor $([Constants]::MessageType.Update) + if ($totalfrontDoors -eq 0) + { + Write-Host "No Front Door CDNs found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + break } - } + + Write-Host "Found [$($totalfrontDoors)] Front Door CDN(s)." -ForegroundColor $([Constants]::MessageType.Update) + } + $totalfrontDoorEndPoints = ($frontDoorEndPoints | Measure-Object).Count @@ -729,39 +561,25 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints { Write-Host "No Front Door CDN endpoints(s) found where WAF Policy is not in Prevention Mode.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) Write-Host $([Constants]::DoubleDashLine) - - if($AutoRemediation -and ($frontDoorFrontendPoints |Measure-Object).Count -gt 0) - { - $logFile = "LogFiles\"+ $($TimeStamp) + "\log_" + $($SubscriptionId) +".json" - $log = Get-content -Raw -path $logFile | ConvertFrom-Json - foreach($logControl in $log.ControlList){ - if($logControl.ControlId -eq $controlIds){ - $logControl.RemediatedResources=$logRemediatedResources - $logControl.SkippedResources=$logSkippedResources - } - } - $log | ConvertTo-json -depth 10 | Out-File $logFile - } return } Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyNotInPrevention)] Front Door CDN Endpoints(s) found where WAF Policy is not in Prevention Mode ." -ForegroundColor $([Constants]::MessageType.Update) Write-Host $([Constants]::SingleDashLine) - if(-not($AutoRemediation)) - { - Write-Host "Following Front Door CDN Endpoints(s) are having WAF Policies with Mode not in Prevention:" -ForegroundColor $([Constants]::MessageType.Info) - $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, - @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, - @{Expression={$_.IsPreventionMode};Label="Is Prevention Mode on ";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} - $frontDoorEndpointsWithWAFPolicyNotInPrevention | Format-Table -Property $colsProperty -Wrap - } + + Write-Host "Following Front Door CDN Endpoints(s) are having WAF Policies with Mode not in Prevention:" -ForegroundColor $([Constants]::MessageType.Info) + $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, + @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, + @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, + @{Expression={$_.IsPreventionMode};Label="Is Prevention Mode on ";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} + $frontDoorEndpointsWithWAFPolicyNotInPrevention | Format-Table -Property $colsProperty -Wrap + # Back up snapshots to `%LocalApplicationData%'. $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\SetFrontDoorCDNPolicyModeToPrevention" @@ -789,27 +607,26 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints if (-not $DryRun) { - if(-not $AutoRemediation) - { - Write-Host "WAF Policy mode will be switched to Prevention for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Warning) + + Write-Host "WAF Policy mode will be switched to Prevention for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Warning) - if (-not $Force) - { - Write-Host "Do you want to switch WAF Policy Mode to Prevention from Detection associated with Front Door CDN Endpoint(s)? " -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline - - $userInput = Read-Host -Prompt "(Y|N)" + if (-not $Force) + { + Write-Host "Do you want to switch WAF Policy Mode to Prevention from Detection associated with Front Door CDN Endpoint(s)? " -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline + + $userInput = Read-Host -Prompt "(Y|N)" - if($userInput -ne "Y") - { - Write-Host " WAF Policy Mode will not be switched to Prevention from Detection for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - break - } - } - else + if($userInput -ne "Y") { - Write-Host "'Force' flag is provided. WAF Policy Mode will be switched to Prevention from Detection on all Front Door CDN Endoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host " WAF Policy Mode will not be switched to Prevention from Detection for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + break } } + else + { + Write-Host "'Force' flag is provided. WAF Policy Mode will be switched to Prevention from Detection on all Front Door CDN Endoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) + } + Write-Host "[Step 5 of 5] Switching Mode to Prevention from Detection for Front Door CDN Endpoint(s)" @@ -875,63 +692,28 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints Write-Host $([Constants]::DoubleDashLine) Write-Host "Remediation Summary:" -ForegroundColor $([Constants]::MessageType.Info) - if($AutoRemediation) + + + if ($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) { - if($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) - { - # Write this to a file. - $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForPreventionMode.csv" - $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation - Write-Host "The information related to Front door CDN Endpoints(s) where WAF Policy is changed to Prevention Mode has been saved to [$($frontDoorEndpointsRemediatedFile)]. Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - - if ($($endpointsSkipped | Measure-Object).Count -gt 0) - { - # Write this to a file. - $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForPreventionMode.csv" - $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation - Write-Host "The information related to Front door CDN Endpoints(s) where WAF Policy is not changed to Prevention Mode has been saved to [$($endpointsSkippedFile)]." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - } - else - { - if ($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) - { - $frontDoorEndpointsRemediated | Format-Table -Property $colsProperty -Wrap + $frontDoorEndpointsRemediated | Format-Table -Property $colsProperty -Wrap - # Write this to a file. - $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForPreventionMode.csv" - $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation - Write-Host "This information has been saved to [$($frontDoorEndpointsRemediatedFile)]" - Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) - } - - if ($($endpointsSkipped | Measure-Object).Count -gt 0) - { - Write-Host "Error performing remediation steps for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) - $endpointsSkipped | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForPreventionMode.csv" - $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation - Write-Host "This information has been saved to [$($endpointsSkippedFile)]" - } + # Write this to a file. + $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForPreventionMode.csv" + $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation + Write-Host "This information has been saved to [$($frontDoorEndpointsRemediatedFile)]" + Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) } - if($AutoRemediation) + if ($($endpointsSkipped | Measure-Object).Count -gt 0) { - $logFile = "LogFiles\"+ $($TimeStamp) + "\log_" + $($SubscriptionId) +".json" - $log = Get-content -Raw -path $logFile | ConvertFrom-Json - foreach($logControl in $log.ControlList){ - if($logControl.ControlId -eq $controlIds){ - $logControl.RemediatedResources=$logRemediatedResources - $logControl.SkippedResources=$logSkippedResources - $logControl.RollbackFile = $frontDoorEndpointsRemediatedFile - } - } - $log | ConvertTo-json -depth 10 | Out-File $logFile + Write-Host "Error performing remediation steps for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) + $endpointsSkipped | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForPreventionMode.csv" + $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation + Write-Host "This information has been saved to [$($endpointsSkippedFile)]" } } else @@ -951,10 +733,10 @@ function Disable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints { <# .SYNOPSIS - Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. .DESCRIPTION - Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration_Trial' Control. + Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. Switches the Policy Mode to Detection from Prevention for all WAF Policies in all Front Door CDN s in the Subscription. .PARAMETER SubscriptionId From 20c26e1a2709b240a1a46ed012619ac211c7eee1 Mon Sep 17 00:00:00 2001 From: Abhishek P Date: Thu, 15 Dec 2022 10:58:02 +0530 Subject: [PATCH 06/26] Changes --- .../Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 index 0e691909..bc8be240 100644 --- a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 +++ b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 @@ -856,7 +856,13 @@ function Disable-WAFPolicyForFrontDoorCDN } Write-Host "Fetching all Front Door CDN Endpoints from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) - + + $frontDoorEndPoints = @() + $resourceAppIdURI = "https://management.azure.com/" + $apiResponse =@() + $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token + $endPointPolicies = New-Object System.Collections.ArrayList + $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } @@ -913,11 +919,7 @@ function Disable-WAFPolicyForFrontDoorCDN } } - $frontDoorEndPoints = @() - $resourceAppIdURI = "https://management.azure.com/" - $apiResponse =@() - $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token - $endPointPolicies = New-Object System.Collections.ArrayList + $validfrontDoorEndpointsDetails | ForEach-Object { $frontdoorEndpointName = $_.EndPointName From 5b6bbdb3c2185ec7b5c15a115536ca8dde673631 Mon Sep 17 00:00:00 2001 From: Abhishek P Date: Thu, 15 Dec 2022 18:04:23 +0530 Subject: [PATCH 07/26] Changes --- ...iate-ConfigureWAFPolicyForFrontDoorCDN.ps1 | 1164 ----------------- 1 file changed, 1164 deletions(-) delete mode 100644 Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 diff --git a/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 deleted file mode 100644 index 489a8dd8..00000000 --- a/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 +++ /dev/null @@ -1,1164 +0,0 @@ -<### -# Overview: - This script is used to configure WAF Policy on all endpoints of Front Door CDNs in a Subscription. - -# Control ID: - Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration - -# Display Name: - Front Door should have Web Application Firewall configured - -# Prerequisites: - 1. Contributor or higher privileges on the Front Door CDNs in a Subscription. - 2. Must be connected to Azure with an authenticated account. - -# Steps performed by the script: - To remediate: - 1. Validate and install the modules required to run the script. - 2. Get the list of all Front Door CDNs Endpoints in a Subscription that do not have WAF Policy Configured - 3. Back up details of Front Door CDN Endpoint(s) that are to be remediated. - 4. Configure WAF Policy for all endpoints in the Frontdoors. - - To roll back: - 1. Validate and install the modules required to run the script. - 2. Get the list of Frontdoors' Endpoint(s) in a Subscription, the changes made to which previously, are to be rolled back. - 3. Revert by removing WAF Policies from endpoints in all the Frontdoors. - -# Instructions to execute the script: - To remediate: - 1. Download the script. - 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. - 3. Execute the script to configure WAF Policy on all endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. - - To roll back: - 1. Download the script. - 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. - 3. Execute the script to remove the configured WAF Policy on all endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. - -# Examples: - To remediate: - 1. To review the Front Door CDNs in a Subscription that will be remediated: - Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun - - 2. To configure WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription: - Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck - - 3. To configure WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription, from a previously taken snapshot: - Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\ConfigureFrontDoorCDNWAFPolicy\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv - - To know more about the options supported by the remediation command, execute: - Get-Help Configure-WAFPolicyForFrontDoorCDN -Detailed - - To roll back: - 1. To remove configured WAF Policy Mode for Endpoint(s) all Front Door CDNs in a Subscription, from a previously taken snapshot: - Remove-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\ConfigureFrontDoorCDNWAFPolicy\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv - - To know more about the options supported by the roll back command, execute: - Get-Help Remove-WAFPolicyForFrontDoorCDN -Detailed -###> - -function Setup-Prerequisites -{ - <# - .SYNOPSIS - Checks if the prerequisites are met, else, sets them up. - - .DESCRIPTION - Checks if the prerequisites are met, else, sets them up. - Includes installing any required Azure modules. - - .INPUTS - None. You cannot pipe objects to Setup-Prerequisites. - - .OUTPUTS - None. Setup-Prerequisites does not return anything that can be piped and used as an input to another command. - - .EXAMPLE - PS> Setup-Prerequisites - - .LINK - None - #> - - # List of required modules - $requiredModules = @("Az.Accounts", "Az.FrontDoor", "Az.Cdn") - - Write-Host "Required modules: $($requiredModules -join ', ')" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Checking if the required modules are present..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - - $availableModules = $(Get-Module -ListAvailable $requiredModules -ErrorAction Stop) - - # Check if the required modules are installed. - $requiredModules | ForEach-Object { - if ($availableModules.Name -notcontains $_) - { - Write-Host "[$($_)] module is not present." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host "Installing [$($_)] module..." -ForegroundColor $([Constants]::MessageType.Info) - Install-Module -Name $_ -Scope CurrentUser -Repository 'PSGallery' -ErrorAction Stop - Write-Host "[$($_)] module installed." -ForegroundColor $([Constants]::MessageType.Update) - } - else - { - Write-Host "[$($_)] module is present." -ForegroundColor $([Constants]::MessageType.Update) - } - } -} - -function Configure-WAFPolicyForFrontDoorCDN -{ - <# - .SYNOPSIS - Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - - .DESCRIPTION - Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - WAF Policy must be configured for Front Door CDN Endpoint(s). - - .PARAMETER SubscriptionId - Specifies the ID of the Subscription to be remediated. - - .PARAMETER Force - Specifies a forceful remediation without any prompts. - - .Parameter PerformPreReqCheck - Specifies validation of prerequisites for the command. - - .PARAMETER DryRun - Specifies a dry run of the actual remediation. - - .PARAMETER FilePath - Specifies the path to the file to be used as input for the remediation. - - .INPUTS - None. You cannot pipe objects to Enable-WAFPolicyForFrontDoors. - - .OUTPUTS - None. Configure-WAFPolicyForFrontDoorCDN does not return anything that can be piped and used as an input to another command. - - .EXAMPLE - PS> Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun - - .EXAMPLE - PS> Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck - - .EXAMPLE - PS> Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202201011212\ConfigureFrontDoorCDNWAFPolicy\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv - - .LINK - None - #> - - param ( - [String] - [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] - [Parameter(ParameterSetName = "WetRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] - $SubscriptionId, - - [Switch] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies a forceful remediation without any prompts")] - $Force, - - [Switch] - [Parameter(ParameterSetName = "DryRun", HelpMessage="Specifies validation of prerequisites for the command")] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies validation of prerequisites for the command")] - $PerformPreReqCheck, - - [Switch] - [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies a dry run of the actual remediation")] - $DryRun, - - [String] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation")] - $FilePath - ) - - Write-Host $([Constants]::DoubleDashLine) - if ($PerformPreReqCheck) - { - try - { - Write-Host "[Step 1 of 5] Validate and install the modules required to run the script and validate the user" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Setup-Prerequisites - Write-Host "Completed setting up prerequisites." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - catch - { - Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - return - } - } - else - { - Write-Host "[Step 1 of 5] Validate the user" - Write-Host $([Constants]::SingleDashLine) - } - - # Connect to Azure account - $context = Get-AzContext - - if ([String]::IsNullOrWhiteSpace($context)) - { - Write-Host "Connecting to Azure account..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null - Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - # Setting up context for the current Subscription. - $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop - - Write-Host "Subscription Name: [$($context.Subscription.Name)]" - Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" - Write-Host "Account Name: [$($context.Account.Id)]" - Write-Host "Account Type: [$($context.Account.Type)]" - Write-Host $([Constants]::SingleDashLine) - - - Write-Host " To configure WAF Policy for Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Write-Host "[Step 2 of 5] Preparing to fetch all Front Door CDNs" - Write-Host $([Constants]::SingleDashLine) - - $frontDoorCDNs = @() - $frontDoorEndPoints = @() - $resourceAppIdURI = "https://management.azure.com/" - $apiResponse =@() - $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token - $endPointPolicies = New-Object System.Collections.ArrayList - - # To keep track of remediated and skipped resources - $logRemediatedResources = @() - $logSkippedResources=@() - - # Control Id - $controlIds = "Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration" - - - # No file path provided as input to the script. Fetch all Front Door CDNs in the Subscription. - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - Write-Host "Fetching all Front Door CDNs in Subscription: [$($context.Subscription.SubscriptionId)]..." -ForegroundColor $([Constants]::MessageType.Info) - - # Get all Front Door CDNs in the Subscription - $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction SilentlyContinue - $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - - if($totalfrontDoors -gt 0) - { - $frontDoorCDNs | ForEach-Object { - $frontDoor = $_ - $frontDoorId = $_.Id - $resourceGroupName = $_.Id.Split('/')[4] - $frontDoorName = $_.Name - - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) - { - if($null -ne $apiResponse.Content) - { - $content = $apiResponse.Content | ConvertFrom-Json - - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) - { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } - } - - - # Get all Endpoint(s) for this Front Door CDN. - $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }} - } - } - } - else - { - if (-not (Test-Path -Path $FilePath)) - { - Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - break - } - - Write-Host "Fetching all Front Door CDN Endpoint(s) from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) - - $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath - $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } - - $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName - - foreach($frontdoor in $uniquefrontDoors) - { - $resourceGroupName = $frontdoor.ResourceGroupName - $frontDoorName = $frontdoor.FrontDoorName - - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) - { - if($null -ne $apiResponse.Content) - { - $content = $apiResponse.Content | ConvertFrom-Json - - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) - { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } - } - } - - $validfrontDoorEndpointsDetails | ForEach-Object { - $frontdoorEndpointName = $_.EndPointName - $resourceGroupName = $_.ResourceGroupName - $frontDoorName = $_.FrontDoorName - - try - { - $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }} - } - catch - { - Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) - } - } - } - - - - - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - - if ($totalfrontDoors -eq 0) - { - Write-Host "No Front Door CDNs found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::DoubleDashLine) - break - } - - Write-Host "Found [$($totalfrontDoors)] Front Door CDN(s)." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - - - - $totalfrontDoorEndPoints = ($frontDoorEndPoints | Measure-Object).Count - - if ($totalfrontDoorEndPoints -eq 0) - { - Write-Host "No Front Door CDN Endpoint(s) having WAF Policy not configured. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::DoubleDashLine) - break - } - - Write-Host "Found [$($totalfrontDoorEndPoints)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - - # Includes Front Door CDN Endpoint(s) where WAF Policy is not configured - $frontDoorEndpointsWithWAFPolicyNotConfigured = @() - - # Includes Front Door CDN Endpoint(s) that were skipped during remediation. There were errors remediating them. - $frontDoorEndpointsSkipped = @() - - - - Write-Host "[Step 3 of 5] Fetching Endpoint(s) for which WAF Policy is not configured" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is not configured..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndPoints | ForEach-Object { - $endPoint = $_ - if($_.IsWAFConfigured -eq $false) - { - $frontDoorEndpointsWithWAFPolicyNotConfigured += $endPoint - } - else - { - $logResource = @{} - $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) - $logResource.Add("ResourceName",($_.FrontDoorName)) - $logResource.Add("EndPointName",($_.EndPointName)) - $logResource.Add("Reason","WAF Policy already Configured on endpoint") - $logSkippedResources += $logResource - - } - } - - $totalfrontDoorEndpointsWithWAFPolicyNotConfigured = ($frontDoorEndpointsWithWAFPolicyNotConfigured | Measure-Object).Count - - if ($totalfrontDoorEndpointsWithWAFPolicyNotConfigured -eq 0) - { - Write-Host "No Front Door CDN endpoints(s) found where WAF Policy is not configured. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - return - } - - Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door CDN Endpoints(s) found where WAF Policy is not configured ." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - - - Write-Host "Following Front Door CDN Endpoints(s) are having WAF Policies not configured:" -ForegroundColor $([Constants]::MessageType.Info) - $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, - @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"} - - $frontDoorEndpointsWithWAFPolicyNotConfigured | Format-Table -Property $colsProperty -Wrap - - # Back up snapshots to `%LocalApplicationData%'. - $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\ConfigureFrontDoorCDNWAFPolicy" - - if (-not (Test-Path -Path $backupFolderPath)) - { - New-Item -ItemType Directory -Path $backupFolderPath | Out-Null - } - - - Write-Host "[Step 4 of 5] Backing up Front Door CDN Endpoint(s) details" - Write-Host $([Constants]::SingleDashLine) - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - - # Backing up Front Door CDN Endpoints details. - $backupFile = "$($backupFolderPath)\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv" - $frontDoorEndpointsWithWAFPolicyNotConfigured | Export-CSV -Path $backupFile -NoTypeInformation - Write-Host "Front Door Endpoint(s) details have been successful backed up to [$($backupFolderPath)]" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - else - { - Write-Host "Skipped as -FilePath is provided" -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - - if (-not $DryRun) - { - - Write-Host "WAF Policy will be configured for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Warning) - - if (-not $Force) - { - Write-Host "Do you want to configure WAF Policy associated with Front Door CDN Endpoint(s)? " -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline - Write-Host $([Constants]::SingleDashLine) - $userInput = Read-Host -Prompt "(Y|N)" - Write-Host $([Constants]::SingleDashLine) - if($userInput -ne "Y") - { - Write-Host "WAF Policy will not be configured for any Front Door CDN Endpoint(s). Exiting." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::DoubleDashLine) - break - } - else - { - Write-Host "WAF Policy will be configured for all Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - } - else - { - Write-Host "'Force' flag is provided. WAF Policy will be configured on all Front Door CDN Endoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - - - - Write-Host "[Step 5 of 5] Configuring WAF Policy for Front Door CDN Endpoint(s)" - Write-Host $([Constants]::SingleDashLine) - # To hold results from the remediation. - $frontDoorEndpointsRemediated = @() - $endpointsSkipped = @() - $otherPolicyEndpointsAssociations = @() - - - # Remidiate Controls by configuring WAF Policy - $frontDoorEndpointsWithWAFPolicyNotConfigured | ForEach-Object { - $frontDoorEndPoint = $_ - $endpointName = $_.EndPointName - $frontdoorName = $_.FrontDoorName - $resourceGroupName = $_.ResourceGroupName - $i= 0 - - try - { - Do - { - $wafPolicyName = Read-Host -Prompt "Enter WAF Policy Name for Endpoint: [$($_.EndPointName)] of Frontdoor [$($frontdoorName)] " - $policyResourceGroup = Read-Host -Prompt "Enter WAF Policy Resource Group Name for Endpoint: [$($_.EndPointName)] of Frontdoor [$($frontdoorName)] " - $policy = Get-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $policyResourceGroup -ErrorAction SilentlyContinue - - if($policy -eq $null) - { - Write-Host "WAF Policy name or WAF Policy Resource Group Name is not correct. Please enter correct details." - Write-Host $([Constants]::SingleDashLine) - } - - if($policy -ne $null -and $policy.Sku -ne 'Standard_AzureFrontDoor') - { - Write-Host "WAF Policy is not of type Front door tier Standard . Please enter correct details." - Write-Host $([Constants]::SingleDashLine) - } - - } - while($policy.Sku -ne 'Standard_AzureFrontDoor' -or $policy -eq $null) - $wafPolicyId = $policy.Id - - $endpoint = Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontdoorName -EndpointName $endpointName - $updateAssociation = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$($endpoint.Id)}) - $updateAssociations = @() - $updateAssociations += $updateAssociation - $otherPolicyEndpointsAssociations = $endPointPolicies | where wafPolicyName -eq ($wafPolicyName) - $otherPolicyEndpointsAssociations = $otherPolicyEndpointsAssociations | where frontDoorName -eq $frontdoorName - - $otherPolicyEndpointsAssociations | ForEach-Object { - $association = $_ - $associatedEndpoint = $_.endpointName - - New-Variable -Force -Name "endpoint$i" -Value (Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontdoorName -EndpointName $associatedEndpoint) - New-Variable -Force -Name "updateAssociation$i" -Value (New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$(Get-Variable -Name "endpoint$i" -ValueOnly).Id})) - $updateAssociations += (Get-Variable -Name "updateAssociation$i" -ValueOnly) - $i++ - } - - $updateWafParameter = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallParametersObject -Association @($updateAssociations) -WafPolicyId $wafPolicyId - $policySecurity = New-AzFrontDoorCdnSecurityPolicy -ResourceGroupName v-abprasadTestRG -ProfileName testFrontdoorCDN -Name Policy -Parameter $updateWafParameter - - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $policyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontdoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - - if ($policySecurity.Name -eq $null) - { - $endpointsSkipped += $frontDoorEndPoint - - } - else - { $frontDoorEndPoint.IsWAFConfigured = $true - $frontDoorEndPoint.WAFPolicyName = $wafPolicyName - $frontDoorEndPoint.WAFPolicyResourceGroup = $policyResourceGroup - $frontDoorEndpointsRemediated += $frontDoorEndPoint - } - } - catch - { - $endpointsSkipped += $frontDoorEndPoint - } - } - - $totalRemediated = ($frontDoorEndpointsRemediated | Measure-Object).Count - - Write-Host $([Constants]::SingleDashLine) - - if ($totalRemediated -eq $totalfrontDoorEndpointsWithWAFPolicyNotConfigured) - { - Write-Host "WAF Policy configured for all [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - else - { - Write-Host "WAF Policy configured for [$totalRemediated] out of [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - - $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, - @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"} - - - - Write-Host "Remediation Summary:" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - - - if ($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) - { - Write-Host "Successfully configured WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndpointsRemediated | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv" - $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation - Write-Host "This information has been saved to [$($frontDoorEndpointsRemediatedFile)]" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - } - - if ($($endpointsSkipped | Measure-Object).Count -gt 0) - { - Write-Host "Error performing remediation steps for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::SingleDashLine) - $endpointsSkipped | Format-Table -Property $colsProperty -Wrap - Write-Host $([Constants]::SingleDashLine) - # Write this to a file. - $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv" - $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation - Write-Host "This information has been saved to [$($endpointsSkippedFile)]" - Write-Host $([Constants]::SingleDashLine) - } - - } - else - { - - Write-Host "[Step 5 of 5] Configuring WAF Policy for Endpoint(s)" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Skipped as -DryRun switch is provided." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - - Write-Host "Next steps:" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host "Run the same command with -FilePath $($backupFile) and without -DryRun, to configure WAF Policy Mode for all Front Door CDN Endpoint(s) listed in the file." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - } -} - -function Remove-WAFPolicyForFrontDoorCDN -{ - <# - .SYNOPSIS - Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - - .DESCRIPTION - Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - Removes configured WAF Policy for all WAF Policies in all Front Door CDN s in the Subscription. - - .PARAMETER SubscriptionId - Specifies the ID of the Subscription that was previously remediated. - - .PARAMETER Force - Specifies a forceful roll back without any prompts. - - .Parameter PerformPreReqCheck - Specifies validation of prerequisites for the command. - - .PARAMETER FilePath - Specifies the path to the file to be used as input for the roll back. - - .INPUTS - None. You cannot pipe objects to Remove-WAFPolicyForFrontDoorCDN. - - .OUTPUTS - None. Remove-WAFPolicyForFrontDoorCDN does not return anything that can be piped and used as an input to another command. - - .EXAMPLE - PS> Remove-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202211190719\ConfigureFrontDoorCDNWAFPolicy\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv - - .LINK - None - #> - - param ( - [String] - [Parameter(Mandatory = $true, HelpMessage="Specifies the ID of the Subscription that was previously remediated.")] - $SubscriptionId, - - [Switch] - [Parameter(HelpMessage="Specifies a forceful roll back without any prompts")] - $Force, - - [Switch] - [Parameter(HelpMessage="Specifies validation of prerequisites for the command")] - $PerformPreReqCheck, - - [String] - [Parameter(Mandatory = $true, HelpMessage="Specifies the path to the file to be used as input for the roll back")] - $FilePath - ) - - Write-Host $([Constants]::DoubleDashLine) - if ($PerformPreReqCheck) - { - try - { - Write-Host "[Step 1 of 4] Validate and install the modules required to run the script and validate the user" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Setup-Prerequisites - Write-Host "Completed setting up prerequisites" - Write-Host $([Constants]::SingleDashLine) - } - catch - { - Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - return - } - } - else - { - Write-Host "[Step 1 of 4] Validate the user" - Write-Host $([Constants]::SingleDashLine) - } - - # Connect to Azure account - $context = Get-AzContext - - if ([String]::IsNullOrWhiteSpace($context)) - { - - Write-Host "Connecting to Azure account..." - Write-Host $([Constants]::SingleDashLine) - Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null - Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - else - { - # Setting up context for the current Subscription. - $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop - } - - Write-Host "Subscription Name: [$($context.Subscription.Name)]" - Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" - Write-Host "Account Name: [$($context.Account.Id)]" - Write-Host "Account Type: [$($context.Account.Type)]" - Write-Host $([Constants]::SingleDashLine) - - # Note about the required access required for remediation - - Write-Host "To remove Configured WAF Policy for all Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Write-Host "[Step 2 of 4] Preparing to fetch all Front Door CDN Endpoints" - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndPoints = @() - $resourceAppIdURI = "https://management.azure.com/" - $apiResponse =@() - $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token - $endPointPolicies = New-Object System.Collections.ArrayList - - if (-not (Test-Path -Path $FilePath)) - { - Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - break - } - - Write-Host "Fetching all Front Door CDN Endpoints from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) - - $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath - $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } - - $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName - - foreach($frontdoor in $uniquefrontDoors) - { - $resourceGroupName = $frontdoor.ResourceGroupName - $frontDoorName = $frontdoor.FrontDoorName - - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) - { - if($null -ne $apiResponse.Content) - { - $content = $apiResponse.Content | ConvertFrom-Json - - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) - { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } - } - } - - - $validfrontDoorEndpointsDetails | ForEach-Object { - $frontdoorEndpointName = $_.EndPointName - $resourceGroupName = $_.ResourceGroupName - $frontDoorName = $_.FrontDoorName - - try - { - $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }} - } - catch - { - Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::SingleDashLine) - Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - } - - - - # Includes Front Door CDN Endpoint(s) where WAF Policy is configured - $frontDoorEndpointsWithWAFPolicyConfigured = @() - - - - Write-Host "[Step 3 of 4] Fetching Endpoint(s) Endpoint(s) where WAF Policy is not configured" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is configured..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndPoints | ForEach-Object { - $endPoint = $_ - if($_.IsWAFConfigured -eq $true) - { - $frontDoorEndpointsWithWAFPolicyConfigured += $endPoint - } - else - { - $logResource = @{} - $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) - $logResource.Add("ResourceName",($_.EndPointName)) - $logResource.Add("Reason","WAF Policy is already configured on Frontdoor CDN Endpoint") - $logSkippedResources += $logResource - - } - } - - $totalfrontDoorEndpointsWithWAFPolicyConfigured = ($frontDoorEndpointsWithWAFPolicyConfigured | Measure-Object).Count - - if ($totalfrontDoorEndpointsWithWAFPolicyConfigured -eq 0) - { - Write-Host "No Front Door CDN Endpoints(s) found where WAF Policy is configured.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - return - } - - - Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyConfigured)] Front Door CDN Endpoints(s) found in file where WAF Policy is configured to Rollback." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - - # Back up snapshots to `%LocalApplicationData%'. - $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\ConfigureFrontDoorCDNWAFPolicy" - - if (-not (Test-Path -Path $backupFolderPath)) - { - New-Item -ItemType Directory -Path $backupFolderPath | Out-Null - } - - if (-not $Force) - { - Write-Host "Do you want remove configured WAF Policy Mode for all Front Door CDN Endpoint(s)?" -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline - Write-Host $([Constants]::SingleDashLine) - $userInput = Read-Host -Prompt "(Y|N)" - Write-Host $([Constants]::SingleDashLine) - if($userInput -ne "Y") - { - Write-Host "Configured WAF Policy will not be removed for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::DoubleDashLine) - break - } - else - { - Write-Host "Configured WAF Policy will be removed for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - } - else - { - Write-Host "'Force' flag is provided. Configured WAF Policy will be removed for all the Front Door CDN Endpoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline - Write-Host $([Constants]::SingleDashLine) - } - - - - - - Write-Host "[Step 4 of 4] Switching WAF Policy mode to Detection for Front Door CDNs Endpoint(s)" - Write-Host $([Constants]::SingleDashLine) - # Includes Front Door CDN s, to which, previously made changes were successfully rolled back. - $frontDoorEndpointsRolledBack = @() - - # Includes Front Door CDN s that were skipped during roll back. There were errors rolling back the changes made previously. - $frontDoorEndpointsSkipped = @() - - - - # Roll back by removing configured WAF Policy on Endpoints - $frontDoorEndpointsWithWAFPolicyConfigured | ForEach-Object { - $frontDoorEndPoint = $_ - $endpointName = $_.EndPointName - $frontdoorName = $_.FrontDoorName - $resourceGroupName = $_.ResourceGroupName - $wafPolicyName = $_.WAFPolicyName - $policyResourceGroup = $_.WAFPolicyResourceGroup - $i = 0 - - $policy = Get-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $policyResourceGroup -ErrorAction SilentlyContinue - $wafPolicyId = $policy.Id - try - { - $updateAssociations = @() - $otherPolicyEndpointsAssociations = @() - - # Remove the endpoint to be rolledback from endpointPolicies List - foreach($policy in $endPointPolicies.Clone()) - { - if($policy.endpointName -eq $endpointName) - { - $endPointPolicies.Remove($policy) - } - - } - - $otherPolicyEndpointsAssociations = $endPointPolicies | where wafPolicyName -eq ($wafPolicyName) - $otherPolicyEndpointsAssociations = $otherPolicyEndpointsAssociations | where frontDoorName -eq $frontdoorName - - $otherPolicyEndpointsAssociations | ForEach-Object { - $association = $_ - $associatedEndpoint = $_.endpointName - - New-Variable -Force -Name "endpoint$i" -Value (Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontdoorName -EndpointName $associatedEndpoint) - New-Variable -Force -Name "updateAssociation$i" -Value (New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$(Get-Variable -Name "endpoint$i" -ValueOnly).Id})) - $updateAssociations += (Get-Variable -Name "updateAssociation$i" -ValueOnly) - $i++ - } - - $updateWafParameter = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallParametersObject -Association @($updateAssociations) -WafPolicyId $wafPolicyId - $policySecurity = New-AzFrontDoorCdnSecurityPolicy -ResourceGroupName v-abprasadTestRG -ProfileName testFrontdoorCDN -Name Policy -Parameter $updateWafParameter - - if ($policySecurity.Name -eq $null) - { - $frontDoorEndpointsSkipped += $frontDoorEndPoint - - } - else - { - $frontDoorEndPoint.IsWAFConfigured = $false - $frontDoorEndPoint.WAFPolicyName = "" - $frontDoorEndPoint.WAFPolicyResourceGroup = "" - $frontDoorEndpointsRolledBack += $frontDoorEndPoint - } - } - catch - { - Write-Host $_ - $frontDoorEndpointsSkipped += $frontDoorEndPoint - } - } - - - $totalfrontDoorEndpointsRolledBack = ($frontDoorEndpointsRolledBack | Measure-Object).Count - - Write-Host $([Constants]::SingleDashLine) - - if ($totalfrontDoorEndpointsRolledBack -eq $totalfrontDoorEndpointsWithWAFPolicyConfigured) - { - Write-Host "Configured WAF Policy removed for all [$($totalfrontDoorEndpointsWithWAFPolicyConfigured)] Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - else - { - Write-Host "Configured WAF Policy removed for [$totalfrontDoorEndpointsRolledBack] out of [$($totalfrontDoorEndpointsWithWAFPolicyConfigured)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - - Write-Host "Rollback Summary:" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, - @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"} - - - if ($($frontDoorEndpointsRolledBack | Measure-Object).Count -gt 0) - { - Write-Host "Successfully removed WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndpointsRolledBack | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $frontDoorEndpointsRolledBackFile = "$($backupFolderPath)\RolledBackFrontDoorCDNEndpointsForConfigureWAFPolicy.csv" - $frontDoorEndpointsRolledBack | Export-CSV -Path $frontDoorEndpointsRolledBackFile -NoTypeInformation - Write-Host "This information has been saved to [$($frontDoorEndpointsRolledBackFile)]" - Write-Host $([Constants]::SingleDashLine) - } - - if ($($frontDoorEndpointsSkipped | Measure-Object).Count -gt 0) - { - Write-Host "Error removing configured WAF Policy for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndpointsSkipped | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $frontDoorEndpointsSkippedFile = "$($backupFolderPath)\RollbackSkippedFrontDoorCDNEndpointsForConfigureWAFPolicy.csv" - $frontDoorEndpointsSkipped | Export-CSV -Path $frontDoorEndpointsSkippedFile -NoTypeInformation - Write-Host "This information has been saved to [$($frontDoorEndpointsSkippedFile)]" - } -} - -# Defines commonly used constants. -class Constants -{ - # Defines commonly used colour codes, corresponding to the severity of the log. - static [Hashtable] $MessageType = @{ - Error = [System.ConsoleColor]::Red - Warning = [System.ConsoleColor]::Yellow - Info = [System.ConsoleColor]::Cyan - Update = [System.ConsoleColor]::Green - Default = [System.ConsoleColor]::White - } - - static [String] $DoubleDashLine = "========================================================================================================================" - static [String] $SingleDashLine = "------------------------------------------------------------------------------------------------------------------------" -} \ No newline at end of file From ed6e57e362470a527c131ac9057740ba77937d6d Mon Sep 17 00:00:00 2001 From: Abhishek P Date: Fri, 16 Dec 2022 13:55:53 +0530 Subject: [PATCH 08/26] Changes --- Control coverage/Feature/FrontDoorCDN.md | 75 +++++++++++++++++++ Control coverage/README.md | 61 +++++++++++++++ Scripts/RemediationScripts/Readme.md | 26 +++++++ ...mediate-EnableWAFPolicyForFrontDoorCDN.ps1 | 2 +- ...WAFPolicyPreventionModeForFrontDoorCDN.ps1 | 2 +- 5 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 Control coverage/Feature/FrontDoorCDN.md diff --git a/Control coverage/Feature/FrontDoorCDN.md b/Control coverage/Feature/FrontDoorCDN.md new file mode 100644 index 00000000..991df92a --- /dev/null +++ b/Control coverage/Feature/FrontDoorCDN.md @@ -0,0 +1,75 @@ +# FrontDoorCDN + +**Resource Type:** Microsoft.Cdn/profiles + + + +- [Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration](#azure_frontdoor_cdnprofile_netsec_enable_waf_configuration) + + + +
+ +___ + +## Azure_FrontDoor_NetSec_Enable_WAF_Configuration + +### Display Name +WAF Policy should be configured on for Endpoints in Front Door + +### Rationale +Azure Web Application Firewall (WAF) on Azure Front Door provides centralized protection for your web applications. WAF defends your web services against common exploits & vulnerablities. It keeps your service highly available for your users and helps you meet compliance requirements. + + ### Control Spec + +> **Passed:** +> Web Application Firewall has been configured on Front Door CDN
+> and Configured WAF Policy mode must be Prevention only.
+> and Configured WAF Policy mode must be in Enabled State only.
+> +> **Failed:** +> WAF is not configured on Front Door CDN.
+> or Configured WAF Policy mode is not Prevention.
+> or Configured WAF Policy mode is not in Enabled State.
+> +> **Error:** +> There was an error fetching WAF Configuration details of Front Door CDN. +> +### Recommendation +- **Azure Portal** + + Use the Azure portal to configure WAF Policy on the Front Door CDN.
+ +- **Powershell** + + Use following Powershell Bulk Remediation scripts to Configure WAF Policy on the Front Door CDN:
+ You can configure WAF Policy on Front Door using below BRS: + [Remediate-ConfigureWAFPolicyForFrontDoorCDN](../../Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1)
+ You can enable State of WAF Policy configured on Front Door using below BRS:
+ [Remediate-EnableWAFPolicyForFrontDoorCDN](../../Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1)
+ You can enable Prevention Mode on WAF Policy configured on Front Door using below BRS:
+ [Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN](../../Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1)
+ +### Azure Policy or ARM API used for evaluation + +- ARM API to get Front Door resources in a subscription: /subscriptions/{0}/ +/subscriptions/{0}/providers/Microsoft.Cdn/profiles?api-version=2021-06-01 +**Properties:** [*] +
+ +- ARM API to get Front Door Endpoints resources in a subscription: /subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Cdn/profiles/{2}/afdEndpoints?api-version=2021-06-01
+**Properties:** [*] +
+ +- ARM API to get WAF Policies in a subscription: /subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Network/frontDoorWebApplicationFirewallPolicies?api-version=2020-11-01
+**Properties:** [*] +
+ +- ARM API to get Security Policies in a subscription: + /subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Cdn/profiles/{2}/securityPolicies?api-version=2021-06-01
+**Properties:** [*] +
+ +___ + + diff --git a/Control coverage/README.md b/Control coverage/README.md index 9165ae99..aa934862 100644 --- a/Control coverage/README.md +++ b/Control coverage/README.md @@ -12,6 +12,7 @@ Below resource types can be checked for validating the security controls: |Feature Name|Resource Type| |---|---| |[APIManagement](Feature/APIManagement.md)|Microsoft.ApiManagement/service| +|[ApplicationGateway](Feature/ApplicationGateway.md)|Microsoft.Network/applicationGateways| |[AppService](Feature/AppService.md)|Microsoft.Web/sites| |[CDN](Feature/CDN.md)|Microsoft.Cdn/profiles| |[CloudService](Feature/CloudService.md)|Microsoft.ClassicCompute/domainNames| @@ -41,6 +42,8 @@ Below resource types can be checked for validating the security controls: |[VirtualMachine](Feature/VirtualMachine.md)|Microsoft.Compute/virtualMachines| |[VirtualMachineScaleSet](Feature/VirtualMachineScaleSet.md)|Microsoft.Compute/virtualMachineScaleSets| |[VirtualNetwork](Feature/VirtualNetwork.md)|Microsoft.Network/virtualNetworks| +|[FrontDoor](Feature/FrontDoor.md)|Microsoft.Network/frontDoor| +|[FrontDoorCDN](Feature/FrontDoorCDN.md)|Microsoft.Cdn/profiles| ## Externally Scanned controls in Azure Tenant Security (AzTS) @@ -59,3 +62,61 @@ Following controls in AzTS are currently externally scanned: | Azure_VirtualMachine_SI_Missing_OS_Patches | Patch assets to protect against vulnerabilities | Virtual Machine must have all the required OS patches installed | | Azure_VirtualMachine_SI_Enable_Antimalware | Ensure all devices have anti-malware protection installed and enabled | Antimalware must be enabled with real time protection on Virtual Machine | | Azure_VirtualMachine_SI_Enable_Sense_Agent | Ensure Sense Agent is installed and healthy | Sense Agent provides Threat and Vulnerability Management (TVM) data and other enhanced telemetry to the backend Microsoft Defender Advanced Threat Protection (MDATP) instance | + + +## List of controls that depends on Microsoft Defender for Cloud (MDC) in Azure Tenant Security (AzTS) + +| ControlId | DisplayName | Description | MDC Recommendation(s) | +|-----------|-------------|-------------|-----------------------| +| Azure_AppService_DP_Dont_Allow_HTTP_Access | Use HTTPS for app services | App Service must only be accessible over HTTPS | Web Application should only be accessible over HTTPS,

Function App should only be accessible over HTTPS | +| Azure_AppService_DP_Use_Secure_TLS_Version | Use Approved TLS Version in App Service | Use approved version of TLS for the App Service | TLS should be updated to the latest version for web apps,

TLS should be updated to the latest version for function apps | +| Azure_AppService_DP_Use_Secure_FTP_Deployment | App Services should use secure FTP deployments | App Services should use secure FTP deployments | FTPS should be required in web apps,

FTPS should be required in function apps | +| Azure_Storage_AuthN_Dont_Allow_Anonymous | Ensure secure access to storage account containers | The Access Type for containers must not be set to 'Anonymous' | Storage account public access should be disallowed | +| Azure_Storage_DP_Encrypt_In_Transit | Enable Secure transfer to storage accounts | HTTPS protocol must be used for accessing Storage Account resources | Secure transfer to storage accounts should be enabled | +| Azure_VirtualMachine_DP_Enable_Disk_Encryption | Disk encryption should be applied on virtual machines | Disk encryption must be enabled on both OS and data disks for Windows Virtual Machine | Virtual machines should encrypt temp disks, caches, and data flows between Compute and Storage resources | +| Azure_VirtualMachine_SI_MDC_OS_Vulnerabilities | Virtual Machine must be in a healthy state in Microsoft Defender for Cloud |Virtual Machine must be in a healthy state in Microsoft Defender for Cloud | Machines should be configured securely | +| Azure_VirtualMachine_SI_MDC_Recommendations | Virtual Machine must implement all the flagged MDC recommendations | Virtual Machine must implement all the flagged MDC recommendations | Virtual machines should encrypt temp disks, caches, and data flows between Compute and Storage resources,

Adaptive application controls for defining safe applications should be enabled on your machines,

Machines should have a vulnerability assessment solution | +| Azure_VirtualMachine_SI_Enable_Vuln_Solution | Install DSRE Qualys Cloud Agent on assets | Vulnerability assessment solution should be installed on VM | Machines should have a vulnerability assessment solution | +| Azure_VirtualMachine_NetSec_Dont_Open_Restricted_Ports | Management ports must not be open on machines | Do not leave restricted ports open on Virtual Machines | Management ports of virtual machines should be protected with just-in-time network access control | +| Azure_VNet_NetSec_Configure_NSG | Associate Subnets with a Network Security Group | NSG should be used for subnets in a virtual network to permit traffic only on required inbound/outbound ports. NSGs should not have a rule to allow any-to-any traffic | Subnets should be associated with a network security group | +| Azure_Subscription_AuthZ_Remove_Deprecated_Accounts | Remove Orphaned accounts from your subscription(s) | Deprecated/stale accounts must not be present on the subscription | Deprecated accounts should be removed from subscriptions | +| Azure_RedisCache_DP_Use_SSL_Port | Non-SSL port must not be enabled for Redis Cache | Non-SSL port must not be enabled for Redis Cache | Redis Cache should allow access only via SSL | +| Azure_ServiceFabric_DP_Set_Property_ClusterProtectionLevel | The ClusterProtectionLevel property must be set to EncryptAndSign for Service Fabric clusters |The ClusterProtectionLevel property must be set to EncryptAndSign for Service Fabric clusters | Service Fabric clusters should have the ClusterProtectionLevel property set to EncryptAndSign | +| Azure_SQLDatabase_AuthZ_Use_AAD_Admin | Use AAD Authentication for SQL Database | Enable Azure AD admin for the SQL Database | SQL servers should have an Azure Active Directory administrator provisioned | +| Azure_SQLDatabase_DP_Enable_TDE | Enable Transparent Data Encryption on SQL databases | Enable Transparent Data Encryption on SQL databases | Transparent Data Encryption on SQL databases should be enabled | + +## Frequently Asked Questions (FAQ) + +
+ +**Even after remediating my resource, it is still showing as failed against controls in AzTS UI. The controls depends on MDC Assessment. What should I do?** + +**NOTE:** *Kindly make sure that the resource(s) is(are) already fixed. The controls which depends on MDC assessment could be found [here](#list-of-controls-that-depends-on-microsoft-defender-for-cloud-mdc-in-azure-tenant-security-azts).* + +If a control depends on MDC assessment(s), validate if resource is in healthy state as per MDC recommendation(s), to validate please follow below mentioned steps: + +1. Go to **Azure Portal**. +2. Search for **Microsoft Defender for Cloud** and **open** that. + + ![Image](../Images/MDCEvaluationImage1.png) + +3. Click on **Recommendation under the General tab**, in the left side panel. + + ![Image](../Images/MDCEvaluationImage2.png) + +4. Click on the **All Recommendations**. +5. Search for the related [recommendations](#list-of-controls-that-depends-on-microsoft-defender-for-cloud-mdc-in-azure-tenant-security-azts) and open it. + + ![Image](../Images/MDCEvaluationImage3.png) + +6. Check the list of **unhealthy resources** to see if your resource is present in that list or not. +7. If your resource(s) is not present in unhealthy resources list, run the scan from AzTS UI and check the status of your resource(s). +8. If your resource(s) is present in **unhealthy resources list** and **'Fix' button is available** in the bottom, select the resource(s) that you need to remediate and click on 'Fix' button and wait till your resource(s) show up in **healthy resources list**. + + ![Image](../Images/MDCEvaluationImage4.png) + +9. If your resource(s) is present in **unhealthy resources list** and **'Fix' button is not available** in the bottom, you have to wait till the MDC evaluation is refreshed and wait till your resource(s) show up in **healthy resources list**. You can find the **refresh interval** at the top. + + ![Image](../Images/MDCEvaluationImage5.png) + +10. Once your resource(s) appear under healthy resources list, run the scan from AzTS UI to check the status of your resource(s). \ No newline at end of file diff --git a/Scripts/RemediationScripts/Readme.md b/Scripts/RemediationScripts/Readme.md index e5336d2e..f02cc4ef 100644 --- a/Scripts/RemediationScripts/Readme.md +++ b/Scripts/RemediationScripts/Readme.md @@ -36,6 +36,7 @@ Bulk remediation scripts (BRS) can be used to remediate non-compliant resources/ 24. [Azure_Subscription_Use_Only_Alt_Credentials](Readme.md#24-Azure_Subscription_Use_Only_Alt_Credentials) 25. [Azure_ServiceFabric_DP_Dont_Expose_Reverse_Proxy_Port](Readme.md#25-Azure_ServiceFabric_DP_Dont_Expose_Reverse_Proxy_Port) 26. [Azure_AppService_DP_Use_Secure_FTP_Deployment](Readme.md#26-Azure_AppService_DP_Use_Secure_FTP_Deployment) +32. [Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration](Readme.md#32-Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration)
@@ -559,6 +560,31 @@ Yes ___ +## 32. Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration + +### Display Name +WAF Policy should be configured on Endpoints in Front Door + +### Link to Bulk Remediation Script (BRS) +You can Configure WAF Policy on Front Door CDN using below BRS:
+[Remediate-ConfigureWAFPolicyForFrontDoorCDN](Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1)
+You can enable State of WAF Policy configured on Front Door using below BRS:
+[Remediate-EnableWAFPolicyForFrontDoorCDN](Remediate-EnableWAFPolicyForFrontDoorCDN.ps1)
+You can enable Prevention Mode on WAF Policy configured on Front Door using below BRS:
+[Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN](Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1)
+ +### Minimum permissions required to run the script +Contributor role at resource level + +### [Supports managed identity](Readme.md#supports-managed-identity-based-remediations) based remediation +Yes + +### Supports rollback? +Yes + + +___ + ## Supports managed identity based remediations Both System assigned and User assigned managed identities are supported. diff --git a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 index bc8be240..4a6a10da 100644 --- a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 +++ b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 @@ -6,7 +6,7 @@ Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration # Display Name: - Front Door should have Web Application Firewall configured + Front Door should have Web Application Firewall configured # Prerequisites: 1. Contributor or higher privileges on the Front Door CDNs in a Subscription. diff --git a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 index 47c1b2ee..ec2271f5 100644 --- a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 +++ b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 @@ -6,7 +6,7 @@ Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration # Display Name: - Front Door should have Web Application Firewall configured + Front Door should have Web Application Firewall configured # Prerequisites: 1. Contributor or higher privileges on the Front Door CDNs in a Subscription. From 20f9080fe552622148b48e4f6051bab3c525aea8 Mon Sep 17 00:00:00 2001 From: Abhishek P Date: Fri, 16 Dec 2022 16:24:59 +0530 Subject: [PATCH 09/26] Changes --- Control coverage/Feature/FrontDoorCDN.md | 4 +- Control coverage/README.md | 5 ++- Scripts/RemediationScripts/Readme.md | 43 +++++++++---------- ...ediate-ConfigureWAFOnAzureFrontDoorCDN.ps1 | 2 +- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Control coverage/Feature/FrontDoorCDN.md b/Control coverage/Feature/FrontDoorCDN.md index 991df92a..9c7ad965 100644 --- a/Control coverage/Feature/FrontDoorCDN.md +++ b/Control coverage/Feature/FrontDoorCDN.md @@ -15,7 +15,7 @@ ___ ## Azure_FrontDoor_NetSec_Enable_WAF_Configuration ### Display Name -WAF Policy should be configured on for Endpoints in Front Door +Front Door should have Web Application Firewall configured ### Rationale Azure Web Application Firewall (WAF) on Azure Front Door provides centralized protection for your web applications. WAF defends your web services against common exploits & vulnerablities. It keeps your service highly available for your users and helps you meet compliance requirements. @@ -43,7 +43,7 @@ Azure Web Application Firewall (WAF) on Azure Front Door provides centralized pr - **Powershell** Use following Powershell Bulk Remediation scripts to Configure WAF Policy on the Front Door CDN:
- You can configure WAF Policy on Front Door using below BRS: + You can configure WAF Policy on Front Door using below BRS:
[Remediate-ConfigureWAFPolicyForFrontDoorCDN](../../Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1)
You can enable State of WAF Policy configured on Front Door using below BRS:
[Remediate-EnableWAFPolicyForFrontDoorCDN](../../Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1)
diff --git a/Control coverage/README.md b/Control coverage/README.md index 5b64c321..26712bb4 100644 --- a/Control coverage/README.md +++ b/Control coverage/README.md @@ -24,6 +24,7 @@ Below resource types can be checked for validating the security controls: |[DBForMySqlFlexibleServer](Feature/DBForMySqlFlexibleServer.md)|Microsoft.DBforMySQL/flexibleServers| |[DBForPostgreSQL](Feature/DBForPostgreSQL.md)|Microsoft.DBforPostgreSQL/servers| |[EventHub](Feature/EventHub.md)|Microsoft.EventHub/namespaces| +|[FrontDoorCDN](Feature/FrontDoorCDN.md)|Microsoft.Cdn/profiles| |[HDInsight](Feature/HDInsight.md)|Microsoft.HDInsight/clusters| |[HybridCompute](Feature/HybridCompute.md)|Microsoft.HybridCompute/machines| |[KeyVault](Feature/KeyVault.md)|Microsoft.KeyVault/vaults| @@ -43,8 +44,8 @@ Below resource types can be checked for validating the security controls: |[VirtualMachine](Feature/VirtualMachine.md)|Microsoft.Compute/virtualMachines| |[VirtualMachineScaleSet](Feature/VirtualMachineScaleSet.md)|Microsoft.Compute/virtualMachineScaleSets| |[VirtualNetwork](Feature/VirtualNetwork.md)|Microsoft.Network/virtualNetworks| -|[FrontDoor](Feature/FrontDoor.md)|Microsoft.Network/frontDoor| -|[FrontDoorCDN](Feature/FrontDoorCDN.md)|Microsoft.Cdn/profiles| + + ## Externally Scanned controls in Azure Tenant Security (AzTS) diff --git a/Scripts/RemediationScripts/Readme.md b/Scripts/RemediationScripts/Readme.md index 31c5f986..386939df 100644 --- a/Scripts/RemediationScripts/Readme.md +++ b/Scripts/RemediationScripts/Readme.md @@ -607,21 +607,16 @@ Yes ___ -## 32. Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration +## 29. Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration ### Display Name -WAF Policy should be configured on Endpoints in Front Door +Application Gateway should have Web Application Firewall configured -### Link to Bulk Remediation Script (BRS) -You can Configure WAF Policy on Front Door CDN using below BRS:
-[Remediate-ConfigureWAFPolicyForFrontDoorCDN](Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1)
-You can enable State of WAF Policy configured on Front Door using below BRS:
-[Remediate-EnableWAFPolicyForFrontDoorCDN](Remediate-EnableWAFPolicyForFrontDoorCDN.ps1)
-You can enable Prevention Mode on WAF Policy configured on Front Door using below BRS:
-[Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN](Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1)
+### Link to Bulk Remediation Script (BRS) for Partially Remediating the control. +[Remediate-NSGConfigurationOnApplicationGatewaySubnet](Remediate-NSGConfigurationOnApplicationGatewaySubnet.ps1) ### Minimum permissions required to run the script -Contributor role at resource level +Contributor or Owner role at resource level ### [Supports managed identity](Readme.md#supports-managed-identity-based-remediations) based remediation Yes @@ -633,13 +628,15 @@ Yes ___ -## 29. Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration +## 30. Azure_LoadBalancer_NetSec_Restrict_Network_Traffic ### Display Name -Application Gateway should have Web Application Firewall configured +Protect Internet First Applications by restricting traffic on Azure Load Balancer -### Link to Bulk Remediation Script (BRS) for Partially Remediating the control. -[Remediate-NSGConfigurationOnApplicationGatewaySubnet](Remediate-NSGConfigurationOnApplicationGatewaySubnet.ps1) +### Link to Bulk Remediation Script (BRS) +[Remediate-NSGConfigurationOnLoadBalancerSubnet](Remediate-NSGConfigurationOnLoadBalancerSubnet.ps1) + +**Note** : BRS script can be used only to remediate control by configuring network security groups. Remediating with Azure Firewall is not part of this script. ### Minimum permissions required to run the script Contributor or Owner role at resource level @@ -654,18 +651,21 @@ Yes ___ -## 30. Azure_LoadBalancer_NetSec_Restrict_Network_Traffic +## 32. Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration ### Display Name -Protect Internet First Applications by restricting traffic on Azure Load Balancer - -### Link to Bulk Remediation Script (BRS) -[Remediate-NSGConfigurationOnLoadBalancerSubnet](Remediate-NSGConfigurationOnLoadBalancerSubnet.ps1) +WAF Policy should be configured on Endpoints in Front Door -**Note** : BRS script can be used only to remediate control by configuring network security groups. Remediating with Azure Firewall is not part of this script. +### Link to Bulk Remediation Script (BRS) +You can Configure WAF Policy on Front Door CDN using below BRS:
+[Remediate-ConfigureWAFPolicyForFrontDoorCDN](Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1)
+You can enable State of WAF Policy configured on Front Door using below BRS:
+[Remediate-EnableWAFPolicyForFrontDoorCDN](Remediate-EnableWAFPolicyForFrontDoorCDN.ps1)
+You can enable Prevention Mode on WAF Policy configured on Front Door using below BRS:
+[Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN](Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1)
### Minimum permissions required to run the script -Contributor or Owner role at resource level +Contributor role at resource level ### [Supports managed identity](Readme.md#supports-managed-identity-based-remediations) based remediation Yes @@ -677,7 +677,6 @@ Yes ___ - ## Supports managed identity based remediations Both System assigned and User assigned managed identities are supported. diff --git a/Scripts/RemediationScripts/Remediate-ConfigureWAFOnAzureFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-ConfigureWAFOnAzureFrontDoorCDN.ps1 index 7d7c7b37..c7cb5b0a 100644 --- a/Scripts/RemediationScripts/Remediate-ConfigureWAFOnAzureFrontDoorCDN.ps1 +++ b/Scripts/RemediationScripts/Remediate-ConfigureWAFOnAzureFrontDoorCDN.ps1 @@ -6,7 +6,7 @@ Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration # Display Name: - Front Door CDN should have Web Application Firewall configured + Front Door should have Web Application Firewall configured # Prerequisites: 1. Contributor or higher privileges on the Front Door CDNs in a Subscription. From e372ebbbf28773904f01613884eb2771fd6ef078 Mon Sep 17 00:00:00 2001 From: Abhishek P Date: Fri, 16 Dec 2022 17:15:56 +0530 Subject: [PATCH 10/26] changes --- .../Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 | 6 +++--- ...mediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 index 4a6a10da..0d0198b7 100644 --- a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 +++ b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 @@ -17,12 +17,12 @@ 1. Validate and install the modules required to run the script. 2. Get the list of all Front Door CDNs Endpoints in a Subscription that do not have WAF Policy Enabled 3. Back up details of Front Door CDN Endpoint(s) that are to be remediated. - 4. Enable Policy for all endpoints in the Frontdoors. + 4. Enable Policy for all endpoints in the Front Doors. To roll back: 1. Validate and install the modules required to run the script. - 2. Get the list of Frontdoors' Endpoint(s) in a Subscription, the changes made to which previously, are to be rolled back. - 3. Revert back Policy State to Disabled for all endpoints in all the Frontdoors. + 2. Get the list of Front Doors' Endpoint(s) in a Subscription, the changes made to which previously, are to be rolled back. + 3. Revert back Policy State to Disabled for all endpoints in all the Front Doors. # Instructions to execute the script: To remediate: diff --git a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 index ec2271f5..4cc1b083 100644 --- a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 +++ b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 @@ -17,12 +17,12 @@ 1. Validate and install the modules required to run the script. 2. Get the list of all Front Door CDNs Endpoints in a Subscription that do not have WAF Policy in Prevention Mode 3. Back up details of Front Door CDN Endpoint(s) that are to be remediated. - 4. Set Policy mode to Prevention for all endpoints in the Frontdoors. + 4. Set Policy mode to Prevention for all endpoints in the Front Doors. To roll back: 1. Validate and install the modules required to run the script. - 2. Get the list of Frontdoors' Endpoint(s) in a Subscription, the changes made to which previously, are to be rolled back. - 3. Revert Policy mode to Detection all endpoints in all the Frontdoors. + 2. Get the list of Front Doors' Endpoint(s) in a Subscription, the changes made to which previously, are to be rolled back. + 3. Revert Policy mode to Detection all endpoints in all the Front Doors. # Instructions to execute the script: To remediate: From 48f5dead057cadaffa56f88d0c9262f72232f141 Mon Sep 17 00:00:00 2001 From: Abhishek P Date: Fri, 16 Dec 2022 18:15:48 +0530 Subject: [PATCH 11/26] changes --- Control coverage/Feature/CDN.md | 65 ++++++++++++++++++++ Control coverage/Feature/FrontDoorCDN.md | 75 ------------------------ Control coverage/README.md | 1 - 3 files changed, 65 insertions(+), 76 deletions(-) delete mode 100644 Control coverage/Feature/FrontDoorCDN.md diff --git a/Control coverage/Feature/CDN.md b/Control coverage/Feature/CDN.md index 0dbee199..d8c17cf9 100644 --- a/Control coverage/Feature/CDN.md +++ b/Control coverage/Feature/CDN.md @@ -5,6 +5,7 @@ - [Azure_CDN_DP_Enable_Https](#azure_cdn_dp_enable_https) +- [Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration](#azure_frontdoor_cdnprofile_netsec_enable_waf_configuration)
@@ -60,5 +61,69 @@ One of the following conditions is met: /subscriptions/{subscriptionId}/resourceGroups/{resourcegroupName}/providers/Microsoft.Cdn/profiles/{profileName}/endpoints?api-version=2019-12-31
**Properties:** properties.isHttpAllowed, properties.isHttpsAllowed, properties.deliveryPolicy.rules + +
+ +___ + + + ## Azure_FrontDoor_NetSec_Enable_WAF_Configuration + +### Display Name +Front Door should have Web Application Firewall configured + +### Rationale +Azure Web Application Firewall (WAF) on Azure Front Door provides centralized protection for your web applications. WAF defends your web services against common exploits & vulnerablities. It keeps your service highly available for your users and helps you meet compliance requirements. + + ### Control Spec + +> **Passed:** +> Web Application Firewall has been configured on Front Door CDN
+> and Configured WAF Policy mode must be Prevention only.
+> and Configured WAF Policy mode must be in Enabled State only.
+> +> **Failed:** +> WAF is not configured on Front Door CDN.
+> or Configured WAF Policy mode is not Prevention.
+> or Configured WAF Policy mode is not in Enabled State.
+> +> **Error:** +> There was an error fetching WAF Configuration details of Front Door CDN. +> +### Recommendation +- **Azure Portal** + + Use the Azure portal to configure WAF Policy on the Front Door CDN.
+ +- **Powershell** + + Use following Powershell Bulk Remediation scripts to Configure WAF Policy on the Front Door CDN:
+ You can configure WAF Policy on Front Door using below BRS:
+ [Remediate-ConfigureWAFPolicyForFrontDoorCDN](../../Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1)
+ You can enable State of WAF Policy configured on Front Door using below BRS:
+ [Remediate-EnableWAFPolicyForFrontDoorCDN](../../Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1)
+ You can enable Prevention Mode on WAF Policy configured on Front Door using below BRS:
+ [Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN](../../Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1)
+ +### Azure Policy or ARM API used for evaluation + +- ARM API to get Front Door resources in a subscription: /subscriptions/{0}/ +/subscriptions/{0}/providers/Microsoft.Cdn/profiles?api-version=2021-06-01 +**Properties:** [*]
+- ARM API to get Front Door Endpoints resources in a subscription: /subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Cdn/profiles/{2}/afdEndpoints?api-version=2021-06-01
+**Properties:** [*] +
+ +- ARM API to get WAF Policies in a subscription: /subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Network/frontDoorWebApplicationFirewallPolicies?api-version=2020-11-01
+**Properties:** [*] +
+ +- ARM API to get Security Policies in a subscription: + /subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Cdn/profiles/{2}/securityPolicies?api-version=2021-06-01
+**Properties:** [*] +
+ +___ + diff --git a/Control coverage/Feature/FrontDoorCDN.md b/Control coverage/Feature/FrontDoorCDN.md deleted file mode 100644 index 9c7ad965..00000000 --- a/Control coverage/Feature/FrontDoorCDN.md +++ /dev/null @@ -1,75 +0,0 @@ -# FrontDoorCDN - -**Resource Type:** Microsoft.Cdn/profiles - - - -- [Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration](#azure_frontdoor_cdnprofile_netsec_enable_waf_configuration) - - - -
- -___ - -## Azure_FrontDoor_NetSec_Enable_WAF_Configuration - -### Display Name -Front Door should have Web Application Firewall configured - -### Rationale -Azure Web Application Firewall (WAF) on Azure Front Door provides centralized protection for your web applications. WAF defends your web services against common exploits & vulnerablities. It keeps your service highly available for your users and helps you meet compliance requirements. - - ### Control Spec - -> **Passed:** -> Web Application Firewall has been configured on Front Door CDN
-> and Configured WAF Policy mode must be Prevention only.
-> and Configured WAF Policy mode must be in Enabled State only.
-> -> **Failed:** -> WAF is not configured on Front Door CDN.
-> or Configured WAF Policy mode is not Prevention.
-> or Configured WAF Policy mode is not in Enabled State.
-> -> **Error:** -> There was an error fetching WAF Configuration details of Front Door CDN. -> -### Recommendation -- **Azure Portal** - - Use the Azure portal to configure WAF Policy on the Front Door CDN.
- -- **Powershell** - - Use following Powershell Bulk Remediation scripts to Configure WAF Policy on the Front Door CDN:
- You can configure WAF Policy on Front Door using below BRS:
- [Remediate-ConfigureWAFPolicyForFrontDoorCDN](../../Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1)
- You can enable State of WAF Policy configured on Front Door using below BRS:
- [Remediate-EnableWAFPolicyForFrontDoorCDN](../../Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1)
- You can enable Prevention Mode on WAF Policy configured on Front Door using below BRS:
- [Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN](../../Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1)
- -### Azure Policy or ARM API used for evaluation - -- ARM API to get Front Door resources in a subscription: /subscriptions/{0}/ -/subscriptions/{0}/providers/Microsoft.Cdn/profiles?api-version=2021-06-01 -**Properties:** [*] -
- -- ARM API to get Front Door Endpoints resources in a subscription: /subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Cdn/profiles/{2}/afdEndpoints?api-version=2021-06-01
-**Properties:** [*] -
- -- ARM API to get WAF Policies in a subscription: /subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Network/frontDoorWebApplicationFirewallPolicies?api-version=2020-11-01
-**Properties:** [*] -
- -- ARM API to get Security Policies in a subscription: - /subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Cdn/profiles/{2}/securityPolicies?api-version=2021-06-01
-**Properties:** [*] -
- -___ - - diff --git a/Control coverage/README.md b/Control coverage/README.md index 26712bb4..14caa234 100644 --- a/Control coverage/README.md +++ b/Control coverage/README.md @@ -24,7 +24,6 @@ Below resource types can be checked for validating the security controls: |[DBForMySqlFlexibleServer](Feature/DBForMySqlFlexibleServer.md)|Microsoft.DBforMySQL/flexibleServers| |[DBForPostgreSQL](Feature/DBForPostgreSQL.md)|Microsoft.DBforPostgreSQL/servers| |[EventHub](Feature/EventHub.md)|Microsoft.EventHub/namespaces| -|[FrontDoorCDN](Feature/FrontDoorCDN.md)|Microsoft.Cdn/profiles| |[HDInsight](Feature/HDInsight.md)|Microsoft.HDInsight/clusters| |[HybridCompute](Feature/HybridCompute.md)|Microsoft.HybridCompute/machines| |[KeyVault](Feature/KeyVault.md)|Microsoft.KeyVault/vaults| From faec7a0d711fdcb433cea42678529437adfab808 Mon Sep 17 00:00:00 2001 From: Abhishek P Date: Fri, 16 Dec 2022 18:28:21 +0530 Subject: [PATCH 12/26] Changes --- Control coverage/Feature/CDN.md | 2 +- ...ediate-ConfigureWAFOnAzureFrontDoorCDN.ps1 | 810 ------------------ 2 files changed, 1 insertion(+), 811 deletions(-) delete mode 100644 Scripts/RemediationScripts/Remediate-ConfigureWAFOnAzureFrontDoorCDN.ps1 diff --git a/Control coverage/Feature/CDN.md b/Control coverage/Feature/CDN.md index d8c17cf9..2e281030 100644 --- a/Control coverage/Feature/CDN.md +++ b/Control coverage/Feature/CDN.md @@ -67,7 +67,7 @@ properties.isHttpAllowed, properties.isHttpsAllowed, properties.deliveryPolicy.r ___ - ## Azure_FrontDoor_NetSec_Enable_WAF_Configuration + ## Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration ### Display Name Front Door should have Web Application Firewall configured diff --git a/Scripts/RemediationScripts/Remediate-ConfigureWAFOnAzureFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-ConfigureWAFOnAzureFrontDoorCDN.ps1 deleted file mode 100644 index c7cb5b0a..00000000 --- a/Scripts/RemediationScripts/Remediate-ConfigureWAFOnAzureFrontDoorCDN.ps1 +++ /dev/null @@ -1,810 +0,0 @@ -<### -# Overview: - This script is used to configure WAF Policy on all endpoints of Front Door CDN(s) in a Subscription. - -# Control ID: - Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration - -# Display Name: - Front Door should have Web Application Firewall configured - -# Prerequisites: - 1. Contributor or higher privileges on the Front Door CDNs in a Subscription. - 2. Must be connected to Azure with same authenticated account with which have required access over Front door CDNs. - -# Steps performed by the script: - To remediate: - 1. Validate and install the modules required to run the script. - 2. Get the list of all Front Door CDNs Endpoints in a Subscription that do not have WAF Policy Configured - 3. Back up details of Front Door CDN Endpoint(s) that are to be remediated. - 4. Configure WAF for all endpoints in the Front Door. - - To roll back: - 1. Validate and install the modules required to run the script. - 2. Get the list of Front Door' Endpoint(s) in a Subscription, the changes made to which previously, are to be rolled back. - 3. Revert by removing WAF Policies from endpoints in all the Front Door. - -# Instructions to execute the script: - To remediate: - 1. Download the script. - 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. - 3. Execute the script to configure WAF Policy on all endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. - - To roll back: - 1. Download the script. - 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. - 3. Execute the script to roll back the WAF Policy Configuration on all endpoints of Front Door CDNs in a Subscription- we previously remediated. Refer `Examples`, below. - -# Examples: - To remediate: - 1. To review the Front Door CDNs in a Subscription that will be remediated: - Configure-WAFConfigurationOnFrontDoorEndpoint -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun - - 2. To configure WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription: - Configure-WAFConfigurationOnFrontDoorEndpoint -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck - - 3. To configure WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription, from a previously taken snapshot: - Configure-WAFConfigurationOnFrontDoorEndpoint -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\ConfigureFrontDoorCDNWAFPolicy\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv - - To know more about the options supported by the remediation command, execute: - Get-Help Configure-WAFConfigurationOnFrontDoorEndpoint -Detailed - - To roll back: - 1. To remove configured WAF Policy Mode for Endpoint(s) all Front Door CDNs in a Subscription, from a previously taken snapshot: - Remove-WAFConfigurationOnFrontDoorEndpoint -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\ConfigureFrontDoorCDNWAFPolicy\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv - - To know more about the options supported by the roll back command, execute: - Get-Help Remove-WAFConfigurationOnFrontDoorEndpoint -Detailed -###> - -function Setup-Prerequisites -{ - <# - .SYNOPSIS - Checks if the prerequisites are met, else, sets them up. - - .DESCRIPTION - Checks if the prerequisites are met, else, sets them up. - Includes installing any required Azure modules. - - .INPUTS - None. You cannot pipe objects to Setup-Prerequisites. - - .OUTPUTS - None. Setup-Prerequisites does not return anything that can be piped and used as an input to another command. - - .EXAMPLE - PS> Setup-Prerequisites - - .LINK - None - #> - - # List of required modules - $requiredModules = @("Az.Accounts", "Az.FrontDoor", "Az.Cdn") - - Write-Host "Required modules: $($requiredModules -join ', ')" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Checking if the required modules are present..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - - $availableModules = $(Get-Module -ListAvailable $requiredModules -ErrorAction Stop) - - # Check if the required modules are installed. - $requiredModules | ForEach-Object { - if ($availableModules.Name -notcontains $_) - { - Write-Host "[$($_)] module is not present." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host "Installing [$($_)] module..." -ForegroundColor $([Constants]::MessageType.Info) - Install-Module -Name $_ -Scope CurrentUser -Repository 'PSGallery' -ErrorAction Stop - Write-Host "[$($_)] module installed." -ForegroundColor $([Constants]::MessageType.Update) - } - else - { - Write-Host "[$($_)] module is present." -ForegroundColor $([Constants]::MessageType.Update) - } - } -} - -function Configure-WAFConfigurationOnFrontDoorEndpoint -{ - <# - .SYNOPSIS - Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - - .DESCRIPTION - Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - Configure WAF Policy for Endpoint(s) in Front Door(s) in the Subscription. - - .PARAMETER SubscriptionId - Specifies the ID of the Subscription that needs to be remediated. - - .PARAMETER Force - Specifies a forceful roll back with mandatory user input prompts only. - - .Parameter PerformPreReqCheck - Specifies validation of prerequisites for the command. - - .PARAMETER FilePath - Specifies the path to the file to be used as input for the remediation. - - .INPUTS - None. You cannot pipe objects to Configure-WAFConfigurationOnFrontDoorEndpoint. - - .OUTPUTS - None. Configure-WAFConfigurationOnFrontDoorEndpoint does not return anything that can be piped and used as an input to another command. - - .EXAMPLE - PS> Configure-WAFConfigurationOnFrontDoorEndpoint -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202211190719\ConfigureFrontDoorCDNWAFPolicy\FrontDoorEndpointWithWAFConfiguration.csv - - .LINK - None - #> - - param ( - - [String] - [Parameter(ParameterSetName = "WetRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] - [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies a dry run of the actual remediation")] - $SubscriptionId, - - [Switch] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies validation of prerequisites for the command")] - [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies a dry run of the actual remediation")] - $PerformPreReqCheck, - - [Switch] - [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies a dry run of the actual remediation")] - $DryRun, - - [String] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation")] - $FilePath - ) - - Write-Host $([Constants]::DoubleDashLine) - if ($PerformPreReqCheck) - { - try - { - Write-Host "[Step 1 of 4] Validate and install the modules required to run the script and validate the user" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Setup-Prerequisites - Write-Host "Completed setting up prerequisites" - Write-Host $([Constants]::SingleDashLine) - } - catch - { - Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - return - } - } - else - { - Write-Host "[Step 1 of 4] Validate the user" - Write-Host $([Constants]::SingleDashLine) - } - - # Connect to Azure account - $context = Get-AzContext - - if ([String]::IsNullOrWhiteSpace($context)) - { - - Write-Host "Connecting to Azure account..." - Write-Host $([Constants]::SingleDashLine) - Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null - Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - else - { - # Setting up context for the current Subscription. - $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop - } - - Write-Host "Subscription Name: [$($context.Subscription.Name)]" - Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" - Write-Host "Account Name: [$($context.Account.Id)]" - Write-Host "Account Type: [$($context.Account.Type)]" - Write-Host $([Constants]::SingleDashLine) - - # Note about the required access required for remediation - - Write-Host "To Configured WAF Policy for all Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Write-Host "[Step 2 of 4] Preparing to fetch all Front Door CDN Endpoints" - Write-Host $([Constants]::SingleDashLine) - - # list to store Front Door End Points - $frontDoors = @() - $frontDoorEndPoints = @() - - # To keep track of remediated and skipped resources - $logRemediatedResources = @() - $logSkippedResources=@() - - # Control Id - $controlIds = "Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration" - - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - Write-Host "Fetch all Front Door(s) in Subscription: [$($context.Subscription.SubscriptionId)]" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - - # Get all Front Door(s) in a Subscription - $frontDoors = Get-AzFrontDoorCdnProfile -ErrorAction Stop - - # Seperating required properties - $totalfrontDoors = ($frontDoors | Measure-Object).Count - - if($totalfrontDoors -gt 0) - { - $frontDoors | ForEach-Object { - $frontDoor = $_ - $frontDoorId = $_.Id - $resourceGroupName = $_.ResourceGroupName - $frontDoorName = $_.Name - - # Get all Frontendpoint(s) for this Front Door. - $frontendpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) - if($frontendpoints -ne $null) - { - $SecurityPolicies = ( Get-AzFrontDoorCdnSecurityPolicy -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) - if($SecurityPolicies -ne $null) - { - $frontDoorEndPoints += $frontendpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='SecurityPolicyName';E="NA"}, - @{N='IsWAFConfigured';E={ - foreach($policy in $SecurityPolicies) - { - foreach($association in $policy.Parameter.Association) - { - if($association.Domain.Id.Split('/')[10] -eq $_.Name) - { - if($policy.Parameter.WafPolicyId -ne $null) - { - $true - } - else - { - $false - } - } - } - } - } - } - } - else - { - Write-Host "Error fetching Security Policies of Front Door(s) resource. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - } - - } - else{ - Write-Host "Error fetching End points of Front Door(s) resource. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - } - - - } - } - } - else{ - - - if (-not (Test-Path -Path $FilePath)) - { - Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - break - } - - Write-Host "Fetch all Front Door CDN Endpoints from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath - $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } - - $validfrontDoorEndpointsDetails| ForEach-Object { - $resourceId = $_.EndpointId - - try - { - $frontendpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $_.ResourceGroupName -ProfileName $_.FrontDoorName -ErrorAction SilentlyContinue) - $SecurityPolicies = ( Get-AzFrontDoorCdnSecurityPolicy -ResourceGroupName $_.ResourceGroupName -ProfileName $_.FrontDoorName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $frontendpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$_.Id.Split('/')[8]}}, - @{N='ResourceGroupName';E={$_.ResourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='SecurityPolicyName';E="NA"}, - @{N='IsWAFConfigured';E={ - foreach($policy in $SecurityPolicies) - { - foreach($association in $policy.Parameter.Association) - { - if($association.Domain.Id.Split('/')[10] -eq $_.Name ) - { - if($policy.Parameter.WafPolicyId -ne $null) - { - $true - } - else - { - $false - } - } - } - } - } - } - } - catch - { - Write-Host "Error fetching End points of Front Door(s) resource: Resource ID: [$($EndpointId)]. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - } - } - } - - - $totalFrontDoorEndPoints = ($frontDoorEndPoints| Measure-Object).Count - - if ($totalFrontDoorEndPoints -eq 0) - { - Write-Host "No Front Door EndPoint(s) found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::DoubleDashLine) - return - } - - Write-Host "Found [$($totalFrontDoorEndPoints)] Front Door End Point(s)." -ForegroundColor $([Constants]::MessageType.Update) - - Write-Host $([Constants]::SingleDashLine) - - # list for storing Front Door EndPoint(s) for which WAF is not configured. - $EndPointsWithoutWAFConfigured = @() - - Write-Host "Separating Endpoint(s) for which WAF is not configured..." -ForegroundColor $([Constants]::MessageType.Info) - - $frontDoorEndPoints | ForEach-Object { - $EndPoint = $_ - if($_.IsWAFConfigured -ne $true) - { - $_.IsWAFConfigured = $false - $EndPointsWithoutWAFConfigured += $EndPoint - } - } - - $totalEndPointsWithoutWAFConfigured = ($EndPointsWithoutWAFConfigured | Measure-Object).Count - - if ($totalEndPointsWithoutWAFConfigured -eq 0) - { - Write-Host "No EndPoint(s) found with where WAF is not configured.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - return - } - - Write-Host "Found [$($totalEndPointsWithoutWAFConfigured)] EndPoint(s) for which WAF is not configured ." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - - $colsProperty = @{Expression={$_.EndpointId};Label="EndpointId";Width=30;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="FrontDoorName";Width=30;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="ResourceGroupName";Width=100;Alignment="left"}, - @{Expression={$_.EndPointName};Label="EndPointName";Width=100;Alignment="left"} - @{Expression={$_.SecurityPolicyName};Label="SecurityPolicyName";Width=100;Alignment="left"} - @{Expression={$_.IsWAFConfigured};Label="IsWAFConfigured";Width=100;Alignment="left"} - - Write-Host "Endpoint(s) without WAF configuration are as follows:" - $EndPointsWithoutWAFConfigured | Format-Table -Property $colsProperty -Wrap - Write-Host $([Constants]::SingleDashLine) - - # Back up snapshots to `%LocalApplicationData%'. - $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\ConfiguredWAFOnEndPoint" - - if (-not (Test-Path -Path $backupFolderPath)) - { - New-Item -ItemType Directory -Path $backupFolderPath | Out-Null - } - - Write-Host "[Step 3 of 4] Back up Endpoint(s) details..." - Write-Host $([Constants]::SingleDashLine) - - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - # Backing up EndPoint(s) details. - $backupFile = "$($backupFolderPath)\FrontDoorEndPointDetailsBackUp.csv" - $EndPointsWithoutWAFConfigured | Export-CSV -Path $backupFile -NoTypeInformation - Write-Host "EndPoint(s) details have been backed up to [$($backupFile)]" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - else - { - Write-Host "Skipped as -FilePath is provided" -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - - if (-not $DryRun) - { - Write-Host $([Constants]::DoubleDashLine) - Write-Host "[Step 4 of 4] Configure the WAF on Endpoint(s) of Front Door(s) in the Subscription..." - Write-Host $([Constants]::SingleDashLine) - - if (-not $Force) - { - Write-Host "Do you want to configure WAF on the Endpoint(s) of Front Door(s) in the Subscription? " -ForegroundColor $([Constants]::MessageType.Warning) - - $userInput = Read-Host -Prompt "(Y|N)" - - if($userInput -ne "Y") - { - Write-Host "we are starting the procedure to configure the WAF on the Endpoint(s) of Front Door(s) in the Subscription. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - return - } - } - - # List for storing remediated Endpoint(s) - $EndpointRemediated = @() - - # List for storing skipped Subnet(s) - $EndpointSkipped = @() - - Write-Host "Enabling the WAF on Endpoint(s)..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - - # Loop through the list of Endpoint(s) which needs to be remediated. - $EndPointsWithoutWAFConfigured | ForEach-Object { - $endpoint = $_ - try - { - Write-Host "To Start configuring the WAF on the Endpoint(s), Please enter the WAF Policy Details for Front Door" + $_.FrontDoorName -ForegroundColor $([Constants]::MessageType.Info) - $policyName = Read-Host -Prompt "Please enter name of Web Application Firewall Policy Name which is not assigned to any other end point of Front door " - $policyRGName = Read-Host -Prompt "Please enter Resource Group of Web Application Firewall Policy" - if($policyName -ne $null -and $policyRGName -ne $null) - { - $policy = $policy = Get-AzFrontDoorWafPolicy -ResourceGroupName $policyRGName -Name $policyName - if($policy -ne $null) - { - $securityPolicyName = $_.EndPointName + "SecurityPolicy" - $endpointDetails = Get-AzFrontDoorCdnEndpoint -ResourceGroupName $_.ResourceGroupName -ProfileName $_.FrontDoorName -EndpointName $_.EndPointName - $updateAssociation = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$($endpointDetails.Id)}) - $updateWafParameter = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallParametersObject -Association @($updateAssociation) -WafPolicyId $policy.Id - $securityPolicy = New-AzFrontDoorCdnSecurityPolicy -ProfileName $_.FrontDoorName -ResourceGroupName $_.ResourceGroupName -Name $securityPolicyName -Parameter $updateWafParameter - - if($securityPolicy.Parameter.WafPolicyId -ne $null) - { - $endpoint.IsWAFConfigured = $true - $endpoint.SecurityPolicyName = $securityPolicyName - $EndpointRemediated += $endpoint - $logResource = @{} - $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) - $logResource.Add("ResourceName",($_.EndPointName)) - $logRemediatedResources += $logResource - } - else - { - $EndpointSkipped += $endpoint - $logResource = @{} - $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) - $logResource.Add("ResourceName",($_.EndPointName)) - $logResource.Add("Reason", "Error Configuring NSG on : [$($endpoint)]") - $logSkippedResources += $logResource - } - } - else - { - $EndpointSkipped += $endpoint - $logResource = @{} - $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) - $logResource.Add("ResourceName",($_.EndPointName)) - $logResource.Add("Reason", "Error Configuring NSG on : [$($endpoint)]") - $logSkippedResources += $logResource - } - } - else - { - Write-Host "WAF Policy Name or Resource Group can not be empty..." -ForegroundColor $([Constants]::MessageType.Info) - $EndpointSkipped += $endpoint - return; - } - } - catch - { - $EndpointSkipped += $endpoint - $logResource = @{} - $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) - $logResource.Add("ResourceName",($_.EndPointName)) - $logResource.Add("Reason","Encountered error Configuring WAF") - $logSkippedResources += $logResource - Write-Host "Skipping this resource..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - } - - Write-Host $([Constants]::DoubleDashLine) - - Write-Host "Remediation Summary: " -ForegroundColor $([Constants]::MessageType.Info) - if ($($EndpointRemediated | Measure-Object).Count -gt 0) - { - Write-Host "Successfully configured the WAF on the Endpoint(s) of Front Door(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - $EndpointRemediated | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $EndpointRemediatedFile = "$($backupFolderPath)\RemediatedFrontDoorEndpoint.csv" - $EndpointRemediated | Export-CSV -Path $EndpointRemediatedFile -NoTypeInformation - - Write-Host "This information has been saved to" -NoNewline - Write-Host " [$($EndpointRemediatedFile)]" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) - } - - if ($($EndpointSkipped | Measure-Object).Count -gt 0) - { - - Write-Host "Error while configuring WAF on the Endpoint(s) of Front Door(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::SingleDashLine) - $EndpointSkipped | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $EndpointSkippedFile = "$($backupFolderPath)\SkippedSubnet.csv" - $EndpointSkipped | Export-CSV -Path $EndpointSkippedFile -NoTypeInformation - Write-Host "This information has been saved to" -NoNewline - Write-Host " [$($EndpointSkippedFile)]" -ForegroundColor $([Constants]::MessageType.Update) - } - - - } - - } - -function Remove-WAFConfigurationOnFrontDoorEndpoint -{ - <# - .SYNOPSIS - Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - - .DESCRIPTION - Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - Remove WAF configuration from the Endpoint(s) in the Subscription. - - .PARAMETER SubscriptionId - Specifies the ID of the Subscription that was previously remediated. - - .Parameter PerformPreReqCheck - Specifies validation of prerequisites for the command. - - .PARAMETER FilePath - Specifies the path to the file to be used as input for the roll back. - - .INPUTS - None. You cannot pipe objects to Remove-WAFConfigurationOnFrontDoorEndpoint. - - .OUTPUTS - None. Remove-WAFConfigurationOnFrontDoorEndpoint does not return anything that can be piped and used as an input to another command. - - .EXAMPLE - PS> Remove-WAFConfigurationOnFrontDoorEndpoint -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\RemoveNSGConfiguration\RemediatedFrontDoorEndpoints.csv - - .LINK - None - #> - - param ( - [String] - [Parameter(Mandatory = $true, HelpMessage="Specifies the ID of the Subscription that was previously remediated.")] - $SubscriptionId, - - [Switch] - [Parameter(HelpMessage="Specifies validation of prerequisites for the command")] - $PerformPreReqCheck, - - [String] - [Parameter(Mandatory = $true, HelpMessage="Specifies the path to the file to be used as input for the roll back")] - $FilePath - ) - - if ($PerformPreReqCheck) - { - try - { - Write-Host "[Step 1 of 3] Validate and install the modules required to run the script and validate the user..." - Write-Host $([Constants]::SingleDashLine) - Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Setup-Prerequisites - Write-Host "Completed setting up prerequisites" - Write-Host $([Constants]::SingleDashLine) - } - catch - { - Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - return - } - } - else - { - Write-Host "[Step 1 of 3] Validate the user..." - Write-Host $([Constants]::SingleDashLine) - } - - # Connect to Azure account - $context = Get-AzContext - - if ([String]::IsNullOrWhiteSpace($context)) - { - Write-Host $([Constants]::SingleDashLine) - Write-Host "Connecting to Azure account..." - Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null - Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) - } - else - { - # Setting up context for the current Subscription. - $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop - } - - Write-Host $([Constants]::SingleDashLine) - Write-Host "Subscription Name: [$($context.Subscription.Name)]" - Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" - Write-Host "Account Name: [$($context.Account.Id)]" - Write-Host "Account Type: [$($context.Account.Type)]" - Write-Host $([Constants]::SingleDashLine) - - # Note about the required access required for remediation - - Write-Host "To remove WAF configuration from the Ebdpoints(s) of Front Door(s) in a Subscription, Contributor or higher privileged role assignment on the Front Door(s) is required." -ForegroundColor $([Constants]::MessageType.Warning) - - Write-Host $([Constants]::SingleDashLine) - Write-Host "[Step 2 of 3] Prepare to fetch all Front Door(s)" - Write-Host $([Constants]::SingleDashLine) - - if (-not (Test-Path -Path $FilePath)) - { - Write-Host "Input file: [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - return - } - - Write-Host "Fetch all Endpoint(s) of Front Door from" -NoNewline - Write-Host " [$($FilePath)\...]..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - $FrontDoorEndpointDetails = Import-Csv -LiteralPath $FilePath - - $validFrontDoorEndpointDetails = $FrontDoorEndpointDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndpointId) -and ![String]::IsNullOrWhiteSpace($_.ResourceGroupName) -and ![String]::IsNullOrWhiteSpace($_.FrontDoorName) -and ![String]::IsNullOrWhiteSpace($_.SecurityPolicyName) } - - $totalFrontDoorEndpoints = $(($validFrontDoorEndpointDetails|Measure-Object).Count) - - if ($totalFrontDoorEndpoints -eq 0) - { - Write-Host "No Endpoint of Front Door(s) found. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - return - } - - Write-Host "Found [$(($validFrontDoorEndpointDetails|Measure-Object).Count)] Subnet(s)." -ForegroundColor $([Constants]::MessageType.Update) - - $colsProperty = @{Expression={$_.EndpointId};Label="EndpointId";Width=30;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="FrontDoorName";Width=30;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="ResourceGroupName";Width=100;Alignment="left"}, - @{Expression={$_.EndPointName};Label="EndPointName";Width=100;Alignment="left"} - @{Expression={$_.SecurityPolicyName};Label="SecurityPolicyName";Width=100;Alignment="left"} - @{Expression={$_.IsWAFConfigured};Label="IsWAFConfigured";Width=100;Alignment="left"} - - - $validFrontDoorEndpointDetails | Format-Table -Property $colsProperty -Wrap - - # Back up snapshots to `%LocalApplicationData%'. - $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\RemoveWAFfromFrontDoorEndPoint" - - if (-not (Test-Path -Path $backupFolderPath)) - { - New-Item -ItemType Directory -Path $backupFolderPath | Out-Null - } - - Write-Host $([Constants]::DoubleDashLine) - Write-Host "[Step 3 of 3] Remove WAF Configuration from all remediated Endpoint(s) of Front Door(s) in the Subscription" - Write-Host $([Constants]::SingleDashLine) - - if( -not $Force) - { - - Write-Host "Do you want to remove WAF Configuration from Endpoint(s) mentioned in the file?" -ForegroundColor $([Constants]::MessageType.Warning) - $userInput = Read-Host -Prompt "(Y|N)" - - if($userInput -ne "Y") - { - Write-Host "WAF Configuration will not be rolled back on Endpoint(s) of Front Door(s) mentioned in the file. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - return - } - Write-Host "WAF Configuration will be rolled back on Endpoint(s) of Front Door(s) mentioned in the file." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - else - { - Write-Host "'Force' flag is provided. WAF Configuration will be rolled back on Endpoint(s) of Front Door(s) in the Subscription without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - - # List for storing rolled back Subnet resource. - $EndpointsRolledBack = @() - - # List for storing skipped rolled back Subnet resource. - $EndpointsSkipped = @() - - $validFrontDoorEndpointDetails | ForEach-Object { - $Endpoint = $_ - try - { - - $remediatedEndpointDetails = Remove-AzFrontDoorCdnSecurityPolicy -ProfileName $_.FrontDoorName -ResourceGroupName $_.ResourceGroupName -Name $_.SecurityPolicyName - if($remediatedEndpointDetails.Parameter.WafPolicyId -eq $null) - { - $Endpoint.IsWAFConfigured = $false - $EndpointsRolledBack += $Endpoint - } - else - { - $EndpointsSkipped += $Endpoint - } - } - catch - { - $EndpointsSkipped += $Subnet - } - } - - Write-Host $([Constants]::DoubleDashLine) - Write-Host "Rollback Summary:`n" -ForegroundColor $([Constants]::MessageType.Info) - - if ($($EndpointsRolledBack | Measure-Object).Count -gt 0) - { - Write-Host "WAF configuration has been removed on the following Endpoint(s) of the Front Door(s) in the Subscription.:" -ForegroundColor $([Constants]::MessageType.Update) - $EndpointsRolledBack | Format-Table -Property $colsProperty -Wrap - Write-Host $([Constants]::SingleDashLine) - - # Write this to a file. - $EndpointsRolledBackFile = "$($backupFolderPath)\RolledBackFrontDoorEndpoints.csv" - $EndpointsRolledBack | Export-CSV -Path $$EndpointsRolledBackFile -NoTypeInformation - Write-Host "This information has been saved to" -NoNewline - Write-Host " [$($EndpointsRolledBackFile)]" -ForegroundColor $([Constants]::MessageType.Update) - } - - if ($($EndpointsSkipped | Measure-Object).Count -gt 0) - { - Write-Host "Error while removing WAF configuration on the Endpoint(s) of Front Door(s) in the Subscription.:" -ForegroundColor $([Constants]::MessageType.Error) - $EndpointsSkipped | Format-Table -Property $colsProperty -Wrap - Write-Host $([Constants]::SingleDashLine) - - - # Write this to a file. - $EndpointsSkippedFile = "$($backupFolderPath)\RollbackSkippedFrontDoorEndpoints.csv" - $EndpointsSkipped | Export-CSV -Path $EndpointsSkippedFile -NoTypeInformation - Write-Host "This information has been saved to" -NoNewline - Write-Host " [$($EndpointsSkippedFile)]" -ForegroundColor $([Constants]::MessageType.Update) - } -} - -# Defines commonly used constants. -class Constants -{ - # Defines commonly used colour codes, corresponding to the severity of the log... - static [Hashtable] $MessageType = @{ - Error = [System.ConsoleColor]::Red - Warning = [System.ConsoleColor]::Yellow - Info = [System.ConsoleColor]::Cyan - Update = [System.ConsoleColor]::Green - Default = [System.ConsoleColor]::White - } - - static [String] $DoubleDashLine = "========================================================================================================================" - static [String] $SingleDashLine = "------------------------------------------------------------------------------------------------------------------------" -} - - \ No newline at end of file From a31df0d1d2f1250b455068fb40fdcd643c4b3bdf Mon Sep 17 00:00:00 2001 From: Abhishek P Date: Fri, 16 Dec 2022 19:06:10 +0530 Subject: [PATCH 13/26] hihh --- ...iate-ConfigureWAFPolicyForFrontDoorCDN.ps1 | 1164 +++++++++++++++++ 1 file changed, 1164 insertions(+) create mode 100644 Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 diff --git a/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 new file mode 100644 index 00000000..489a8dd8 --- /dev/null +++ b/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 @@ -0,0 +1,1164 @@ +<### +# Overview: + This script is used to configure WAF Policy on all endpoints of Front Door CDNs in a Subscription. + +# Control ID: + Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration + +# Display Name: + Front Door should have Web Application Firewall configured + +# Prerequisites: + 1. Contributor or higher privileges on the Front Door CDNs in a Subscription. + 2. Must be connected to Azure with an authenticated account. + +# Steps performed by the script: + To remediate: + 1. Validate and install the modules required to run the script. + 2. Get the list of all Front Door CDNs Endpoints in a Subscription that do not have WAF Policy Configured + 3. Back up details of Front Door CDN Endpoint(s) that are to be remediated. + 4. Configure WAF Policy for all endpoints in the Frontdoors. + + To roll back: + 1. Validate and install the modules required to run the script. + 2. Get the list of Frontdoors' Endpoint(s) in a Subscription, the changes made to which previously, are to be rolled back. + 3. Revert by removing WAF Policies from endpoints in all the Frontdoors. + +# Instructions to execute the script: + To remediate: + 1. Download the script. + 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. + 3. Execute the script to configure WAF Policy on all endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. + + To roll back: + 1. Download the script. + 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. + 3. Execute the script to remove the configured WAF Policy on all endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. + +# Examples: + To remediate: + 1. To review the Front Door CDNs in a Subscription that will be remediated: + Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun + + 2. To configure WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription: + Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck + + 3. To configure WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription, from a previously taken snapshot: + Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\ConfigureFrontDoorCDNWAFPolicy\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv + + To know more about the options supported by the remediation command, execute: + Get-Help Configure-WAFPolicyForFrontDoorCDN -Detailed + + To roll back: + 1. To remove configured WAF Policy Mode for Endpoint(s) all Front Door CDNs in a Subscription, from a previously taken snapshot: + Remove-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\ConfigureFrontDoorCDNWAFPolicy\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv + + To know more about the options supported by the roll back command, execute: + Get-Help Remove-WAFPolicyForFrontDoorCDN -Detailed +###> + +function Setup-Prerequisites +{ + <# + .SYNOPSIS + Checks if the prerequisites are met, else, sets them up. + + .DESCRIPTION + Checks if the prerequisites are met, else, sets them up. + Includes installing any required Azure modules. + + .INPUTS + None. You cannot pipe objects to Setup-Prerequisites. + + .OUTPUTS + None. Setup-Prerequisites does not return anything that can be piped and used as an input to another command. + + .EXAMPLE + PS> Setup-Prerequisites + + .LINK + None + #> + + # List of required modules + $requiredModules = @("Az.Accounts", "Az.FrontDoor", "Az.Cdn") + + Write-Host "Required modules: $($requiredModules -join ', ')" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Checking if the required modules are present..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + + $availableModules = $(Get-Module -ListAvailable $requiredModules -ErrorAction Stop) + + # Check if the required modules are installed. + $requiredModules | ForEach-Object { + if ($availableModules.Name -notcontains $_) + { + Write-Host "[$($_)] module is not present." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host "Installing [$($_)] module..." -ForegroundColor $([Constants]::MessageType.Info) + Install-Module -Name $_ -Scope CurrentUser -Repository 'PSGallery' -ErrorAction Stop + Write-Host "[$($_)] module installed." -ForegroundColor $([Constants]::MessageType.Update) + } + else + { + Write-Host "[$($_)] module is present." -ForegroundColor $([Constants]::MessageType.Update) + } + } +} + +function Configure-WAFPolicyForFrontDoorCDN +{ + <# + .SYNOPSIS + Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. + + .DESCRIPTION + Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. + WAF Policy must be configured for Front Door CDN Endpoint(s). + + .PARAMETER SubscriptionId + Specifies the ID of the Subscription to be remediated. + + .PARAMETER Force + Specifies a forceful remediation without any prompts. + + .Parameter PerformPreReqCheck + Specifies validation of prerequisites for the command. + + .PARAMETER DryRun + Specifies a dry run of the actual remediation. + + .PARAMETER FilePath + Specifies the path to the file to be used as input for the remediation. + + .INPUTS + None. You cannot pipe objects to Enable-WAFPolicyForFrontDoors. + + .OUTPUTS + None. Configure-WAFPolicyForFrontDoorCDN does not return anything that can be piped and used as an input to another command. + + .EXAMPLE + PS> Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun + + .EXAMPLE + PS> Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck + + .EXAMPLE + PS> Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202201011212\ConfigureFrontDoorCDNWAFPolicy\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv + + .LINK + None + #> + + param ( + [String] + [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] + [Parameter(ParameterSetName = "WetRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] + $SubscriptionId, + + [Switch] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies a forceful remediation without any prompts")] + $Force, + + [Switch] + [Parameter(ParameterSetName = "DryRun", HelpMessage="Specifies validation of prerequisites for the command")] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies validation of prerequisites for the command")] + $PerformPreReqCheck, + + [Switch] + [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies a dry run of the actual remediation")] + $DryRun, + + [String] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation")] + $FilePath + ) + + Write-Host $([Constants]::DoubleDashLine) + if ($PerformPreReqCheck) + { + try + { + Write-Host "[Step 1 of 5] Validate and install the modules required to run the script and validate the user" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Setup-Prerequisites + Write-Host "Completed setting up prerequisites." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + catch + { + Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + return + } + } + else + { + Write-Host "[Step 1 of 5] Validate the user" + Write-Host $([Constants]::SingleDashLine) + } + + # Connect to Azure account + $context = Get-AzContext + + if ([String]::IsNullOrWhiteSpace($context)) + { + Write-Host "Connecting to Azure account..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null + Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + # Setting up context for the current Subscription. + $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop + + Write-Host "Subscription Name: [$($context.Subscription.Name)]" + Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" + Write-Host "Account Name: [$($context.Account.Id)]" + Write-Host "Account Type: [$($context.Account.Type)]" + Write-Host $([Constants]::SingleDashLine) + + + Write-Host " To configure WAF Policy for Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Write-Host "[Step 2 of 5] Preparing to fetch all Front Door CDNs" + Write-Host $([Constants]::SingleDashLine) + + $frontDoorCDNs = @() + $frontDoorEndPoints = @() + $resourceAppIdURI = "https://management.azure.com/" + $apiResponse =@() + $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token + $endPointPolicies = New-Object System.Collections.ArrayList + + # To keep track of remediated and skipped resources + $logRemediatedResources = @() + $logSkippedResources=@() + + # Control Id + $controlIds = "Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration" + + + # No file path provided as input to the script. Fetch all Front Door CDNs in the Subscription. + if ([String]::IsNullOrWhiteSpace($FilePath)) + { + Write-Host "Fetching all Front Door CDNs in Subscription: [$($context.Subscription.SubscriptionId)]..." -ForegroundColor $([Constants]::MessageType.Info) + + # Get all Front Door CDNs in the Subscription + $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction SilentlyContinue + $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count + + if($totalfrontDoors -gt 0) + { + $frontDoorCDNs | ForEach-Object { + $frontDoor = $_ + $frontDoorId = $_.Id + $resourceGroupName = $_.Id.Split('/')[4] + $frontDoorName = $_.Name + + if($null -ne $classicAccessToken) + { + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + { + if($null -ne $apiResponse.Content) + { + $content = $apiResponse.Content | ConvertFrom-Json + + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) + { + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) + { + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) + { + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } + } + } + + + # Get all Endpoint(s) for this Front Door CDN. + $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }} + } + } + } + else + { + if (-not (Test-Path -Path $FilePath)) + { + Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + break + } + + Write-Host "Fetching all Front Door CDN Endpoint(s) from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) + + $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath + $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } + + $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName + + foreach($frontdoor in $uniquefrontDoors) + { + $resourceGroupName = $frontdoor.ResourceGroupName + $frontDoorName = $frontdoor.FrontDoorName + + if($null -ne $classicAccessToken) + { + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + { + if($null -ne $apiResponse.Content) + { + $content = $apiResponse.Content | ConvertFrom-Json + + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) + { + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) + { + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) + { + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } + } + } + } + + $validfrontDoorEndpointsDetails | ForEach-Object { + $frontdoorEndpointName = $_.EndPointName + $resourceGroupName = $_.ResourceGroupName + $frontDoorName = $_.FrontDoorName + + try + { + $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }} + } + catch + { + Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) + } + } + } + + + + + if ([String]::IsNullOrWhiteSpace($FilePath)) + { + $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count + + if ($totalfrontDoors -eq 0) + { + Write-Host "No Front Door CDNs found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::DoubleDashLine) + break + } + + Write-Host "Found [$($totalfrontDoors)] Front Door CDN(s)." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + + + + $totalfrontDoorEndPoints = ($frontDoorEndPoints | Measure-Object).Count + + if ($totalfrontDoorEndPoints -eq 0) + { + Write-Host "No Front Door CDN Endpoint(s) having WAF Policy not configured. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::DoubleDashLine) + break + } + + Write-Host "Found [$($totalfrontDoorEndPoints)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + + # Includes Front Door CDN Endpoint(s) where WAF Policy is not configured + $frontDoorEndpointsWithWAFPolicyNotConfigured = @() + + # Includes Front Door CDN Endpoint(s) that were skipped during remediation. There were errors remediating them. + $frontDoorEndpointsSkipped = @() + + + + Write-Host "[Step 3 of 5] Fetching Endpoint(s) for which WAF Policy is not configured" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is not configured..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + $frontDoorEndPoints | ForEach-Object { + $endPoint = $_ + if($_.IsWAFConfigured -eq $false) + { + $frontDoorEndpointsWithWAFPolicyNotConfigured += $endPoint + } + else + { + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.FrontDoorName)) + $logResource.Add("EndPointName",($_.EndPointName)) + $logResource.Add("Reason","WAF Policy already Configured on endpoint") + $logSkippedResources += $logResource + + } + } + + $totalfrontDoorEndpointsWithWAFPolicyNotConfigured = ($frontDoorEndpointsWithWAFPolicyNotConfigured | Measure-Object).Count + + if ($totalfrontDoorEndpointsWithWAFPolicyNotConfigured -eq 0) + { + Write-Host "No Front Door CDN endpoints(s) found where WAF Policy is not configured. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + return + } + + Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door CDN Endpoints(s) found where WAF Policy is not configured ." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + + + Write-Host "Following Front Door CDN Endpoints(s) are having WAF Policies not configured:" -ForegroundColor $([Constants]::MessageType.Info) + $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, + @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, + @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"} + + $frontDoorEndpointsWithWAFPolicyNotConfigured | Format-Table -Property $colsProperty -Wrap + + # Back up snapshots to `%LocalApplicationData%'. + $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\ConfigureFrontDoorCDNWAFPolicy" + + if (-not (Test-Path -Path $backupFolderPath)) + { + New-Item -ItemType Directory -Path $backupFolderPath | Out-Null + } + + + Write-Host "[Step 4 of 5] Backing up Front Door CDN Endpoint(s) details" + Write-Host $([Constants]::SingleDashLine) + if ([String]::IsNullOrWhiteSpace($FilePath)) + { + + # Backing up Front Door CDN Endpoints details. + $backupFile = "$($backupFolderPath)\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv" + $frontDoorEndpointsWithWAFPolicyNotConfigured | Export-CSV -Path $backupFile -NoTypeInformation + Write-Host "Front Door Endpoint(s) details have been successful backed up to [$($backupFolderPath)]" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + else + { + Write-Host "Skipped as -FilePath is provided" -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + + if (-not $DryRun) + { + + Write-Host "WAF Policy will be configured for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Warning) + + if (-not $Force) + { + Write-Host "Do you want to configure WAF Policy associated with Front Door CDN Endpoint(s)? " -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline + Write-Host $([Constants]::SingleDashLine) + $userInput = Read-Host -Prompt "(Y|N)" + Write-Host $([Constants]::SingleDashLine) + if($userInput -ne "Y") + { + Write-Host "WAF Policy will not be configured for any Front Door CDN Endpoint(s). Exiting." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::DoubleDashLine) + break + } + else + { + Write-Host "WAF Policy will be configured for all Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + } + else + { + Write-Host "'Force' flag is provided. WAF Policy will be configured on all Front Door CDN Endoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + + + + Write-Host "[Step 5 of 5] Configuring WAF Policy for Front Door CDN Endpoint(s)" + Write-Host $([Constants]::SingleDashLine) + # To hold results from the remediation. + $frontDoorEndpointsRemediated = @() + $endpointsSkipped = @() + $otherPolicyEndpointsAssociations = @() + + + # Remidiate Controls by configuring WAF Policy + $frontDoorEndpointsWithWAFPolicyNotConfigured | ForEach-Object { + $frontDoorEndPoint = $_ + $endpointName = $_.EndPointName + $frontdoorName = $_.FrontDoorName + $resourceGroupName = $_.ResourceGroupName + $i= 0 + + try + { + Do + { + $wafPolicyName = Read-Host -Prompt "Enter WAF Policy Name for Endpoint: [$($_.EndPointName)] of Frontdoor [$($frontdoorName)] " + $policyResourceGroup = Read-Host -Prompt "Enter WAF Policy Resource Group Name for Endpoint: [$($_.EndPointName)] of Frontdoor [$($frontdoorName)] " + $policy = Get-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $policyResourceGroup -ErrorAction SilentlyContinue + + if($policy -eq $null) + { + Write-Host "WAF Policy name or WAF Policy Resource Group Name is not correct. Please enter correct details." + Write-Host $([Constants]::SingleDashLine) + } + + if($policy -ne $null -and $policy.Sku -ne 'Standard_AzureFrontDoor') + { + Write-Host "WAF Policy is not of type Front door tier Standard . Please enter correct details." + Write-Host $([Constants]::SingleDashLine) + } + + } + while($policy.Sku -ne 'Standard_AzureFrontDoor' -or $policy -eq $null) + $wafPolicyId = $policy.Id + + $endpoint = Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontdoorName -EndpointName $endpointName + $updateAssociation = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$($endpoint.Id)}) + $updateAssociations = @() + $updateAssociations += $updateAssociation + $otherPolicyEndpointsAssociations = $endPointPolicies | where wafPolicyName -eq ($wafPolicyName) + $otherPolicyEndpointsAssociations = $otherPolicyEndpointsAssociations | where frontDoorName -eq $frontdoorName + + $otherPolicyEndpointsAssociations | ForEach-Object { + $association = $_ + $associatedEndpoint = $_.endpointName + + New-Variable -Force -Name "endpoint$i" -Value (Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontdoorName -EndpointName $associatedEndpoint) + New-Variable -Force -Name "updateAssociation$i" -Value (New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$(Get-Variable -Name "endpoint$i" -ValueOnly).Id})) + $updateAssociations += (Get-Variable -Name "updateAssociation$i" -ValueOnly) + $i++ + } + + $updateWafParameter = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallParametersObject -Association @($updateAssociations) -WafPolicyId $wafPolicyId + $policySecurity = New-AzFrontDoorCdnSecurityPolicy -ResourceGroupName v-abprasadTestRG -ProfileName testFrontdoorCDN -Name Policy -Parameter $updateWafParameter + + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $policyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontdoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + + if ($policySecurity.Name -eq $null) + { + $endpointsSkipped += $frontDoorEndPoint + + } + else + { $frontDoorEndPoint.IsWAFConfigured = $true + $frontDoorEndPoint.WAFPolicyName = $wafPolicyName + $frontDoorEndPoint.WAFPolicyResourceGroup = $policyResourceGroup + $frontDoorEndpointsRemediated += $frontDoorEndPoint + } + } + catch + { + $endpointsSkipped += $frontDoorEndPoint + } + } + + $totalRemediated = ($frontDoorEndpointsRemediated | Measure-Object).Count + + Write-Host $([Constants]::SingleDashLine) + + if ($totalRemediated -eq $totalfrontDoorEndpointsWithWAFPolicyNotConfigured) + { + Write-Host "WAF Policy configured for all [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + else + { + Write-Host "WAF Policy configured for [$totalRemediated] out of [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + + $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, + @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, + @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"} + + + + Write-Host "Remediation Summary:" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + + + if ($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) + { + Write-Host "Successfully configured WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + $frontDoorEndpointsRemediated | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv" + $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation + Write-Host "This information has been saved to [$($frontDoorEndpointsRemediatedFile)]" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + } + + if ($($endpointsSkipped | Measure-Object).Count -gt 0) + { + Write-Host "Error performing remediation steps for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::SingleDashLine) + $endpointsSkipped | Format-Table -Property $colsProperty -Wrap + Write-Host $([Constants]::SingleDashLine) + # Write this to a file. + $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv" + $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation + Write-Host "This information has been saved to [$($endpointsSkippedFile)]" + Write-Host $([Constants]::SingleDashLine) + } + + } + else + { + + Write-Host "[Step 5 of 5] Configuring WAF Policy for Endpoint(s)" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Skipped as -DryRun switch is provided." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + + Write-Host "Next steps:" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host "Run the same command with -FilePath $($backupFile) and without -DryRun, to configure WAF Policy Mode for all Front Door CDN Endpoint(s) listed in the file." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + } +} + +function Remove-WAFPolicyForFrontDoorCDN +{ + <# + .SYNOPSIS + Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. + + .DESCRIPTION + Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. + Removes configured WAF Policy for all WAF Policies in all Front Door CDN s in the Subscription. + + .PARAMETER SubscriptionId + Specifies the ID of the Subscription that was previously remediated. + + .PARAMETER Force + Specifies a forceful roll back without any prompts. + + .Parameter PerformPreReqCheck + Specifies validation of prerequisites for the command. + + .PARAMETER FilePath + Specifies the path to the file to be used as input for the roll back. + + .INPUTS + None. You cannot pipe objects to Remove-WAFPolicyForFrontDoorCDN. + + .OUTPUTS + None. Remove-WAFPolicyForFrontDoorCDN does not return anything that can be piped and used as an input to another command. + + .EXAMPLE + PS> Remove-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202211190719\ConfigureFrontDoorCDNWAFPolicy\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv + + .LINK + None + #> + + param ( + [String] + [Parameter(Mandatory = $true, HelpMessage="Specifies the ID of the Subscription that was previously remediated.")] + $SubscriptionId, + + [Switch] + [Parameter(HelpMessage="Specifies a forceful roll back without any prompts")] + $Force, + + [Switch] + [Parameter(HelpMessage="Specifies validation of prerequisites for the command")] + $PerformPreReqCheck, + + [String] + [Parameter(Mandatory = $true, HelpMessage="Specifies the path to the file to be used as input for the roll back")] + $FilePath + ) + + Write-Host $([Constants]::DoubleDashLine) + if ($PerformPreReqCheck) + { + try + { + Write-Host "[Step 1 of 4] Validate and install the modules required to run the script and validate the user" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Setup-Prerequisites + Write-Host "Completed setting up prerequisites" + Write-Host $([Constants]::SingleDashLine) + } + catch + { + Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + return + } + } + else + { + Write-Host "[Step 1 of 4] Validate the user" + Write-Host $([Constants]::SingleDashLine) + } + + # Connect to Azure account + $context = Get-AzContext + + if ([String]::IsNullOrWhiteSpace($context)) + { + + Write-Host "Connecting to Azure account..." + Write-Host $([Constants]::SingleDashLine) + Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null + Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + else + { + # Setting up context for the current Subscription. + $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop + } + + Write-Host "Subscription Name: [$($context.Subscription.Name)]" + Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" + Write-Host "Account Name: [$($context.Account.Id)]" + Write-Host "Account Type: [$($context.Account.Type)]" + Write-Host $([Constants]::SingleDashLine) + + # Note about the required access required for remediation + + Write-Host "To remove Configured WAF Policy for all Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Write-Host "[Step 2 of 4] Preparing to fetch all Front Door CDN Endpoints" + Write-Host $([Constants]::SingleDashLine) + $frontDoorEndPoints = @() + $resourceAppIdURI = "https://management.azure.com/" + $apiResponse =@() + $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token + $endPointPolicies = New-Object System.Collections.ArrayList + + if (-not (Test-Path -Path $FilePath)) + { + Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + break + } + + Write-Host "Fetching all Front Door CDN Endpoints from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) + + $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath + $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } + + $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName + + foreach($frontdoor in $uniquefrontDoors) + { + $resourceGroupName = $frontdoor.ResourceGroupName + $frontDoorName = $frontdoor.FrontDoorName + + if($null -ne $classicAccessToken) + { + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + { + if($null -ne $apiResponse.Content) + { + $content = $apiResponse.Content | ConvertFrom-Json + + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) + { + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) + { + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) + { + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } + } + } + } + + + $validfrontDoorEndpointsDetails | ForEach-Object { + $frontdoorEndpointName = $_.EndPointName + $resourceGroupName = $_.ResourceGroupName + $frontDoorName = $_.FrontDoorName + + try + { + $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }} + } + catch + { + Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::SingleDashLine) + Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + } + + + + # Includes Front Door CDN Endpoint(s) where WAF Policy is configured + $frontDoorEndpointsWithWAFPolicyConfigured = @() + + + + Write-Host "[Step 3 of 4] Fetching Endpoint(s) Endpoint(s) where WAF Policy is not configured" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is configured..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + $frontDoorEndPoints | ForEach-Object { + $endPoint = $_ + if($_.IsWAFConfigured -eq $true) + { + $frontDoorEndpointsWithWAFPolicyConfigured += $endPoint + } + else + { + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.EndPointName)) + $logResource.Add("Reason","WAF Policy is already configured on Frontdoor CDN Endpoint") + $logSkippedResources += $logResource + + } + } + + $totalfrontDoorEndpointsWithWAFPolicyConfigured = ($frontDoorEndpointsWithWAFPolicyConfigured | Measure-Object).Count + + if ($totalfrontDoorEndpointsWithWAFPolicyConfigured -eq 0) + { + Write-Host "No Front Door CDN Endpoints(s) found where WAF Policy is configured.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + return + } + + + Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyConfigured)] Front Door CDN Endpoints(s) found in file where WAF Policy is configured to Rollback." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + + # Back up snapshots to `%LocalApplicationData%'. + $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\ConfigureFrontDoorCDNWAFPolicy" + + if (-not (Test-Path -Path $backupFolderPath)) + { + New-Item -ItemType Directory -Path $backupFolderPath | Out-Null + } + + if (-not $Force) + { + Write-Host "Do you want remove configured WAF Policy Mode for all Front Door CDN Endpoint(s)?" -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline + Write-Host $([Constants]::SingleDashLine) + $userInput = Read-Host -Prompt "(Y|N)" + Write-Host $([Constants]::SingleDashLine) + if($userInput -ne "Y") + { + Write-Host "Configured WAF Policy will not be removed for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::DoubleDashLine) + break + } + else + { + Write-Host "Configured WAF Policy will be removed for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + } + else + { + Write-Host "'Force' flag is provided. Configured WAF Policy will be removed for all the Front Door CDN Endpoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline + Write-Host $([Constants]::SingleDashLine) + } + + + + + + Write-Host "[Step 4 of 4] Switching WAF Policy mode to Detection for Front Door CDNs Endpoint(s)" + Write-Host $([Constants]::SingleDashLine) + # Includes Front Door CDN s, to which, previously made changes were successfully rolled back. + $frontDoorEndpointsRolledBack = @() + + # Includes Front Door CDN s that were skipped during roll back. There were errors rolling back the changes made previously. + $frontDoorEndpointsSkipped = @() + + + + # Roll back by removing configured WAF Policy on Endpoints + $frontDoorEndpointsWithWAFPolicyConfigured | ForEach-Object { + $frontDoorEndPoint = $_ + $endpointName = $_.EndPointName + $frontdoorName = $_.FrontDoorName + $resourceGroupName = $_.ResourceGroupName + $wafPolicyName = $_.WAFPolicyName + $policyResourceGroup = $_.WAFPolicyResourceGroup + $i = 0 + + $policy = Get-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $policyResourceGroup -ErrorAction SilentlyContinue + $wafPolicyId = $policy.Id + try + { + $updateAssociations = @() + $otherPolicyEndpointsAssociations = @() + + # Remove the endpoint to be rolledback from endpointPolicies List + foreach($policy in $endPointPolicies.Clone()) + { + if($policy.endpointName -eq $endpointName) + { + $endPointPolicies.Remove($policy) + } + + } + + $otherPolicyEndpointsAssociations = $endPointPolicies | where wafPolicyName -eq ($wafPolicyName) + $otherPolicyEndpointsAssociations = $otherPolicyEndpointsAssociations | where frontDoorName -eq $frontdoorName + + $otherPolicyEndpointsAssociations | ForEach-Object { + $association = $_ + $associatedEndpoint = $_.endpointName + + New-Variable -Force -Name "endpoint$i" -Value (Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontdoorName -EndpointName $associatedEndpoint) + New-Variable -Force -Name "updateAssociation$i" -Value (New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$(Get-Variable -Name "endpoint$i" -ValueOnly).Id})) + $updateAssociations += (Get-Variable -Name "updateAssociation$i" -ValueOnly) + $i++ + } + + $updateWafParameter = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallParametersObject -Association @($updateAssociations) -WafPolicyId $wafPolicyId + $policySecurity = New-AzFrontDoorCdnSecurityPolicy -ResourceGroupName v-abprasadTestRG -ProfileName testFrontdoorCDN -Name Policy -Parameter $updateWafParameter + + if ($policySecurity.Name -eq $null) + { + $frontDoorEndpointsSkipped += $frontDoorEndPoint + + } + else + { + $frontDoorEndPoint.IsWAFConfigured = $false + $frontDoorEndPoint.WAFPolicyName = "" + $frontDoorEndPoint.WAFPolicyResourceGroup = "" + $frontDoorEndpointsRolledBack += $frontDoorEndPoint + } + } + catch + { + Write-Host $_ + $frontDoorEndpointsSkipped += $frontDoorEndPoint + } + } + + + $totalfrontDoorEndpointsRolledBack = ($frontDoorEndpointsRolledBack | Measure-Object).Count + + Write-Host $([Constants]::SingleDashLine) + + if ($totalfrontDoorEndpointsRolledBack -eq $totalfrontDoorEndpointsWithWAFPolicyConfigured) + { + Write-Host "Configured WAF Policy removed for all [$($totalfrontDoorEndpointsWithWAFPolicyConfigured)] Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + else + { + Write-Host "Configured WAF Policy removed for [$totalfrontDoorEndpointsRolledBack] out of [$($totalfrontDoorEndpointsWithWAFPolicyConfigured)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + + Write-Host "Rollback Summary:" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, + @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, + @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"} + + + if ($($frontDoorEndpointsRolledBack | Measure-Object).Count -gt 0) + { + Write-Host "Successfully removed WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + $frontDoorEndpointsRolledBack | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $frontDoorEndpointsRolledBackFile = "$($backupFolderPath)\RolledBackFrontDoorCDNEndpointsForConfigureWAFPolicy.csv" + $frontDoorEndpointsRolledBack | Export-CSV -Path $frontDoorEndpointsRolledBackFile -NoTypeInformation + Write-Host "This information has been saved to [$($frontDoorEndpointsRolledBackFile)]" + Write-Host $([Constants]::SingleDashLine) + } + + if ($($frontDoorEndpointsSkipped | Measure-Object).Count -gt 0) + { + Write-Host "Error removing configured WAF Policy for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::SingleDashLine) + $frontDoorEndpointsSkipped | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $frontDoorEndpointsSkippedFile = "$($backupFolderPath)\RollbackSkippedFrontDoorCDNEndpointsForConfigureWAFPolicy.csv" + $frontDoorEndpointsSkipped | Export-CSV -Path $frontDoorEndpointsSkippedFile -NoTypeInformation + Write-Host "This information has been saved to [$($frontDoorEndpointsSkippedFile)]" + } +} + +# Defines commonly used constants. +class Constants +{ + # Defines commonly used colour codes, corresponding to the severity of the log. + static [Hashtable] $MessageType = @{ + Error = [System.ConsoleColor]::Red + Warning = [System.ConsoleColor]::Yellow + Info = [System.ConsoleColor]::Cyan + Update = [System.ConsoleColor]::Green + Default = [System.ConsoleColor]::White + } + + static [String] $DoubleDashLine = "========================================================================================================================" + static [String] $SingleDashLine = "------------------------------------------------------------------------------------------------------------------------" +} \ No newline at end of file From 424c84e9b3df577964974861c6ed04bbee647b09 Mon Sep 17 00:00:00 2001 From: Abhishek P Date: Fri, 16 Dec 2022 19:06:39 +0530 Subject: [PATCH 14/26] changes --- ...iate-ConfigureWAFPolicyForFrontDoorCDN.ps1 | 1164 ----------------- 1 file changed, 1164 deletions(-) delete mode 100644 Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 diff --git a/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 deleted file mode 100644 index 489a8dd8..00000000 --- a/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 +++ /dev/null @@ -1,1164 +0,0 @@ -<### -# Overview: - This script is used to configure WAF Policy on all endpoints of Front Door CDNs in a Subscription. - -# Control ID: - Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration - -# Display Name: - Front Door should have Web Application Firewall configured - -# Prerequisites: - 1. Contributor or higher privileges on the Front Door CDNs in a Subscription. - 2. Must be connected to Azure with an authenticated account. - -# Steps performed by the script: - To remediate: - 1. Validate and install the modules required to run the script. - 2. Get the list of all Front Door CDNs Endpoints in a Subscription that do not have WAF Policy Configured - 3. Back up details of Front Door CDN Endpoint(s) that are to be remediated. - 4. Configure WAF Policy for all endpoints in the Frontdoors. - - To roll back: - 1. Validate and install the modules required to run the script. - 2. Get the list of Frontdoors' Endpoint(s) in a Subscription, the changes made to which previously, are to be rolled back. - 3. Revert by removing WAF Policies from endpoints in all the Frontdoors. - -# Instructions to execute the script: - To remediate: - 1. Download the script. - 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. - 3. Execute the script to configure WAF Policy on all endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. - - To roll back: - 1. Download the script. - 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. - 3. Execute the script to remove the configured WAF Policy on all endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. - -# Examples: - To remediate: - 1. To review the Front Door CDNs in a Subscription that will be remediated: - Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun - - 2. To configure WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription: - Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck - - 3. To configure WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription, from a previously taken snapshot: - Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\ConfigureFrontDoorCDNWAFPolicy\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv - - To know more about the options supported by the remediation command, execute: - Get-Help Configure-WAFPolicyForFrontDoorCDN -Detailed - - To roll back: - 1. To remove configured WAF Policy Mode for Endpoint(s) all Front Door CDNs in a Subscription, from a previously taken snapshot: - Remove-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\ConfigureFrontDoorCDNWAFPolicy\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv - - To know more about the options supported by the roll back command, execute: - Get-Help Remove-WAFPolicyForFrontDoorCDN -Detailed -###> - -function Setup-Prerequisites -{ - <# - .SYNOPSIS - Checks if the prerequisites are met, else, sets them up. - - .DESCRIPTION - Checks if the prerequisites are met, else, sets them up. - Includes installing any required Azure modules. - - .INPUTS - None. You cannot pipe objects to Setup-Prerequisites. - - .OUTPUTS - None. Setup-Prerequisites does not return anything that can be piped and used as an input to another command. - - .EXAMPLE - PS> Setup-Prerequisites - - .LINK - None - #> - - # List of required modules - $requiredModules = @("Az.Accounts", "Az.FrontDoor", "Az.Cdn") - - Write-Host "Required modules: $($requiredModules -join ', ')" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Checking if the required modules are present..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - - $availableModules = $(Get-Module -ListAvailable $requiredModules -ErrorAction Stop) - - # Check if the required modules are installed. - $requiredModules | ForEach-Object { - if ($availableModules.Name -notcontains $_) - { - Write-Host "[$($_)] module is not present." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host "Installing [$($_)] module..." -ForegroundColor $([Constants]::MessageType.Info) - Install-Module -Name $_ -Scope CurrentUser -Repository 'PSGallery' -ErrorAction Stop - Write-Host "[$($_)] module installed." -ForegroundColor $([Constants]::MessageType.Update) - } - else - { - Write-Host "[$($_)] module is present." -ForegroundColor $([Constants]::MessageType.Update) - } - } -} - -function Configure-WAFPolicyForFrontDoorCDN -{ - <# - .SYNOPSIS - Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - - .DESCRIPTION - Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - WAF Policy must be configured for Front Door CDN Endpoint(s). - - .PARAMETER SubscriptionId - Specifies the ID of the Subscription to be remediated. - - .PARAMETER Force - Specifies a forceful remediation without any prompts. - - .Parameter PerformPreReqCheck - Specifies validation of prerequisites for the command. - - .PARAMETER DryRun - Specifies a dry run of the actual remediation. - - .PARAMETER FilePath - Specifies the path to the file to be used as input for the remediation. - - .INPUTS - None. You cannot pipe objects to Enable-WAFPolicyForFrontDoors. - - .OUTPUTS - None. Configure-WAFPolicyForFrontDoorCDN does not return anything that can be piped and used as an input to another command. - - .EXAMPLE - PS> Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun - - .EXAMPLE - PS> Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck - - .EXAMPLE - PS> Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202201011212\ConfigureFrontDoorCDNWAFPolicy\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv - - .LINK - None - #> - - param ( - [String] - [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] - [Parameter(ParameterSetName = "WetRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] - $SubscriptionId, - - [Switch] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies a forceful remediation without any prompts")] - $Force, - - [Switch] - [Parameter(ParameterSetName = "DryRun", HelpMessage="Specifies validation of prerequisites for the command")] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies validation of prerequisites for the command")] - $PerformPreReqCheck, - - [Switch] - [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies a dry run of the actual remediation")] - $DryRun, - - [String] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation")] - $FilePath - ) - - Write-Host $([Constants]::DoubleDashLine) - if ($PerformPreReqCheck) - { - try - { - Write-Host "[Step 1 of 5] Validate and install the modules required to run the script and validate the user" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Setup-Prerequisites - Write-Host "Completed setting up prerequisites." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - catch - { - Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - return - } - } - else - { - Write-Host "[Step 1 of 5] Validate the user" - Write-Host $([Constants]::SingleDashLine) - } - - # Connect to Azure account - $context = Get-AzContext - - if ([String]::IsNullOrWhiteSpace($context)) - { - Write-Host "Connecting to Azure account..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null - Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - # Setting up context for the current Subscription. - $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop - - Write-Host "Subscription Name: [$($context.Subscription.Name)]" - Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" - Write-Host "Account Name: [$($context.Account.Id)]" - Write-Host "Account Type: [$($context.Account.Type)]" - Write-Host $([Constants]::SingleDashLine) - - - Write-Host " To configure WAF Policy for Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Write-Host "[Step 2 of 5] Preparing to fetch all Front Door CDNs" - Write-Host $([Constants]::SingleDashLine) - - $frontDoorCDNs = @() - $frontDoorEndPoints = @() - $resourceAppIdURI = "https://management.azure.com/" - $apiResponse =@() - $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token - $endPointPolicies = New-Object System.Collections.ArrayList - - # To keep track of remediated and skipped resources - $logRemediatedResources = @() - $logSkippedResources=@() - - # Control Id - $controlIds = "Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration" - - - # No file path provided as input to the script. Fetch all Front Door CDNs in the Subscription. - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - Write-Host "Fetching all Front Door CDNs in Subscription: [$($context.Subscription.SubscriptionId)]..." -ForegroundColor $([Constants]::MessageType.Info) - - # Get all Front Door CDNs in the Subscription - $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction SilentlyContinue - $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - - if($totalfrontDoors -gt 0) - { - $frontDoorCDNs | ForEach-Object { - $frontDoor = $_ - $frontDoorId = $_.Id - $resourceGroupName = $_.Id.Split('/')[4] - $frontDoorName = $_.Name - - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) - { - if($null -ne $apiResponse.Content) - { - $content = $apiResponse.Content | ConvertFrom-Json - - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) - { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } - } - - - # Get all Endpoint(s) for this Front Door CDN. - $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }} - } - } - } - else - { - if (-not (Test-Path -Path $FilePath)) - { - Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - break - } - - Write-Host "Fetching all Front Door CDN Endpoint(s) from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) - - $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath - $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } - - $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName - - foreach($frontdoor in $uniquefrontDoors) - { - $resourceGroupName = $frontdoor.ResourceGroupName - $frontDoorName = $frontdoor.FrontDoorName - - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) - { - if($null -ne $apiResponse.Content) - { - $content = $apiResponse.Content | ConvertFrom-Json - - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) - { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } - } - } - - $validfrontDoorEndpointsDetails | ForEach-Object { - $frontdoorEndpointName = $_.EndPointName - $resourceGroupName = $_.ResourceGroupName - $frontDoorName = $_.FrontDoorName - - try - { - $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }} - } - catch - { - Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) - } - } - } - - - - - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - - if ($totalfrontDoors -eq 0) - { - Write-Host "No Front Door CDNs found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::DoubleDashLine) - break - } - - Write-Host "Found [$($totalfrontDoors)] Front Door CDN(s)." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - - - - $totalfrontDoorEndPoints = ($frontDoorEndPoints | Measure-Object).Count - - if ($totalfrontDoorEndPoints -eq 0) - { - Write-Host "No Front Door CDN Endpoint(s) having WAF Policy not configured. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::DoubleDashLine) - break - } - - Write-Host "Found [$($totalfrontDoorEndPoints)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - - # Includes Front Door CDN Endpoint(s) where WAF Policy is not configured - $frontDoorEndpointsWithWAFPolicyNotConfigured = @() - - # Includes Front Door CDN Endpoint(s) that were skipped during remediation. There were errors remediating them. - $frontDoorEndpointsSkipped = @() - - - - Write-Host "[Step 3 of 5] Fetching Endpoint(s) for which WAF Policy is not configured" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is not configured..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndPoints | ForEach-Object { - $endPoint = $_ - if($_.IsWAFConfigured -eq $false) - { - $frontDoorEndpointsWithWAFPolicyNotConfigured += $endPoint - } - else - { - $logResource = @{} - $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) - $logResource.Add("ResourceName",($_.FrontDoorName)) - $logResource.Add("EndPointName",($_.EndPointName)) - $logResource.Add("Reason","WAF Policy already Configured on endpoint") - $logSkippedResources += $logResource - - } - } - - $totalfrontDoorEndpointsWithWAFPolicyNotConfigured = ($frontDoorEndpointsWithWAFPolicyNotConfigured | Measure-Object).Count - - if ($totalfrontDoorEndpointsWithWAFPolicyNotConfigured -eq 0) - { - Write-Host "No Front Door CDN endpoints(s) found where WAF Policy is not configured. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - return - } - - Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door CDN Endpoints(s) found where WAF Policy is not configured ." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - - - Write-Host "Following Front Door CDN Endpoints(s) are having WAF Policies not configured:" -ForegroundColor $([Constants]::MessageType.Info) - $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, - @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"} - - $frontDoorEndpointsWithWAFPolicyNotConfigured | Format-Table -Property $colsProperty -Wrap - - # Back up snapshots to `%LocalApplicationData%'. - $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\ConfigureFrontDoorCDNWAFPolicy" - - if (-not (Test-Path -Path $backupFolderPath)) - { - New-Item -ItemType Directory -Path $backupFolderPath | Out-Null - } - - - Write-Host "[Step 4 of 5] Backing up Front Door CDN Endpoint(s) details" - Write-Host $([Constants]::SingleDashLine) - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - - # Backing up Front Door CDN Endpoints details. - $backupFile = "$($backupFolderPath)\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv" - $frontDoorEndpointsWithWAFPolicyNotConfigured | Export-CSV -Path $backupFile -NoTypeInformation - Write-Host "Front Door Endpoint(s) details have been successful backed up to [$($backupFolderPath)]" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - else - { - Write-Host "Skipped as -FilePath is provided" -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - - if (-not $DryRun) - { - - Write-Host "WAF Policy will be configured for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Warning) - - if (-not $Force) - { - Write-Host "Do you want to configure WAF Policy associated with Front Door CDN Endpoint(s)? " -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline - Write-Host $([Constants]::SingleDashLine) - $userInput = Read-Host -Prompt "(Y|N)" - Write-Host $([Constants]::SingleDashLine) - if($userInput -ne "Y") - { - Write-Host "WAF Policy will not be configured for any Front Door CDN Endpoint(s). Exiting." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::DoubleDashLine) - break - } - else - { - Write-Host "WAF Policy will be configured for all Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - } - else - { - Write-Host "'Force' flag is provided. WAF Policy will be configured on all Front Door CDN Endoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - - - - Write-Host "[Step 5 of 5] Configuring WAF Policy for Front Door CDN Endpoint(s)" - Write-Host $([Constants]::SingleDashLine) - # To hold results from the remediation. - $frontDoorEndpointsRemediated = @() - $endpointsSkipped = @() - $otherPolicyEndpointsAssociations = @() - - - # Remidiate Controls by configuring WAF Policy - $frontDoorEndpointsWithWAFPolicyNotConfigured | ForEach-Object { - $frontDoorEndPoint = $_ - $endpointName = $_.EndPointName - $frontdoorName = $_.FrontDoorName - $resourceGroupName = $_.ResourceGroupName - $i= 0 - - try - { - Do - { - $wafPolicyName = Read-Host -Prompt "Enter WAF Policy Name for Endpoint: [$($_.EndPointName)] of Frontdoor [$($frontdoorName)] " - $policyResourceGroup = Read-Host -Prompt "Enter WAF Policy Resource Group Name for Endpoint: [$($_.EndPointName)] of Frontdoor [$($frontdoorName)] " - $policy = Get-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $policyResourceGroup -ErrorAction SilentlyContinue - - if($policy -eq $null) - { - Write-Host "WAF Policy name or WAF Policy Resource Group Name is not correct. Please enter correct details." - Write-Host $([Constants]::SingleDashLine) - } - - if($policy -ne $null -and $policy.Sku -ne 'Standard_AzureFrontDoor') - { - Write-Host "WAF Policy is not of type Front door tier Standard . Please enter correct details." - Write-Host $([Constants]::SingleDashLine) - } - - } - while($policy.Sku -ne 'Standard_AzureFrontDoor' -or $policy -eq $null) - $wafPolicyId = $policy.Id - - $endpoint = Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontdoorName -EndpointName $endpointName - $updateAssociation = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$($endpoint.Id)}) - $updateAssociations = @() - $updateAssociations += $updateAssociation - $otherPolicyEndpointsAssociations = $endPointPolicies | where wafPolicyName -eq ($wafPolicyName) - $otherPolicyEndpointsAssociations = $otherPolicyEndpointsAssociations | where frontDoorName -eq $frontdoorName - - $otherPolicyEndpointsAssociations | ForEach-Object { - $association = $_ - $associatedEndpoint = $_.endpointName - - New-Variable -Force -Name "endpoint$i" -Value (Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontdoorName -EndpointName $associatedEndpoint) - New-Variable -Force -Name "updateAssociation$i" -Value (New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$(Get-Variable -Name "endpoint$i" -ValueOnly).Id})) - $updateAssociations += (Get-Variable -Name "updateAssociation$i" -ValueOnly) - $i++ - } - - $updateWafParameter = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallParametersObject -Association @($updateAssociations) -WafPolicyId $wafPolicyId - $policySecurity = New-AzFrontDoorCdnSecurityPolicy -ResourceGroupName v-abprasadTestRG -ProfileName testFrontdoorCDN -Name Policy -Parameter $updateWafParameter - - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $policyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontdoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - - if ($policySecurity.Name -eq $null) - { - $endpointsSkipped += $frontDoorEndPoint - - } - else - { $frontDoorEndPoint.IsWAFConfigured = $true - $frontDoorEndPoint.WAFPolicyName = $wafPolicyName - $frontDoorEndPoint.WAFPolicyResourceGroup = $policyResourceGroup - $frontDoorEndpointsRemediated += $frontDoorEndPoint - } - } - catch - { - $endpointsSkipped += $frontDoorEndPoint - } - } - - $totalRemediated = ($frontDoorEndpointsRemediated | Measure-Object).Count - - Write-Host $([Constants]::SingleDashLine) - - if ($totalRemediated -eq $totalfrontDoorEndpointsWithWAFPolicyNotConfigured) - { - Write-Host "WAF Policy configured for all [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - else - { - Write-Host "WAF Policy configured for [$totalRemediated] out of [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - - $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, - @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"} - - - - Write-Host "Remediation Summary:" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - - - if ($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) - { - Write-Host "Successfully configured WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndpointsRemediated | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv" - $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation - Write-Host "This information has been saved to [$($frontDoorEndpointsRemediatedFile)]" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - } - - if ($($endpointsSkipped | Measure-Object).Count -gt 0) - { - Write-Host "Error performing remediation steps for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::SingleDashLine) - $endpointsSkipped | Format-Table -Property $colsProperty -Wrap - Write-Host $([Constants]::SingleDashLine) - # Write this to a file. - $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv" - $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation - Write-Host "This information has been saved to [$($endpointsSkippedFile)]" - Write-Host $([Constants]::SingleDashLine) - } - - } - else - { - - Write-Host "[Step 5 of 5] Configuring WAF Policy for Endpoint(s)" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Skipped as -DryRun switch is provided." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - - Write-Host "Next steps:" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host "Run the same command with -FilePath $($backupFile) and without -DryRun, to configure WAF Policy Mode for all Front Door CDN Endpoint(s) listed in the file." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - } -} - -function Remove-WAFPolicyForFrontDoorCDN -{ - <# - .SYNOPSIS - Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - - .DESCRIPTION - Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - Removes configured WAF Policy for all WAF Policies in all Front Door CDN s in the Subscription. - - .PARAMETER SubscriptionId - Specifies the ID of the Subscription that was previously remediated. - - .PARAMETER Force - Specifies a forceful roll back without any prompts. - - .Parameter PerformPreReqCheck - Specifies validation of prerequisites for the command. - - .PARAMETER FilePath - Specifies the path to the file to be used as input for the roll back. - - .INPUTS - None. You cannot pipe objects to Remove-WAFPolicyForFrontDoorCDN. - - .OUTPUTS - None. Remove-WAFPolicyForFrontDoorCDN does not return anything that can be piped and used as an input to another command. - - .EXAMPLE - PS> Remove-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202211190719\ConfigureFrontDoorCDNWAFPolicy\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv - - .LINK - None - #> - - param ( - [String] - [Parameter(Mandatory = $true, HelpMessage="Specifies the ID of the Subscription that was previously remediated.")] - $SubscriptionId, - - [Switch] - [Parameter(HelpMessage="Specifies a forceful roll back without any prompts")] - $Force, - - [Switch] - [Parameter(HelpMessage="Specifies validation of prerequisites for the command")] - $PerformPreReqCheck, - - [String] - [Parameter(Mandatory = $true, HelpMessage="Specifies the path to the file to be used as input for the roll back")] - $FilePath - ) - - Write-Host $([Constants]::DoubleDashLine) - if ($PerformPreReqCheck) - { - try - { - Write-Host "[Step 1 of 4] Validate and install the modules required to run the script and validate the user" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Setup-Prerequisites - Write-Host "Completed setting up prerequisites" - Write-Host $([Constants]::SingleDashLine) - } - catch - { - Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - return - } - } - else - { - Write-Host "[Step 1 of 4] Validate the user" - Write-Host $([Constants]::SingleDashLine) - } - - # Connect to Azure account - $context = Get-AzContext - - if ([String]::IsNullOrWhiteSpace($context)) - { - - Write-Host "Connecting to Azure account..." - Write-Host $([Constants]::SingleDashLine) - Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null - Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - else - { - # Setting up context for the current Subscription. - $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop - } - - Write-Host "Subscription Name: [$($context.Subscription.Name)]" - Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" - Write-Host "Account Name: [$($context.Account.Id)]" - Write-Host "Account Type: [$($context.Account.Type)]" - Write-Host $([Constants]::SingleDashLine) - - # Note about the required access required for remediation - - Write-Host "To remove Configured WAF Policy for all Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Write-Host "[Step 2 of 4] Preparing to fetch all Front Door CDN Endpoints" - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndPoints = @() - $resourceAppIdURI = "https://management.azure.com/" - $apiResponse =@() - $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token - $endPointPolicies = New-Object System.Collections.ArrayList - - if (-not (Test-Path -Path $FilePath)) - { - Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - break - } - - Write-Host "Fetching all Front Door CDN Endpoints from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) - - $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath - $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } - - $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName - - foreach($frontdoor in $uniquefrontDoors) - { - $resourceGroupName = $frontdoor.ResourceGroupName - $frontDoorName = $frontdoor.FrontDoorName - - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) - { - if($null -ne $apiResponse.Content) - { - $content = $apiResponse.Content | ConvertFrom-Json - - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) - { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } - } - } - - - $validfrontDoorEndpointsDetails | ForEach-Object { - $frontdoorEndpointName = $_.EndPointName - $resourceGroupName = $_.ResourceGroupName - $frontDoorName = $_.FrontDoorName - - try - { - $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }} - } - catch - { - Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::SingleDashLine) - Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - } - - - - # Includes Front Door CDN Endpoint(s) where WAF Policy is configured - $frontDoorEndpointsWithWAFPolicyConfigured = @() - - - - Write-Host "[Step 3 of 4] Fetching Endpoint(s) Endpoint(s) where WAF Policy is not configured" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is configured..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndPoints | ForEach-Object { - $endPoint = $_ - if($_.IsWAFConfigured -eq $true) - { - $frontDoorEndpointsWithWAFPolicyConfigured += $endPoint - } - else - { - $logResource = @{} - $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) - $logResource.Add("ResourceName",($_.EndPointName)) - $logResource.Add("Reason","WAF Policy is already configured on Frontdoor CDN Endpoint") - $logSkippedResources += $logResource - - } - } - - $totalfrontDoorEndpointsWithWAFPolicyConfigured = ($frontDoorEndpointsWithWAFPolicyConfigured | Measure-Object).Count - - if ($totalfrontDoorEndpointsWithWAFPolicyConfigured -eq 0) - { - Write-Host "No Front Door CDN Endpoints(s) found where WAF Policy is configured.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - return - } - - - Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyConfigured)] Front Door CDN Endpoints(s) found in file where WAF Policy is configured to Rollback." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - - # Back up snapshots to `%LocalApplicationData%'. - $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\ConfigureFrontDoorCDNWAFPolicy" - - if (-not (Test-Path -Path $backupFolderPath)) - { - New-Item -ItemType Directory -Path $backupFolderPath | Out-Null - } - - if (-not $Force) - { - Write-Host "Do you want remove configured WAF Policy Mode for all Front Door CDN Endpoint(s)?" -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline - Write-Host $([Constants]::SingleDashLine) - $userInput = Read-Host -Prompt "(Y|N)" - Write-Host $([Constants]::SingleDashLine) - if($userInput -ne "Y") - { - Write-Host "Configured WAF Policy will not be removed for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::DoubleDashLine) - break - } - else - { - Write-Host "Configured WAF Policy will be removed for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - } - else - { - Write-Host "'Force' flag is provided. Configured WAF Policy will be removed for all the Front Door CDN Endpoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline - Write-Host $([Constants]::SingleDashLine) - } - - - - - - Write-Host "[Step 4 of 4] Switching WAF Policy mode to Detection for Front Door CDNs Endpoint(s)" - Write-Host $([Constants]::SingleDashLine) - # Includes Front Door CDN s, to which, previously made changes were successfully rolled back. - $frontDoorEndpointsRolledBack = @() - - # Includes Front Door CDN s that were skipped during roll back. There were errors rolling back the changes made previously. - $frontDoorEndpointsSkipped = @() - - - - # Roll back by removing configured WAF Policy on Endpoints - $frontDoorEndpointsWithWAFPolicyConfigured | ForEach-Object { - $frontDoorEndPoint = $_ - $endpointName = $_.EndPointName - $frontdoorName = $_.FrontDoorName - $resourceGroupName = $_.ResourceGroupName - $wafPolicyName = $_.WAFPolicyName - $policyResourceGroup = $_.WAFPolicyResourceGroup - $i = 0 - - $policy = Get-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $policyResourceGroup -ErrorAction SilentlyContinue - $wafPolicyId = $policy.Id - try - { - $updateAssociations = @() - $otherPolicyEndpointsAssociations = @() - - # Remove the endpoint to be rolledback from endpointPolicies List - foreach($policy in $endPointPolicies.Clone()) - { - if($policy.endpointName -eq $endpointName) - { - $endPointPolicies.Remove($policy) - } - - } - - $otherPolicyEndpointsAssociations = $endPointPolicies | where wafPolicyName -eq ($wafPolicyName) - $otherPolicyEndpointsAssociations = $otherPolicyEndpointsAssociations | where frontDoorName -eq $frontdoorName - - $otherPolicyEndpointsAssociations | ForEach-Object { - $association = $_ - $associatedEndpoint = $_.endpointName - - New-Variable -Force -Name "endpoint$i" -Value (Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontdoorName -EndpointName $associatedEndpoint) - New-Variable -Force -Name "updateAssociation$i" -Value (New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$(Get-Variable -Name "endpoint$i" -ValueOnly).Id})) - $updateAssociations += (Get-Variable -Name "updateAssociation$i" -ValueOnly) - $i++ - } - - $updateWafParameter = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallParametersObject -Association @($updateAssociations) -WafPolicyId $wafPolicyId - $policySecurity = New-AzFrontDoorCdnSecurityPolicy -ResourceGroupName v-abprasadTestRG -ProfileName testFrontdoorCDN -Name Policy -Parameter $updateWafParameter - - if ($policySecurity.Name -eq $null) - { - $frontDoorEndpointsSkipped += $frontDoorEndPoint - - } - else - { - $frontDoorEndPoint.IsWAFConfigured = $false - $frontDoorEndPoint.WAFPolicyName = "" - $frontDoorEndPoint.WAFPolicyResourceGroup = "" - $frontDoorEndpointsRolledBack += $frontDoorEndPoint - } - } - catch - { - Write-Host $_ - $frontDoorEndpointsSkipped += $frontDoorEndPoint - } - } - - - $totalfrontDoorEndpointsRolledBack = ($frontDoorEndpointsRolledBack | Measure-Object).Count - - Write-Host $([Constants]::SingleDashLine) - - if ($totalfrontDoorEndpointsRolledBack -eq $totalfrontDoorEndpointsWithWAFPolicyConfigured) - { - Write-Host "Configured WAF Policy removed for all [$($totalfrontDoorEndpointsWithWAFPolicyConfigured)] Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - else - { - Write-Host "Configured WAF Policy removed for [$totalfrontDoorEndpointsRolledBack] out of [$($totalfrontDoorEndpointsWithWAFPolicyConfigured)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - - Write-Host "Rollback Summary:" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, - @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"} - - - if ($($frontDoorEndpointsRolledBack | Measure-Object).Count -gt 0) - { - Write-Host "Successfully removed WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndpointsRolledBack | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $frontDoorEndpointsRolledBackFile = "$($backupFolderPath)\RolledBackFrontDoorCDNEndpointsForConfigureWAFPolicy.csv" - $frontDoorEndpointsRolledBack | Export-CSV -Path $frontDoorEndpointsRolledBackFile -NoTypeInformation - Write-Host "This information has been saved to [$($frontDoorEndpointsRolledBackFile)]" - Write-Host $([Constants]::SingleDashLine) - } - - if ($($frontDoorEndpointsSkipped | Measure-Object).Count -gt 0) - { - Write-Host "Error removing configured WAF Policy for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndpointsSkipped | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $frontDoorEndpointsSkippedFile = "$($backupFolderPath)\RollbackSkippedFrontDoorCDNEndpointsForConfigureWAFPolicy.csv" - $frontDoorEndpointsSkipped | Export-CSV -Path $frontDoorEndpointsSkippedFile -NoTypeInformation - Write-Host "This information has been saved to [$($frontDoorEndpointsSkippedFile)]" - } -} - -# Defines commonly used constants. -class Constants -{ - # Defines commonly used colour codes, corresponding to the severity of the log. - static [Hashtable] $MessageType = @{ - Error = [System.ConsoleColor]::Red - Warning = [System.ConsoleColor]::Yellow - Info = [System.ConsoleColor]::Cyan - Update = [System.ConsoleColor]::Green - Default = [System.ConsoleColor]::White - } - - static [String] $DoubleDashLine = "========================================================================================================================" - static [String] $SingleDashLine = "------------------------------------------------------------------------------------------------------------------------" -} \ No newline at end of file From 55948e1e889db9b5d9bb5c271bdad29ff7d2ed63 Mon Sep 17 00:00:00 2001 From: Abhishek Prasad Date: Fri, 16 Dec 2022 19:07:43 +0530 Subject: [PATCH 15/26] Delete Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 --- ...mediate-EnableWAFPolicyForFrontDoorCDN.ps1 | 1157 ----------------- 1 file changed, 1157 deletions(-) delete mode 100644 Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 diff --git a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 deleted file mode 100644 index 0d0198b7..00000000 --- a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 +++ /dev/null @@ -1,1157 +0,0 @@ -<### -# Overview: - This script is used to enable state of the WAF Policy configured on endpoints of Front Door CDNs in a Subscription. - -# Control ID: - Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration - -# Display Name: - Front Door should have Web Application Firewall configured - -# Prerequisites: - 1. Contributor or higher privileges on the Front Door CDNs in a Subscription. - 2. Must be connected to Azure with an authenticated account. - -# Steps performed by the script: - To remediate: - 1. Validate and install the modules required to run the script. - 2. Get the list of all Front Door CDNs Endpoints in a Subscription that do not have WAF Policy Enabled - 3. Back up details of Front Door CDN Endpoint(s) that are to be remediated. - 4. Enable Policy for all endpoints in the Front Doors. - - To roll back: - 1. Validate and install the modules required to run the script. - 2. Get the list of Front Doors' Endpoint(s) in a Subscription, the changes made to which previously, are to be rolled back. - 3. Revert back Policy State to Disabled for all endpoints in all the Front Doors. - -# Instructions to execute the script: - To remediate: - 1. Download the script. - 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. - 3. Execute the script to enable WAF Policy on All endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. - - To roll back: - 1. Download the script. - 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. - 3. Execute the script to disable WAF Policy on All endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. - -# Examples: - To remediate: - 1. To review the Front Door CDNs in a Subscription that will be remediated: - Enable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun - - 2. To Enable WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription: - Enable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck - - 3. To Enable WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription, from a previously taken snapshot: - Enable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\EnableFrontDoorCDNWAFPolicy\frontdoorCDNEndpointsWithoutPolicyEnabled.csv - - To know more about the options supported by the remediation command, execute: - Get-Help Enable-WAFPolicyForFrontDoorCDN -Detailed - - To roll back: - 1. To Disable WAF Policy for Endpoint(s) all Front Door CDNs in a Subscription, from a previously taken snapshot: - Disable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\EnableFrontDoorCDNWAFPolicy\RemediatedfrontDoorCDNEndpointsForEnabledState.csv - - To know more about the options supported by the roll back command, execute: - Get-Help Disable-WAFPolicyForFrontDoorCDN -Detailed -###> - -function Setup-Prerequisites -{ - <# - .SYNOPSIS - Checks if the prerequisites are met, else, sets them up. - - .DESCRIPTION - Checks if the prerequisites are met, else, sets them up. - Includes installing any required Azure modules. - - .INPUTS - None. You cannot pipe objects to Setup-Prerequisites. - - .OUTPUTS - None. Setup-Prerequisites does not return anything that can be piped and used as an input to another command. - - .EXAMPLE - PS> Setup-Prerequisites - - .LINK - None - #> - - # List of required modules - $requiredModules = @("Az.Accounts", "Az.FrontDoor", "Az.Cdn") - - Write-Host "Required modules: $($requiredModules -join ', ')" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Checking if the required modules are present..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - - $availableModules = $(Get-Module -ListAvailable $requiredModules -ErrorAction Stop) - - # Check if the required modules are installed. - $requiredModules | ForEach-Object { - if ($availableModules.Name -notcontains $_) - { - Write-Host "[$($_)] module is not present." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host "Installing [$($_)] module..." -ForegroundColor $([Constants]::MessageType.Info) - Install-Module -Name $_ -Scope CurrentUser -Repository 'PSGallery' -ErrorAction Stop - Write-Host "[$($_)] module installed." -ForegroundColor $([Constants]::MessageType.Update) - } - else - { - Write-Host "[$($_)] module is present." -ForegroundColor $([Constants]::MessageType.Update) - } - } -} - -function Enable-WAFPolicyForFrontDoorCDN -{ - <# - .SYNOPSIS - Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - - .DESCRIPTION - Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - WAF Policy must be Enabled for Front Door CDN Endpoint(s). - - .PARAMETER SubscriptionId - Specifies the ID of the Subscription to be remediated. - - .PARAMETER Force - Specifies a forceful remediation without any prompts. - - .Parameter PerformPreReqCheck - Specifies validation of prerequisites for the command. - - .PARAMETER DryRun - Specifies a dry run of the actual remediation. - - .PARAMETER FilePath - Specifies the path to the file to be used as input for the remediation. - - .INPUTS - None. You cannot pipe objects to Enable-WAFPolicyForFrontDoors. - - .OUTPUTS - None. Enable-WAFPolicyForFrontDoorCDN does not return anything that can be piped and used as an input to another command. - - .EXAMPLE - PS> Enable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun - - .EXAMPLE - PS> Enable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck - - .EXAMPLE - PS> Enable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202201011212\EnableFrontDoorCDNWAFPolicy\frontdoorCDNEndpointsWithoutPolicyEnabled.csv - - .LINK - None - #> - - param ( - [String] - [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] - [Parameter(ParameterSetName = "WetRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] - $SubscriptionId, - - [Switch] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies a forceful remediation without any prompts")] - $Force, - - [Switch] - [Parameter(ParameterSetName = "DryRun", HelpMessage="Specifies validation of prerequisites for the command")] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies validation of prerequisites for the command")] - $PerformPreReqCheck, - - [Switch] - [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies a dry run of the actual remediation")] - $DryRun, - - [String] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation")] - $FilePath - ) - - Write-Host $([Constants]::DoubleDashLine) - if ($PerformPreReqCheck) - { - try - { - Write-Host "[Step 1 of 5] Validate and install the modules required to run the script and validate the user" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Setup-Prerequisites - Write-Host "Completed setting up prerequisites." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - catch - { - Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - return - } - } - else - { - Write-Host "[Step 1 of 5] Validate the user" - Write-Host $([Constants]::SingleDashLine) - } - - # Connect to Azure account - $context = Get-AzContext - - if ([String]::IsNullOrWhiteSpace($context)) - { - Write-Host "Connecting to Azure account..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null - Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - # Setting up context for the current Subscription. - $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop - - Write-Host "Subscription Name: [$($context.Subscription.Name)]" - Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" - Write-Host "Account Name: [$($context.Account.Id)]" - Write-Host "Account Type: [$($context.Account.Type)]" - Write-Host $([Constants]::SingleDashLine) - - - Write-Host "To enable WAF Policy for Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Write-Host "[Step 2 of 5] Preparing to fetch all Front Door CDNs" - Write-Host $([Constants]::SingleDashLine) - $frontDoorCDNs = @() - $frontDoorEndPoints = @() - $resourceAppIdURI = "https://management.azure.com/" - $apiResponse =@() - $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token - $endPointPolicies = New-Object System.Collections.ArrayList - - # To keep track of remediated and skipped resources - $logRemediatedResources = @() - $logSkippedResources=@() - - # Control Id - $controlIds = "Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration" - - - # No file path provided as input to the script. Fetch all Front Door CDNs in the Subscription. - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - Write-Host "Fetching all Front Door CDNs in Subscription: [$($context.Subscription.SubscriptionId)]..." -ForegroundColor $([Constants]::MessageType.Info) - - # Get all Front Door CDNs in the Subscription - $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction SilentlyContinue - $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - - if($totalfrontDoors -gt 0) - { - $frontDoorCDNs | ForEach-Object { - $frontDoor = $_ - $frontDoorId = $_.Id - $resourceGroupName = $_.Id.Split('/')[4] - $frontDoorName = $_.Name - - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) - { - if($null -ne $apiResponse.Content) - { - $content = $apiResponse.Content | ConvertFrom-Json - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) - { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } - } - - - # Get all Endpoint(s) for this Front Door CDN. - $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }}, - @{N='IsPreventionMode';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - if($WAFPolicy.PolicyMode -eq 'Prevention') - { - $true - } - else - { - $false - - } - } - }}, - @{N='IsWAFPolicyStateEnabled';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - - if($WAFPolicy.PolicyEnabledState -eq 'Enabled') - { - $true - } - else - { - $false - } - } - }} - - } - } - } - else - { - if (-not (Test-Path -Path $FilePath)) - { - Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) - break - } - - Write-Host "Fetching all Front Door CDN Endpoint(s) from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) - - $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath - $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } - - $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName - - foreach($frontdoor in $uniquefrontDoors) - { - $resourceGroupName = $frontdoor.ResourceGroupName - $frontDoorName = $frontdoor.FrontDoorName - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) - { - if($null -ne $apiResponse.Content) - { - $content = $apiResponse.Content | ConvertFrom-Json - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) - { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } - } - - } - - $validfrontDoorEndpointsDetails | ForEach-Object { - $frontdoorEndpointName = $_.EndPointName - $resourceGroupName = $_.ResourceGroupName - $frontDoorName = $_.FrontDoorName - - try - { - - - $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }}, - @{N='IsPreventionMode';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - if($WAFPolicy.PolicyMode -eq 'Prevention') - { - $true - } - else - { - $false - - } - } - }}, - @{N='IsWAFPolicyStateEnabled';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - - if($WAFPolicy.PolicyEnabledState -eq 'Enabled') - { - $true - } - else - { - $false - } - } - }} - } - catch - { - Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) - } - } - } - - - - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - - if ($totalfrontDoors -eq 0) - { - Write-Host "No Front Door CDNs found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - break - } - - Write-Host "Found $($totalfrontDoors) Front Door CDN(s)." -ForegroundColor $([Constants]::MessageType.Update) - } - - - - $totalfrontDoorEndPoints = ($frontDoorEndPoints | Measure-Object).Count - - if ($totalfrontDoorEndPoints -eq 0) - { - Write-Host "No Front Door CDN Endpoint(s) found having WAF Policy not Enabled. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - break - } - - Write-Host "Found [$($totalfrontDoorEndPoints)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) - - - # Includes Front Door CDN Endpoint(s) where WAF Policy is in not Enabled - $frontDoorEndpointsWithWAFPolicyNotEnabled = @() - - # Includes Front Door CDN Endpoint(s) that were skipped during remediation. There were errors remediating them. - $frontDoorEndpointsSkipped = @() - - - Write-Host "[Step 3 of 5] Fetching Endpoint(s) for which WAF Policy is not enabled" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is not enabled..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndPoints | ForEach-Object { - $endPoint = $_ - if(($_.IsWAFPolicyStateEnabled -eq $false) -and ($_.IsWAFConfigured -eq $true)) - { - $frontDoorEndpointsWithWAFPolicyNotEnabled += $endPoint - } - else - { - $logResource = @{} - $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) - $logResource.Add("ResourceName",($_.FrontDoorName)) - $logResource.Add("EndPointName",($_.EndPointName)) - $logResource.Add("Reason","WAF Policy already enabled on endpoint") - $logSkippedResources += $logResource - - } - } - - $totalfrontDoorEndpointsWithWAFPolicyNotEnabled = ($frontDoorEndpointsWithWAFPolicyNotEnabled | Measure-Object).Count - - if ($totalfrontDoorEndpointsWithWAFPolicyNotEnabled -eq 0) - { - Write-Host "No Front Door CDN endpoints(s) found where WAF Policy is not Enabled.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - return - } - - Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyNotEnabled)] Front Door CDN Endpoints(s) found where WAF Policy is not Enabled ." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - - - Write-Host "Following Front Door CDN Endpoints(s) are having WAF Policies not Enabled:" -ForegroundColor $([Constants]::MessageType.Info) - $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, - @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFPolicyStateEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} - $frontDoorEndpointsWithWAFPolicyNotEnabled | Format-Table -Property $colsProperty -Wrap - - - # Back up snapshots to `%LocalApplicationData%'. - $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\EnableFrontDoorCDNWAFPolicy" - - if (-not (Test-Path -Path $backupFolderPath)) - { - New-Item -ItemType Directory -Path $backupFolderPath | Out-Null - } - - - Write-Host "[Step 4 of 5] Backing up Front Door CDN Endpoint(s) details" - Write-Host $([Constants]::SingleDashLine) - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - - # Backing up Front Door CDN Endpoints details. - $backupFileForWAFNotEnabled = "$($backupFolderPath)\frontdoorCDNEndpointsWithoutPolicyEnabled.csv" - $frontDoorEndpointsWithWAFPolicyNotEnabled | Export-CSV -Path $backupFileForWAFNotEnabled -NoTypeInformation - Write-Host "Front Door Endpoint(s) details have been successful backed up to $($backupFolderPath)" -ForegroundColor $([Constants]::MessageType.Update) - } - else - { - Write-Host "Skipped as -FilePath is provided" -ForegroundColor $([Constants]::MessageType.Warning) - } - - if (-not $DryRun) - { - - Write-Host "WAF Policy will be Enabled for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Warning) - - if (-not $Force) - { - Write-Host "Do you want to Enable WAF Policies associated with Front Door CDN Endpoint(s)? " -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline - - $userInput = Read-Host -Prompt "(Y|N)" - - if($userInput -ne "Y") - { - Write-Host " WAF Policy will not be enabled for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - break - } - else - { - Write-Host "WAF Policy will be enabled for all Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - } - else - { - Write-Host "'Force' flag is provided. WAF Policy will be enabled on all Front Door CDN Endoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) - } - - - - Write-Host "[Step 5 of 5] Enabling WAF Policy for Front Door CDN Endpoint(s)" - Write-Host $([Constants]::SingleDashLine) - - # To hold results from the remediation. - $frontDoorEndpointsRemediated = @() - - - # Remidiate Controls by Eanbling WAF Policy - $frontDoorEndpointsWithWAFPolicyNotEnabled | ForEach-Object { - $frontDoorEndPoint = $_ - $wafPolicyName = $_.WAFPolicyName - $wafPolicyRG = $_.WAFPolicyResourceGroup - $endpointsSkipped = @() - - - try - { - $updatedPolicy = Update-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $wafPolicyRG -EnabledState Enabled - - if ($updatedPolicy.PolicyEnabledState -ne 'Enabled') - { - $endpointsSkipped += $frontDoorEndPoint - - } - else - { - $frontDoorEndPoint.IsWAFPolicyStateEnabled = $true - $frontDoorEndpointsRemediated += $frontDoorEndPoint - } - } - catch - { - $endpointsSkipped += $frontDoorEndPoint - } - } - - $totalRemediated = ($frontDoorEndpointsRemediated | Measure-Object).Count - - Write-Host $([Constants]::SingleDashLine) - - if ($totalRemediated -eq $totalfrontDoorEndpointsWithWAFPolicyNotEnabled) - { - Write-Host "WAF Policy Enabled for all [$($totalfrontDoorEndpointsWithWAFPolicyNotEnabled)] Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) - } - else - { - Write-Host "WAF Policy Enabled for [$totalRemediated] out of [$($totalfrontDoorEndpointsWithWAFPolicyNotEnabled)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) - } - - $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, - @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFPolicyStateEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} - - - - Write-Host "Remediation Summary:" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - - - - if ($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) - { - Write-Host "Successfully enabled WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) - $frontDoorEndpointsRemediated | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForEnabledState.csv" - $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation - Write-Host "This information has been saved to [$($frontDoorEndpointsRemediatedFile)]" - Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) - } - - if ($($endpointsSkipped | Measure-Object).Count -gt 0) - { - Write-Host "Error performing remediation steps for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) - $endpointsSkipped | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForEnabledState.csv" - $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation - Write-Host "This information has been saved to [$($endpointsSkippedFile)]" - } - - } - else - { - - Write-Host "[Step 5 of 5] Enabling WAF Policy for Endpoint(s)" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Skipped as -DryRun switch is provided." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - - Write-Host "Next steps:" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host "Run the same command with -FilePath $($backupFile) and without -DryRun, to Enable WAF Policy for all Front Door CDN Endpoint(s) listed in the file." -ForegroundColor $([Constants]::MessageType.Info) - } -} - -function Disable-WAFPolicyForFrontDoorCDN -{ - <# - .SYNOPSIS - Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - - .DESCRIPTION - Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - Disables back all WAF Policies in all Front Door CDNs in the Subscription. - - .PARAMETER SubscriptionId - Specifies the ID of the Subscription that was previously remediated. - - .PARAMETER Force - Specifies a forceful roll back without any prompts. - - .Parameter PerformPreReqCheck - Specifies validation of prerequisites for the command. - - .PARAMETER FilePath - Specifies the path to the file to be used as input for the roll back. - - .INPUTS - None. You cannot pipe objects to Disable-WAFPolicyForFrontDoorCDN. - - .OUTPUTS - None. Disable-WAFPolicyForFrontDoorCDN does not return anything that can be piped and used as an input to another command. - - .EXAMPLE - PS> Disable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202211190719\EnableFrontDoorCDNWAFPolicy\RemediatedfrontDoorCDNEndpointsForEnabledState.csv - - .LINK - None - #> - - param ( - [String] - [Parameter(Mandatory = $true, HelpMessage="Specifies the ID of the Subscription that was previously remediated.")] - $SubscriptionId, - - [Switch] - [Parameter(HelpMessage="Specifies a forceful roll back without any prompts")] - $Force, - - [Switch] - [Parameter(HelpMessage="Specifies validation of prerequisites for the command")] - $PerformPreReqCheck, - - [String] - [Parameter(Mandatory = $true, HelpMessage="Specifies the path to the file to be used as input for the roll back")] - $FilePath - ) - - Write-Host $([Constants]::DoubleDashLine) - if ($PerformPreReqCheck) - { - try - { - Write-Host "[Step 1 of 4] Validate and install the modules required to run the script and validate the user" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Setup-Prerequisites - Write-Host "Completed setting up prerequisites" - Write-Host $([Constants]::SingleDashLine) - } - catch - { - Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - return - } - } - else - { - Write-Host "[Step 1 of 4] Validate the user" - Write-Host $([Constants]::SingleDashLine) - } - - # Connect to Azure account - $context = Get-AzContext - - if ([String]::IsNullOrWhiteSpace($context)) - { - - Write-Host "Connecting to Azure account..." - Write-Host $([Constants]::SingleDashLine) - Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null - Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - else - { - # Setting up context for the current Subscription. - $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop - } - - Write-Host "Subscription Name: [$($context.Subscription.Name)]" - Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" - Write-Host "Account Name: [$($context.Account.Id)]" - Write-Host "Account Type: [$($context.Account.Type)]" - Write-Host $([Constants]::SingleDashLine) - - # Note about the required access required for remediation - - Write-Host "To Disable WAF Policy for all Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Write-Host "[Step 2 of 4] Preparing to fetch all Front Door CDN Endpoints" - Write-Host $([Constants]::SingleDashLine) - - if (-not (Test-Path -Path $FilePath)) - { - Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) - break - } - - Write-Host "Fetching all Front Door CDN Endpoints from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) - - $frontDoorEndPoints = @() - $resourceAppIdURI = "https://management.azure.com/" - $apiResponse =@() - $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token - $endPointPolicies = New-Object System.Collections.ArrayList - - $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath - $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } - - $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName - - foreach($frontdoor in $uniquefrontDoors) - { - $resourceGroupName = $frontdoor.ResourceGroupName - $frontDoorName = $frontdoor.FrontDoorName - - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) - { - if($null -ne $apiResponse.Content) - { - $content = $apiResponse.Content | ConvertFrom-Json - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) - { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } - } - } - - - - $validfrontDoorEndpointsDetails | ForEach-Object { - $frontdoorEndpointName = $_.EndPointName - $resourceGroupName = $_.ResourceGroupName - $frontDoorName = $_.FrontDoorName - - try - { - - $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }}, - @{N='IsPreventionMode';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - if($WAFPolicy.PolicyMode -eq 'Prevention') - { - $true - } - else - { - $false - - } - } - }}, - @{N='IsWAFPolicyStateEnabled';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - - if($WAFPolicy.PolicyEnabledState -eq 'Enabled') - { - $true - } - else - { - $false - } - } - }} - } - catch - { - Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]... Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) - } - } - - - - # Includes Front Door CDN Endpoint(s) where WAF Policy is in Enabled - $frontDoorEndpointsWithWAFPolicyEnabled = @() - - - - Write-Host "[Step 3 of 4] Fetching Endpoint(s) for which WAF Policy is enabled" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is enabled..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - - $frontDoorEndPoints | ForEach-Object { - $endPoint = $_ - if(($_.IsWAFPolicyStateEnabled -eq $true) -and ($_.IsWAFConfigured -eq $true)) - { - $frontDoorEndpointsWithWAFPolicyEnabled += $endPoint - } - } - - $totalfrontDoorEndpointsWithWAFPolicyEnabled = ($frontDoorEndpointsWithWAFPolicyEnabled | Measure-Object).Count - - if ($totalfrontDoorEndpointsWithWAFPolicyEnabled -eq 0) - { - Write-Host "No Front Door CDN Endpoints(s) found where WAF Policy is Enabled.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - return - } - - - Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyEnabled)] Front Door CDN Endpoints(s) found where WAF Policy is Enabled in file to Rollback." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - - # Back up snapshots to `%LocalApplicationData%'. - $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\EnableFrontDoorCDNWAFPolicy" - - if (-not (Test-Path -Path $backupFolderPath)) - { - New-Item -ItemType Directory -Path $backupFolderPath | Out-Null - } - - if (-not $Force) - { - Write-Host "Do you want to Disable WAF Policy for all Front Door CDN Endpoint(s)?" -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline - - $userInput = Read-Host -Prompt "(Y|N)" - - if($userInput -ne "Y") - { - Write-Host "WAF Policy will not be Disabled for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - break - } - } - else - { - Write-Host "'Force' flag is provided. WAF Policy will be Disabled for all the Front Door CDN Endpoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline - } - - - - Write-Host "[Step 4 of 4] Disabling WAF Policy for Front Door CDNs Endpoint(s)" - Write-Host $([Constants]::SingleDashLine) - - # Includes Front Door CDN s, to which, previously made changes were successfully rolled back. - $frontDoorEndpointsRolledBack = @() - - # Includes Front Door CDN s that were skipped during roll back. There were errors rolling back the changes made previously. - $frontDoorEndpointsSkipped = @() - - - # Roll back by disabling WAF Policy - $frontDoorEndpointsWithWAFPolicyEnabled | ForEach-Object { - $frontDoorEndPoint = $_ - $wafPolicyName = $_.WAFPolicyName - $wafPolicyRG = $_.WAFPolicyResourceGroup - - try - { - $endpointResource = Update-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $wafPolicyRG -EnabledState Disabled - - if ($endpointResource.PolicyEnabledState -ne 'Disabled') - { - $frontDoorEndpointsSkipped += $frontDoorEndPoint - - } - else - { - $frontDoorEndPoint.IsWAFPolicyStateEnabled = $false - $frontDoorEndpointsRolledBack += $frontDoorEndPoint - } - } - catch - { - $frontDoorEndpointsSkipped += $frontDoorEndPoint - } - } - - - $totalfrontDoorEndpointsRolledBack = ($frontDoorEndpointsRolledBack | Measure-Object).Count - - Write-Host $([Constants]::SingleDashLine) - - if ($totalfrontDoorEndpointsRolledBack -eq $totalfrontDoorEndpointsWithWAFPolicyEnabled) - { - Write-Host "WAF Policy Disabled for all [$($totalfrontDoorEndpointsWithWAFPolicyEnabled)] Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) - } - else - { - Write-Host "WAF Policy disabled for [$totalfrontDoorEndpointsRolledBack] out of [$($totalfrontDoorEndpointsWithWAFPolicyEnabled)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) - } - - Write-Host "Rollback Summary:" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - - $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, - @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFPolicyStateEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} - - - if ($($frontDoorEndpointsRolledBack | Measure-Object).Count -gt 0) - { - Write-Host "Successfully disabled WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) - $frontDoorEndpointsRolledBack | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $frontDoorEndpointsRolledBackFile = "$($backupFolderPath)\RolledBackfrontDoorCDNEndpointsForWAFPolicyState.csv" - $frontDoorEndpointsRolledBack | Export-CSV -Path $frontDoorEndpointsRolledBackFile -NoTypeInformation - Write-Host "This information has been saved to $($frontDoorEndpointsRolledBackFile)" - } - - if ($($frontDoorEndpointsSkipped | Measure-Object).Count -gt 0) - { - Write-Host "Error Disabling WAF Policy for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) - $frontDoorEndpointsSkipped | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $frontDoorEndpointsSkippedFile = "$($backupFolderPath)\RolledBackSkippedfrontDoorCDNEndpointsForWAFPolicyState.csv" - $frontDoorEndpointsSkipped | Export-CSV -Path $frontDoorEndpointsSkippedFile -NoTypeInformation - Write-Host "This information has been saved to $($frontDoorEndpointsSkippedFile)" - } - -} - -# Defines commonly used constants. -class Constants -{ - # Defines commonly used colour codes, corresponding to the severity of the log. - static [Hashtable] $MessageType = @{ - Error = [System.ConsoleColor]::Red - Warning = [System.ConsoleColor]::Yellow - Info = [System.ConsoleColor]::Cyan - Update = [System.ConsoleColor]::Green - Default = [System.ConsoleColor]::White - } - - static [String] $DoubleDashLine = "========================================================================================================================" - static [String] $SingleDashLine = "------------------------------------------------------------------------------------------------------------------------" -} \ No newline at end of file From 68f4030a134cf231a582700aec50b6ab15f51a1b Mon Sep 17 00:00:00 2001 From: Abhishek Prasad Date: Fri, 16 Dec 2022 19:08:01 +0530 Subject: [PATCH 16/26] Delete Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 --- ...iate-ConfigureWAFPolicyForFrontDoorCDN.ps1 | 1164 ----------------- 1 file changed, 1164 deletions(-) delete mode 100644 Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 diff --git a/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 deleted file mode 100644 index 489a8dd8..00000000 --- a/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 +++ /dev/null @@ -1,1164 +0,0 @@ -<### -# Overview: - This script is used to configure WAF Policy on all endpoints of Front Door CDNs in a Subscription. - -# Control ID: - Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration - -# Display Name: - Front Door should have Web Application Firewall configured - -# Prerequisites: - 1. Contributor or higher privileges on the Front Door CDNs in a Subscription. - 2. Must be connected to Azure with an authenticated account. - -# Steps performed by the script: - To remediate: - 1. Validate and install the modules required to run the script. - 2. Get the list of all Front Door CDNs Endpoints in a Subscription that do not have WAF Policy Configured - 3. Back up details of Front Door CDN Endpoint(s) that are to be remediated. - 4. Configure WAF Policy for all endpoints in the Frontdoors. - - To roll back: - 1. Validate and install the modules required to run the script. - 2. Get the list of Frontdoors' Endpoint(s) in a Subscription, the changes made to which previously, are to be rolled back. - 3. Revert by removing WAF Policies from endpoints in all the Frontdoors. - -# Instructions to execute the script: - To remediate: - 1. Download the script. - 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. - 3. Execute the script to configure WAF Policy on all endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. - - To roll back: - 1. Download the script. - 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. - 3. Execute the script to remove the configured WAF Policy on all endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. - -# Examples: - To remediate: - 1. To review the Front Door CDNs in a Subscription that will be remediated: - Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun - - 2. To configure WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription: - Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck - - 3. To configure WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription, from a previously taken snapshot: - Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\ConfigureFrontDoorCDNWAFPolicy\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv - - To know more about the options supported by the remediation command, execute: - Get-Help Configure-WAFPolicyForFrontDoorCDN -Detailed - - To roll back: - 1. To remove configured WAF Policy Mode for Endpoint(s) all Front Door CDNs in a Subscription, from a previously taken snapshot: - Remove-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\ConfigureFrontDoorCDNWAFPolicy\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv - - To know more about the options supported by the roll back command, execute: - Get-Help Remove-WAFPolicyForFrontDoorCDN -Detailed -###> - -function Setup-Prerequisites -{ - <# - .SYNOPSIS - Checks if the prerequisites are met, else, sets them up. - - .DESCRIPTION - Checks if the prerequisites are met, else, sets them up. - Includes installing any required Azure modules. - - .INPUTS - None. You cannot pipe objects to Setup-Prerequisites. - - .OUTPUTS - None. Setup-Prerequisites does not return anything that can be piped and used as an input to another command. - - .EXAMPLE - PS> Setup-Prerequisites - - .LINK - None - #> - - # List of required modules - $requiredModules = @("Az.Accounts", "Az.FrontDoor", "Az.Cdn") - - Write-Host "Required modules: $($requiredModules -join ', ')" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Checking if the required modules are present..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - - $availableModules = $(Get-Module -ListAvailable $requiredModules -ErrorAction Stop) - - # Check if the required modules are installed. - $requiredModules | ForEach-Object { - if ($availableModules.Name -notcontains $_) - { - Write-Host "[$($_)] module is not present." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host "Installing [$($_)] module..." -ForegroundColor $([Constants]::MessageType.Info) - Install-Module -Name $_ -Scope CurrentUser -Repository 'PSGallery' -ErrorAction Stop - Write-Host "[$($_)] module installed." -ForegroundColor $([Constants]::MessageType.Update) - } - else - { - Write-Host "[$($_)] module is present." -ForegroundColor $([Constants]::MessageType.Update) - } - } -} - -function Configure-WAFPolicyForFrontDoorCDN -{ - <# - .SYNOPSIS - Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - - .DESCRIPTION - Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - WAF Policy must be configured for Front Door CDN Endpoint(s). - - .PARAMETER SubscriptionId - Specifies the ID of the Subscription to be remediated. - - .PARAMETER Force - Specifies a forceful remediation without any prompts. - - .Parameter PerformPreReqCheck - Specifies validation of prerequisites for the command. - - .PARAMETER DryRun - Specifies a dry run of the actual remediation. - - .PARAMETER FilePath - Specifies the path to the file to be used as input for the remediation. - - .INPUTS - None. You cannot pipe objects to Enable-WAFPolicyForFrontDoors. - - .OUTPUTS - None. Configure-WAFPolicyForFrontDoorCDN does not return anything that can be piped and used as an input to another command. - - .EXAMPLE - PS> Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun - - .EXAMPLE - PS> Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck - - .EXAMPLE - PS> Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202201011212\ConfigureFrontDoorCDNWAFPolicy\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv - - .LINK - None - #> - - param ( - [String] - [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] - [Parameter(ParameterSetName = "WetRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] - $SubscriptionId, - - [Switch] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies a forceful remediation without any prompts")] - $Force, - - [Switch] - [Parameter(ParameterSetName = "DryRun", HelpMessage="Specifies validation of prerequisites for the command")] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies validation of prerequisites for the command")] - $PerformPreReqCheck, - - [Switch] - [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies a dry run of the actual remediation")] - $DryRun, - - [String] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation")] - $FilePath - ) - - Write-Host $([Constants]::DoubleDashLine) - if ($PerformPreReqCheck) - { - try - { - Write-Host "[Step 1 of 5] Validate and install the modules required to run the script and validate the user" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Setup-Prerequisites - Write-Host "Completed setting up prerequisites." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - catch - { - Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - return - } - } - else - { - Write-Host "[Step 1 of 5] Validate the user" - Write-Host $([Constants]::SingleDashLine) - } - - # Connect to Azure account - $context = Get-AzContext - - if ([String]::IsNullOrWhiteSpace($context)) - { - Write-Host "Connecting to Azure account..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null - Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - # Setting up context for the current Subscription. - $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop - - Write-Host "Subscription Name: [$($context.Subscription.Name)]" - Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" - Write-Host "Account Name: [$($context.Account.Id)]" - Write-Host "Account Type: [$($context.Account.Type)]" - Write-Host $([Constants]::SingleDashLine) - - - Write-Host " To configure WAF Policy for Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Write-Host "[Step 2 of 5] Preparing to fetch all Front Door CDNs" - Write-Host $([Constants]::SingleDashLine) - - $frontDoorCDNs = @() - $frontDoorEndPoints = @() - $resourceAppIdURI = "https://management.azure.com/" - $apiResponse =@() - $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token - $endPointPolicies = New-Object System.Collections.ArrayList - - # To keep track of remediated and skipped resources - $logRemediatedResources = @() - $logSkippedResources=@() - - # Control Id - $controlIds = "Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration" - - - # No file path provided as input to the script. Fetch all Front Door CDNs in the Subscription. - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - Write-Host "Fetching all Front Door CDNs in Subscription: [$($context.Subscription.SubscriptionId)]..." -ForegroundColor $([Constants]::MessageType.Info) - - # Get all Front Door CDNs in the Subscription - $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction SilentlyContinue - $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - - if($totalfrontDoors -gt 0) - { - $frontDoorCDNs | ForEach-Object { - $frontDoor = $_ - $frontDoorId = $_.Id - $resourceGroupName = $_.Id.Split('/')[4] - $frontDoorName = $_.Name - - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) - { - if($null -ne $apiResponse.Content) - { - $content = $apiResponse.Content | ConvertFrom-Json - - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) - { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } - } - - - # Get all Endpoint(s) for this Front Door CDN. - $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }} - } - } - } - else - { - if (-not (Test-Path -Path $FilePath)) - { - Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - break - } - - Write-Host "Fetching all Front Door CDN Endpoint(s) from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) - - $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath - $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } - - $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName - - foreach($frontdoor in $uniquefrontDoors) - { - $resourceGroupName = $frontdoor.ResourceGroupName - $frontDoorName = $frontdoor.FrontDoorName - - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) - { - if($null -ne $apiResponse.Content) - { - $content = $apiResponse.Content | ConvertFrom-Json - - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) - { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } - } - } - - $validfrontDoorEndpointsDetails | ForEach-Object { - $frontdoorEndpointName = $_.EndPointName - $resourceGroupName = $_.ResourceGroupName - $frontDoorName = $_.FrontDoorName - - try - { - $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }} - } - catch - { - Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) - } - } - } - - - - - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - - if ($totalfrontDoors -eq 0) - { - Write-Host "No Front Door CDNs found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::DoubleDashLine) - break - } - - Write-Host "Found [$($totalfrontDoors)] Front Door CDN(s)." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - - - - $totalfrontDoorEndPoints = ($frontDoorEndPoints | Measure-Object).Count - - if ($totalfrontDoorEndPoints -eq 0) - { - Write-Host "No Front Door CDN Endpoint(s) having WAF Policy not configured. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::DoubleDashLine) - break - } - - Write-Host "Found [$($totalfrontDoorEndPoints)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - - # Includes Front Door CDN Endpoint(s) where WAF Policy is not configured - $frontDoorEndpointsWithWAFPolicyNotConfigured = @() - - # Includes Front Door CDN Endpoint(s) that were skipped during remediation. There were errors remediating them. - $frontDoorEndpointsSkipped = @() - - - - Write-Host "[Step 3 of 5] Fetching Endpoint(s) for which WAF Policy is not configured" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is not configured..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndPoints | ForEach-Object { - $endPoint = $_ - if($_.IsWAFConfigured -eq $false) - { - $frontDoorEndpointsWithWAFPolicyNotConfigured += $endPoint - } - else - { - $logResource = @{} - $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) - $logResource.Add("ResourceName",($_.FrontDoorName)) - $logResource.Add("EndPointName",($_.EndPointName)) - $logResource.Add("Reason","WAF Policy already Configured on endpoint") - $logSkippedResources += $logResource - - } - } - - $totalfrontDoorEndpointsWithWAFPolicyNotConfigured = ($frontDoorEndpointsWithWAFPolicyNotConfigured | Measure-Object).Count - - if ($totalfrontDoorEndpointsWithWAFPolicyNotConfigured -eq 0) - { - Write-Host "No Front Door CDN endpoints(s) found where WAF Policy is not configured. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - return - } - - Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door CDN Endpoints(s) found where WAF Policy is not configured ." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - - - Write-Host "Following Front Door CDN Endpoints(s) are having WAF Policies not configured:" -ForegroundColor $([Constants]::MessageType.Info) - $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, - @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"} - - $frontDoorEndpointsWithWAFPolicyNotConfigured | Format-Table -Property $colsProperty -Wrap - - # Back up snapshots to `%LocalApplicationData%'. - $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\ConfigureFrontDoorCDNWAFPolicy" - - if (-not (Test-Path -Path $backupFolderPath)) - { - New-Item -ItemType Directory -Path $backupFolderPath | Out-Null - } - - - Write-Host "[Step 4 of 5] Backing up Front Door CDN Endpoint(s) details" - Write-Host $([Constants]::SingleDashLine) - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - - # Backing up Front Door CDN Endpoints details. - $backupFile = "$($backupFolderPath)\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv" - $frontDoorEndpointsWithWAFPolicyNotConfigured | Export-CSV -Path $backupFile -NoTypeInformation - Write-Host "Front Door Endpoint(s) details have been successful backed up to [$($backupFolderPath)]" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - else - { - Write-Host "Skipped as -FilePath is provided" -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - - if (-not $DryRun) - { - - Write-Host "WAF Policy will be configured for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Warning) - - if (-not $Force) - { - Write-Host "Do you want to configure WAF Policy associated with Front Door CDN Endpoint(s)? " -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline - Write-Host $([Constants]::SingleDashLine) - $userInput = Read-Host -Prompt "(Y|N)" - Write-Host $([Constants]::SingleDashLine) - if($userInput -ne "Y") - { - Write-Host "WAF Policy will not be configured for any Front Door CDN Endpoint(s). Exiting." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::DoubleDashLine) - break - } - else - { - Write-Host "WAF Policy will be configured for all Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - } - else - { - Write-Host "'Force' flag is provided. WAF Policy will be configured on all Front Door CDN Endoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - - - - Write-Host "[Step 5 of 5] Configuring WAF Policy for Front Door CDN Endpoint(s)" - Write-Host $([Constants]::SingleDashLine) - # To hold results from the remediation. - $frontDoorEndpointsRemediated = @() - $endpointsSkipped = @() - $otherPolicyEndpointsAssociations = @() - - - # Remidiate Controls by configuring WAF Policy - $frontDoorEndpointsWithWAFPolicyNotConfigured | ForEach-Object { - $frontDoorEndPoint = $_ - $endpointName = $_.EndPointName - $frontdoorName = $_.FrontDoorName - $resourceGroupName = $_.ResourceGroupName - $i= 0 - - try - { - Do - { - $wafPolicyName = Read-Host -Prompt "Enter WAF Policy Name for Endpoint: [$($_.EndPointName)] of Frontdoor [$($frontdoorName)] " - $policyResourceGroup = Read-Host -Prompt "Enter WAF Policy Resource Group Name for Endpoint: [$($_.EndPointName)] of Frontdoor [$($frontdoorName)] " - $policy = Get-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $policyResourceGroup -ErrorAction SilentlyContinue - - if($policy -eq $null) - { - Write-Host "WAF Policy name or WAF Policy Resource Group Name is not correct. Please enter correct details." - Write-Host $([Constants]::SingleDashLine) - } - - if($policy -ne $null -and $policy.Sku -ne 'Standard_AzureFrontDoor') - { - Write-Host "WAF Policy is not of type Front door tier Standard . Please enter correct details." - Write-Host $([Constants]::SingleDashLine) - } - - } - while($policy.Sku -ne 'Standard_AzureFrontDoor' -or $policy -eq $null) - $wafPolicyId = $policy.Id - - $endpoint = Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontdoorName -EndpointName $endpointName - $updateAssociation = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$($endpoint.Id)}) - $updateAssociations = @() - $updateAssociations += $updateAssociation - $otherPolicyEndpointsAssociations = $endPointPolicies | where wafPolicyName -eq ($wafPolicyName) - $otherPolicyEndpointsAssociations = $otherPolicyEndpointsAssociations | where frontDoorName -eq $frontdoorName - - $otherPolicyEndpointsAssociations | ForEach-Object { - $association = $_ - $associatedEndpoint = $_.endpointName - - New-Variable -Force -Name "endpoint$i" -Value (Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontdoorName -EndpointName $associatedEndpoint) - New-Variable -Force -Name "updateAssociation$i" -Value (New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$(Get-Variable -Name "endpoint$i" -ValueOnly).Id})) - $updateAssociations += (Get-Variable -Name "updateAssociation$i" -ValueOnly) - $i++ - } - - $updateWafParameter = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallParametersObject -Association @($updateAssociations) -WafPolicyId $wafPolicyId - $policySecurity = New-AzFrontDoorCdnSecurityPolicy -ResourceGroupName v-abprasadTestRG -ProfileName testFrontdoorCDN -Name Policy -Parameter $updateWafParameter - - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $policyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontdoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - - if ($policySecurity.Name -eq $null) - { - $endpointsSkipped += $frontDoorEndPoint - - } - else - { $frontDoorEndPoint.IsWAFConfigured = $true - $frontDoorEndPoint.WAFPolicyName = $wafPolicyName - $frontDoorEndPoint.WAFPolicyResourceGroup = $policyResourceGroup - $frontDoorEndpointsRemediated += $frontDoorEndPoint - } - } - catch - { - $endpointsSkipped += $frontDoorEndPoint - } - } - - $totalRemediated = ($frontDoorEndpointsRemediated | Measure-Object).Count - - Write-Host $([Constants]::SingleDashLine) - - if ($totalRemediated -eq $totalfrontDoorEndpointsWithWAFPolicyNotConfigured) - { - Write-Host "WAF Policy configured for all [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - else - { - Write-Host "WAF Policy configured for [$totalRemediated] out of [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - - $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, - @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"} - - - - Write-Host "Remediation Summary:" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - - - if ($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) - { - Write-Host "Successfully configured WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndpointsRemediated | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv" - $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation - Write-Host "This information has been saved to [$($frontDoorEndpointsRemediatedFile)]" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - } - - if ($($endpointsSkipped | Measure-Object).Count -gt 0) - { - Write-Host "Error performing remediation steps for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::SingleDashLine) - $endpointsSkipped | Format-Table -Property $colsProperty -Wrap - Write-Host $([Constants]::SingleDashLine) - # Write this to a file. - $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv" - $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation - Write-Host "This information has been saved to [$($endpointsSkippedFile)]" - Write-Host $([Constants]::SingleDashLine) - } - - } - else - { - - Write-Host "[Step 5 of 5] Configuring WAF Policy for Endpoint(s)" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Skipped as -DryRun switch is provided." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - - Write-Host "Next steps:" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host "Run the same command with -FilePath $($backupFile) and without -DryRun, to configure WAF Policy Mode for all Front Door CDN Endpoint(s) listed in the file." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - } -} - -function Remove-WAFPolicyForFrontDoorCDN -{ - <# - .SYNOPSIS - Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - - .DESCRIPTION - Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - Removes configured WAF Policy for all WAF Policies in all Front Door CDN s in the Subscription. - - .PARAMETER SubscriptionId - Specifies the ID of the Subscription that was previously remediated. - - .PARAMETER Force - Specifies a forceful roll back without any prompts. - - .Parameter PerformPreReqCheck - Specifies validation of prerequisites for the command. - - .PARAMETER FilePath - Specifies the path to the file to be used as input for the roll back. - - .INPUTS - None. You cannot pipe objects to Remove-WAFPolicyForFrontDoorCDN. - - .OUTPUTS - None. Remove-WAFPolicyForFrontDoorCDN does not return anything that can be piped and used as an input to another command. - - .EXAMPLE - PS> Remove-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202211190719\ConfigureFrontDoorCDNWAFPolicy\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv - - .LINK - None - #> - - param ( - [String] - [Parameter(Mandatory = $true, HelpMessage="Specifies the ID of the Subscription that was previously remediated.")] - $SubscriptionId, - - [Switch] - [Parameter(HelpMessage="Specifies a forceful roll back without any prompts")] - $Force, - - [Switch] - [Parameter(HelpMessage="Specifies validation of prerequisites for the command")] - $PerformPreReqCheck, - - [String] - [Parameter(Mandatory = $true, HelpMessage="Specifies the path to the file to be used as input for the roll back")] - $FilePath - ) - - Write-Host $([Constants]::DoubleDashLine) - if ($PerformPreReqCheck) - { - try - { - Write-Host "[Step 1 of 4] Validate and install the modules required to run the script and validate the user" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Setup-Prerequisites - Write-Host "Completed setting up prerequisites" - Write-Host $([Constants]::SingleDashLine) - } - catch - { - Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - return - } - } - else - { - Write-Host "[Step 1 of 4] Validate the user" - Write-Host $([Constants]::SingleDashLine) - } - - # Connect to Azure account - $context = Get-AzContext - - if ([String]::IsNullOrWhiteSpace($context)) - { - - Write-Host "Connecting to Azure account..." - Write-Host $([Constants]::SingleDashLine) - Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null - Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - else - { - # Setting up context for the current Subscription. - $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop - } - - Write-Host "Subscription Name: [$($context.Subscription.Name)]" - Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" - Write-Host "Account Name: [$($context.Account.Id)]" - Write-Host "Account Type: [$($context.Account.Type)]" - Write-Host $([Constants]::SingleDashLine) - - # Note about the required access required for remediation - - Write-Host "To remove Configured WAF Policy for all Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Write-Host "[Step 2 of 4] Preparing to fetch all Front Door CDN Endpoints" - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndPoints = @() - $resourceAppIdURI = "https://management.azure.com/" - $apiResponse =@() - $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token - $endPointPolicies = New-Object System.Collections.ArrayList - - if (-not (Test-Path -Path $FilePath)) - { - Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - break - } - - Write-Host "Fetching all Front Door CDN Endpoints from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) - - $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath - $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } - - $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName - - foreach($frontdoor in $uniquefrontDoors) - { - $resourceGroupName = $frontdoor.ResourceGroupName - $frontDoorName = $frontdoor.FrontDoorName - - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) - { - if($null -ne $apiResponse.Content) - { - $content = $apiResponse.Content | ConvertFrom-Json - - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) - { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } - } - } - - - $validfrontDoorEndpointsDetails | ForEach-Object { - $frontdoorEndpointName = $_.EndPointName - $resourceGroupName = $_.ResourceGroupName - $frontDoorName = $_.FrontDoorName - - try - { - $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }} - } - catch - { - Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::SingleDashLine) - Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - } - - - - # Includes Front Door CDN Endpoint(s) where WAF Policy is configured - $frontDoorEndpointsWithWAFPolicyConfigured = @() - - - - Write-Host "[Step 3 of 4] Fetching Endpoint(s) Endpoint(s) where WAF Policy is not configured" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is configured..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndPoints | ForEach-Object { - $endPoint = $_ - if($_.IsWAFConfigured -eq $true) - { - $frontDoorEndpointsWithWAFPolicyConfigured += $endPoint - } - else - { - $logResource = @{} - $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) - $logResource.Add("ResourceName",($_.EndPointName)) - $logResource.Add("Reason","WAF Policy is already configured on Frontdoor CDN Endpoint") - $logSkippedResources += $logResource - - } - } - - $totalfrontDoorEndpointsWithWAFPolicyConfigured = ($frontDoorEndpointsWithWAFPolicyConfigured | Measure-Object).Count - - if ($totalfrontDoorEndpointsWithWAFPolicyConfigured -eq 0) - { - Write-Host "No Front Door CDN Endpoints(s) found where WAF Policy is configured.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - return - } - - - Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyConfigured)] Front Door CDN Endpoints(s) found in file where WAF Policy is configured to Rollback." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - - # Back up snapshots to `%LocalApplicationData%'. - $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\ConfigureFrontDoorCDNWAFPolicy" - - if (-not (Test-Path -Path $backupFolderPath)) - { - New-Item -ItemType Directory -Path $backupFolderPath | Out-Null - } - - if (-not $Force) - { - Write-Host "Do you want remove configured WAF Policy Mode for all Front Door CDN Endpoint(s)?" -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline - Write-Host $([Constants]::SingleDashLine) - $userInput = Read-Host -Prompt "(Y|N)" - Write-Host $([Constants]::SingleDashLine) - if($userInput -ne "Y") - { - Write-Host "Configured WAF Policy will not be removed for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::DoubleDashLine) - break - } - else - { - Write-Host "Configured WAF Policy will be removed for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - } - else - { - Write-Host "'Force' flag is provided. Configured WAF Policy will be removed for all the Front Door CDN Endpoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline - Write-Host $([Constants]::SingleDashLine) - } - - - - - - Write-Host "[Step 4 of 4] Switching WAF Policy mode to Detection for Front Door CDNs Endpoint(s)" - Write-Host $([Constants]::SingleDashLine) - # Includes Front Door CDN s, to which, previously made changes were successfully rolled back. - $frontDoorEndpointsRolledBack = @() - - # Includes Front Door CDN s that were skipped during roll back. There were errors rolling back the changes made previously. - $frontDoorEndpointsSkipped = @() - - - - # Roll back by removing configured WAF Policy on Endpoints - $frontDoorEndpointsWithWAFPolicyConfigured | ForEach-Object { - $frontDoorEndPoint = $_ - $endpointName = $_.EndPointName - $frontdoorName = $_.FrontDoorName - $resourceGroupName = $_.ResourceGroupName - $wafPolicyName = $_.WAFPolicyName - $policyResourceGroup = $_.WAFPolicyResourceGroup - $i = 0 - - $policy = Get-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $policyResourceGroup -ErrorAction SilentlyContinue - $wafPolicyId = $policy.Id - try - { - $updateAssociations = @() - $otherPolicyEndpointsAssociations = @() - - # Remove the endpoint to be rolledback from endpointPolicies List - foreach($policy in $endPointPolicies.Clone()) - { - if($policy.endpointName -eq $endpointName) - { - $endPointPolicies.Remove($policy) - } - - } - - $otherPolicyEndpointsAssociations = $endPointPolicies | where wafPolicyName -eq ($wafPolicyName) - $otherPolicyEndpointsAssociations = $otherPolicyEndpointsAssociations | where frontDoorName -eq $frontdoorName - - $otherPolicyEndpointsAssociations | ForEach-Object { - $association = $_ - $associatedEndpoint = $_.endpointName - - New-Variable -Force -Name "endpoint$i" -Value (Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontdoorName -EndpointName $associatedEndpoint) - New-Variable -Force -Name "updateAssociation$i" -Value (New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$(Get-Variable -Name "endpoint$i" -ValueOnly).Id})) - $updateAssociations += (Get-Variable -Name "updateAssociation$i" -ValueOnly) - $i++ - } - - $updateWafParameter = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallParametersObject -Association @($updateAssociations) -WafPolicyId $wafPolicyId - $policySecurity = New-AzFrontDoorCdnSecurityPolicy -ResourceGroupName v-abprasadTestRG -ProfileName testFrontdoorCDN -Name Policy -Parameter $updateWafParameter - - if ($policySecurity.Name -eq $null) - { - $frontDoorEndpointsSkipped += $frontDoorEndPoint - - } - else - { - $frontDoorEndPoint.IsWAFConfigured = $false - $frontDoorEndPoint.WAFPolicyName = "" - $frontDoorEndPoint.WAFPolicyResourceGroup = "" - $frontDoorEndpointsRolledBack += $frontDoorEndPoint - } - } - catch - { - Write-Host $_ - $frontDoorEndpointsSkipped += $frontDoorEndPoint - } - } - - - $totalfrontDoorEndpointsRolledBack = ($frontDoorEndpointsRolledBack | Measure-Object).Count - - Write-Host $([Constants]::SingleDashLine) - - if ($totalfrontDoorEndpointsRolledBack -eq $totalfrontDoorEndpointsWithWAFPolicyConfigured) - { - Write-Host "Configured WAF Policy removed for all [$($totalfrontDoorEndpointsWithWAFPolicyConfigured)] Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - else - { - Write-Host "Configured WAF Policy removed for [$totalfrontDoorEndpointsRolledBack] out of [$($totalfrontDoorEndpointsWithWAFPolicyConfigured)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - - Write-Host "Rollback Summary:" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, - @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"} - - - if ($($frontDoorEndpointsRolledBack | Measure-Object).Count -gt 0) - { - Write-Host "Successfully removed WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndpointsRolledBack | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $frontDoorEndpointsRolledBackFile = "$($backupFolderPath)\RolledBackFrontDoorCDNEndpointsForConfigureWAFPolicy.csv" - $frontDoorEndpointsRolledBack | Export-CSV -Path $frontDoorEndpointsRolledBackFile -NoTypeInformation - Write-Host "This information has been saved to [$($frontDoorEndpointsRolledBackFile)]" - Write-Host $([Constants]::SingleDashLine) - } - - if ($($frontDoorEndpointsSkipped | Measure-Object).Count -gt 0) - { - Write-Host "Error removing configured WAF Policy for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndpointsSkipped | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $frontDoorEndpointsSkippedFile = "$($backupFolderPath)\RollbackSkippedFrontDoorCDNEndpointsForConfigureWAFPolicy.csv" - $frontDoorEndpointsSkipped | Export-CSV -Path $frontDoorEndpointsSkippedFile -NoTypeInformation - Write-Host "This information has been saved to [$($frontDoorEndpointsSkippedFile)]" - } -} - -# Defines commonly used constants. -class Constants -{ - # Defines commonly used colour codes, corresponding to the severity of the log. - static [Hashtable] $MessageType = @{ - Error = [System.ConsoleColor]::Red - Warning = [System.ConsoleColor]::Yellow - Info = [System.ConsoleColor]::Cyan - Update = [System.ConsoleColor]::Green - Default = [System.ConsoleColor]::White - } - - static [String] $DoubleDashLine = "========================================================================================================================" - static [String] $SingleDashLine = "------------------------------------------------------------------------------------------------------------------------" -} \ No newline at end of file From b13dd531701c2f1e349ce291c2a935f6c38831e3 Mon Sep 17 00:00:00 2001 From: Abhishek P Date: Tue, 20 Dec 2022 18:29:46 +0530 Subject: [PATCH 17/26] Changes --- .../Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 index 0d0198b7..f2e3d8d1 100644 --- a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 +++ b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 @@ -220,7 +220,6 @@ function Enable-WAFPolicyForFrontDoorCDN Write-Host "Account Type: [$($context.Account.Type)]" Write-Host $([Constants]::SingleDashLine) - Write-Host "To enable WAF Policy for Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) Write-Host $([Constants]::SingleDashLine) Write-Host "[Step 2 of 5] Preparing to fetch all Front Door CDNs" From 6402d14b51dd9d89961dd36e486d79da0ce6e811 Mon Sep 17 00:00:00 2001 From: Abhishek P Date: Tue, 20 Dec 2022 18:47:49 +0530 Subject: [PATCH 18/26] Revert "Changes" This reverts commit b13dd531701c2f1e349ce291c2a935f6c38831e3. --- .../Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 index f2e3d8d1..0d0198b7 100644 --- a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 +++ b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 @@ -220,6 +220,7 @@ function Enable-WAFPolicyForFrontDoorCDN Write-Host "Account Type: [$($context.Account.Type)]" Write-Host $([Constants]::SingleDashLine) + Write-Host "To enable WAF Policy for Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) Write-Host $([Constants]::SingleDashLine) Write-Host "[Step 2 of 5] Preparing to fetch all Front Door CDNs" From 04b9a0f62e3b48c370d4cfaf2064feaf100d7b52 Mon Sep 17 00:00:00 2001 From: Abhishek P Date: Tue, 20 Dec 2022 18:55:28 +0530 Subject: [PATCH 19/26] Revert "Changes" This reverts commit b13dd531701c2f1e349ce291c2a935f6c38831e3. From 1597f95ddc3273283ae3a45518def673750bb8fa Mon Sep 17 00:00:00 2001 From: Abhishek P Date: Tue, 20 Dec 2022 19:00:37 +0530 Subject: [PATCH 20/26] rvert --- .../Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 index f2e3d8d1..0d0198b7 100644 --- a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 +++ b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 @@ -220,6 +220,7 @@ function Enable-WAFPolicyForFrontDoorCDN Write-Host "Account Type: [$($context.Account.Type)]" Write-Host $([Constants]::SingleDashLine) + Write-Host "To enable WAF Policy for Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) Write-Host $([Constants]::SingleDashLine) Write-Host "[Step 2 of 5] Preparing to fetch all Front Door CDNs" From c061b13ac1759ede66a126380b26f25eb13bae50 Mon Sep 17 00:00:00 2001 From: Abhishek P Date: Tue, 20 Dec 2022 19:46:35 +0530 Subject: [PATCH 21/26] Revert "Merge branch 'users/abhishek/frontdoorcdnbrs' of https://github.com/azsk/AzTS-docs into users/abhishek/frontdoorcdnbrs" This reverts commit b84cf647c81f077143ede355a0d25e3f14263459, reversing changes made to b13dd531701c2f1e349ce291c2a935f6c38831e3. --- ...cf502096-fac2-41a8-b202-7e8fb911b807.vsidx | Bin 1170568 -> 0 bytes ...fd7d5234-f436-4326-8495-40c7c9b45535.vsidx | Bin 72559 -> 0 bytes .vs/AzTS-docs/FileContentIndex/read.lock | 0 .vs/AzTS-docs/v17/.wsuo | Bin 13824 -> 0 bytes .vs/VSWorkspaceState.json | 6 ------ .vs/slnx.sqlite | Bin 143360 -> 0 bytes 6 files changed, 6 deletions(-) delete mode 100644 .vs/AzTS-docs/FileContentIndex/cf502096-fac2-41a8-b202-7e8fb911b807.vsidx delete mode 100644 .vs/AzTS-docs/FileContentIndex/fd7d5234-f436-4326-8495-40c7c9b45535.vsidx delete mode 100644 .vs/AzTS-docs/FileContentIndex/read.lock delete mode 100644 .vs/AzTS-docs/v17/.wsuo delete mode 100644 .vs/VSWorkspaceState.json delete mode 100644 .vs/slnx.sqlite diff --git a/.vs/AzTS-docs/FileContentIndex/cf502096-fac2-41a8-b202-7e8fb911b807.vsidx b/.vs/AzTS-docs/FileContentIndex/cf502096-fac2-41a8-b202-7e8fb911b807.vsidx deleted file mode 100644 index 687ba09244627a937afcb8722c7c3c0810dc573b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1170568 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 )Hk=_Q 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

}3ler=<u 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]Wo8~I5M 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%0D`+HxSg3BTd32xn~|=oW6@+>DJ*MJ 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 P?DVp;Se*F#g9i9Ncv!Ih diff --git a/.vs/AzTS-docs/FileContentIndex/read.lock b/.vs/AzTS-docs/FileContentIndex/read.lock deleted file mode 100644 index e69de29b..00000000 diff --git a/.vs/AzTS-docs/v17/.wsuo b/.vs/AzTS-docs/v17/.wsuo deleted file mode 100644 index 0a7eae9e2f4d88ce5618c996c48d44ac139949d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13824 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 From 2e9236fa26006e58a2e6a3f119a0f9886bc93cf5 Mon Sep 17 00:00:00 2001 From: Abhishek P Date: Wed, 21 Dec 2022 12:10:50 +0530 Subject: [PATCH 22/26] Revert "Changes" This reverts commit 5b6bbdb3c2185ec7b5c15a115536ca8dde673631. --- ...iate-ConfigureWAFPolicyForFrontDoorCDN.ps1 | 1164 +++++++++++++++++ 1 file changed, 1164 insertions(+) create mode 100644 Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 diff --git a/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 new file mode 100644 index 00000000..489a8dd8 --- /dev/null +++ b/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 @@ -0,0 +1,1164 @@ +<### +# Overview: + This script is used to configure WAF Policy on all endpoints of Front Door CDNs in a Subscription. + +# Control ID: + Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration + +# Display Name: + Front Door should have Web Application Firewall configured + +# Prerequisites: + 1. Contributor or higher privileges on the Front Door CDNs in a Subscription. + 2. Must be connected to Azure with an authenticated account. + +# Steps performed by the script: + To remediate: + 1. Validate and install the modules required to run the script. + 2. Get the list of all Front Door CDNs Endpoints in a Subscription that do not have WAF Policy Configured + 3. Back up details of Front Door CDN Endpoint(s) that are to be remediated. + 4. Configure WAF Policy for all endpoints in the Frontdoors. + + To roll back: + 1. Validate and install the modules required to run the script. + 2. Get the list of Frontdoors' Endpoint(s) in a Subscription, the changes made to which previously, are to be rolled back. + 3. Revert by removing WAF Policies from endpoints in all the Frontdoors. + +# Instructions to execute the script: + To remediate: + 1. Download the script. + 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. + 3. Execute the script to configure WAF Policy on all endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. + + To roll back: + 1. Download the script. + 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. + 3. Execute the script to remove the configured WAF Policy on all endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. + +# Examples: + To remediate: + 1. To review the Front Door CDNs in a Subscription that will be remediated: + Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun + + 2. To configure WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription: + Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck + + 3. To configure WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription, from a previously taken snapshot: + Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\ConfigureFrontDoorCDNWAFPolicy\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv + + To know more about the options supported by the remediation command, execute: + Get-Help Configure-WAFPolicyForFrontDoorCDN -Detailed + + To roll back: + 1. To remove configured WAF Policy Mode for Endpoint(s) all Front Door CDNs in a Subscription, from a previously taken snapshot: + Remove-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\ConfigureFrontDoorCDNWAFPolicy\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv + + To know more about the options supported by the roll back command, execute: + Get-Help Remove-WAFPolicyForFrontDoorCDN -Detailed +###> + +function Setup-Prerequisites +{ + <# + .SYNOPSIS + Checks if the prerequisites are met, else, sets them up. + + .DESCRIPTION + Checks if the prerequisites are met, else, sets them up. + Includes installing any required Azure modules. + + .INPUTS + None. You cannot pipe objects to Setup-Prerequisites. + + .OUTPUTS + None. Setup-Prerequisites does not return anything that can be piped and used as an input to another command. + + .EXAMPLE + PS> Setup-Prerequisites + + .LINK + None + #> + + # List of required modules + $requiredModules = @("Az.Accounts", "Az.FrontDoor", "Az.Cdn") + + Write-Host "Required modules: $($requiredModules -join ', ')" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Checking if the required modules are present..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + + $availableModules = $(Get-Module -ListAvailable $requiredModules -ErrorAction Stop) + + # Check if the required modules are installed. + $requiredModules | ForEach-Object { + if ($availableModules.Name -notcontains $_) + { + Write-Host "[$($_)] module is not present." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host "Installing [$($_)] module..." -ForegroundColor $([Constants]::MessageType.Info) + Install-Module -Name $_ -Scope CurrentUser -Repository 'PSGallery' -ErrorAction Stop + Write-Host "[$($_)] module installed." -ForegroundColor $([Constants]::MessageType.Update) + } + else + { + Write-Host "[$($_)] module is present." -ForegroundColor $([Constants]::MessageType.Update) + } + } +} + +function Configure-WAFPolicyForFrontDoorCDN +{ + <# + .SYNOPSIS + Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. + + .DESCRIPTION + Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. + WAF Policy must be configured for Front Door CDN Endpoint(s). + + .PARAMETER SubscriptionId + Specifies the ID of the Subscription to be remediated. + + .PARAMETER Force + Specifies a forceful remediation without any prompts. + + .Parameter PerformPreReqCheck + Specifies validation of prerequisites for the command. + + .PARAMETER DryRun + Specifies a dry run of the actual remediation. + + .PARAMETER FilePath + Specifies the path to the file to be used as input for the remediation. + + .INPUTS + None. You cannot pipe objects to Enable-WAFPolicyForFrontDoors. + + .OUTPUTS + None. Configure-WAFPolicyForFrontDoorCDN does not return anything that can be piped and used as an input to another command. + + .EXAMPLE + PS> Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun + + .EXAMPLE + PS> Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck + + .EXAMPLE + PS> Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202201011212\ConfigureFrontDoorCDNWAFPolicy\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv + + .LINK + None + #> + + param ( + [String] + [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] + [Parameter(ParameterSetName = "WetRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] + $SubscriptionId, + + [Switch] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies a forceful remediation without any prompts")] + $Force, + + [Switch] + [Parameter(ParameterSetName = "DryRun", HelpMessage="Specifies validation of prerequisites for the command")] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies validation of prerequisites for the command")] + $PerformPreReqCheck, + + [Switch] + [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies a dry run of the actual remediation")] + $DryRun, + + [String] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation")] + $FilePath + ) + + Write-Host $([Constants]::DoubleDashLine) + if ($PerformPreReqCheck) + { + try + { + Write-Host "[Step 1 of 5] Validate and install the modules required to run the script and validate the user" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Setup-Prerequisites + Write-Host "Completed setting up prerequisites." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + catch + { + Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + return + } + } + else + { + Write-Host "[Step 1 of 5] Validate the user" + Write-Host $([Constants]::SingleDashLine) + } + + # Connect to Azure account + $context = Get-AzContext + + if ([String]::IsNullOrWhiteSpace($context)) + { + Write-Host "Connecting to Azure account..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null + Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + # Setting up context for the current Subscription. + $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop + + Write-Host "Subscription Name: [$($context.Subscription.Name)]" + Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" + Write-Host "Account Name: [$($context.Account.Id)]" + Write-Host "Account Type: [$($context.Account.Type)]" + Write-Host $([Constants]::SingleDashLine) + + + Write-Host " To configure WAF Policy for Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Write-Host "[Step 2 of 5] Preparing to fetch all Front Door CDNs" + Write-Host $([Constants]::SingleDashLine) + + $frontDoorCDNs = @() + $frontDoorEndPoints = @() + $resourceAppIdURI = "https://management.azure.com/" + $apiResponse =@() + $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token + $endPointPolicies = New-Object System.Collections.ArrayList + + # To keep track of remediated and skipped resources + $logRemediatedResources = @() + $logSkippedResources=@() + + # Control Id + $controlIds = "Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration" + + + # No file path provided as input to the script. Fetch all Front Door CDNs in the Subscription. + if ([String]::IsNullOrWhiteSpace($FilePath)) + { + Write-Host "Fetching all Front Door CDNs in Subscription: [$($context.Subscription.SubscriptionId)]..." -ForegroundColor $([Constants]::MessageType.Info) + + # Get all Front Door CDNs in the Subscription + $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction SilentlyContinue + $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count + + if($totalfrontDoors -gt 0) + { + $frontDoorCDNs | ForEach-Object { + $frontDoor = $_ + $frontDoorId = $_.Id + $resourceGroupName = $_.Id.Split('/')[4] + $frontDoorName = $_.Name + + if($null -ne $classicAccessToken) + { + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + { + if($null -ne $apiResponse.Content) + { + $content = $apiResponse.Content | ConvertFrom-Json + + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) + { + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) + { + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) + { + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } + } + } + + + # Get all Endpoint(s) for this Front Door CDN. + $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }} + } + } + } + else + { + if (-not (Test-Path -Path $FilePath)) + { + Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + break + } + + Write-Host "Fetching all Front Door CDN Endpoint(s) from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) + + $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath + $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } + + $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName + + foreach($frontdoor in $uniquefrontDoors) + { + $resourceGroupName = $frontdoor.ResourceGroupName + $frontDoorName = $frontdoor.FrontDoorName + + if($null -ne $classicAccessToken) + { + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + { + if($null -ne $apiResponse.Content) + { + $content = $apiResponse.Content | ConvertFrom-Json + + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) + { + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) + { + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) + { + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } + } + } + } + + $validfrontDoorEndpointsDetails | ForEach-Object { + $frontdoorEndpointName = $_.EndPointName + $resourceGroupName = $_.ResourceGroupName + $frontDoorName = $_.FrontDoorName + + try + { + $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }} + } + catch + { + Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) + } + } + } + + + + + if ([String]::IsNullOrWhiteSpace($FilePath)) + { + $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count + + if ($totalfrontDoors -eq 0) + { + Write-Host "No Front Door CDNs found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::DoubleDashLine) + break + } + + Write-Host "Found [$($totalfrontDoors)] Front Door CDN(s)." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + + + + $totalfrontDoorEndPoints = ($frontDoorEndPoints | Measure-Object).Count + + if ($totalfrontDoorEndPoints -eq 0) + { + Write-Host "No Front Door CDN Endpoint(s) having WAF Policy not configured. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::DoubleDashLine) + break + } + + Write-Host "Found [$($totalfrontDoorEndPoints)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + + # Includes Front Door CDN Endpoint(s) where WAF Policy is not configured + $frontDoorEndpointsWithWAFPolicyNotConfigured = @() + + # Includes Front Door CDN Endpoint(s) that were skipped during remediation. There were errors remediating them. + $frontDoorEndpointsSkipped = @() + + + + Write-Host "[Step 3 of 5] Fetching Endpoint(s) for which WAF Policy is not configured" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is not configured..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + $frontDoorEndPoints | ForEach-Object { + $endPoint = $_ + if($_.IsWAFConfigured -eq $false) + { + $frontDoorEndpointsWithWAFPolicyNotConfigured += $endPoint + } + else + { + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.FrontDoorName)) + $logResource.Add("EndPointName",($_.EndPointName)) + $logResource.Add("Reason","WAF Policy already Configured on endpoint") + $logSkippedResources += $logResource + + } + } + + $totalfrontDoorEndpointsWithWAFPolicyNotConfigured = ($frontDoorEndpointsWithWAFPolicyNotConfigured | Measure-Object).Count + + if ($totalfrontDoorEndpointsWithWAFPolicyNotConfigured -eq 0) + { + Write-Host "No Front Door CDN endpoints(s) found where WAF Policy is not configured. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + return + } + + Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door CDN Endpoints(s) found where WAF Policy is not configured ." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + + + Write-Host "Following Front Door CDN Endpoints(s) are having WAF Policies not configured:" -ForegroundColor $([Constants]::MessageType.Info) + $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, + @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, + @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"} + + $frontDoorEndpointsWithWAFPolicyNotConfigured | Format-Table -Property $colsProperty -Wrap + + # Back up snapshots to `%LocalApplicationData%'. + $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\ConfigureFrontDoorCDNWAFPolicy" + + if (-not (Test-Path -Path $backupFolderPath)) + { + New-Item -ItemType Directory -Path $backupFolderPath | Out-Null + } + + + Write-Host "[Step 4 of 5] Backing up Front Door CDN Endpoint(s) details" + Write-Host $([Constants]::SingleDashLine) + if ([String]::IsNullOrWhiteSpace($FilePath)) + { + + # Backing up Front Door CDN Endpoints details. + $backupFile = "$($backupFolderPath)\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv" + $frontDoorEndpointsWithWAFPolicyNotConfigured | Export-CSV -Path $backupFile -NoTypeInformation + Write-Host "Front Door Endpoint(s) details have been successful backed up to [$($backupFolderPath)]" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + else + { + Write-Host "Skipped as -FilePath is provided" -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + + if (-not $DryRun) + { + + Write-Host "WAF Policy will be configured for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Warning) + + if (-not $Force) + { + Write-Host "Do you want to configure WAF Policy associated with Front Door CDN Endpoint(s)? " -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline + Write-Host $([Constants]::SingleDashLine) + $userInput = Read-Host -Prompt "(Y|N)" + Write-Host $([Constants]::SingleDashLine) + if($userInput -ne "Y") + { + Write-Host "WAF Policy will not be configured for any Front Door CDN Endpoint(s). Exiting." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::DoubleDashLine) + break + } + else + { + Write-Host "WAF Policy will be configured for all Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + } + else + { + Write-Host "'Force' flag is provided. WAF Policy will be configured on all Front Door CDN Endoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + + + + Write-Host "[Step 5 of 5] Configuring WAF Policy for Front Door CDN Endpoint(s)" + Write-Host $([Constants]::SingleDashLine) + # To hold results from the remediation. + $frontDoorEndpointsRemediated = @() + $endpointsSkipped = @() + $otherPolicyEndpointsAssociations = @() + + + # Remidiate Controls by configuring WAF Policy + $frontDoorEndpointsWithWAFPolicyNotConfigured | ForEach-Object { + $frontDoorEndPoint = $_ + $endpointName = $_.EndPointName + $frontdoorName = $_.FrontDoorName + $resourceGroupName = $_.ResourceGroupName + $i= 0 + + try + { + Do + { + $wafPolicyName = Read-Host -Prompt "Enter WAF Policy Name for Endpoint: [$($_.EndPointName)] of Frontdoor [$($frontdoorName)] " + $policyResourceGroup = Read-Host -Prompt "Enter WAF Policy Resource Group Name for Endpoint: [$($_.EndPointName)] of Frontdoor [$($frontdoorName)] " + $policy = Get-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $policyResourceGroup -ErrorAction SilentlyContinue + + if($policy -eq $null) + { + Write-Host "WAF Policy name or WAF Policy Resource Group Name is not correct. Please enter correct details." + Write-Host $([Constants]::SingleDashLine) + } + + if($policy -ne $null -and $policy.Sku -ne 'Standard_AzureFrontDoor') + { + Write-Host "WAF Policy is not of type Front door tier Standard . Please enter correct details." + Write-Host $([Constants]::SingleDashLine) + } + + } + while($policy.Sku -ne 'Standard_AzureFrontDoor' -or $policy -eq $null) + $wafPolicyId = $policy.Id + + $endpoint = Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontdoorName -EndpointName $endpointName + $updateAssociation = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$($endpoint.Id)}) + $updateAssociations = @() + $updateAssociations += $updateAssociation + $otherPolicyEndpointsAssociations = $endPointPolicies | where wafPolicyName -eq ($wafPolicyName) + $otherPolicyEndpointsAssociations = $otherPolicyEndpointsAssociations | where frontDoorName -eq $frontdoorName + + $otherPolicyEndpointsAssociations | ForEach-Object { + $association = $_ + $associatedEndpoint = $_.endpointName + + New-Variable -Force -Name "endpoint$i" -Value (Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontdoorName -EndpointName $associatedEndpoint) + New-Variable -Force -Name "updateAssociation$i" -Value (New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$(Get-Variable -Name "endpoint$i" -ValueOnly).Id})) + $updateAssociations += (Get-Variable -Name "updateAssociation$i" -ValueOnly) + $i++ + } + + $updateWafParameter = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallParametersObject -Association @($updateAssociations) -WafPolicyId $wafPolicyId + $policySecurity = New-AzFrontDoorCdnSecurityPolicy -ResourceGroupName v-abprasadTestRG -ProfileName testFrontdoorCDN -Name Policy -Parameter $updateWafParameter + + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $policyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontdoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + + if ($policySecurity.Name -eq $null) + { + $endpointsSkipped += $frontDoorEndPoint + + } + else + { $frontDoorEndPoint.IsWAFConfigured = $true + $frontDoorEndPoint.WAFPolicyName = $wafPolicyName + $frontDoorEndPoint.WAFPolicyResourceGroup = $policyResourceGroup + $frontDoorEndpointsRemediated += $frontDoorEndPoint + } + } + catch + { + $endpointsSkipped += $frontDoorEndPoint + } + } + + $totalRemediated = ($frontDoorEndpointsRemediated | Measure-Object).Count + + Write-Host $([Constants]::SingleDashLine) + + if ($totalRemediated -eq $totalfrontDoorEndpointsWithWAFPolicyNotConfigured) + { + Write-Host "WAF Policy configured for all [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + else + { + Write-Host "WAF Policy configured for [$totalRemediated] out of [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + + $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, + @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, + @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"} + + + + Write-Host "Remediation Summary:" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + + + if ($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) + { + Write-Host "Successfully configured WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + $frontDoorEndpointsRemediated | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv" + $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation + Write-Host "This information has been saved to [$($frontDoorEndpointsRemediatedFile)]" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + } + + if ($($endpointsSkipped | Measure-Object).Count -gt 0) + { + Write-Host "Error performing remediation steps for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::SingleDashLine) + $endpointsSkipped | Format-Table -Property $colsProperty -Wrap + Write-Host $([Constants]::SingleDashLine) + # Write this to a file. + $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv" + $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation + Write-Host "This information has been saved to [$($endpointsSkippedFile)]" + Write-Host $([Constants]::SingleDashLine) + } + + } + else + { + + Write-Host "[Step 5 of 5] Configuring WAF Policy for Endpoint(s)" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Skipped as -DryRun switch is provided." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + + Write-Host "Next steps:" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host "Run the same command with -FilePath $($backupFile) and without -DryRun, to configure WAF Policy Mode for all Front Door CDN Endpoint(s) listed in the file." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + } +} + +function Remove-WAFPolicyForFrontDoorCDN +{ + <# + .SYNOPSIS + Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. + + .DESCRIPTION + Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. + Removes configured WAF Policy for all WAF Policies in all Front Door CDN s in the Subscription. + + .PARAMETER SubscriptionId + Specifies the ID of the Subscription that was previously remediated. + + .PARAMETER Force + Specifies a forceful roll back without any prompts. + + .Parameter PerformPreReqCheck + Specifies validation of prerequisites for the command. + + .PARAMETER FilePath + Specifies the path to the file to be used as input for the roll back. + + .INPUTS + None. You cannot pipe objects to Remove-WAFPolicyForFrontDoorCDN. + + .OUTPUTS + None. Remove-WAFPolicyForFrontDoorCDN does not return anything that can be piped and used as an input to another command. + + .EXAMPLE + PS> Remove-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202211190719\ConfigureFrontDoorCDNWAFPolicy\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv + + .LINK + None + #> + + param ( + [String] + [Parameter(Mandatory = $true, HelpMessage="Specifies the ID of the Subscription that was previously remediated.")] + $SubscriptionId, + + [Switch] + [Parameter(HelpMessage="Specifies a forceful roll back without any prompts")] + $Force, + + [Switch] + [Parameter(HelpMessage="Specifies validation of prerequisites for the command")] + $PerformPreReqCheck, + + [String] + [Parameter(Mandatory = $true, HelpMessage="Specifies the path to the file to be used as input for the roll back")] + $FilePath + ) + + Write-Host $([Constants]::DoubleDashLine) + if ($PerformPreReqCheck) + { + try + { + Write-Host "[Step 1 of 4] Validate and install the modules required to run the script and validate the user" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Setup-Prerequisites + Write-Host "Completed setting up prerequisites" + Write-Host $([Constants]::SingleDashLine) + } + catch + { + Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + return + } + } + else + { + Write-Host "[Step 1 of 4] Validate the user" + Write-Host $([Constants]::SingleDashLine) + } + + # Connect to Azure account + $context = Get-AzContext + + if ([String]::IsNullOrWhiteSpace($context)) + { + + Write-Host "Connecting to Azure account..." + Write-Host $([Constants]::SingleDashLine) + Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null + Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + else + { + # Setting up context for the current Subscription. + $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop + } + + Write-Host "Subscription Name: [$($context.Subscription.Name)]" + Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" + Write-Host "Account Name: [$($context.Account.Id)]" + Write-Host "Account Type: [$($context.Account.Type)]" + Write-Host $([Constants]::SingleDashLine) + + # Note about the required access required for remediation + + Write-Host "To remove Configured WAF Policy for all Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Write-Host "[Step 2 of 4] Preparing to fetch all Front Door CDN Endpoints" + Write-Host $([Constants]::SingleDashLine) + $frontDoorEndPoints = @() + $resourceAppIdURI = "https://management.azure.com/" + $apiResponse =@() + $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token + $endPointPolicies = New-Object System.Collections.ArrayList + + if (-not (Test-Path -Path $FilePath)) + { + Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + break + } + + Write-Host "Fetching all Front Door CDN Endpoints from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) + + $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath + $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } + + $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName + + foreach($frontdoor in $uniquefrontDoors) + { + $resourceGroupName = $frontdoor.ResourceGroupName + $frontDoorName = $frontdoor.FrontDoorName + + if($null -ne $classicAccessToken) + { + $header = "Bearer " + $classicAccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} + $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) + $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing + + if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) + { + if($null -ne $apiResponse.Content) + { + $content = $apiResponse.Content | ConvertFrom-Json + + $value = $content.value + $totalValues = ($value | Measure-Object).Count + for($i=0; $i -lt $totalValues; $i++) + { + $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id + $wafPolicyName = $wafPolicyId.Split('/')[8] + $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] + $associations = $value[$i].properties.parameters.associations + $totalAssociations = ($associations | Measure-Object).Count + for($j=0; $j -lt $totalAssociations; $j++) + { + $association = $associations[$j] + $domains = $association.domains + $totalDomains = ($domains | Measure-Object).Count + for($k=0; $k -lt $totalDomains; $k++) + { + $domain = $domains[$k] + $id = $domain.id + $endpointName = $id.Split('/')[10] + $EndpointPolicy = New-Object System.Object + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId + $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName + $endPointPolicies.Add($EndpointPolicy) | Out-Null + } + } + } + } + } + } + } + + + $validfrontDoorEndpointsDetails | ForEach-Object { + $frontdoorEndpointName = $_.EndPointName + $resourceGroupName = $_.ResourceGroupName + $frontDoorName = $_.FrontDoorName + + try + { + $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, + @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, + @{N='IsWAFConfigured';E={ + if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) + { + $false + } + else + { + $true + } + }} + } + catch + { + Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::SingleDashLine) + Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + } + + + + # Includes Front Door CDN Endpoint(s) where WAF Policy is configured + $frontDoorEndpointsWithWAFPolicyConfigured = @() + + + + Write-Host "[Step 3 of 4] Fetching Endpoint(s) Endpoint(s) where WAF Policy is not configured" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is configured..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + $frontDoorEndPoints | ForEach-Object { + $endPoint = $_ + if($_.IsWAFConfigured -eq $true) + { + $frontDoorEndpointsWithWAFPolicyConfigured += $endPoint + } + else + { + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.EndPointName)) + $logResource.Add("Reason","WAF Policy is already configured on Frontdoor CDN Endpoint") + $logSkippedResources += $logResource + + } + } + + $totalfrontDoorEndpointsWithWAFPolicyConfigured = ($frontDoorEndpointsWithWAFPolicyConfigured | Measure-Object).Count + + if ($totalfrontDoorEndpointsWithWAFPolicyConfigured -eq 0) + { + Write-Host "No Front Door CDN Endpoints(s) found where WAF Policy is configured.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + return + } + + + Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyConfigured)] Front Door CDN Endpoints(s) found in file where WAF Policy is configured to Rollback." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + + # Back up snapshots to `%LocalApplicationData%'. + $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\ConfigureFrontDoorCDNWAFPolicy" + + if (-not (Test-Path -Path $backupFolderPath)) + { + New-Item -ItemType Directory -Path $backupFolderPath | Out-Null + } + + if (-not $Force) + { + Write-Host "Do you want remove configured WAF Policy Mode for all Front Door CDN Endpoint(s)?" -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline + Write-Host $([Constants]::SingleDashLine) + $userInput = Read-Host -Prompt "(Y|N)" + Write-Host $([Constants]::SingleDashLine) + if($userInput -ne "Y") + { + Write-Host "Configured WAF Policy will not be removed for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::DoubleDashLine) + break + } + else + { + Write-Host "Configured WAF Policy will be removed for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + } + else + { + Write-Host "'Force' flag is provided. Configured WAF Policy will be removed for all the Front Door CDN Endpoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline + Write-Host $([Constants]::SingleDashLine) + } + + + + + + Write-Host "[Step 4 of 4] Switching WAF Policy mode to Detection for Front Door CDNs Endpoint(s)" + Write-Host $([Constants]::SingleDashLine) + # Includes Front Door CDN s, to which, previously made changes were successfully rolled back. + $frontDoorEndpointsRolledBack = @() + + # Includes Front Door CDN s that were skipped during roll back. There were errors rolling back the changes made previously. + $frontDoorEndpointsSkipped = @() + + + + # Roll back by removing configured WAF Policy on Endpoints + $frontDoorEndpointsWithWAFPolicyConfigured | ForEach-Object { + $frontDoorEndPoint = $_ + $endpointName = $_.EndPointName + $frontdoorName = $_.FrontDoorName + $resourceGroupName = $_.ResourceGroupName + $wafPolicyName = $_.WAFPolicyName + $policyResourceGroup = $_.WAFPolicyResourceGroup + $i = 0 + + $policy = Get-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $policyResourceGroup -ErrorAction SilentlyContinue + $wafPolicyId = $policy.Id + try + { + $updateAssociations = @() + $otherPolicyEndpointsAssociations = @() + + # Remove the endpoint to be rolledback from endpointPolicies List + foreach($policy in $endPointPolicies.Clone()) + { + if($policy.endpointName -eq $endpointName) + { + $endPointPolicies.Remove($policy) + } + + } + + $otherPolicyEndpointsAssociations = $endPointPolicies | where wafPolicyName -eq ($wafPolicyName) + $otherPolicyEndpointsAssociations = $otherPolicyEndpointsAssociations | where frontDoorName -eq $frontdoorName + + $otherPolicyEndpointsAssociations | ForEach-Object { + $association = $_ + $associatedEndpoint = $_.endpointName + + New-Variable -Force -Name "endpoint$i" -Value (Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontdoorName -EndpointName $associatedEndpoint) + New-Variable -Force -Name "updateAssociation$i" -Value (New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$(Get-Variable -Name "endpoint$i" -ValueOnly).Id})) + $updateAssociations += (Get-Variable -Name "updateAssociation$i" -ValueOnly) + $i++ + } + + $updateWafParameter = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallParametersObject -Association @($updateAssociations) -WafPolicyId $wafPolicyId + $policySecurity = New-AzFrontDoorCdnSecurityPolicy -ResourceGroupName v-abprasadTestRG -ProfileName testFrontdoorCDN -Name Policy -Parameter $updateWafParameter + + if ($policySecurity.Name -eq $null) + { + $frontDoorEndpointsSkipped += $frontDoorEndPoint + + } + else + { + $frontDoorEndPoint.IsWAFConfigured = $false + $frontDoorEndPoint.WAFPolicyName = "" + $frontDoorEndPoint.WAFPolicyResourceGroup = "" + $frontDoorEndpointsRolledBack += $frontDoorEndPoint + } + } + catch + { + Write-Host $_ + $frontDoorEndpointsSkipped += $frontDoorEndPoint + } + } + + + $totalfrontDoorEndpointsRolledBack = ($frontDoorEndpointsRolledBack | Measure-Object).Count + + Write-Host $([Constants]::SingleDashLine) + + if ($totalfrontDoorEndpointsRolledBack -eq $totalfrontDoorEndpointsWithWAFPolicyConfigured) + { + Write-Host "Configured WAF Policy removed for all [$($totalfrontDoorEndpointsWithWAFPolicyConfigured)] Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + else + { + Write-Host "Configured WAF Policy removed for [$totalfrontDoorEndpointsRolledBack] out of [$($totalfrontDoorEndpointsWithWAFPolicyConfigured)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + + Write-Host "Rollback Summary:" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, + @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, + @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, + @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, + @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"} + + + if ($($frontDoorEndpointsRolledBack | Measure-Object).Count -gt 0) + { + Write-Host "Successfully removed WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + $frontDoorEndpointsRolledBack | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $frontDoorEndpointsRolledBackFile = "$($backupFolderPath)\RolledBackFrontDoorCDNEndpointsForConfigureWAFPolicy.csv" + $frontDoorEndpointsRolledBack | Export-CSV -Path $frontDoorEndpointsRolledBackFile -NoTypeInformation + Write-Host "This information has been saved to [$($frontDoorEndpointsRolledBackFile)]" + Write-Host $([Constants]::SingleDashLine) + } + + if ($($frontDoorEndpointsSkipped | Measure-Object).Count -gt 0) + { + Write-Host "Error removing configured WAF Policy for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::SingleDashLine) + $frontDoorEndpointsSkipped | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $frontDoorEndpointsSkippedFile = "$($backupFolderPath)\RollbackSkippedFrontDoorCDNEndpointsForConfigureWAFPolicy.csv" + $frontDoorEndpointsSkipped | Export-CSV -Path $frontDoorEndpointsSkippedFile -NoTypeInformation + Write-Host "This information has been saved to [$($frontDoorEndpointsSkippedFile)]" + } +} + +# Defines commonly used constants. +class Constants +{ + # Defines commonly used colour codes, corresponding to the severity of the log. + static [Hashtable] $MessageType = @{ + Error = [System.ConsoleColor]::Red + Warning = [System.ConsoleColor]::Yellow + Info = [System.ConsoleColor]::Cyan + Update = [System.ConsoleColor]::Green + Default = [System.ConsoleColor]::White + } + + static [String] $DoubleDashLine = "========================================================================================================================" + static [String] $SingleDashLine = "------------------------------------------------------------------------------------------------------------------------" +} \ No newline at end of file From 772bd17a36ae4ac368ef1164df0eb9a0b4080667 Mon Sep 17 00:00:00 2001 From: Abhishek Prasad Date: Wed, 21 Dec 2022 12:13:52 +0530 Subject: [PATCH 23/26] Delete Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 --- ...mediate-EnableWAFPolicyForFrontDoorCDN.ps1 | 1157 ----------------- 1 file changed, 1157 deletions(-) delete mode 100644 Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 diff --git a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 deleted file mode 100644 index 0d0198b7..00000000 --- a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 +++ /dev/null @@ -1,1157 +0,0 @@ -<### -# Overview: - This script is used to enable state of the WAF Policy configured on endpoints of Front Door CDNs in a Subscription. - -# Control ID: - Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration - -# Display Name: - Front Door should have Web Application Firewall configured - -# Prerequisites: - 1. Contributor or higher privileges on the Front Door CDNs in a Subscription. - 2. Must be connected to Azure with an authenticated account. - -# Steps performed by the script: - To remediate: - 1. Validate and install the modules required to run the script. - 2. Get the list of all Front Door CDNs Endpoints in a Subscription that do not have WAF Policy Enabled - 3. Back up details of Front Door CDN Endpoint(s) that are to be remediated. - 4. Enable Policy for all endpoints in the Front Doors. - - To roll back: - 1. Validate and install the modules required to run the script. - 2. Get the list of Front Doors' Endpoint(s) in a Subscription, the changes made to which previously, are to be rolled back. - 3. Revert back Policy State to Disabled for all endpoints in all the Front Doors. - -# Instructions to execute the script: - To remediate: - 1. Download the script. - 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. - 3. Execute the script to enable WAF Policy on All endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. - - To roll back: - 1. Download the script. - 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. - 3. Execute the script to disable WAF Policy on All endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. - -# Examples: - To remediate: - 1. To review the Front Door CDNs in a Subscription that will be remediated: - Enable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun - - 2. To Enable WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription: - Enable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck - - 3. To Enable WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription, from a previously taken snapshot: - Enable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\EnableFrontDoorCDNWAFPolicy\frontdoorCDNEndpointsWithoutPolicyEnabled.csv - - To know more about the options supported by the remediation command, execute: - Get-Help Enable-WAFPolicyForFrontDoorCDN -Detailed - - To roll back: - 1. To Disable WAF Policy for Endpoint(s) all Front Door CDNs in a Subscription, from a previously taken snapshot: - Disable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\EnableFrontDoorCDNWAFPolicy\RemediatedfrontDoorCDNEndpointsForEnabledState.csv - - To know more about the options supported by the roll back command, execute: - Get-Help Disable-WAFPolicyForFrontDoorCDN -Detailed -###> - -function Setup-Prerequisites -{ - <# - .SYNOPSIS - Checks if the prerequisites are met, else, sets them up. - - .DESCRIPTION - Checks if the prerequisites are met, else, sets them up. - Includes installing any required Azure modules. - - .INPUTS - None. You cannot pipe objects to Setup-Prerequisites. - - .OUTPUTS - None. Setup-Prerequisites does not return anything that can be piped and used as an input to another command. - - .EXAMPLE - PS> Setup-Prerequisites - - .LINK - None - #> - - # List of required modules - $requiredModules = @("Az.Accounts", "Az.FrontDoor", "Az.Cdn") - - Write-Host "Required modules: $($requiredModules -join ', ')" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Checking if the required modules are present..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - - $availableModules = $(Get-Module -ListAvailable $requiredModules -ErrorAction Stop) - - # Check if the required modules are installed. - $requiredModules | ForEach-Object { - if ($availableModules.Name -notcontains $_) - { - Write-Host "[$($_)] module is not present." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host "Installing [$($_)] module..." -ForegroundColor $([Constants]::MessageType.Info) - Install-Module -Name $_ -Scope CurrentUser -Repository 'PSGallery' -ErrorAction Stop - Write-Host "[$($_)] module installed." -ForegroundColor $([Constants]::MessageType.Update) - } - else - { - Write-Host "[$($_)] module is present." -ForegroundColor $([Constants]::MessageType.Update) - } - } -} - -function Enable-WAFPolicyForFrontDoorCDN -{ - <# - .SYNOPSIS - Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - - .DESCRIPTION - Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - WAF Policy must be Enabled for Front Door CDN Endpoint(s). - - .PARAMETER SubscriptionId - Specifies the ID of the Subscription to be remediated. - - .PARAMETER Force - Specifies a forceful remediation without any prompts. - - .Parameter PerformPreReqCheck - Specifies validation of prerequisites for the command. - - .PARAMETER DryRun - Specifies a dry run of the actual remediation. - - .PARAMETER FilePath - Specifies the path to the file to be used as input for the remediation. - - .INPUTS - None. You cannot pipe objects to Enable-WAFPolicyForFrontDoors. - - .OUTPUTS - None. Enable-WAFPolicyForFrontDoorCDN does not return anything that can be piped and used as an input to another command. - - .EXAMPLE - PS> Enable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun - - .EXAMPLE - PS> Enable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck - - .EXAMPLE - PS> Enable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202201011212\EnableFrontDoorCDNWAFPolicy\frontdoorCDNEndpointsWithoutPolicyEnabled.csv - - .LINK - None - #> - - param ( - [String] - [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] - [Parameter(ParameterSetName = "WetRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] - $SubscriptionId, - - [Switch] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies a forceful remediation without any prompts")] - $Force, - - [Switch] - [Parameter(ParameterSetName = "DryRun", HelpMessage="Specifies validation of prerequisites for the command")] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies validation of prerequisites for the command")] - $PerformPreReqCheck, - - [Switch] - [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies a dry run of the actual remediation")] - $DryRun, - - [String] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation")] - $FilePath - ) - - Write-Host $([Constants]::DoubleDashLine) - if ($PerformPreReqCheck) - { - try - { - Write-Host "[Step 1 of 5] Validate and install the modules required to run the script and validate the user" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Setup-Prerequisites - Write-Host "Completed setting up prerequisites." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - catch - { - Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - return - } - } - else - { - Write-Host "[Step 1 of 5] Validate the user" - Write-Host $([Constants]::SingleDashLine) - } - - # Connect to Azure account - $context = Get-AzContext - - if ([String]::IsNullOrWhiteSpace($context)) - { - Write-Host "Connecting to Azure account..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null - Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - # Setting up context for the current Subscription. - $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop - - Write-Host "Subscription Name: [$($context.Subscription.Name)]" - Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" - Write-Host "Account Name: [$($context.Account.Id)]" - Write-Host "Account Type: [$($context.Account.Type)]" - Write-Host $([Constants]::SingleDashLine) - - - Write-Host "To enable WAF Policy for Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Write-Host "[Step 2 of 5] Preparing to fetch all Front Door CDNs" - Write-Host $([Constants]::SingleDashLine) - $frontDoorCDNs = @() - $frontDoorEndPoints = @() - $resourceAppIdURI = "https://management.azure.com/" - $apiResponse =@() - $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token - $endPointPolicies = New-Object System.Collections.ArrayList - - # To keep track of remediated and skipped resources - $logRemediatedResources = @() - $logSkippedResources=@() - - # Control Id - $controlIds = "Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration" - - - # No file path provided as input to the script. Fetch all Front Door CDNs in the Subscription. - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - Write-Host "Fetching all Front Door CDNs in Subscription: [$($context.Subscription.SubscriptionId)]..." -ForegroundColor $([Constants]::MessageType.Info) - - # Get all Front Door CDNs in the Subscription - $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction SilentlyContinue - $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - - if($totalfrontDoors -gt 0) - { - $frontDoorCDNs | ForEach-Object { - $frontDoor = $_ - $frontDoorId = $_.Id - $resourceGroupName = $_.Id.Split('/')[4] - $frontDoorName = $_.Name - - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) - { - if($null -ne $apiResponse.Content) - { - $content = $apiResponse.Content | ConvertFrom-Json - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) - { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } - } - - - # Get all Endpoint(s) for this Front Door CDN. - $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }}, - @{N='IsPreventionMode';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - if($WAFPolicy.PolicyMode -eq 'Prevention') - { - $true - } - else - { - $false - - } - } - }}, - @{N='IsWAFPolicyStateEnabled';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - - if($WAFPolicy.PolicyEnabledState -eq 'Enabled') - { - $true - } - else - { - $false - } - } - }} - - } - } - } - else - { - if (-not (Test-Path -Path $FilePath)) - { - Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) - break - } - - Write-Host "Fetching all Front Door CDN Endpoint(s) from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) - - $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath - $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } - - $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName - - foreach($frontdoor in $uniquefrontDoors) - { - $resourceGroupName = $frontdoor.ResourceGroupName - $frontDoorName = $frontdoor.FrontDoorName - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) - { - if($null -ne $apiResponse.Content) - { - $content = $apiResponse.Content | ConvertFrom-Json - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) - { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } - } - - } - - $validfrontDoorEndpointsDetails | ForEach-Object { - $frontdoorEndpointName = $_.EndPointName - $resourceGroupName = $_.ResourceGroupName - $frontDoorName = $_.FrontDoorName - - try - { - - - $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }}, - @{N='IsPreventionMode';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - if($WAFPolicy.PolicyMode -eq 'Prevention') - { - $true - } - else - { - $false - - } - } - }}, - @{N='IsWAFPolicyStateEnabled';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - - if($WAFPolicy.PolicyEnabledState -eq 'Enabled') - { - $true - } - else - { - $false - } - } - }} - } - catch - { - Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) - } - } - } - - - - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - - if ($totalfrontDoors -eq 0) - { - Write-Host "No Front Door CDNs found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - break - } - - Write-Host "Found $($totalfrontDoors) Front Door CDN(s)." -ForegroundColor $([Constants]::MessageType.Update) - } - - - - $totalfrontDoorEndPoints = ($frontDoorEndPoints | Measure-Object).Count - - if ($totalfrontDoorEndPoints -eq 0) - { - Write-Host "No Front Door CDN Endpoint(s) found having WAF Policy not Enabled. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - break - } - - Write-Host "Found [$($totalfrontDoorEndPoints)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) - - - # Includes Front Door CDN Endpoint(s) where WAF Policy is in not Enabled - $frontDoorEndpointsWithWAFPolicyNotEnabled = @() - - # Includes Front Door CDN Endpoint(s) that were skipped during remediation. There were errors remediating them. - $frontDoorEndpointsSkipped = @() - - - Write-Host "[Step 3 of 5] Fetching Endpoint(s) for which WAF Policy is not enabled" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is not enabled..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndPoints | ForEach-Object { - $endPoint = $_ - if(($_.IsWAFPolicyStateEnabled -eq $false) -and ($_.IsWAFConfigured -eq $true)) - { - $frontDoorEndpointsWithWAFPolicyNotEnabled += $endPoint - } - else - { - $logResource = @{} - $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) - $logResource.Add("ResourceName",($_.FrontDoorName)) - $logResource.Add("EndPointName",($_.EndPointName)) - $logResource.Add("Reason","WAF Policy already enabled on endpoint") - $logSkippedResources += $logResource - - } - } - - $totalfrontDoorEndpointsWithWAFPolicyNotEnabled = ($frontDoorEndpointsWithWAFPolicyNotEnabled | Measure-Object).Count - - if ($totalfrontDoorEndpointsWithWAFPolicyNotEnabled -eq 0) - { - Write-Host "No Front Door CDN endpoints(s) found where WAF Policy is not Enabled.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - return - } - - Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyNotEnabled)] Front Door CDN Endpoints(s) found where WAF Policy is not Enabled ." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - - - Write-Host "Following Front Door CDN Endpoints(s) are having WAF Policies not Enabled:" -ForegroundColor $([Constants]::MessageType.Info) - $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, - @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFPolicyStateEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} - $frontDoorEndpointsWithWAFPolicyNotEnabled | Format-Table -Property $colsProperty -Wrap - - - # Back up snapshots to `%LocalApplicationData%'. - $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\EnableFrontDoorCDNWAFPolicy" - - if (-not (Test-Path -Path $backupFolderPath)) - { - New-Item -ItemType Directory -Path $backupFolderPath | Out-Null - } - - - Write-Host "[Step 4 of 5] Backing up Front Door CDN Endpoint(s) details" - Write-Host $([Constants]::SingleDashLine) - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - - # Backing up Front Door CDN Endpoints details. - $backupFileForWAFNotEnabled = "$($backupFolderPath)\frontdoorCDNEndpointsWithoutPolicyEnabled.csv" - $frontDoorEndpointsWithWAFPolicyNotEnabled | Export-CSV -Path $backupFileForWAFNotEnabled -NoTypeInformation - Write-Host "Front Door Endpoint(s) details have been successful backed up to $($backupFolderPath)" -ForegroundColor $([Constants]::MessageType.Update) - } - else - { - Write-Host "Skipped as -FilePath is provided" -ForegroundColor $([Constants]::MessageType.Warning) - } - - if (-not $DryRun) - { - - Write-Host "WAF Policy will be Enabled for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Warning) - - if (-not $Force) - { - Write-Host "Do you want to Enable WAF Policies associated with Front Door CDN Endpoint(s)? " -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline - - $userInput = Read-Host -Prompt "(Y|N)" - - if($userInput -ne "Y") - { - Write-Host " WAF Policy will not be enabled for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - break - } - else - { - Write-Host "WAF Policy will be enabled for all Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - } - else - { - Write-Host "'Force' flag is provided. WAF Policy will be enabled on all Front Door CDN Endoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) - } - - - - Write-Host "[Step 5 of 5] Enabling WAF Policy for Front Door CDN Endpoint(s)" - Write-Host $([Constants]::SingleDashLine) - - # To hold results from the remediation. - $frontDoorEndpointsRemediated = @() - - - # Remidiate Controls by Eanbling WAF Policy - $frontDoorEndpointsWithWAFPolicyNotEnabled | ForEach-Object { - $frontDoorEndPoint = $_ - $wafPolicyName = $_.WAFPolicyName - $wafPolicyRG = $_.WAFPolicyResourceGroup - $endpointsSkipped = @() - - - try - { - $updatedPolicy = Update-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $wafPolicyRG -EnabledState Enabled - - if ($updatedPolicy.PolicyEnabledState -ne 'Enabled') - { - $endpointsSkipped += $frontDoorEndPoint - - } - else - { - $frontDoorEndPoint.IsWAFPolicyStateEnabled = $true - $frontDoorEndpointsRemediated += $frontDoorEndPoint - } - } - catch - { - $endpointsSkipped += $frontDoorEndPoint - } - } - - $totalRemediated = ($frontDoorEndpointsRemediated | Measure-Object).Count - - Write-Host $([Constants]::SingleDashLine) - - if ($totalRemediated -eq $totalfrontDoorEndpointsWithWAFPolicyNotEnabled) - { - Write-Host "WAF Policy Enabled for all [$($totalfrontDoorEndpointsWithWAFPolicyNotEnabled)] Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) - } - else - { - Write-Host "WAF Policy Enabled for [$totalRemediated] out of [$($totalfrontDoorEndpointsWithWAFPolicyNotEnabled)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) - } - - $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, - @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFPolicyStateEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} - - - - Write-Host "Remediation Summary:" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - - - - if ($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) - { - Write-Host "Successfully enabled WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) - $frontDoorEndpointsRemediated | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForEnabledState.csv" - $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation - Write-Host "This information has been saved to [$($frontDoorEndpointsRemediatedFile)]" - Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) - } - - if ($($endpointsSkipped | Measure-Object).Count -gt 0) - { - Write-Host "Error performing remediation steps for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) - $endpointsSkipped | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForEnabledState.csv" - $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation - Write-Host "This information has been saved to [$($endpointsSkippedFile)]" - } - - } - else - { - - Write-Host "[Step 5 of 5] Enabling WAF Policy for Endpoint(s)" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Skipped as -DryRun switch is provided." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - - Write-Host "Next steps:" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host "Run the same command with -FilePath $($backupFile) and without -DryRun, to Enable WAF Policy for all Front Door CDN Endpoint(s) listed in the file." -ForegroundColor $([Constants]::MessageType.Info) - } -} - -function Disable-WAFPolicyForFrontDoorCDN -{ - <# - .SYNOPSIS - Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - - .DESCRIPTION - Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - Disables back all WAF Policies in all Front Door CDNs in the Subscription. - - .PARAMETER SubscriptionId - Specifies the ID of the Subscription that was previously remediated. - - .PARAMETER Force - Specifies a forceful roll back without any prompts. - - .Parameter PerformPreReqCheck - Specifies validation of prerequisites for the command. - - .PARAMETER FilePath - Specifies the path to the file to be used as input for the roll back. - - .INPUTS - None. You cannot pipe objects to Disable-WAFPolicyForFrontDoorCDN. - - .OUTPUTS - None. Disable-WAFPolicyForFrontDoorCDN does not return anything that can be piped and used as an input to another command. - - .EXAMPLE - PS> Disable-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202211190719\EnableFrontDoorCDNWAFPolicy\RemediatedfrontDoorCDNEndpointsForEnabledState.csv - - .LINK - None - #> - - param ( - [String] - [Parameter(Mandatory = $true, HelpMessage="Specifies the ID of the Subscription that was previously remediated.")] - $SubscriptionId, - - [Switch] - [Parameter(HelpMessage="Specifies a forceful roll back without any prompts")] - $Force, - - [Switch] - [Parameter(HelpMessage="Specifies validation of prerequisites for the command")] - $PerformPreReqCheck, - - [String] - [Parameter(Mandatory = $true, HelpMessage="Specifies the path to the file to be used as input for the roll back")] - $FilePath - ) - - Write-Host $([Constants]::DoubleDashLine) - if ($PerformPreReqCheck) - { - try - { - Write-Host "[Step 1 of 4] Validate and install the modules required to run the script and validate the user" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Setup-Prerequisites - Write-Host "Completed setting up prerequisites" - Write-Host $([Constants]::SingleDashLine) - } - catch - { - Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - return - } - } - else - { - Write-Host "[Step 1 of 4] Validate the user" - Write-Host $([Constants]::SingleDashLine) - } - - # Connect to Azure account - $context = Get-AzContext - - if ([String]::IsNullOrWhiteSpace($context)) - { - - Write-Host "Connecting to Azure account..." - Write-Host $([Constants]::SingleDashLine) - Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null - Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - else - { - # Setting up context for the current Subscription. - $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop - } - - Write-Host "Subscription Name: [$($context.Subscription.Name)]" - Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" - Write-Host "Account Name: [$($context.Account.Id)]" - Write-Host "Account Type: [$($context.Account.Type)]" - Write-Host $([Constants]::SingleDashLine) - - # Note about the required access required for remediation - - Write-Host "To Disable WAF Policy for all Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Write-Host "[Step 2 of 4] Preparing to fetch all Front Door CDN Endpoints" - Write-Host $([Constants]::SingleDashLine) - - if (-not (Test-Path -Path $FilePath)) - { - Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) - break - } - - Write-Host "Fetching all Front Door CDN Endpoints from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) - - $frontDoorEndPoints = @() - $resourceAppIdURI = "https://management.azure.com/" - $apiResponse =@() - $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token - $endPointPolicies = New-Object System.Collections.ArrayList - - $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath - $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } - - $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName - - foreach($frontdoor in $uniquefrontDoors) - { - $resourceGroupName = $frontdoor.ResourceGroupName - $frontDoorName = $frontdoor.FrontDoorName - - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) - { - if($null -ne $apiResponse.Content) - { - $content = $apiResponse.Content | ConvertFrom-Json - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) - { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } - } - } - - - - $validfrontDoorEndpointsDetails | ForEach-Object { - $frontdoorEndpointName = $_.EndPointName - $resourceGroupName = $_.ResourceGroupName - $frontDoorName = $_.FrontDoorName - - try - { - - $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }}, - @{N='IsPreventionMode';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - if($WAFPolicy.PolicyMode -eq 'Prevention') - { - $true - } - else - { - $false - - } - } - }}, - @{N='IsWAFPolicyStateEnabled';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $WAFPolicy = Get-AzFrontDoorWafPolicy -Name ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -ResourceGroupName ($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup) - - if($WAFPolicy.PolicyEnabledState -eq 'Enabled') - { - $true - } - else - { - $false - } - } - }} - } - catch - { - Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]... Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) - } - } - - - - # Includes Front Door CDN Endpoint(s) where WAF Policy is in Enabled - $frontDoorEndpointsWithWAFPolicyEnabled = @() - - - - Write-Host "[Step 3 of 4] Fetching Endpoint(s) for which WAF Policy is enabled" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is enabled..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - - $frontDoorEndPoints | ForEach-Object { - $endPoint = $_ - if(($_.IsWAFPolicyStateEnabled -eq $true) -and ($_.IsWAFConfigured -eq $true)) - { - $frontDoorEndpointsWithWAFPolicyEnabled += $endPoint - } - } - - $totalfrontDoorEndpointsWithWAFPolicyEnabled = ($frontDoorEndpointsWithWAFPolicyEnabled | Measure-Object).Count - - if ($totalfrontDoorEndpointsWithWAFPolicyEnabled -eq 0) - { - Write-Host "No Front Door CDN Endpoints(s) found where WAF Policy is Enabled.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - return - } - - - Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyEnabled)] Front Door CDN Endpoints(s) found where WAF Policy is Enabled in file to Rollback." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - - # Back up snapshots to `%LocalApplicationData%'. - $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\EnableFrontDoorCDNWAFPolicy" - - if (-not (Test-Path -Path $backupFolderPath)) - { - New-Item -ItemType Directory -Path $backupFolderPath | Out-Null - } - - if (-not $Force) - { - Write-Host "Do you want to Disable WAF Policy for all Front Door CDN Endpoint(s)?" -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline - - $userInput = Read-Host -Prompt "(Y|N)" - - if($userInput -ne "Y") - { - Write-Host "WAF Policy will not be Disabled for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - break - } - } - else - { - Write-Host "'Force' flag is provided. WAF Policy will be Disabled for all the Front Door CDN Endpoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline - } - - - - Write-Host "[Step 4 of 4] Disabling WAF Policy for Front Door CDNs Endpoint(s)" - Write-Host $([Constants]::SingleDashLine) - - # Includes Front Door CDN s, to which, previously made changes were successfully rolled back. - $frontDoorEndpointsRolledBack = @() - - # Includes Front Door CDN s that were skipped during roll back. There were errors rolling back the changes made previously. - $frontDoorEndpointsSkipped = @() - - - # Roll back by disabling WAF Policy - $frontDoorEndpointsWithWAFPolicyEnabled | ForEach-Object { - $frontDoorEndPoint = $_ - $wafPolicyName = $_.WAFPolicyName - $wafPolicyRG = $_.WAFPolicyResourceGroup - - try - { - $endpointResource = Update-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $wafPolicyRG -EnabledState Disabled - - if ($endpointResource.PolicyEnabledState -ne 'Disabled') - { - $frontDoorEndpointsSkipped += $frontDoorEndPoint - - } - else - { - $frontDoorEndPoint.IsWAFPolicyStateEnabled = $false - $frontDoorEndpointsRolledBack += $frontDoorEndPoint - } - } - catch - { - $frontDoorEndpointsSkipped += $frontDoorEndPoint - } - } - - - $totalfrontDoorEndpointsRolledBack = ($frontDoorEndpointsRolledBack | Measure-Object).Count - - Write-Host $([Constants]::SingleDashLine) - - if ($totalfrontDoorEndpointsRolledBack -eq $totalfrontDoorEndpointsWithWAFPolicyEnabled) - { - Write-Host "WAF Policy Disabled for all [$($totalfrontDoorEndpointsWithWAFPolicyEnabled)] Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) - } - else - { - Write-Host "WAF Policy disabled for [$totalfrontDoorEndpointsRolledBack] out of [$($totalfrontDoorEndpointsWithWAFPolicyEnabled)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) - } - - Write-Host "Rollback Summary:" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - - $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, - @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFPolicyStateEnabled};Label="Is associated WAF Policy in Enabled State";Width=7;Alignment="left"} - - - if ($($frontDoorEndpointsRolledBack | Measure-Object).Count -gt 0) - { - Write-Host "Successfully disabled WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) - $frontDoorEndpointsRolledBack | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $frontDoorEndpointsRolledBackFile = "$($backupFolderPath)\RolledBackfrontDoorCDNEndpointsForWAFPolicyState.csv" - $frontDoorEndpointsRolledBack | Export-CSV -Path $frontDoorEndpointsRolledBackFile -NoTypeInformation - Write-Host "This information has been saved to $($frontDoorEndpointsRolledBackFile)" - } - - if ($($frontDoorEndpointsSkipped | Measure-Object).Count -gt 0) - { - Write-Host "Error Disabling WAF Policy for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) - $frontDoorEndpointsSkipped | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $frontDoorEndpointsSkippedFile = "$($backupFolderPath)\RolledBackSkippedfrontDoorCDNEndpointsForWAFPolicyState.csv" - $frontDoorEndpointsSkipped | Export-CSV -Path $frontDoorEndpointsSkippedFile -NoTypeInformation - Write-Host "This information has been saved to $($frontDoorEndpointsSkippedFile)" - } - -} - -# Defines commonly used constants. -class Constants -{ - # Defines commonly used colour codes, corresponding to the severity of the log. - static [Hashtable] $MessageType = @{ - Error = [System.ConsoleColor]::Red - Warning = [System.ConsoleColor]::Yellow - Info = [System.ConsoleColor]::Cyan - Update = [System.ConsoleColor]::Green - Default = [System.ConsoleColor]::White - } - - static [String] $DoubleDashLine = "========================================================================================================================" - static [String] $SingleDashLine = "------------------------------------------------------------------------------------------------------------------------" -} \ No newline at end of file From b525c9bccbeaefd0a767cda018df78b57a0e53a3 Mon Sep 17 00:00:00 2001 From: Abhishek P Date: Wed, 21 Dec 2022 17:28:32 +0530 Subject: [PATCH 24/26] Changes --- Control coverage/Feature/CDN.md | 8 ++-- ...mediate-EnableWAFPolicyForFrontDoorCDN.ps1 | 37 ++++++++++++------- ...WAFPolicyPreventionModeForFrontDoorCDN.ps1 | 13 ++++++- 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/Control coverage/Feature/CDN.md b/Control coverage/Feature/CDN.md index 2e281030..f6a554b8 100644 --- a/Control coverage/Feature/CDN.md +++ b/Control coverage/Feature/CDN.md @@ -109,20 +109,20 @@ Azure Web Application Firewall (WAF) on Azure Front Door provides centralized pr - ARM API to get Front Door resources in a subscription: /subscriptions/{0}/ /subscriptions/{0}/providers/Microsoft.Cdn/profiles?api-version=2021-06-01 -**Properties:** [*] +**Properties:** [*]
- ARM API to get Front Door Endpoints resources in a subscription: /subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Cdn/profiles/{2}/afdEndpoints?api-version=2021-06-01
-**Properties:** [*] +**Properties:** [*].properties.hostname, [*].properties.enabledState,
- ARM API to get WAF Policies in a subscription: /subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Network/frontDoorWebApplicationFirewallPolicies?api-version=2020-11-01
-**Properties:** [*] +**Properties:** [*].properties.policySettings
- ARM API to get Security Policies in a subscription: /subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Cdn/profiles/{2}/securityPolicies?api-version=2021-06-01
-**Properties:** [*] +**Properties:** [*].properties.parameters
___ diff --git a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 index 0d0198b7..5f1e9537 100644 --- a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 +++ b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyForFrontDoorCDN.ps1 @@ -532,6 +532,7 @@ function Enable-WAFPolicyForFrontDoorCDN # Includes Front Door CDN Endpoint(s) where WAF Policy is in not Enabled $frontDoorEndpointsWithWAFPolicyNotEnabled = @() + $totalfrontDoorEndpointsWithWAFPolicyNotConfigured = 0 # Includes Front Door CDN Endpoint(s) that were skipped during remediation. There were errors remediating them. $frontDoorEndpointsSkipped = @() @@ -543,24 +544,34 @@ function Enable-WAFPolicyForFrontDoorCDN Write-Host $([Constants]::SingleDashLine) $frontDoorEndPoints | ForEach-Object { $endPoint = $_ - if(($_.IsWAFPolicyStateEnabled -eq $false) -and ($_.IsWAFConfigured -eq $true)) - { - $frontDoorEndpointsWithWAFPolicyNotEnabled += $endPoint - } - else - { - $logResource = @{} - $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) - $logResource.Add("ResourceName",($_.FrontDoorName)) - $logResource.Add("EndPointName",($_.EndPointName)) - $logResource.Add("Reason","WAF Policy already enabled on endpoint") - $logSkippedResources += $logResource + if($_.IsWAFConfigured -eq $false) + { + $totalfrontDoorEndpointsWithWAFPolicyNotConfigured = $totalfrontDoorEndpointsWithWAFPolicyNotConfigured + 1 + } + if(($_.IsWAFPolicyStateEnabled -eq $false) -and ($_.IsWAFConfigured -eq $true)) + { + $frontDoorEndpointsWithWAFPolicyNotEnabled += $endPoint + } + else + { + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.FrontDoorName)) + $logResource.Add("EndPointName",($_.EndPointName)) + $logResource.Add("Reason","WAF Policy already enabled on endpoint") + $logSkippedResources += $logResource - } + } } $totalfrontDoorEndpointsWithWAFPolicyNotEnabled = ($frontDoorEndpointsWithWAFPolicyNotEnabled | Measure-Object).Count + if ($totalfrontDoorEndpointsWithWAFPolicyNotConfigured -gt 0) + { + Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door Endpoints(s) found where WAF Policy is not configured. Use [Remediate-ConfigureWAFOnAzureFrontDoor.ps1] BRS to Configure WAF Policy on Front Door Endpoints without WAF Policy Configured." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + if ($totalfrontDoorEndpointsWithWAFPolicyNotEnabled -eq 0) { Write-Host "No Front Door CDN endpoints(s) found where WAF Policy is not Enabled.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) diff --git a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 index 4cc1b083..9a214f39 100644 --- a/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 +++ b/Scripts/RemediationScripts/Remediate-EnableWAFPolicyPreventionModeForFrontDoorCDN.ps1 @@ -527,6 +527,7 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints # Includes Front Door CDN Endpoint(s) where WAF Policy is in Prevention Mode $frontDoorEndpointsWithWAFPolicyNotInPrevention = @() + $totalfrontDoorEndpointsWithWAFPolicyNotConfigured = 0 # Includes Front Door CDN Endpoint(s) that were skipped during remediation. There were errors remediating them. $frontDoorEndpointsSkipped = @() @@ -538,7 +539,11 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is not in Prevention Mode..." -ForegroundColor $([Constants]::MessageType.Info) $frontDoorEndPoints | ForEach-Object { - $endPoint = $_ + $endPoint = $_ + if($_.IsWAFConfigured -eq $false) + { + $totalfrontDoorEndpointsWithWAFPolicyNotConfigured = $totalfrontDoorEndpointsWithWAFPolicyNotConfigured + 1 + } if(($_.IsPreventionMode -eq $false) -and ($_.IsWAFConfigured -eq $true)) { $frontDoorEndpointsWithWAFPolicyNotInPrevention += $endPoint @@ -557,6 +562,12 @@ function Enable-WAFPolicyPreventionModeForFrontDoorCDNEndPoints $totalfrontDoorEndpointsWithWAFPolicyNotInPrevention = ($frontDoorEndpointsWithWAFPolicyNotInPrevention | Measure-Object).Count + if ($totalfrontDoorEndpointsWithWAFPolicyNotConfigured -gt 0) + { + Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door Endpoints(s) found where WAF Policy is not configured. Use [Remediate-ConfigureWAFOnAzureFrontDoor.ps1] BRS to Configure WAF Policy on Front Door Endpoints without WAF Policy Configured." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + if ($totalfrontDoorEndpointsWithWAFPolicyNotInPrevention -eq 0) { Write-Host "No Front Door CDN endpoints(s) found where WAF Policy is not in Prevention Mode.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) From e485cca013d67f2c050181b546915d9c07a57617 Mon Sep 17 00:00:00 2001 From: Abhishek Prasad Date: Wed, 21 Dec 2022 22:03:55 +0530 Subject: [PATCH 25/26] Delete Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 --- ...iate-ConfigureWAFPolicyForFrontDoorCDN.ps1 | 1164 ----------------- 1 file changed, 1164 deletions(-) delete mode 100644 Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 diff --git a/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 deleted file mode 100644 index 489a8dd8..00000000 --- a/Scripts/RemediationScripts/Remediate-ConfigureWAFPolicyForFrontDoorCDN.ps1 +++ /dev/null @@ -1,1164 +0,0 @@ -<### -# Overview: - This script is used to configure WAF Policy on all endpoints of Front Door CDNs in a Subscription. - -# Control ID: - Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration - -# Display Name: - Front Door should have Web Application Firewall configured - -# Prerequisites: - 1. Contributor or higher privileges on the Front Door CDNs in a Subscription. - 2. Must be connected to Azure with an authenticated account. - -# Steps performed by the script: - To remediate: - 1. Validate and install the modules required to run the script. - 2. Get the list of all Front Door CDNs Endpoints in a Subscription that do not have WAF Policy Configured - 3. Back up details of Front Door CDN Endpoint(s) that are to be remediated. - 4. Configure WAF Policy for all endpoints in the Frontdoors. - - To roll back: - 1. Validate and install the modules required to run the script. - 2. Get the list of Frontdoors' Endpoint(s) in a Subscription, the changes made to which previously, are to be rolled back. - 3. Revert by removing WAF Policies from endpoints in all the Frontdoors. - -# Instructions to execute the script: - To remediate: - 1. Download the script. - 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. - 3. Execute the script to configure WAF Policy on all endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. - - To roll back: - 1. Download the script. - 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. - 3. Execute the script to remove the configured WAF Policy on all endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. - -# Examples: - To remediate: - 1. To review the Front Door CDNs in a Subscription that will be remediated: - Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun - - 2. To configure WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription: - Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck - - 3. To configure WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription, from a previously taken snapshot: - Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\ConfigureFrontDoorCDNWAFPolicy\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv - - To know more about the options supported by the remediation command, execute: - Get-Help Configure-WAFPolicyForFrontDoorCDN -Detailed - - To roll back: - 1. To remove configured WAF Policy Mode for Endpoint(s) all Front Door CDNs in a Subscription, from a previously taken snapshot: - Remove-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\ConfigureFrontDoorCDNWAFPolicy\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv - - To know more about the options supported by the roll back command, execute: - Get-Help Remove-WAFPolicyForFrontDoorCDN -Detailed -###> - -function Setup-Prerequisites -{ - <# - .SYNOPSIS - Checks if the prerequisites are met, else, sets them up. - - .DESCRIPTION - Checks if the prerequisites are met, else, sets them up. - Includes installing any required Azure modules. - - .INPUTS - None. You cannot pipe objects to Setup-Prerequisites. - - .OUTPUTS - None. Setup-Prerequisites does not return anything that can be piped and used as an input to another command. - - .EXAMPLE - PS> Setup-Prerequisites - - .LINK - None - #> - - # List of required modules - $requiredModules = @("Az.Accounts", "Az.FrontDoor", "Az.Cdn") - - Write-Host "Required modules: $($requiredModules -join ', ')" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Checking if the required modules are present..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - - $availableModules = $(Get-Module -ListAvailable $requiredModules -ErrorAction Stop) - - # Check if the required modules are installed. - $requiredModules | ForEach-Object { - if ($availableModules.Name -notcontains $_) - { - Write-Host "[$($_)] module is not present." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host "Installing [$($_)] module..." -ForegroundColor $([Constants]::MessageType.Info) - Install-Module -Name $_ -Scope CurrentUser -Repository 'PSGallery' -ErrorAction Stop - Write-Host "[$($_)] module installed." -ForegroundColor $([Constants]::MessageType.Update) - } - else - { - Write-Host "[$($_)] module is present." -ForegroundColor $([Constants]::MessageType.Update) - } - } -} - -function Configure-WAFPolicyForFrontDoorCDN -{ - <# - .SYNOPSIS - Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - - .DESCRIPTION - Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - WAF Policy must be configured for Front Door CDN Endpoint(s). - - .PARAMETER SubscriptionId - Specifies the ID of the Subscription to be remediated. - - .PARAMETER Force - Specifies a forceful remediation without any prompts. - - .Parameter PerformPreReqCheck - Specifies validation of prerequisites for the command. - - .PARAMETER DryRun - Specifies a dry run of the actual remediation. - - .PARAMETER FilePath - Specifies the path to the file to be used as input for the remediation. - - .INPUTS - None. You cannot pipe objects to Enable-WAFPolicyForFrontDoors. - - .OUTPUTS - None. Configure-WAFPolicyForFrontDoorCDN does not return anything that can be piped and used as an input to another command. - - .EXAMPLE - PS> Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun - - .EXAMPLE - PS> Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck - - .EXAMPLE - PS> Configure-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202201011212\ConfigureFrontDoorCDNWAFPolicy\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv - - .LINK - None - #> - - param ( - [String] - [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] - [Parameter(ParameterSetName = "WetRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] - $SubscriptionId, - - [Switch] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies a forceful remediation without any prompts")] - $Force, - - [Switch] - [Parameter(ParameterSetName = "DryRun", HelpMessage="Specifies validation of prerequisites for the command")] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies validation of prerequisites for the command")] - $PerformPreReqCheck, - - [Switch] - [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies a dry run of the actual remediation")] - $DryRun, - - [String] - [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation")] - $FilePath - ) - - Write-Host $([Constants]::DoubleDashLine) - if ($PerformPreReqCheck) - { - try - { - Write-Host "[Step 1 of 5] Validate and install the modules required to run the script and validate the user" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Setup-Prerequisites - Write-Host "Completed setting up prerequisites." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - catch - { - Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - return - } - } - else - { - Write-Host "[Step 1 of 5] Validate the user" - Write-Host $([Constants]::SingleDashLine) - } - - # Connect to Azure account - $context = Get-AzContext - - if ([String]::IsNullOrWhiteSpace($context)) - { - Write-Host "Connecting to Azure account..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null - Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - # Setting up context for the current Subscription. - $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop - - Write-Host "Subscription Name: [$($context.Subscription.Name)]" - Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" - Write-Host "Account Name: [$($context.Account.Id)]" - Write-Host "Account Type: [$($context.Account.Type)]" - Write-Host $([Constants]::SingleDashLine) - - - Write-Host " To configure WAF Policy for Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Write-Host "[Step 2 of 5] Preparing to fetch all Front Door CDNs" - Write-Host $([Constants]::SingleDashLine) - - $frontDoorCDNs = @() - $frontDoorEndPoints = @() - $resourceAppIdURI = "https://management.azure.com/" - $apiResponse =@() - $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token - $endPointPolicies = New-Object System.Collections.ArrayList - - # To keep track of remediated and skipped resources - $logRemediatedResources = @() - $logSkippedResources=@() - - # Control Id - $controlIds = "Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration" - - - # No file path provided as input to the script. Fetch all Front Door CDNs in the Subscription. - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - Write-Host "Fetching all Front Door CDNs in Subscription: [$($context.Subscription.SubscriptionId)]..." -ForegroundColor $([Constants]::MessageType.Info) - - # Get all Front Door CDNs in the Subscription - $frontDoorCDNs = Get-AzFrontDoorCdnProfile -ErrorAction SilentlyContinue - $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - - if($totalfrontDoors -gt 0) - { - $frontDoorCDNs | ForEach-Object { - $frontDoor = $_ - $frontDoorId = $_.Id - $resourceGroupName = $_.Id.Split('/')[4] - $frontDoorName = $_.Name - - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) - { - if($null -ne $apiResponse.Content) - { - $content = $apiResponse.Content | ConvertFrom-Json - - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) - { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } - } - - - # Get all Endpoint(s) for this Front Door CDN. - $endpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }} - } - } - } - else - { - if (-not (Test-Path -Path $FilePath)) - { - Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - break - } - - Write-Host "Fetching all Front Door CDN Endpoint(s) from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) - - $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath - $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } - - $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName - - foreach($frontdoor in $uniquefrontDoors) - { - $resourceGroupName = $frontdoor.ResourceGroupName - $frontDoorName = $frontdoor.FrontDoorName - - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) - { - if($null -ne $apiResponse.Content) - { - $content = $apiResponse.Content | ConvertFrom-Json - - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) - { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } - } - } - - $validfrontDoorEndpointsDetails | ForEach-Object { - $frontdoorEndpointName = $_.EndPointName - $resourceGroupName = $_.ResourceGroupName - $frontDoorName = $_.FrontDoorName - - try - { - $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }} - } - catch - { - Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) - } - } - } - - - - - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - $totalfrontDoors = ($frontDoorCDNs | Measure-Object).Count - - if ($totalfrontDoors -eq 0) - { - Write-Host "No Front Door CDNs found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::DoubleDashLine) - break - } - - Write-Host "Found [$($totalfrontDoors)] Front Door CDN(s)." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - - - - $totalfrontDoorEndPoints = ($frontDoorEndPoints | Measure-Object).Count - - if ($totalfrontDoorEndPoints -eq 0) - { - Write-Host "No Front Door CDN Endpoint(s) having WAF Policy not configured. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::DoubleDashLine) - break - } - - Write-Host "Found [$($totalfrontDoorEndPoints)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - - # Includes Front Door CDN Endpoint(s) where WAF Policy is not configured - $frontDoorEndpointsWithWAFPolicyNotConfigured = @() - - # Includes Front Door CDN Endpoint(s) that were skipped during remediation. There were errors remediating them. - $frontDoorEndpointsSkipped = @() - - - - Write-Host "[Step 3 of 5] Fetching Endpoint(s) for which WAF Policy is not configured" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is not configured..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndPoints | ForEach-Object { - $endPoint = $_ - if($_.IsWAFConfigured -eq $false) - { - $frontDoorEndpointsWithWAFPolicyNotConfigured += $endPoint - } - else - { - $logResource = @{} - $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) - $logResource.Add("ResourceName",($_.FrontDoorName)) - $logResource.Add("EndPointName",($_.EndPointName)) - $logResource.Add("Reason","WAF Policy already Configured on endpoint") - $logSkippedResources += $logResource - - } - } - - $totalfrontDoorEndpointsWithWAFPolicyNotConfigured = ($frontDoorEndpointsWithWAFPolicyNotConfigured | Measure-Object).Count - - if ($totalfrontDoorEndpointsWithWAFPolicyNotConfigured -eq 0) - { - Write-Host "No Front Door CDN endpoints(s) found where WAF Policy is not configured. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - return - } - - Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door CDN Endpoints(s) found where WAF Policy is not configured ." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - - - Write-Host "Following Front Door CDN Endpoints(s) are having WAF Policies not configured:" -ForegroundColor $([Constants]::MessageType.Info) - $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, - @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"} - - $frontDoorEndpointsWithWAFPolicyNotConfigured | Format-Table -Property $colsProperty -Wrap - - # Back up snapshots to `%LocalApplicationData%'. - $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\ConfigureFrontDoorCDNWAFPolicy" - - if (-not (Test-Path -Path $backupFolderPath)) - { - New-Item -ItemType Directory -Path $backupFolderPath | Out-Null - } - - - Write-Host "[Step 4 of 5] Backing up Front Door CDN Endpoint(s) details" - Write-Host $([Constants]::SingleDashLine) - if ([String]::IsNullOrWhiteSpace($FilePath)) - { - - # Backing up Front Door CDN Endpoints details. - $backupFile = "$($backupFolderPath)\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv" - $frontDoorEndpointsWithWAFPolicyNotConfigured | Export-CSV -Path $backupFile -NoTypeInformation - Write-Host "Front Door Endpoint(s) details have been successful backed up to [$($backupFolderPath)]" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - else - { - Write-Host "Skipped as -FilePath is provided" -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - - if (-not $DryRun) - { - - Write-Host "WAF Policy will be configured for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Warning) - - if (-not $Force) - { - Write-Host "Do you want to configure WAF Policy associated with Front Door CDN Endpoint(s)? " -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline - Write-Host $([Constants]::SingleDashLine) - $userInput = Read-Host -Prompt "(Y|N)" - Write-Host $([Constants]::SingleDashLine) - if($userInput -ne "Y") - { - Write-Host "WAF Policy will not be configured for any Front Door CDN Endpoint(s). Exiting." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::DoubleDashLine) - break - } - else - { - Write-Host "WAF Policy will be configured for all Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - } - else - { - Write-Host "'Force' flag is provided. WAF Policy will be configured on all Front Door CDN Endoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - - - - Write-Host "[Step 5 of 5] Configuring WAF Policy for Front Door CDN Endpoint(s)" - Write-Host $([Constants]::SingleDashLine) - # To hold results from the remediation. - $frontDoorEndpointsRemediated = @() - $endpointsSkipped = @() - $otherPolicyEndpointsAssociations = @() - - - # Remidiate Controls by configuring WAF Policy - $frontDoorEndpointsWithWAFPolicyNotConfigured | ForEach-Object { - $frontDoorEndPoint = $_ - $endpointName = $_.EndPointName - $frontdoorName = $_.FrontDoorName - $resourceGroupName = $_.ResourceGroupName - $i= 0 - - try - { - Do - { - $wafPolicyName = Read-Host -Prompt "Enter WAF Policy Name for Endpoint: [$($_.EndPointName)] of Frontdoor [$($frontdoorName)] " - $policyResourceGroup = Read-Host -Prompt "Enter WAF Policy Resource Group Name for Endpoint: [$($_.EndPointName)] of Frontdoor [$($frontdoorName)] " - $policy = Get-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $policyResourceGroup -ErrorAction SilentlyContinue - - if($policy -eq $null) - { - Write-Host "WAF Policy name or WAF Policy Resource Group Name is not correct. Please enter correct details." - Write-Host $([Constants]::SingleDashLine) - } - - if($policy -ne $null -and $policy.Sku -ne 'Standard_AzureFrontDoor') - { - Write-Host "WAF Policy is not of type Front door tier Standard . Please enter correct details." - Write-Host $([Constants]::SingleDashLine) - } - - } - while($policy.Sku -ne 'Standard_AzureFrontDoor' -or $policy -eq $null) - $wafPolicyId = $policy.Id - - $endpoint = Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontdoorName -EndpointName $endpointName - $updateAssociation = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$($endpoint.Id)}) - $updateAssociations = @() - $updateAssociations += $updateAssociation - $otherPolicyEndpointsAssociations = $endPointPolicies | where wafPolicyName -eq ($wafPolicyName) - $otherPolicyEndpointsAssociations = $otherPolicyEndpointsAssociations | where frontDoorName -eq $frontdoorName - - $otherPolicyEndpointsAssociations | ForEach-Object { - $association = $_ - $associatedEndpoint = $_.endpointName - - New-Variable -Force -Name "endpoint$i" -Value (Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontdoorName -EndpointName $associatedEndpoint) - New-Variable -Force -Name "updateAssociation$i" -Value (New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$(Get-Variable -Name "endpoint$i" -ValueOnly).Id})) - $updateAssociations += (Get-Variable -Name "updateAssociation$i" -ValueOnly) - $i++ - } - - $updateWafParameter = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallParametersObject -Association @($updateAssociations) -WafPolicyId $wafPolicyId - $policySecurity = New-AzFrontDoorCdnSecurityPolicy -ResourceGroupName v-abprasadTestRG -ProfileName testFrontdoorCDN -Name Policy -Parameter $updateWafParameter - - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $policyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontdoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - - if ($policySecurity.Name -eq $null) - { - $endpointsSkipped += $frontDoorEndPoint - - } - else - { $frontDoorEndPoint.IsWAFConfigured = $true - $frontDoorEndPoint.WAFPolicyName = $wafPolicyName - $frontDoorEndPoint.WAFPolicyResourceGroup = $policyResourceGroup - $frontDoorEndpointsRemediated += $frontDoorEndPoint - } - } - catch - { - $endpointsSkipped += $frontDoorEndPoint - } - } - - $totalRemediated = ($frontDoorEndpointsRemediated | Measure-Object).Count - - Write-Host $([Constants]::SingleDashLine) - - if ($totalRemediated -eq $totalfrontDoorEndpointsWithWAFPolicyNotConfigured) - { - Write-Host "WAF Policy configured for all [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - else - { - Write-Host "WAF Policy configured for [$totalRemediated] out of [$($totalfrontDoorEndpointsWithWAFPolicyNotConfigured)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - - $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, - @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"} - - - - Write-Host "Remediation Summary:" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - - - if ($($frontDoorEndpointsRemediated | Measure-Object).Count -gt 0) - { - Write-Host "Successfully configured WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndpointsRemediated | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $frontDoorEndpointsRemediatedFile = "$($backupFolderPath)\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv" - $frontDoorEndpointsRemediated | Export-CSV -Path $frontDoorEndpointsRemediatedFile -NoTypeInformation - Write-Host "This information has been saved to [$($frontDoorEndpointsRemediatedFile)]" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - } - - if ($($endpointsSkipped | Measure-Object).Count -gt 0) - { - Write-Host "Error performing remediation steps for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::SingleDashLine) - $endpointsSkipped | Format-Table -Property $colsProperty -Wrap - Write-Host $([Constants]::SingleDashLine) - # Write this to a file. - $endpointsSkippedFile = "$($backupFolderPath)\SkippedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv" - $endpointsSkipped | Export-CSV -Path $endpointsSkippedFile -NoTypeInformation - Write-Host "This information has been saved to [$($endpointsSkippedFile)]" - Write-Host $([Constants]::SingleDashLine) - } - - } - else - { - - Write-Host "[Step 5 of 5] Configuring WAF Policy for Endpoint(s)" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Skipped as -DryRun switch is provided." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - - Write-Host "Next steps:" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host "Run the same command with -FilePath $($backupFile) and without -DryRun, to configure WAF Policy Mode for all Front Door CDN Endpoint(s) listed in the file." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - } -} - -function Remove-WAFPolicyForFrontDoorCDN -{ - <# - .SYNOPSIS - Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - - .DESCRIPTION - Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. - Removes configured WAF Policy for all WAF Policies in all Front Door CDN s in the Subscription. - - .PARAMETER SubscriptionId - Specifies the ID of the Subscription that was previously remediated. - - .PARAMETER Force - Specifies a forceful roll back without any prompts. - - .Parameter PerformPreReqCheck - Specifies validation of prerequisites for the command. - - .PARAMETER FilePath - Specifies the path to the file to be used as input for the roll back. - - .INPUTS - None. You cannot pipe objects to Remove-WAFPolicyForFrontDoorCDN. - - .OUTPUTS - None. Remove-WAFPolicyForFrontDoorCDN does not return anything that can be piped and used as an input to another command. - - .EXAMPLE - PS> Remove-WAFPolicyForFrontDoorCDN -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202211190719\ConfigureFrontDoorCDNWAFPolicy\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv - - .LINK - None - #> - - param ( - [String] - [Parameter(Mandatory = $true, HelpMessage="Specifies the ID of the Subscription that was previously remediated.")] - $SubscriptionId, - - [Switch] - [Parameter(HelpMessage="Specifies a forceful roll back without any prompts")] - $Force, - - [Switch] - [Parameter(HelpMessage="Specifies validation of prerequisites for the command")] - $PerformPreReqCheck, - - [String] - [Parameter(Mandatory = $true, HelpMessage="Specifies the path to the file to be used as input for the roll back")] - $FilePath - ) - - Write-Host $([Constants]::DoubleDashLine) - if ($PerformPreReqCheck) - { - try - { - Write-Host "[Step 1 of 4] Validate and install the modules required to run the script and validate the user" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Setup-Prerequisites - Write-Host "Completed setting up prerequisites" - Write-Host $([Constants]::SingleDashLine) - } - catch - { - Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - return - } - } - else - { - Write-Host "[Step 1 of 4] Validate the user" - Write-Host $([Constants]::SingleDashLine) - } - - # Connect to Azure account - $context = Get-AzContext - - if ([String]::IsNullOrWhiteSpace($context)) - { - - Write-Host "Connecting to Azure account..." - Write-Host $([Constants]::SingleDashLine) - Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null - Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - else - { - # Setting up context for the current Subscription. - $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop - } - - Write-Host "Subscription Name: [$($context.Subscription.Name)]" - Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" - Write-Host "Account Name: [$($context.Account.Id)]" - Write-Host "Account Type: [$($context.Account.Type)]" - Write-Host $([Constants]::SingleDashLine) - - # Note about the required access required for remediation - - Write-Host "To remove Configured WAF Policy for all Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Write-Host "[Step 2 of 4] Preparing to fetch all Front Door CDN Endpoints" - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndPoints = @() - $resourceAppIdURI = "https://management.azure.com/" - $apiResponse =@() - $classicAccessToken= (Get-AzAccessToken -ResourceUrl $ResourceAppIdURI).Token - $endPointPolicies = New-Object System.Collections.ArrayList - - if (-not (Test-Path -Path $FilePath)) - { - Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::DoubleDashLine) - break - } - - Write-Host "Fetching all Front Door CDN Endpoints from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) - - $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath - $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } - - $uniquefrontDoors = $validfrontDoorEndpointsDetails | Select-Object -Unique -Property FrontDoorName, ResourceGroupName - - foreach($frontdoor in $uniquefrontDoors) - { - $resourceGroupName = $frontdoor.ResourceGroupName - $frontDoorName = $frontdoor.FrontDoorName - - if($null -ne $classicAccessToken) - { - $header = "Bearer " + $classicAccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} - $uri = [string]:: Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Cdn/profiles/{3}/securityPolicies?api-version=2021-06-01",$resourceAppIdURI,$SubscriptionId,$resourceGroupName,$frontDoorName) - $apiResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing - - if($apiResponse.StatusCode -ge 200 -and $apiResponse.StatusCode -le 399) - { - if($null -ne $apiResponse.Content) - { - $content = $apiResponse.Content | ConvertFrom-Json - - $value = $content.value - $totalValues = ($value | Measure-Object).Count - for($i=0; $i -lt $totalValues; $i++) - { - $wafPolicyId = $value[$i].properties.parameters.wafPolicy.id - $wafPolicyName = $wafPolicyId.Split('/')[8] - $wafPolicyResourceGroup = $wafPolicyId.Split('/')[4] - $associations = $value[$i].properties.parameters.associations - $totalAssociations = ($associations | Measure-Object).Count - for($j=0; $j -lt $totalAssociations; $j++) - { - $association = $associations[$j] - $domains = $association.domains - $totalDomains = ($domains | Measure-Object).Count - for($k=0; $k -lt $totalDomains; $k++) - { - $domain = $domains[$k] - $id = $domain.id - $endpointName = $id.Split('/')[10] - $EndpointPolicy = New-Object System.Object - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "endpointName" -Value $endpointName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyName" -Value $wafPolicyName - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyResourceGroup" -Value $wafPolicyResourceGroup - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "wafPolicyId" -Value $wafPolicyId - $EndpointPolicy | Add-Member -MemberType NoteProperty -Name "frontdoorName" -Value $frontDoorName - $endPointPolicies.Add($EndpointPolicy) | Out-Null - } - } - } - } - } - } - } - - - $validfrontDoorEndpointsDetails | ForEach-Object { - $frontdoorEndpointName = $_.EndPointName - $resourceGroupName = $_.ResourceGroupName - $frontDoorName = $_.FrontDoorName - - try - { - $endpoints = ( Get-AzFrontDoorCdnEndpoint -EndpointName $frontdoorEndpointName -ProfileName $frontDoorName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue) - $frontDoorEndPoints += $endpoints | Select-Object @{N='EndpointId';E={$_.Id}}, - @{N='FrontDoorName';E={$frontDoorName}}, - @{N='ResourceGroupName';E={$resourceGroupName}}, - @{N='EndPointName';E={$_.Name}}, - @{N='WAFPolicyName';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName }}, - @{N='WAFPolicyResourceGroup';E={ $endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyResourceGroup}}, - @{N='IsWAFConfigured';E={ - if(($endPointPolicies | where endpointName -eq $_.Name | select -ExpandProperty wafPolicyName) -eq $null) - { - $false - } - else - { - $true - } - }} - } - catch - { - Write-Host "Error fetching Front Door CDN Endpoint: ID - [$($frontdoorEndpointId)]. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::SingleDashLine) - Write-Host "Skipping this Front Door CDN Endpoint..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - } - - - - # Includes Front Door CDN Endpoint(s) where WAF Policy is configured - $frontDoorEndpointsWithWAFPolicyConfigured = @() - - - - Write-Host "[Step 3 of 4] Fetching Endpoint(s) Endpoint(s) where WAF Policy is not configured" - Write-Host $([Constants]::SingleDashLine) - Write-Host "Separating Front Door CDN Endpoint(s) for which WAF Policy is configured..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndPoints | ForEach-Object { - $endPoint = $_ - if($_.IsWAFConfigured -eq $true) - { - $frontDoorEndpointsWithWAFPolicyConfigured += $endPoint - } - else - { - $logResource = @{} - $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) - $logResource.Add("ResourceName",($_.EndPointName)) - $logResource.Add("Reason","WAF Policy is already configured on Frontdoor CDN Endpoint") - $logSkippedResources += $logResource - - } - } - - $totalfrontDoorEndpointsWithWAFPolicyConfigured = ($frontDoorEndpointsWithWAFPolicyConfigured | Measure-Object).Count - - if ($totalfrontDoorEndpointsWithWAFPolicyConfigured -eq 0) - { - Write-Host "No Front Door CDN Endpoints(s) found where WAF Policy is configured.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::DoubleDashLine) - return - } - - - Write-Host "Found [$($totalfrontDoorEndpointsWithWAFPolicyConfigured)] Front Door CDN Endpoints(s) found in file where WAF Policy is configured to Rollback." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - - # Back up snapshots to `%LocalApplicationData%'. - $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\ConfigureFrontDoorCDNWAFPolicy" - - if (-not (Test-Path -Path $backupFolderPath)) - { - New-Item -ItemType Directory -Path $backupFolderPath | Out-Null - } - - if (-not $Force) - { - Write-Host "Do you want remove configured WAF Policy Mode for all Front Door CDN Endpoint(s)?" -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline - Write-Host $([Constants]::SingleDashLine) - $userInput = Read-Host -Prompt "(Y|N)" - Write-Host $([Constants]::SingleDashLine) - if($userInput -ne "Y") - { - Write-Host "Configured WAF Policy will not be removed for any Front Door CDN Endpoint(s). Exiting..." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::DoubleDashLine) - break - } - else - { - Write-Host "Configured WAF Policy will be removed for all Front Door CDN Endpoint(s)." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - } - else - { - Write-Host "'Force' flag is provided. Configured WAF Policy will be removed for all the Front Door CDN Endpoint(s) without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) -NoNewline - Write-Host $([Constants]::SingleDashLine) - } - - - - - - Write-Host "[Step 4 of 4] Switching WAF Policy mode to Detection for Front Door CDNs Endpoint(s)" - Write-Host $([Constants]::SingleDashLine) - # Includes Front Door CDN s, to which, previously made changes were successfully rolled back. - $frontDoorEndpointsRolledBack = @() - - # Includes Front Door CDN s that were skipped during roll back. There were errors rolling back the changes made previously. - $frontDoorEndpointsSkipped = @() - - - - # Roll back by removing configured WAF Policy on Endpoints - $frontDoorEndpointsWithWAFPolicyConfigured | ForEach-Object { - $frontDoorEndPoint = $_ - $endpointName = $_.EndPointName - $frontdoorName = $_.FrontDoorName - $resourceGroupName = $_.ResourceGroupName - $wafPolicyName = $_.WAFPolicyName - $policyResourceGroup = $_.WAFPolicyResourceGroup - $i = 0 - - $policy = Get-AzFrontDoorWafPolicy -Name $wafPolicyName -ResourceGroupName $policyResourceGroup -ErrorAction SilentlyContinue - $wafPolicyId = $policy.Id - try - { - $updateAssociations = @() - $otherPolicyEndpointsAssociations = @() - - # Remove the endpoint to be rolledback from endpointPolicies List - foreach($policy in $endPointPolicies.Clone()) - { - if($policy.endpointName -eq $endpointName) - { - $endPointPolicies.Remove($policy) - } - - } - - $otherPolicyEndpointsAssociations = $endPointPolicies | where wafPolicyName -eq ($wafPolicyName) - $otherPolicyEndpointsAssociations = $otherPolicyEndpointsAssociations | where frontDoorName -eq $frontdoorName - - $otherPolicyEndpointsAssociations | ForEach-Object { - $association = $_ - $associatedEndpoint = $_.endpointName - - New-Variable -Force -Name "endpoint$i" -Value (Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontdoorName -EndpointName $associatedEndpoint) - New-Variable -Force -Name "updateAssociation$i" -Value (New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$(Get-Variable -Name "endpoint$i" -ValueOnly).Id})) - $updateAssociations += (Get-Variable -Name "updateAssociation$i" -ValueOnly) - $i++ - } - - $updateWafParameter = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallParametersObject -Association @($updateAssociations) -WafPolicyId $wafPolicyId - $policySecurity = New-AzFrontDoorCdnSecurityPolicy -ResourceGroupName v-abprasadTestRG -ProfileName testFrontdoorCDN -Name Policy -Parameter $updateWafParameter - - if ($policySecurity.Name -eq $null) - { - $frontDoorEndpointsSkipped += $frontDoorEndPoint - - } - else - { - $frontDoorEndPoint.IsWAFConfigured = $false - $frontDoorEndPoint.WAFPolicyName = "" - $frontDoorEndPoint.WAFPolicyResourceGroup = "" - $frontDoorEndpointsRolledBack += $frontDoorEndPoint - } - } - catch - { - Write-Host $_ - $frontDoorEndpointsSkipped += $frontDoorEndPoint - } - } - - - $totalfrontDoorEndpointsRolledBack = ($frontDoorEndpointsRolledBack | Measure-Object).Count - - Write-Host $([Constants]::SingleDashLine) - - if ($totalfrontDoorEndpointsRolledBack -eq $totalfrontDoorEndpointsWithWAFPolicyConfigured) - { - Write-Host "Configured WAF Policy removed for all [$($totalfrontDoorEndpointsWithWAFPolicyConfigured)] Front Door CDN Endpoint(s) ." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - } - else - { - Write-Host "Configured WAF Policy removed for [$totalfrontDoorEndpointsRolledBack] out of [$($totalfrontDoorEndpointsWithWAFPolicyConfigured)] Front Door CDN Endpoint(s)" -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - } - - Write-Host "Rollback Summary:" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - $colsProperty = @{Expression={$_.EndpointId};Label="Endpoint Id";Width=10;Alignment="left"}, - @{Expression={$_.EndPointName};Label="Endpoint";Width=10;Alignment="left"}, - @{Expression={$_.ResourceGroupName};Label="Resource Group";Width=10;Alignment="left"}, - @{Expression={$_.FrontDoorName};Label="Front Door";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyName};Label="WAF Policy Name";Width=7;Alignment="left"}, - @{Expression={$_.WAFPolicyResourceGroup};Label="WAF Policy RG";Width=7;Alignment="left"}, - @{Expression={$_.IsWAFConfigured};Label="Is WAF Policy Configured?";Width=7;Alignment="left"} - - - if ($($frontDoorEndpointsRolledBack | Measure-Object).Count -gt 0) - { - Write-Host "Successfully removed WAF Policy on the following Frontdoor CDN Endpoint(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndpointsRolledBack | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $frontDoorEndpointsRolledBackFile = "$($backupFolderPath)\RolledBackFrontDoorCDNEndpointsForConfigureWAFPolicy.csv" - $frontDoorEndpointsRolledBack | Export-CSV -Path $frontDoorEndpointsRolledBackFile -NoTypeInformation - Write-Host "This information has been saved to [$($frontDoorEndpointsRolledBackFile)]" - Write-Host $([Constants]::SingleDashLine) - } - - if ($($frontDoorEndpointsSkipped | Measure-Object).Count -gt 0) - { - Write-Host "Error removing configured WAF Policy for the following Front Door CDN Endpoint(s):" -ForegroundColor $([Constants]::MessageType.Error) - Write-Host $([Constants]::SingleDashLine) - $frontDoorEndpointsSkipped | Format-Table -Property $colsProperty -Wrap - - # Write this to a file. - $frontDoorEndpointsSkippedFile = "$($backupFolderPath)\RollbackSkippedFrontDoorCDNEndpointsForConfigureWAFPolicy.csv" - $frontDoorEndpointsSkipped | Export-CSV -Path $frontDoorEndpointsSkippedFile -NoTypeInformation - Write-Host "This information has been saved to [$($frontDoorEndpointsSkippedFile)]" - } -} - -# Defines commonly used constants. -class Constants -{ - # Defines commonly used colour codes, corresponding to the severity of the log. - static [Hashtable] $MessageType = @{ - Error = [System.ConsoleColor]::Red - Warning = [System.ConsoleColor]::Yellow - Info = [System.ConsoleColor]::Cyan - Update = [System.ConsoleColor]::Green - Default = [System.ConsoleColor]::White - } - - static [String] $DoubleDashLine = "========================================================================================================================" - static [String] $SingleDashLine = "------------------------------------------------------------------------------------------------------------------------" -} \ No newline at end of file From a052c825949e5f48b45df130e18095c37d48902d Mon Sep 17 00:00:00 2001 From: Richa Jaiswal Date: Thu, 22 Dec 2022 22:26:43 +0530 Subject: [PATCH 26/26] Remediated File added --- ...ediate-ConfigureWAFOnAzureFrontDoorCDN.ps1 | 810 ++++++++++++++++++ 1 file changed, 810 insertions(+) create mode 100644 Scripts/RemediationScripts/Remediate-ConfigureWAFOnAzureFrontDoorCDN.ps1 diff --git a/Scripts/RemediationScripts/Remediate-ConfigureWAFOnAzureFrontDoorCDN.ps1 b/Scripts/RemediationScripts/Remediate-ConfigureWAFOnAzureFrontDoorCDN.ps1 new file mode 100644 index 00000000..7d7c7b37 --- /dev/null +++ b/Scripts/RemediationScripts/Remediate-ConfigureWAFOnAzureFrontDoorCDN.ps1 @@ -0,0 +1,810 @@ +<### +# Overview: + This script is used to configure WAF Policy on all endpoints of Front Door CDN(s) in a Subscription. + +# Control ID: + Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration + +# Display Name: + Front Door CDN should have Web Application Firewall configured + +# Prerequisites: + 1. Contributor or higher privileges on the Front Door CDNs in a Subscription. + 2. Must be connected to Azure with same authenticated account with which have required access over Front door CDNs. + +# Steps performed by the script: + To remediate: + 1. Validate and install the modules required to run the script. + 2. Get the list of all Front Door CDNs Endpoints in a Subscription that do not have WAF Policy Configured + 3. Back up details of Front Door CDN Endpoint(s) that are to be remediated. + 4. Configure WAF for all endpoints in the Front Door. + + To roll back: + 1. Validate and install the modules required to run the script. + 2. Get the list of Front Door' Endpoint(s) in a Subscription, the changes made to which previously, are to be rolled back. + 3. Revert by removing WAF Policies from endpoints in all the Front Door. + +# Instructions to execute the script: + To remediate: + 1. Download the script. + 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. + 3. Execute the script to configure WAF Policy on all endpoints of Front Door CDNs in a Subscription. Refer `Examples`, below. + + To roll back: + 1. Download the script. + 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. + 3. Execute the script to roll back the WAF Policy Configuration on all endpoints of Front Door CDNs in a Subscription- we previously remediated. Refer `Examples`, below. + +# Examples: + To remediate: + 1. To review the Front Door CDNs in a Subscription that will be remediated: + Configure-WAFConfigurationOnFrontDoorEndpoint -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun + + 2. To configure WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription: + Configure-WAFConfigurationOnFrontDoorEndpoint -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck + + 3. To configure WAF Policy for Endpoint(s) of all Front Door CDNs in a Subscription, from a previously taken snapshot: + Configure-WAFConfigurationOnFrontDoorEndpoint -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\ConfigureFrontDoorCDNWAFPolicy\frontdoorCDNEndpointsWithoutWAFPolicyConfigured.csv + + To know more about the options supported by the remediation command, execute: + Get-Help Configure-WAFConfigurationOnFrontDoorEndpoint -Detailed + + To roll back: + 1. To remove configured WAF Policy Mode for Endpoint(s) all Front Door CDNs in a Subscription, from a previously taken snapshot: + Remove-WAFConfigurationOnFrontDoorEndpoint -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\ConfigureFrontDoorCDNWAFPolicy\RemediatedfrontDoorCDNEndpointsForConfigureWAFPolicy.csv + + To know more about the options supported by the roll back command, execute: + Get-Help Remove-WAFConfigurationOnFrontDoorEndpoint -Detailed +###> + +function Setup-Prerequisites +{ + <# + .SYNOPSIS + Checks if the prerequisites are met, else, sets them up. + + .DESCRIPTION + Checks if the prerequisites are met, else, sets them up. + Includes installing any required Azure modules. + + .INPUTS + None. You cannot pipe objects to Setup-Prerequisites. + + .OUTPUTS + None. Setup-Prerequisites does not return anything that can be piped and used as an input to another command. + + .EXAMPLE + PS> Setup-Prerequisites + + .LINK + None + #> + + # List of required modules + $requiredModules = @("Az.Accounts", "Az.FrontDoor", "Az.Cdn") + + Write-Host "Required modules: $($requiredModules -join ', ')" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Checking if the required modules are present..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + + $availableModules = $(Get-Module -ListAvailable $requiredModules -ErrorAction Stop) + + # Check if the required modules are installed. + $requiredModules | ForEach-Object { + if ($availableModules.Name -notcontains $_) + { + Write-Host "[$($_)] module is not present." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host "Installing [$($_)] module..." -ForegroundColor $([Constants]::MessageType.Info) + Install-Module -Name $_ -Scope CurrentUser -Repository 'PSGallery' -ErrorAction Stop + Write-Host "[$($_)] module installed." -ForegroundColor $([Constants]::MessageType.Update) + } + else + { + Write-Host "[$($_)] module is present." -ForegroundColor $([Constants]::MessageType.Update) + } + } +} + +function Configure-WAFConfigurationOnFrontDoorEndpoint +{ + <# + .SYNOPSIS + Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. + + .DESCRIPTION + Remediates 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. + Configure WAF Policy for Endpoint(s) in Front Door(s) in the Subscription. + + .PARAMETER SubscriptionId + Specifies the ID of the Subscription that needs to be remediated. + + .PARAMETER Force + Specifies a forceful roll back with mandatory user input prompts only. + + .Parameter PerformPreReqCheck + Specifies validation of prerequisites for the command. + + .PARAMETER FilePath + Specifies the path to the file to be used as input for the remediation. + + .INPUTS + None. You cannot pipe objects to Configure-WAFConfigurationOnFrontDoorEndpoint. + + .OUTPUTS + None. Configure-WAFConfigurationOnFrontDoorEndpoint does not return anything that can be piped and used as an input to another command. + + .EXAMPLE + PS> Configure-WAFConfigurationOnFrontDoorEndpoint -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202211190719\ConfigureFrontDoorCDNWAFPolicy\FrontDoorEndpointWithWAFConfiguration.csv + + .LINK + None + #> + + param ( + + [String] + [Parameter(ParameterSetName = "WetRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] + [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies a dry run of the actual remediation")] + $SubscriptionId, + + [Switch] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies validation of prerequisites for the command")] + [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies a dry run of the actual remediation")] + $PerformPreReqCheck, + + [Switch] + [Parameter(ParameterSetName = "DryRun", Mandatory = $true, HelpMessage="Specifies a dry run of the actual remediation")] + $DryRun, + + [String] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies the path to the file to be used as input for the remediation")] + $FilePath + ) + + Write-Host $([Constants]::DoubleDashLine) + if ($PerformPreReqCheck) + { + try + { + Write-Host "[Step 1 of 4] Validate and install the modules required to run the script and validate the user" + Write-Host $([Constants]::SingleDashLine) + Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Setup-Prerequisites + Write-Host "Completed setting up prerequisites" + Write-Host $([Constants]::SingleDashLine) + } + catch + { + Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + return + } + } + else + { + Write-Host "[Step 1 of 4] Validate the user" + Write-Host $([Constants]::SingleDashLine) + } + + # Connect to Azure account + $context = Get-AzContext + + if ([String]::IsNullOrWhiteSpace($context)) + { + + Write-Host "Connecting to Azure account..." + Write-Host $([Constants]::SingleDashLine) + Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null + Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + else + { + # Setting up context for the current Subscription. + $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop + } + + Write-Host "Subscription Name: [$($context.Subscription.Name)]" + Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" + Write-Host "Account Name: [$($context.Account.Id)]" + Write-Host "Account Type: [$($context.Account.Type)]" + Write-Host $([Constants]::SingleDashLine) + + # Note about the required access required for remediation + + Write-Host "To Configured WAF Policy for all Front Door CDN Endpoint(s) in a Subscription, Contributor or higher privileges on the Front Door CDNs are required." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Write-Host "[Step 2 of 4] Preparing to fetch all Front Door CDN Endpoints" + Write-Host $([Constants]::SingleDashLine) + + # list to store Front Door End Points + $frontDoors = @() + $frontDoorEndPoints = @() + + # To keep track of remediated and skipped resources + $logRemediatedResources = @() + $logSkippedResources=@() + + # Control Id + $controlIds = "Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration" + + if ([String]::IsNullOrWhiteSpace($FilePath)) + { + Write-Host "Fetch all Front Door(s) in Subscription: [$($context.Subscription.SubscriptionId)]" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + + # Get all Front Door(s) in a Subscription + $frontDoors = Get-AzFrontDoorCdnProfile -ErrorAction Stop + + # Seperating required properties + $totalfrontDoors = ($frontDoors | Measure-Object).Count + + if($totalfrontDoors -gt 0) + { + $frontDoors | ForEach-Object { + $frontDoor = $_ + $frontDoorId = $_.Id + $resourceGroupName = $_.ResourceGroupName + $frontDoorName = $_.Name + + # Get all Frontendpoint(s) for this Front Door. + $frontendpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) + if($frontendpoints -ne $null) + { + $SecurityPolicies = ( Get-AzFrontDoorCdnSecurityPolicy -ResourceGroupName $resourceGroupName -ProfileName $frontDoorName -ErrorAction SilentlyContinue) + if($SecurityPolicies -ne $null) + { + $frontDoorEndPoints += $frontendpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$frontDoorName}}, + @{N='ResourceGroupName';E={$resourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='SecurityPolicyName';E="NA"}, + @{N='IsWAFConfigured';E={ + foreach($policy in $SecurityPolicies) + { + foreach($association in $policy.Parameter.Association) + { + if($association.Domain.Id.Split('/')[10] -eq $_.Name) + { + if($policy.Parameter.WafPolicyId -ne $null) + { + $true + } + else + { + $false + } + } + } + } + } + } + } + else + { + Write-Host "Error fetching Security Policies of Front Door(s) resource. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + } + + } + else{ + Write-Host "Error fetching End points of Front Door(s) resource. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + } + + + } + } + } + else{ + + + if (-not (Test-Path -Path $FilePath)) + { + Write-Host "Input file - [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + break + } + + Write-Host "Fetch all Front Door CDN Endpoints from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + $frontDoorEndpointsDetails = Import-Csv -LiteralPath $FilePath + $validfrontDoorEndpointsDetails = $frontDoorEndpointsDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndPointName) } + + $validfrontDoorEndpointsDetails| ForEach-Object { + $resourceId = $_.EndpointId + + try + { + $frontendpoints = ( Get-AzFrontDoorCdnEndpoint -ResourceGroupName $_.ResourceGroupName -ProfileName $_.FrontDoorName -ErrorAction SilentlyContinue) + $SecurityPolicies = ( Get-AzFrontDoorCdnSecurityPolicy -ResourceGroupName $_.ResourceGroupName -ProfileName $_.FrontDoorName -ErrorAction SilentlyContinue) + $frontDoorEndPoints += $frontendpoints | Select-Object @{N='EndpointId';E={$_.Id}}, + @{N='FrontDoorName';E={$_.Id.Split('/')[8]}}, + @{N='ResourceGroupName';E={$_.ResourceGroupName}}, + @{N='EndPointName';E={$_.Name}}, + @{N='SecurityPolicyName';E="NA"}, + @{N='IsWAFConfigured';E={ + foreach($policy in $SecurityPolicies) + { + foreach($association in $policy.Parameter.Association) + { + if($association.Domain.Id.Split('/')[10] -eq $_.Name ) + { + if($policy.Parameter.WafPolicyId -ne $null) + { + $true + } + else + { + $false + } + } + } + } + } + } + } + catch + { + Write-Host "Error fetching End points of Front Door(s) resource: Resource ID: [$($EndpointId)]. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + } + } + } + + + $totalFrontDoorEndPoints = ($frontDoorEndPoints| Measure-Object).Count + + if ($totalFrontDoorEndPoints -eq 0) + { + Write-Host "No Front Door EndPoint(s) found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::DoubleDashLine) + return + } + + Write-Host "Found [$($totalFrontDoorEndPoints)] Front Door End Point(s)." -ForegroundColor $([Constants]::MessageType.Update) + + Write-Host $([Constants]::SingleDashLine) + + # list for storing Front Door EndPoint(s) for which WAF is not configured. + $EndPointsWithoutWAFConfigured = @() + + Write-Host "Separating Endpoint(s) for which WAF is not configured..." -ForegroundColor $([Constants]::MessageType.Info) + + $frontDoorEndPoints | ForEach-Object { + $EndPoint = $_ + if($_.IsWAFConfigured -ne $true) + { + $_.IsWAFConfigured = $false + $EndPointsWithoutWAFConfigured += $EndPoint + } + } + + $totalEndPointsWithoutWAFConfigured = ($EndPointsWithoutWAFConfigured | Measure-Object).Count + + if ($totalEndPointsWithoutWAFConfigured -eq 0) + { + Write-Host "No EndPoint(s) found with where WAF is not configured.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + return + } + + Write-Host "Found [$($totalEndPointsWithoutWAFConfigured)] EndPoint(s) for which WAF is not configured ." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + + $colsProperty = @{Expression={$_.EndpointId};Label="EndpointId";Width=30;Alignment="left"}, + @{Expression={$_.FrontDoorName};Label="FrontDoorName";Width=30;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="ResourceGroupName";Width=100;Alignment="left"}, + @{Expression={$_.EndPointName};Label="EndPointName";Width=100;Alignment="left"} + @{Expression={$_.SecurityPolicyName};Label="SecurityPolicyName";Width=100;Alignment="left"} + @{Expression={$_.IsWAFConfigured};Label="IsWAFConfigured";Width=100;Alignment="left"} + + Write-Host "Endpoint(s) without WAF configuration are as follows:" + $EndPointsWithoutWAFConfigured | Format-Table -Property $colsProperty -Wrap + Write-Host $([Constants]::SingleDashLine) + + # Back up snapshots to `%LocalApplicationData%'. + $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\ConfiguredWAFOnEndPoint" + + if (-not (Test-Path -Path $backupFolderPath)) + { + New-Item -ItemType Directory -Path $backupFolderPath | Out-Null + } + + Write-Host "[Step 3 of 4] Back up Endpoint(s) details..." + Write-Host $([Constants]::SingleDashLine) + + if ([String]::IsNullOrWhiteSpace($FilePath)) + { + # Backing up EndPoint(s) details. + $backupFile = "$($backupFolderPath)\FrontDoorEndPointDetailsBackUp.csv" + $EndPointsWithoutWAFConfigured | Export-CSV -Path $backupFile -NoTypeInformation + Write-Host "EndPoint(s) details have been backed up to [$($backupFile)]" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + else + { + Write-Host "Skipped as -FilePath is provided" -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + + if (-not $DryRun) + { + Write-Host $([Constants]::DoubleDashLine) + Write-Host "[Step 4 of 4] Configure the WAF on Endpoint(s) of Front Door(s) in the Subscription..." + Write-Host $([Constants]::SingleDashLine) + + if (-not $Force) + { + Write-Host "Do you want to configure WAF on the Endpoint(s) of Front Door(s) in the Subscription? " -ForegroundColor $([Constants]::MessageType.Warning) + + $userInput = Read-Host -Prompt "(Y|N)" + + if($userInput -ne "Y") + { + Write-Host "we are starting the procedure to configure the WAF on the Endpoint(s) of Front Door(s) in the Subscription. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + return + } + } + + # List for storing remediated Endpoint(s) + $EndpointRemediated = @() + + # List for storing skipped Subnet(s) + $EndpointSkipped = @() + + Write-Host "Enabling the WAF on Endpoint(s)..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + + # Loop through the list of Endpoint(s) which needs to be remediated. + $EndPointsWithoutWAFConfigured | ForEach-Object { + $endpoint = $_ + try + { + Write-Host "To Start configuring the WAF on the Endpoint(s), Please enter the WAF Policy Details for Front Door" + $_.FrontDoorName -ForegroundColor $([Constants]::MessageType.Info) + $policyName = Read-Host -Prompt "Please enter name of Web Application Firewall Policy Name which is not assigned to any other end point of Front door " + $policyRGName = Read-Host -Prompt "Please enter Resource Group of Web Application Firewall Policy" + if($policyName -ne $null -and $policyRGName -ne $null) + { + $policy = $policy = Get-AzFrontDoorWafPolicy -ResourceGroupName $policyRGName -Name $policyName + if($policy -ne $null) + { + $securityPolicyName = $_.EndPointName + "SecurityPolicy" + $endpointDetails = Get-AzFrontDoorCdnEndpoint -ResourceGroupName $_.ResourceGroupName -ProfileName $_.FrontDoorName -EndpointName $_.EndPointName + $updateAssociation = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallAssociationObject -PatternsToMatch @("/*") -Domain @(@{"Id"=$($endpointDetails.Id)}) + $updateWafParameter = New-AzFrontDoorCdnSecurityPolicyWebApplicationFirewallParametersObject -Association @($updateAssociation) -WafPolicyId $policy.Id + $securityPolicy = New-AzFrontDoorCdnSecurityPolicy -ProfileName $_.FrontDoorName -ResourceGroupName $_.ResourceGroupName -Name $securityPolicyName -Parameter $updateWafParameter + + if($securityPolicy.Parameter.WafPolicyId -ne $null) + { + $endpoint.IsWAFConfigured = $true + $endpoint.SecurityPolicyName = $securityPolicyName + $EndpointRemediated += $endpoint + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.EndPointName)) + $logRemediatedResources += $logResource + } + else + { + $EndpointSkipped += $endpoint + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.EndPointName)) + $logResource.Add("Reason", "Error Configuring NSG on : [$($endpoint)]") + $logSkippedResources += $logResource + } + } + else + { + $EndpointSkipped += $endpoint + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.EndPointName)) + $logResource.Add("Reason", "Error Configuring NSG on : [$($endpoint)]") + $logSkippedResources += $logResource + } + } + else + { + Write-Host "WAF Policy Name or Resource Group can not be empty..." -ForegroundColor $([Constants]::MessageType.Info) + $EndpointSkipped += $endpoint + return; + } + } + catch + { + $EndpointSkipped += $endpoint + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.EndPointName)) + $logResource.Add("Reason","Encountered error Configuring WAF") + $logSkippedResources += $logResource + Write-Host "Skipping this resource..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + } + + Write-Host $([Constants]::DoubleDashLine) + + Write-Host "Remediation Summary: " -ForegroundColor $([Constants]::MessageType.Info) + if ($($EndpointRemediated | Measure-Object).Count -gt 0) + { + Write-Host "Successfully configured the WAF on the Endpoint(s) of Front Door(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + $EndpointRemediated | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $EndpointRemediatedFile = "$($backupFolderPath)\RemediatedFrontDoorEndpoint.csv" + $EndpointRemediated | Export-CSV -Path $EndpointRemediatedFile -NoTypeInformation + + Write-Host "This information has been saved to" -NoNewline + Write-Host " [$($EndpointRemediatedFile)]" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) + } + + if ($($EndpointSkipped | Measure-Object).Count -gt 0) + { + + Write-Host "Error while configuring WAF on the Endpoint(s) of Front Door(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::SingleDashLine) + $EndpointSkipped | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $EndpointSkippedFile = "$($backupFolderPath)\SkippedSubnet.csv" + $EndpointSkipped | Export-CSV -Path $EndpointSkippedFile -NoTypeInformation + Write-Host "This information has been saved to" -NoNewline + Write-Host " [$($EndpointSkippedFile)]" -ForegroundColor $([Constants]::MessageType.Update) + } + + + } + + } + +function Remove-WAFConfigurationOnFrontDoorEndpoint +{ + <# + .SYNOPSIS + Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. + + .DESCRIPTION + Rolls back remediation done for 'Azure_FrontDoor_CDNProfile_NetSec_Enable_WAF_Configuration' Control. + Remove WAF configuration from the Endpoint(s) in the Subscription. + + .PARAMETER SubscriptionId + Specifies the ID of the Subscription that was previously remediated. + + .Parameter PerformPreReqCheck + Specifies validation of prerequisites for the command. + + .PARAMETER FilePath + Specifies the path to the file to be used as input for the roll back. + + .INPUTS + None. You cannot pipe objects to Remove-WAFConfigurationOnFrontDoorEndpoint. + + .OUTPUTS + None. Remove-WAFConfigurationOnFrontDoorEndpoint does not return anything that can be piped and used as an input to another command. + + .EXAMPLE + PS> Remove-WAFConfigurationOnFrontDoorEndpoint -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\RemoveNSGConfiguration\RemediatedFrontDoorEndpoints.csv + + .LINK + None + #> + + param ( + [String] + [Parameter(Mandatory = $true, HelpMessage="Specifies the ID of the Subscription that was previously remediated.")] + $SubscriptionId, + + [Switch] + [Parameter(HelpMessage="Specifies validation of prerequisites for the command")] + $PerformPreReqCheck, + + [String] + [Parameter(Mandatory = $true, HelpMessage="Specifies the path to the file to be used as input for the roll back")] + $FilePath + ) + + if ($PerformPreReqCheck) + { + try + { + Write-Host "[Step 1 of 3] Validate and install the modules required to run the script and validate the user..." + Write-Host $([Constants]::SingleDashLine) + Write-Host "Setting up prerequisites..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Setup-Prerequisites + Write-Host "Completed setting up prerequisites" + Write-Host $([Constants]::SingleDashLine) + } + catch + { + Write-Host "Error occurred while setting up prerequisites. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + return + } + } + else + { + Write-Host "[Step 1 of 3] Validate the user..." + Write-Host $([Constants]::SingleDashLine) + } + + # Connect to Azure account + $context = Get-AzContext + + if ([String]::IsNullOrWhiteSpace($context)) + { + Write-Host $([Constants]::SingleDashLine) + Write-Host "Connecting to Azure account..." + Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop | Out-Null + Write-Host "Connected to Azure account." -ForegroundColor $([Constants]::MessageType.Update) + } + else + { + # Setting up context for the current Subscription. + $context = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop + } + + Write-Host $([Constants]::SingleDashLine) + Write-Host "Subscription Name: [$($context.Subscription.Name)]" + Write-Host "Subscription ID: [$($context.Subscription.SubscriptionId)]" + Write-Host "Account Name: [$($context.Account.Id)]" + Write-Host "Account Type: [$($context.Account.Type)]" + Write-Host $([Constants]::SingleDashLine) + + # Note about the required access required for remediation + + Write-Host "To remove WAF configuration from the Ebdpoints(s) of Front Door(s) in a Subscription, Contributor or higher privileged role assignment on the Front Door(s) is required." -ForegroundColor $([Constants]::MessageType.Warning) + + Write-Host $([Constants]::SingleDashLine) + Write-Host "[Step 2 of 3] Prepare to fetch all Front Door(s)" + Write-Host $([Constants]::SingleDashLine) + + if (-not (Test-Path -Path $FilePath)) + { + Write-Host "Input file: [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + return + } + + Write-Host "Fetch all Endpoint(s) of Front Door from" -NoNewline + Write-Host " [$($FilePath)\...]..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + $FrontDoorEndpointDetails = Import-Csv -LiteralPath $FilePath + + $validFrontDoorEndpointDetails = $FrontDoorEndpointDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.EndpointId) -and ![String]::IsNullOrWhiteSpace($_.ResourceGroupName) -and ![String]::IsNullOrWhiteSpace($_.FrontDoorName) -and ![String]::IsNullOrWhiteSpace($_.SecurityPolicyName) } + + $totalFrontDoorEndpoints = $(($validFrontDoorEndpointDetails|Measure-Object).Count) + + if ($totalFrontDoorEndpoints -eq 0) + { + Write-Host "No Endpoint of Front Door(s) found. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + return + } + + Write-Host "Found [$(($validFrontDoorEndpointDetails|Measure-Object).Count)] Subnet(s)." -ForegroundColor $([Constants]::MessageType.Update) + + $colsProperty = @{Expression={$_.EndpointId};Label="EndpointId";Width=30;Alignment="left"}, + @{Expression={$_.FrontDoorName};Label="FrontDoorName";Width=30;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="ResourceGroupName";Width=100;Alignment="left"}, + @{Expression={$_.EndPointName};Label="EndPointName";Width=100;Alignment="left"} + @{Expression={$_.SecurityPolicyName};Label="SecurityPolicyName";Width=100;Alignment="left"} + @{Expression={$_.IsWAFConfigured};Label="IsWAFConfigured";Width=100;Alignment="left"} + + + $validFrontDoorEndpointDetails | Format-Table -Property $colsProperty -Wrap + + # Back up snapshots to `%LocalApplicationData%'. + $backupFolderPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Remediation\Subscriptions\$($context.Subscription.SubscriptionId.replace('-','_'))\$($(Get-Date).ToString('yyyyMMddhhmm'))\RemoveWAFfromFrontDoorEndPoint" + + if (-not (Test-Path -Path $backupFolderPath)) + { + New-Item -ItemType Directory -Path $backupFolderPath | Out-Null + } + + Write-Host $([Constants]::DoubleDashLine) + Write-Host "[Step 3 of 3] Remove WAF Configuration from all remediated Endpoint(s) of Front Door(s) in the Subscription" + Write-Host $([Constants]::SingleDashLine) + + if( -not $Force) + { + + Write-Host "Do you want to remove WAF Configuration from Endpoint(s) mentioned in the file?" -ForegroundColor $([Constants]::MessageType.Warning) + $userInput = Read-Host -Prompt "(Y|N)" + + if($userInput -ne "Y") + { + Write-Host "WAF Configuration will not be rolled back on Endpoint(s) of Front Door(s) mentioned in the file. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + return + } + Write-Host "WAF Configuration will be rolled back on Endpoint(s) of Front Door(s) mentioned in the file." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + else + { + Write-Host "'Force' flag is provided. WAF Configuration will be rolled back on Endpoint(s) of Front Door(s) in the Subscription without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + + # List for storing rolled back Subnet resource. + $EndpointsRolledBack = @() + + # List for storing skipped rolled back Subnet resource. + $EndpointsSkipped = @() + + $validFrontDoorEndpointDetails | ForEach-Object { + $Endpoint = $_ + try + { + + $remediatedEndpointDetails = Remove-AzFrontDoorCdnSecurityPolicy -ProfileName $_.FrontDoorName -ResourceGroupName $_.ResourceGroupName -Name $_.SecurityPolicyName + if($remediatedEndpointDetails.Parameter.WafPolicyId -eq $null) + { + $Endpoint.IsWAFConfigured = $false + $EndpointsRolledBack += $Endpoint + } + else + { + $EndpointsSkipped += $Endpoint + } + } + catch + { + $EndpointsSkipped += $Subnet + } + } + + Write-Host $([Constants]::DoubleDashLine) + Write-Host "Rollback Summary:`n" -ForegroundColor $([Constants]::MessageType.Info) + + if ($($EndpointsRolledBack | Measure-Object).Count -gt 0) + { + Write-Host "WAF configuration has been removed on the following Endpoint(s) of the Front Door(s) in the Subscription.:" -ForegroundColor $([Constants]::MessageType.Update) + $EndpointsRolledBack | Format-Table -Property $colsProperty -Wrap + Write-Host $([Constants]::SingleDashLine) + + # Write this to a file. + $EndpointsRolledBackFile = "$($backupFolderPath)\RolledBackFrontDoorEndpoints.csv" + $EndpointsRolledBack | Export-CSV -Path $$EndpointsRolledBackFile -NoTypeInformation + Write-Host "This information has been saved to" -NoNewline + Write-Host " [$($EndpointsRolledBackFile)]" -ForegroundColor $([Constants]::MessageType.Update) + } + + if ($($EndpointsSkipped | Measure-Object).Count -gt 0) + { + Write-Host "Error while removing WAF configuration on the Endpoint(s) of Front Door(s) in the Subscription.:" -ForegroundColor $([Constants]::MessageType.Error) + $EndpointsSkipped | Format-Table -Property $colsProperty -Wrap + Write-Host $([Constants]::SingleDashLine) + + + # Write this to a file. + $EndpointsSkippedFile = "$($backupFolderPath)\RollbackSkippedFrontDoorEndpoints.csv" + $EndpointsSkipped | Export-CSV -Path $EndpointsSkippedFile -NoTypeInformation + Write-Host "This information has been saved to" -NoNewline + Write-Host " [$($EndpointsSkippedFile)]" -ForegroundColor $([Constants]::MessageType.Update) + } +} + +# Defines commonly used constants. +class Constants +{ + # Defines commonly used colour codes, corresponding to the severity of the log... + static [Hashtable] $MessageType = @{ + Error = [System.ConsoleColor]::Red + Warning = [System.ConsoleColor]::Yellow + Info = [System.ConsoleColor]::Cyan + Update = [System.ConsoleColor]::Green + Default = [System.ConsoleColor]::White + } + + static [String] $DoubleDashLine = "========================================================================================================================" + static [String] $SingleDashLine = "------------------------------------------------------------------------------------------------------------------------" +} + + \ No newline at end of file