diff --git a/Control coverage/Feature/ApplicationGateway.md b/Control coverage/Feature/ApplicationGateway.md new file mode 100644 index 00000000..d614a096 --- /dev/null +++ b/Control coverage/Feature/ApplicationGateway.md @@ -0,0 +1,108 @@ +# ApplicationGateway + +**Resource Type:** Microsoft.Network/applicationGateways + + + +- [Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration_Trial](#azure_applicationgateway_netsec_enable_waf_configuration_trial) + + +
+ +___ + +## Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration_Trial + +### Display Name +[Trial] Application Gateway should have Web Application Firewall configured + +### Rationale +Web application firewall configuration protects Application Gateway from internet based vulnerabilities and attacks without modification to back-end code. + +### Control Spec + +> **Passed:** +> Web Application Firewall has been configured on Application Gateway. +> Configured WAF Policy mode must be Prevention only. +> DDoS is enabled on the Virtual Network assoicated with the Application Gateway. +> Network Security Group is configured on the subnet assoicated with the Application Gateway. +> +> **Failed:** +> WAF is not configured on Application Gateway. +> Configured WAF Policy mode is not Prevention. +> DDoS is not enabled on the Virtual Network assoicated with the Application Gateway. +> Network Security Group is not configured on the subnet assoicated with the Application Gateway. +> +> **Error:** +> There was an error fetching WAF Configuration details of Application Gateway. +> +### Recommendation + +- **Azure Portal** + + Use the Azure portal to configure WAF on the Application Gateway. + +- **PowerShell** + + ```powershell + + # Below commands will be useful to Configure WAF on Application Gateway + Connect-AzAccount + Set-AzContext -SubscriptionId ""` + #Get Application Gateway and existing policy object + $appgw = Get-AzApplicationGateway -Name "applicationgatewayName" -ResourceGroupName "RgName" + $policy = Get-AzApplicationGatewayFirewallPolicy -Name "WAFPolicyName" -ResourceGroupName "RgName" + + #Attach the policy to an Application Gateway + $appgw.FirewallPolicy = $policy + #Save the Application Gateway + Set-AzApplicationGateway -ApplicationGateway $appgw + + #Below commands will be useful to configure the WAF at the listener level in the Application Gateway: + Connect-AzAccount + Set-AzContext -SubscriptionId ""` + #Get Application Gateway, Listener and existing policy object + $appgw = Get-AzApplicationGateway -Name "applicationgatewayName" -ResourceGroupName "RgName" + $policy = Get-AzApplicationGatewayFirewallPolicy -Name "WAFPolicyName" -ResourceGroupName "RgName" + $listener = Get-AzApplicationGatewayHttpListener -Name "L1" -ApplicationGateway $appgw + #Attach the policy to an Application Gateway Listener + $listener.FirewallPolicy = $policy + + #Save the Application Gateway Listener + Set-AzApplicationGatewayHttpListener -FirewallPolicy $policy -ApplicationGateway $appgw + + Below commands could be run to change the Policy Mode to Prevention mode: + Connect-AzAccount + Set-AzContext -SubscriptionId ""` + #Get Application Gateway Firewall policy + $policy = Get-azapplicationGatewayFirewallPolicy -Name "WAFPolicyName" -ResourceGroupName "RGName" + #Get the Policy Settings and Set the Mode to prevention + $Policysettings = $policy.PolicySettings + $Policysettings.Mode = "Prevention" + #Save the WAF Policy + Set-AzApplicationGatewayFirewallPolicy -PolicySetting $Policysettings -InputObject $policy + + Run command + Retrieve-ApplicationGatewayVirtualNetworkDDoSNotConfigured - to retrieve the list of Application Gateway virtual network where DDoS is not enabled. + Run Enable-DDoSProtectionPlanOnVirtualNetwork to remediate the Virtual Network(s) retrieved from above command. + + Run command Retrieve-ApplicationGatewaySubnetNSGNotConfigured to retrieve the list of Application Gateway subnet where NSG is not configured. + Run Add-NSGConfigurationOnSubnet to remediate the Subnet(s) retrieved from above command. + + # For more help run: + Get-Help Retrieve-ApplicationGatewayVirtualNetworkDDoSNotConfigured -Detailed + Get-Help Enable-DDoSProtectionPlanOnVirtualNetwork -Detailed + Get-Help Retrieve-ApplicationGatewaySubnetNSGNotConfigured -Detailed + Get-Help Add-NSGConfigurationOnSubnet -Detailed + ``` + +### Azure Policy or ARM API used for evaluation + +- ARM API to list all Application Gateway: /subscriptions/{0}/providers/Microsoft.Network/applicationGateways?api-version=2022-01-01
+**Properties:** properties.rights +
+ +
+ +___ + diff --git a/Scripts/RemediationScripts/Remediate-ConfigureNSGOnVirtualNetworkSubnet.ps1 b/Scripts/RemediationScripts/Remediate-ConfigureNSGOnVirtualNetworkSubnet.ps1 new file mode 100644 index 00000000..d6fd5dfc --- /dev/null +++ b/Scripts/RemediationScripts/Remediate-ConfigureNSGOnVirtualNetworkSubnet.ps1 @@ -0,0 +1,762 @@ +<### +# Overview: + This script is used to configure the NSG on subnet of virtual network available in the App Gateway in a Subscription. + +# Control ID: + Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration_Trial + +# Display Name: + NGS must be configured on the Subnet. + +# Prerequisites: + Owner or higher priviliged role on the Virtual Network(s) is required for remediation. + +# Steps performed by the script: + To remediate: + 1. Validating and installing the modules required to run the script and validating the user. + 2. Get the list of Subnet(s) in a Subscription that have NSG is not configured. + 3. Back up details of Subnet(s) that are to be remediated. + 4. Configure the NSG on the Subnet(s) of Virtual Network in the Subscription. + + To roll back: + 1. Validate and install the modules required to run the script and validating the user. + 2. Get the list of Subnet(s) in a Subscription, the changes made to which previously, are to be rolled back. + 3. Remove the NSG configuration from the Subnet(s) in the Subscription. + +# 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 NSG on the Subnet(s) in the 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 NSG configuration on the Subnet(s) in the Subscription. Refer `Examples`, below. + +# Examples: + To remediate: + 1. To review the Subnet(s) in a Subscription that will be remediated: + + File has already been generated using the previous script. + + 2. Configure the NSG on the Subnet(s)(s) in the Subscription: + + Add-NSGConfigurationOnSubnet -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck + + 3. Configure the NSG on the Subnet(s) in the Subscription, from a previously taken snapshot: + + Add-NSGConfigurationOnSubnet -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\ConfigureNSG\SubnetDetailsBackUp.csv + + To know more about the options supported by the remediation command, execute: + + Get-Help Add-NSGConfigurationOnSubnet -Detailed + + To roll back: + 1. Remove the NSG configuration on the Subnet(s) in the Subscription, from a previously taken snapshot: + Remove-NSGConfigurationOnSubnet -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\DisableDDoSProtectionPlan\RemediatedSubnetDetails.csv + + To know more about the options supported by the roll back command, execute: + + Get-Help Remove-NSGConfigurationOnSubnet -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.Network") + + 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) + } + } + Write-Host "$($_) module is not present." -ForegroundColor $([Constants]::MessageType.Warning) +} + + +function Add-NSGConfigurationOnSubnet +{ + <# + .SYNOPSIS + Remediates 'Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration_Trial' Control. + + .DESCRIPTION + Remediates 'Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration_Trial' Control. + Add the NSG configuration on the Subnet(s) in the Subscription. + + .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 FilePath + Specifies the path to the file to be used as input for the remediation. + + .INPUTS + None. You cannot pipe objects to Add-NSGConfigurationOnSubnet. + + .OUTPUTS + None. Add-NSGConfigurationOnSubnet does not return anything that can be piped and used as an input to another command. + + .EXAMPLE + PS> Add-NSGConfigurationOnSubnet -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck + + .EXAMPLE + PS> Add-NSGConfigurationOnSubnet -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\ConfigureNSG\SubnetDetailsBackUp.csv + + .LINK + None + #> + + param ( + [String] + [Parameter(ParameterSetName = "WetRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] + $SubscriptionId, + + [Switch] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies validation of prerequisites for the command")] + $PerformPreReqCheck, + + [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." -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 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..." -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 the NSG on the Subnet in a Subscription, Contributor or higher privileged role assignment on the Virtual Network(s) is required." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + + Write-Host "[Step 2 of 4] Fetch all Subnets(s)" + Write-Host $([Constants]::SingleDashLine) + + # list to store Container details. + $SubnetDetails = @() + + # To keep track of remediated and skipped resources + $logRemediatedResources = @() + $logSkippedResources=@() + + # Control Id + $controlIds = "Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration_Trial" + + + + if (-not (Test-Path -Path $FilePath)) + { + Write-Host "ERROR: Input file: [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + return + } + + Write-Host "Fetch all Subnet(s) from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + $SubnetResources = Import-Csv -LiteralPath $FilePath + + $validSubnetResources = $SubnetResources| Where-Object { ![String]::IsNullOrWhiteSpace($_.ResourceId) } + + $validSubnetResources| ForEach-Object { + $resourceId = $_.ResourceId + + try + { + $VNetResource = Get-AzVirtualNetwork -ResourceGroupName $_.ResourceVirtualNetworkRGName -Name $_.ResourceVirtualNetworkName -ErrorAction SilentlyContinue + $SubnetResource = Get-AzVirtualNetworkSubnetConfig -VirtualNetwork $VNetResource -Name $_.ResourceSubNetName + $SubnetDetails += $SubnetResource | Select-Object @{N='ResourceId';E={$_.Id}}, + @{N='ResourceGroupName';E={$_.Id.Split("/")[4]}}, + @{N='ResourceName';E={$_.Name}}, + @{N='ResourceVirtualNetworkName';E={$_.Id.Split("/")[8]}}, + @{N='IsNSGConfigured';E={ + if($_.NetworkSecurityGroup -eq $null) + { + $false + } + else + { + $true + } + }} + + + } + catch + { + Write-Host "Error fetching subnet of Virtual Network(s) resource: Resource ID: [$($ResourceVNetName)]. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + } + + } + + + + $totalSubnet = ($SubnetDetails| Measure-Object).Count + + if ($totalSubnet -eq 0) + { + Write-Host "No Subnet(s) found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::DoubleDashLine) + return + } + + Write-Host "Found [$($totalSubnet)] Subnet(s)." -ForegroundColor $([Constants]::MessageType.Update) + + Write-Host $([Constants]::SingleDashLine) + + # list for storing Subnet(s) for which NSG is not configured. + $SubnetWithoutNSGConfigured = @() + + Write-Host "Separating Subnet(s) for which NSG is not configured..." -ForegroundColor $([Constants]::MessageType.Info) + + $SubnetDetails | ForEach-Object { + $Subnet = $_ + if($_.IsNSGConfigured -eq $false) + { + $SubnetWithoutNSGConfigured += $Subnet + } + } + + $totalSubnetWithoutNSGConfigured = ($SubnetWithoutNSGConfigured | Measure-Object).Count + + if ($totalSubnetWithoutNSGConfigured -eq 0) + { + Write-Host "No Subnet(s) found with where NSG is not configured.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + return + } + + Write-Host "Found [$($totalSubnetWithoutNSGConfigured)] Subnet(s) for which NSG is not configured ." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + + $colsProperty = @{Expression={$_.ResourceName};Label="ResourceName";Width=30;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="ResourceGroupName";Width=30;Alignment="left"}, + @{Expression={$_.ResourceId};Label="ResourceId";Width=100;Alignment="left"}, + @{Expression={$_.VirtualNetworkName};Label="VirtualNetworkName";Width=100;Alignment="left"} + @{Expression={$_.IsNSGConfigured};Label="IsNSGConfigured";Width=100;Alignment="left"} + + if(-not $AutoRemediation) + { + Write-Host "Subnet(s) without NSG configuration are as follows:" + $SubnetWithoutNSGConfigured | 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'))\ConfiguredNSGOnSubnet" + + if (-not (Test-Path -Path $backupFolderPath)) + { + New-Item -ItemType Directory -Path $backupFolderPath | Out-Null + } + + Write-Host "[Step 3 of 4] Back up Subnet(s) details..." + Write-Host $([Constants]::SingleDashLine) + + if ([String]::IsNullOrWhiteSpace($FilePath)) + { + # Backing up Subnet(s) details. + $backupFile = "$($backupFolderPath)\SubnetDetailsBackUp.csv" + $SubnetWithoutNSGConfigured | Export-CSV -Path $backupFile -NoTypeInformation + Write-Host "Subnet(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] Enable the DDoS Protection Plan on Subnet(s) in the Subscription..." + Write-Host $([Constants]::SingleDashLine) + + + if (-not $Force) + { + Write-Host "Do you want to configure NSG on the Subnet(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 NSG on the Subnet(s) in the Subscription. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + return + } + } + + + # List for storing remediated Subnet(s) + $SubnetRemediated = @() + + # List for storing skipped Subnet(s) + $SubnetSkipped = @() + + Write-Host "Enabling the NSG on Subnet(s)..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + + # Loop through the list of Subnet(s) which needs to be remediated. + $SubnetWithoutNSGConfigured | ForEach-Object { + $subnet = $_ + try + { + + Write-Host "To Start configuring the NSG on the Subnet(s), Please enter the Network Security Group Name..." -ForegroundColor $([Constants]::MessageType.Info) + $NSGName = Read-Host -Prompt "Please enter name of Network Security Group" + $NSGRGName = Read-Host -Prompt "Please enter Resource Group of Network Security Group" + if($NSGName -ne $null -and $NSGRGName -ne $null) + { + $nsg = Get-AzNetworkSecurityGroup -ResourceGroupName $NSGRGName -Name $NSGName + if($NSGName -ne $null) + { + + $vnet = Get-AzVirtualNetwork -Name $_.ResourceVirtualNetworkName -ResourceGroupName $_.ResourceGroupName + $vNetSubnet = Get-AzVirtualNetworkSubnetConfig -VirtualNetwork $Vnet -Name $_.ResourceName + $vNetSubnet.NetworkSecurityGroup = $nsg + # $remediatedVNet = Set-AzVirtualNetwork -VirtualNetwork $vnet + $remediatedVnet = $vnet | Set-AzVirtualNetwork + $remediatedSubnet = Get-AzVirtualNetworkSubnetConfig -VirtualNetwork $remediatedVnet -Name $_.ResourceName + + if($remediatedSubnet.NetworkSecurityGroup -ne $null) + { + $subnet.IsNSGConfigured = $true + $SubnetRemediated += $subnet + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.ResourceName)) + $logRemediatedResources += $logResource + } + else + { + $SubnetSkipped += $subnet + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.ResourceName)) + $logResource.Add("Reason", "Error Configuring NSG on : [$($subnet)]") + $logSkippedResources += $logResource + + } + } + else + { + $SubnetSkipped += $subnet + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.ResourceName)) + $logResource.Add("Reason", "Error Configuring NSG on : [$($subnet)]") + $logSkippedResources += $logResource + } + } + else + { + Write-Host "Network Security Group Name or Resource Group can not be empty..." -ForegroundColor $([Constants]::MessageType.Info) + $SubnetSkipped += $subnet + return; + } + + + + + + } + catch + { + $SubnetSkipped += $subnet + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.ResourceName)) + $logResource.Add("Reason","Encountered error Enabling DDoS Plan") + $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 ($($SubnetRemediated | Measure-Object).Count -gt 0) + { + Write-Host "Successfully configured the NSG on the Suvbnet(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + $SubnetRemediated | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $SubnetRemediatedFile = "$($backupFolderPath)\RemediatedSubnets.csv" + $SubnetRemediated | Export-CSV -Path $SubnetRemediatedFile -NoTypeInformation + + Write-Host "This information has been saved to" -NoNewline + Write-Host " [$($SubnetRemediatedFile)]" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) + } + + + + if ($($SubnetSkipped | Measure-Object).Count -gt 0) + { + + Write-Host "Error while configuring NSG on the subnet(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::SingleDashLine) + $SubnetSkipped | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $SubnetSkippedFile = "$($backupFolderPath)\SkippedSubnet.csv" + $SubnetSkipped | Export-CSV -Path $SubnetSkippedFile -NoTypeInformation + Write-Host "This information has been saved to" -NoNewline + Write-Host " [$($SubnetSkippedFile)]" -ForegroundColor $([Constants]::MessageType.Update) + } + + + } + +} + +function Remove-NSGConfigurationOnSubnet +{ + <# + .SYNOPSIS + Rolls back remediation done for 'Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration_Trial' Control. + + .DESCRIPTION + Rolls back remediation done for 'Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration_Trial' Control. + Remove NSG configuration from the subnet(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-NSGConfigurationOnSubnet. + + .OUTPUTS + None. Remove-NSGConfigurationOnSubnet does not return anything that can be piped and used as an input to another command. + + .EXAMPLE + PS> Remove-NSGConfigurationOnSubnet -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\RemoveNSGConfiguration\RemediatedSubnets.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 NSG configuration from the Subnet(s) in a Subscription, Contributor or higher privileged role assignment on the Virtual Network(s) is required." -ForegroundColor $([Constants]::MessageType.Warning) + + Write-Host $([Constants]::SingleDashLine) + Write-Host "[Step 2 of 3] Prepare to fetch all Subnet(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 Subnet(s) from" -NoNewline + Write-Host " [$($FilePath)\...]..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + $SubnetDetails = Import-Csv -LiteralPath $FilePath + + $validSubnetDetails = $SubnetDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.ResourceId) -and ![String]::IsNullOrWhiteSpace($_.ResourceGroupName) -and ![String]::IsNullOrWhiteSpace($_.ResourceName) } + + $totalSubnet = $(($validSubnetDetails|Measure-Object).Count) + + if ($totalSubnet -eq 0) + { + Write-Host "No Subnet(s) found. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + return + } + + Write-Host "Found [$(($validSubnetDetails|Measure-Object).Count)] Subnet(s)." -ForegroundColor $([Constants]::MessageType.Update) + + $colsProperty = @{Expression={$_.ResourceName};Label="ResourceName";Width=30;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="ResourceGroupName";Width=30;Alignment="left"}, + @{Expression={$_.ResourceId};Label="ResourceId";Width=100;Alignment="left"}, + @{Expression={$_.VirtualNetworkName};Label="VirtualNetworkName";Width=100;Alignment="left"} + @{Expression={$_.ResourceId};Label="IsNSGConfigured";Width=100;Alignment="left"}, + + + $validSubnetDetails | 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'))\RemoveNSGfromSubnet" + + 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 NSG Configuration from all remediated Subnet(s) in the Subscription" + Write-Host $([Constants]::SingleDashLine) + + if( -not $Force) + { + + Write-Host "Do you want to remove NSG Configuration from Subnet(s) mentioned in the file?" -ForegroundColor $([Constants]::MessageType.Warning) + $userInput = Read-Host -Prompt "(Y|N)" + + if($userInput -ne "Y") + { + Write-Host "NSG Configuration will not be rolled back on Subnet(s) mentioned in the file. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + return + } + Write-Host "NSG Configuration will be rolled back on Subnet(s) mentioned in the file." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + else + { + Write-Host "'Force' flag is provided. NSG Configuration will be rolled back on Subnet(s) in the Subscription without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + + # List for storing rolled back Subnet resource. + $SubnetRolledBack = @() + + # List for storing skipped rolled back Subnet resource. + $SubnetSkipped = @() + + + $validSubnetDetails | ForEach-Object { + $Subnet = $_ + try + { + + $vnet = Get-AzVirtualNetwork -Name $_.ResourceVirtualNetworkName -ResourceGroupName $_.ResourceGroupName + $VnetSubnet = Get-AzVirtualNetworkSubnetConfig -VirtualNetwork $Vnet -Name $_.ResourceName + $VnetSubnet.NetworkSecurityGroup = $null + # $remediatedVNet = Set-AzVirtualNetwork -VirtualNetwork $vnet + $remediatedVnet = $vnet | Set-AzVirtualNetwork + $remediatedSubnet = Get-AzVirtualNetworkSubnetConfig -VirtualNetwork $remediatedVnet -Name $_.ResourceName + + if($remediatedSubnet.NetworkSecurityGroup -eq $null) + { + $Subnet.IsNSGConfigured = $false + $SubnetRolledBack += $Subnet + } + else + { + $SubnetSkipped += $Subnet + } + + } + catch + { + $SubnetSkipped += $Subnet + } + } + + + Write-Host $([Constants]::DoubleDashLine) + Write-Host "Rollback Summary:`n" -ForegroundColor $([Constants]::MessageType.Info) + + if ($($SubnetRolledBack | Measure-Object).Count -gt 0) + { + Write-Host "NSG configuration has been removed on the following subnet(s) in the Subscription.:" -ForegroundColor $([Constants]::MessageType.Update) + $SubnetRolledBack | Format-Table -Property $colsProperty -Wrap + Write-Host $([Constants]::SingleDashLine) + + # Write this to a file. + $SubnetRolledBackFile = "$($backupFolderPath)\RolledBackSubnet.csv" + $SubnetRolledBack | Export-CSV -Path $$SubnetRolledBackFile -NoTypeInformation + Write-Host "This information has been saved to" -NoNewline + Write-Host " [$($SubnetRolledBackFile)]" -ForegroundColor $([Constants]::MessageType.Update) + } + + if ($($SubnetSkipped | Measure-Object).Count -gt 0) + { + Write-Host "Error while removing NSG configuration on the Subnet(s) in the Subscription.:" -ForegroundColor $([Constants]::MessageType.Error) + $SubnetSkipped | Format-Table -Property $colsProperty -Wrap + Write-Host $([Constants]::SingleDashLine) + + + # Write this to a file. + $SubnetSkippedFile = "$($backupFolderPath)\RollbackSkippedSubnet.csv" + $SubnetSkipped | Export-CSV -Path $SubnetSkippedFile -NoTypeInformation + Write-Host "This information has been saved to" -NoNewline + Write-Host " [$($SubnetSkippedFile)]" -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 = "------------------------------------------------------------------------------------------------------------------------" +} diff --git a/Scripts/RemediationScripts/Remediate-EnableDDOSVirtualNetwork.ps1 b/Scripts/RemediationScripts/Remediate-EnableDDOSVirtualNetwork.ps1 new file mode 100644 index 00000000..039826b3 --- /dev/null +++ b/Scripts/RemediationScripts/Remediate-EnableDDOSVirtualNetwork.ps1 @@ -0,0 +1,756 @@ +<### +# Overview: + This script is used to enable the DDOS on virtual network available in the App Gateway in a Subscription. + +# Control ID: + Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration_Trial + +# Display Name: + DDoS must be configured. + +# Prerequisites: + Owner or higher priviliged role on the Virtual Network(s) is required for remediation. + +# Steps performed by the script: + To remediate: + 1. Validating and installing the modules required to run the script and validating the user. + 2. Get the list of Virtual Network(s) in a Subscription that have DDoS Protection Plan is not enabled. + 3. Back up details of Virtual Network(s) that are to be remediated. + 4. Enable the DDoS Protection Plan on the Virtual Network(s) in the Subscription. + + To roll back: + 1. Validate and install the modules required to run the script and validating the user. + 2. Get the list of Virtual Network(s) in a Subscription, the changes made to which previously, are to be rolled back. + 3. Disable the DDoS Protection Plan on the Virtual Network(s) in the Subscription. + +# 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 the DDoS Protection Plan on the Virtual Network(s) in the 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 the DDoS Protection Plan on the Virtual Network(s) in the Subscription. Refer `Examples`, below. + +# Examples: + To remediate: + 1. To review the Virtual Network(s) in a Subscription that will be remediated: + + File has already been generated using the previous script. + + 2. Enable the DDoS Protection Plan on the Virtual Network(s)(s) in the Subscription: + + Enable-DDoSProtectionPlanOnVirtualNetwork -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck + + 3. Enable the DDoS Protection Plan on the Virtual Network(s) in the Subscription, from a previously taken snapshot: + + Enable-DDoSProtectionPlanOnVirtualNetwork -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\EnableDDoSProtectionPlan\VirtualNetworkDetailsBackUp.csv + + To know more about the options supported by the remediation command, execute: + + Get-Help Enable-DDoSProtectionPlanOnVirtualNetwork -Detailed + + To roll back: + 1. Disable the DDoS Protection Plan on the Virtual Network(s) in the Subscription, from a previously taken snapshot: + Disable-DDoSProtectionPlanOnVirtualNetwork -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\DisableDDoSProtectionPlan\RemediatedVirtualNetworkDetails.csv + + To know more about the options supported by the roll back command, execute: + + Get-Help Disable-DDoSProtectionPlanOnVirtualNetwork -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.Network") + + 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) + } + } + Write-Host "$($_) module is not present." -ForegroundColor $([Constants]::MessageType.Warning) +} + + +function Enable-DDoSProtectionPlanOnVirtualNetwork +{ + <# + .SYNOPSIS + Remediates 'Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration_Trial' Control. + + .DESCRIPTION + Remediates 'Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration_Trial' Control. + Enable the DDoS Protection Plan on the Virtual Network(s) in the Subscription. + + .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 FilePath + Specifies the path to the file to be used as input for the remediation. + + .INPUTS + None. You cannot pipe objects to Enable-DDoSProtectionPlanOnVirtualNetwork. + + .OUTPUTS + None. Enable-DDoSProtectionPlanOnVirtualNetwork does not return anything that can be piped and used as an input to another command. + + .EXAMPLE + PS> Enable-DDoSProtectionPlanOnVirtualNetwork -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck + + .EXAMPLE + PS> Enable-DDoSProtectionPlanOnVirtualNetwork -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\EnableDDoSProtectionPlan\VirtualNetworkDetailsBackUp.csv + + .LINK + None + #> + + param ( + [String] + [Parameter(ParameterSetName = "WetRun", Mandatory = $true, HelpMessage="Specifies the ID of the Subscription to be remediated")] + $SubscriptionId, + + [Switch] + [Parameter(ParameterSetName = "WetRun", HelpMessage="Specifies validation of prerequisites for the command")] + $PerformPreReqCheck, + + [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." -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 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..." -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 the DDOS on the Virtual Network in a Subscription, Contributor or higher privileged role assignment on the Virtual Network(s) is required." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + + Write-Host "[Step 2 of 4] Fetch all Virtual Network(s)" + Write-Host $([Constants]::SingleDashLine) + + # list to store Container details. + $VirtualNetworkDetails = @() + + # To keep track of remediated and skipped resources + $logRemediatedResources = @() + $logSkippedResources=@() + + # Control Id + $controlIds = "Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration_Trial" + + + + if (-not (Test-Path -Path $FilePath)) + { + Write-Host "ERROR: Input file: [$($FilePath)] not found. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::DoubleDashLine) + return + } + + Write-Host "Fetch all Virtual Network(s) from [$($FilePath)]..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + $VirtualNetworkResources = Import-Csv -LiteralPath $FilePath + + $validVirtualNetworkResources = $VirtualNetworkResources| Where-Object { ![String]::IsNullOrWhiteSpace($_.ResourceId) } + + $validVirtualNetworkResources| ForEach-Object { + $resourceId = $_.ResourceId + + try + { + $VirtualNetworkResource = Get-AzVirtualNetwork -ResourceGroupName $_.ResourceVNetRGName -Name $_.ResourceVNetName -ErrorAction SilentlyContinue + + $VirtualNetworkDetails += $VirtualNetworkResource | Select-Object @{N='ResourceId';E={$_.Id}}, + @{N='ResourceGroupName';E={$_.Id.Split("/")[4]}}, + @{N='ResourceName';E={$_.Name}}, + @{N='IsDDOSEnabled';E={$_.EnableDdosProtection}} + + + } + catch + { + Write-Host "Error fetching Virtual Network(s) resource: Resource ID: [$($ResourceVNetName)]. Error: $($_)" -ForegroundColor $([Constants]::MessageType.Error) + } + + } + + + + $totalVirtualNetwork = ($VirtualNetworkDetails| Measure-Object).Count + + if ($totalVirtualNetwork -eq 0) + { + Write-Host "No Virtual Network(s) found. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::DoubleDashLine) + return + } + + Write-Host "Found [$($totalVirtualNetwork)] Virtual Network(s)." -ForegroundColor $([Constants]::MessageType.Update) + + Write-Host $([Constants]::SingleDashLine) + + # list for storing Virtual Network(s) for which DDoS Protection Plan is not enabled. + $VirtualNetworkWithoutDDoSEnabled = @() + + Write-Host "Separating Virtual Network(s) for which DDoS is not enabled..." -ForegroundColor $([Constants]::MessageType.Info) + + $VirtualNetworkDetails | ForEach-Object { + $VirtualNetwork = $_ + if($_.IsDDOSEnabled -eq $false) + { + $VirtualNetworkWithoutDDoSEnabled += $VirtualNetwork + } + } + + $totalVirtualNetworkWithoutDDoSEnabled = ($VirtualNetworkWithoutDDoSEnabled | Measure-Object).Count + + if ($totalVirtualNetworkWithoutDDoSEnabled -eq 0) + { + Write-Host "No Virtual Network(s) found with where DDOS is not enabled.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + return + } + + Write-Host "Found [$($totalVirtualNetworkWithoutDDoSEnabled)] Virtual Network(s) for which DDoS is not enabled ." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + + $colsProperty = @{Expression={$_.ResourceName};Label="ResourceName";Width=30;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="ResourceGroupName";Width=30;Alignment="left"}, + @{Expression={$_.ResourceId};Label="ResourceId";Width=100;Alignment="left"}, + @{Expression={$_.IsDDOSEnabled};Label="IsDDOSEnabled";Width=100;Alignment="left"} + + if(-not $AutoRemediation) + { + Write-Host "Virtual Network(s) without DDOS enabled are as follows:" + $VirtualNetworkWithoutDDoSEnabled | 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'))\EnableDDoSProtectionOnVNet" + + if (-not (Test-Path -Path $backupFolderPath)) + { + New-Item -ItemType Directory -Path $backupFolderPath | Out-Null + } + + Write-Host "[Step 3 of 4] Back up Virtual Network(s) details..." + Write-Host $([Constants]::SingleDashLine) + + if ([String]::IsNullOrWhiteSpace($FilePath)) + { + # Backing up Virtual Network(s) details. + $backupFile = "$($backupFolderPath)\VirtualNetworkDetailsBackUp.csv" + $VirtualNetworkWithoutDDoSEnabled | Export-CSV -Path $backupFile -NoTypeInformation + Write-Host "Virtual Network(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] Enable the DDoS Protection Plan on Virtual Network(s) in the Subscription..." + Write-Host $([Constants]::SingleDashLine) + + + if (-not $Force) + { + Write-Host "Do you want to enable DDoS Protection Plan on the Virtual Network(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 enable the DDoS Protection Plan on Virtual Network(s) in the Subscription. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + return + } + } + + + # List for storing remediated Virtual Network(s) + $VirtualNetworkRemediated = @() + + # List for storing skipped Virtual Network(s) + $VirtualNetworkSkipped = @() + + Write-Host "Enabling the DDoS on Virtual Network(s)..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + + # Loop through the list of Virtual Network(s) which needs to be remediated. + $VirtualNetworkWithoutDDoSEnabled | ForEach-Object { + $VirtualNetwork = $_ + try + { + + Write-Host "To Start enabling the DDoS on Virtual Network(s), Please enter the DDoS Protection Plan Name..." -ForegroundColor $([Constants]::MessageType.Info) + + # Ask about the DDoS Plan Name + $DDoSPlanName = Read-Host -Prompt "Please enter DDoS Plan Name" + # Ask about the DDoS Plan RG Name + $DDoSPlanRGName = Read-Host -Prompt "Please enter the name of Resource Group where this DDoS Plan is available" + if($DDoSPlanName -ne $null -and $DDoSPlanRGName -ne $null) + { + + $ddosProtectionPlanID = Get-AzDdosProtectionPlan -Name $DDoSPlanName -ResourceGroupName $DDoSPlanRGName + if($DDoSPlanName -ne $null -and $DDoSPlanRGName -ne $null) + { + $vnet = Get-AzVirtualNetwork -Name $_.ResourceName -ResourceGroupName $_.ResourceGroupName + $vnet.DdosProtectionPlan = New-Object Microsoft.Azure.Commands.Network.Models.PSResourceId + + # Update the properties and enable DDoS protection + $vnet.DdosProtectionPlan.Id = $ddosProtectionPlanID.Id + $vnet.EnableDdosProtection = $true + $vnet = Set-AzVirtualNetwork -VirtualNetwork $vnet + # $vnet | Set-AzVirtualNetwork + } + else + { + Write-Host "Could not find the DDoS Plan with the given details..." -ForegroundColor $([Constants]::MessageType.Info) + $VirtualNetworkSkipped += $VirtualNetwork + return + } + + } + else + { + Write-Host "DDoS Protection Plan Name/RG Name can not be empty..." -ForegroundColor $([Constants]::MessageType.Info) + $VirtualNetworkSkipped += $VirtualNetwork + return + } + + + + if($vnet.EnableDdosProtection -eq $true) + { + $VirtualNetwork.IsDDOSEnabled = $true + $VirtualNetworkRemediated += $VirtualNetwork + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.ResourceName)) + $logRemediatedResources += $logResource + } + else + { + $VirtualNetworkSkipped += $VirtualNetwork + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.ResourceName)) + $logResource.Add("Reason", "Error Enabling the DDoS Plan: [$($VirtualNetwork)]") + $logSkippedResources += $logResource + + } + + } + catch + { + $VirtualNetworkSkipped += $VirtualNetwork + $logResource = @{} + $logResource.Add("ResourceGroupName",($_.ResourceGroupName)) + $logResource.Add("ResourceName",($_.ResourceName)) + $logResource.Add("Reason","Encountered error Enabling DDoS Plan") + $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 ($($VirtualNetworkRemediated | Measure-Object).Count -gt 0) + { + Write-Host "Successfully enabled the DDoS on Virtual Network(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + $VirtualNetworkRemediated | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $VirtualNetworkRemediatedFile = "$($backupFolderPath)\RemediatedVirtualNetwork.csv" + $VirtualNetworkRemediated | Export-CSV -Path $VirtualNetworkRemediatedFile -NoTypeInformation + + Write-Host "This information has been saved to" -NoNewline + Write-Host " [$($VirtualNetworkRemediatedFile)]" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host "Use this file for any roll back that may be required." -ForegroundColor $([Constants]::MessageType.Info) + } + + + + if ($($VirtualNetworkSkipped | Measure-Object).Count -gt 0) + { + + Write-Host "Error while enabling DDoS Protection Plan On Virtual Network(s) in the subscription:" -ForegroundColor $([Constants]::MessageType.Error) + Write-Host $([Constants]::SingleDashLine) + $VirtualNetworkSkipped | Format-Table -Property $colsProperty -Wrap + + # Write this to a file. + $VirtualNetworkSkippedFile = "$($backupFolderPath)\SkippedVirtualNetwork.csv" + $VirtualNetworkSkipped | Export-CSV -Path $VirtualNetworkSkippedFile -NoTypeInformation + Write-Host "This information has been saved to" -NoNewline + Write-Host " [$($VirtualNetworkSkippedFile)]" -ForegroundColor $([Constants]::MessageType.Update) + } + + + } +} + +function Disable-DDoSProtectionPlanOnVirtualNetwork +{ + <# + .SYNOPSIS + Rolls back remediation done for 'Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration_Trial' Control. + + .DESCRIPTION + Rolls back remediation done for 'Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration_Trial' Control. + Disable DDoS Protecion Plan on Virtual Network(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-DDoSProtectionPlanOnVirtualNetwork. + + .OUTPUTS + None. Disable-DDoSProtectionPlanOnVirtualNetwork does not return anything that can be piped and used as an input to another command. + + .EXAMPLE + PS> Disable-DDoSProtectionPlanOnVirtualNetwork -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -FilePath C:\AzTS\Subscriptions\00000000-xxxx-0000-xxxx-000000000000\202109131040\EnableDDoSOnVNet\RemediatedVirtualNetwork.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 + ) + + 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 disable DDoS Protection Plan on Virtual Network(s) in a Subscription, Contributor or higher privileged role assignment on the Virtual Network(s) is required." -ForegroundColor $([Constants]::MessageType.Warning) + + Write-Host $([Constants]::SingleDashLine) + Write-Host "[Step 2 of 3] Prepare to fetch all Virtual Network(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 "Fetching all Virtual Network(s) from" -NoNewline + Write-Host " [$($FilePath)\...]..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + $VirtualNetworkDetails = Import-Csv -LiteralPath $FilePath + + $validVirtualNetworkDetails = $VirtualNetworkDetails | Where-Object { ![String]::IsNullOrWhiteSpace($_.ResourceId) -and ![String]::IsNullOrWhiteSpace($_.ResourceGroupName) -and ![String]::IsNullOrWhiteSpace($_.ResourceName) } + + $totalVirtualNetwork = $(($validVirtualNetworkDetails|Measure-Object).Count) + + if ($totalVirtualNetwork -eq 0) + { + Write-Host "No Virtual Network(s) found. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + return + } + + Write-Host "Found [$(($validVirtualNetworkDetails|Measure-Object).Count)] Virtual Network(s)." -ForegroundColor $([Constants]::MessageType.Update) + + $colsProperty = @{Expression={$_.ResourceName};Label="ResourceName";Width=30;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="ResourceGroupName";Width=30;Alignment="left"}, + @{Expression={$_.ResourceId};Label="ResourceId";Width=100;Alignment="left"}, + @{Expression={$_.IsDDOSEnabled};Label="IsDDOSEnabled";Width=100;Alignment="left"} + + + $validVirtualNetworkDetails | 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'))\DisableDDoSOnVNet" + + if (-not (Test-Path -Path $backupFolderPath)) + { + New-Item -ItemType Directory -Path $backupFolderPath | Out-Null + } + + + Write-Host $([Constants]::DoubleDashLine) + Write-Host "[Step 3 of 3] Disable DDoS Protection Plan on all remediated Virtual Network(s) in the Subscription" + Write-Host $([Constants]::SingleDashLine) + + if( -not $Force) + { + + Write-Host "Do you want to disable DDoS Protection Plan on Virtual Network(s) mentioned in the file?" -ForegroundColor $([Constants]::MessageType.Warning) + $userInput = Read-Host -Prompt "(Y|N)" + + if($userInput -ne "Y") + { + Write-Host "DDoS Protection Plan will not be rolled back on Virtual Network(s) mentioned in the file. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + return + } + Write-Host "DDoS Protection Plan will be rolled back on Virtual Network(s) mentioned in the file." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + else + { + Write-Host "'Force' flag is provided. DDoS Protection Plan will be rolled back on Virtual Network(s) in the Subscription without any further prompts." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + } + + # List for storing rolled back Virtual Network resource. + $VirtualNetworkRolledBack = @() + + # List for storing skipped rolled back Virtual Network resource. + $VirtualNetworkSkipped = @() + + + $validVirtualNetworkDetails | ForEach-Object { + $VirtualNetwork = $_ + try + { + + $vnet = Get-AzVirtualNetwork -ResourceGroupName $_.ResourceGroupName -Name $_.ResourceName -ErrorAction SilentlyContinue + $vnet.DdosProtectionPlan = $null + $vnet.EnableDdosProtection = $false + $vnet = Set-AzVirtualNetwork -VirtualNetwork $vnet + # $vnet | Set-AzVirtualNetwork + + if($vnet.EnableDdosProtection -eq $false) + { + $VirtualNetwork.IsDDOSEnabled = $false + $VirtualNetworkRolledBack += $VirtualNetwork + } + else + { + $VirtualNetworkSkipped += $VirtualNetwork + } + + } + catch + { + $VirtualNetworkSkipped += $VirtualNetwork + } + } + + + Write-Host $([Constants]::DoubleDashLine) + Write-Host "Rollback Summary:`n" -ForegroundColor $([Constants]::MessageType.Info) + + if ($($VirtualNetworkRolledBack | Measure-Object).Count -gt 0) + { + Write-Host "DDoS Protection Plan has been disabled on the following Virtual Network(s) in the Subscription.:" -ForegroundColor $([Constants]::MessageType.Update) + $VirtualNetworkRolledBack | Format-Table -Property $colsProperty -Wrap + Write-Host $([Constants]::SingleDashLine) + + # Write this to a file. + $VirtualNetworkRolledBackFile = "$($backupFolderPath)\RolledBackVirtualNetwork.csv" + $VirtualNetworkRolledBack | Export-CSV -Path $VirtualNetworkRolledBackFile -NoTypeInformation + Write-Host "This information has been saved to" -NoNewline + Write-Host " [$($VirtualNetworkRolledBackFile)]" -ForegroundColor $([Constants]::MessageType.Update) + } + + if ($($VirtualNetworkSkipped | Measure-Object).Count -gt 0) + { + Write-Host "Error while disabling DDoS Protection Plan on Virtual Network(s) in the Subscription.:" -ForegroundColor $([Constants]::MessageType.Error) + $VirtualNetworkSkipped | Format-Table -Property $colsProperty -Wrap + Write-Host $([Constants]::SingleDashLine) + + + # Write this to a file. + $VirtualNetworkSkippedFile = "$($backupFolderPath)\RollbackSkippedVirtualNetwork.csv" + $VirtualNetworkSkipped | Export-CSV -Path $VirtualNetworkSkippedFile -NoTypeInformation + Write-Host "This information has been saved to" -NoNewline + Write-Host " [$($VirtualNetworkSkippedFile)]" -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 = "------------------------------------------------------------------------------------------------------------------------" +} diff --git a/Scripts/RemediationScripts/Remediate-RetrieveSubnetsOfAppGateway.ps1 b/Scripts/RemediationScripts/Remediate-RetrieveSubnetsOfAppGateway.ps1 new file mode 100644 index 00000000..377de764 --- /dev/null +++ b/Scripts/RemediationScripts/Remediate-RetrieveSubnetsOfAppGateway.ps1 @@ -0,0 +1,363 @@ +<### +# Overview: + This script is used to get the subnet(s) of Application Gateway which are not part of NetworkSecurityGroup in a Subscription. + +# Control ID: + Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration_Trial + +# Display Name: + Get the Data of Subnets where NSG is not configured. + +# Prerequisites: + Owner or higher priviliged role on the Application Gateway(s) is required. + +# Steps performed by the script: + To Retrieve the list of Subnet(s): + 1. Validating and installing the modules required to run the script and validating the user. + 2. Get the list of Application Gateway(s) in a Subscription that have subnets where NSG is not configured. + 3. Back up details of Application Gateway(s) that are to be remediated. + 4. Configure the NSG on the subnet associated in the Application Gateway(s) in the Subscription. + +# 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 get the list of subnets of Application Gateway(s) in the Subscription. Refer `Examples`, below. + +# Examples: + To remediate: + 1. To review the list of Subnets of Application Gateway(s) in a Subscription: + + Retrieve-ApplicationGatewaySubnetNSGNotConfigured -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun + + To know more about the options supported by the remediation command, execute: + + Get-Help Retrieve-ApplicationGatewaySubnetNSGNotConfigured -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.Network") + + 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) + } + } + Write-Host "$($_) module is not present." -ForegroundColor $([Constants]::MessageType.Warning) +} + + +function Retrieve-ApplicationGatewaySubnetNSGNotConfigured +{ + <# + .SYNOPSIS + Remediates 'Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration_Trial' Control. + + .DESCRIPTION + Remediates 'Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration_Trial' Control. + Get the list of subnets of Application Gateway(s) in the Subscription. + + .PARAMETER SubscriptionId + Specifies the ID of the Subscription to be remediated. + + .Parameter PerformPreReqCheck + Specifies validation of prerequisites for the command. + + .PARAMETER DryRun + Specifies a dry run of the actual remediation. + + .INPUTS + None. You cannot pipe objects to Retrieve-ApplicationGatewaySubnetNSGNotConfigured. + + .OUTPUTS + None. Retrieve-ApplicationGatewaySubnetNSGNotConfigured does return the list of Subnets that can be piped and used as an input to another script where NSG would be configured for these Subnet(s). + + .EXAMPLE + PS> Retrieve-ApplicationGatewaySubnetNSGNotConfigured -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun + + .EXAMPLE + PS> Retrieve-ApplicationGatewaySubnetNSGNotConfigured -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck + + .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 = "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 + ) + + 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." -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 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..." -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 get the data from Application Gateway(s) in a Subscription, Contributor or higher privileged role assignment on the Application Gateway(s) is required." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + + Write-Host "[Step 2 of 4] Fetch all Application Gateway(s)" + Write-Host $([Constants]::SingleDashLine) + + # list to store Container details. + $ApplicationGatewayDetails = @() + + # To keep track of remediated and skipped resources + $logRemediatedResources = @() + $logSkippedResources=@() + + # Control Id + $controlIds = "Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration_Trial" + + + # No file path provided as input to the script. Fetch all Application Gateway(s) in the Subscription. + + + + Write-Host "Fetching all Application Gateway(s) in Subscription: [$($context.Subscription.SubscriptionId)]" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + + # Get all Application Gateway(s) in a Subscription + $ApplicationGatewayDetails = Get-AzApplicationGateway -ErrorAction Stop + + # Seperating required properties + $ApplicationGatewayDetails = $ApplicationGatewayDetails | Select-Object @{N='ResourceId';E={$_.Id}}, + @{N='ResourceGroupName';E={$_.Id.Split("/")[4]}}, + @{N='ResourceName';E={$_.Name}}, + @{N='ResourceSubNetId';E={$_.GatewayIPConfigurations.SubnetText}}, + @{N='ResourceSubNetName';E={ + + $subnetDetails = $_.GatewayIPConfigurations.SubnetText | ConvertFrom-Json + $subnetDetails.Id.Split('/')[10] + + } + }, + @{N='ResourceVirtualNetworkName';E={$_.GatewayIPConfigurations.SubnetText.Split('/')[8]}}, + @{N='ResourceVirtualNetworkRGName';E={$_.GatewayIPConfigurations.SubnetText.Split('/')[4]}}, + @{N='IsNSGConfigured';E= + { + if($_.Sku.Name -ne "Standard_v2" -and $_.Sku.Name -ne "WAF_v2") + { + $VnetDetails = Get-AzVirtualNetwork -Name $_.GatewayIPConfigurations.SubnetText.Split('/')[8] -ErrorAction Stop + Foreach($subnet in $VnetDetails.Subnets) + { + $subnetDetails = $_.GatewayIPConfigurations.SubnetText | ConvertFrom-Json + if($subnet.Id -eq $subnetDetails.Id) + { + if($subnet.NetworkSecurityGroup.Id -eq $null) + { + $false; + } + else + { + $true; + } + } + } + } + else{ + $true; + } + + } + } + + + + + + $totalApplicationGateways = ($ApplicationGatewayDetails| Measure-Object).Count + + if ($totalApplicationGateways -eq 0) + { + Write-Host "No subnet of Application Gateway(s) found where NSG is not configured. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::DoubleDashLine) + return + } + + Write-Host "Found [$($totalApplicationGateways)] Application Gateway(s)." -ForegroundColor $([Constants]::MessageType.Update) + + Write-Host $([Constants]::SingleDashLine) + + # list for storing Application Gateway(s) for which NSG is not configured on associated Subnet. + $ApplicationGatewaySubnetWithoutNSG = @() + + Write-Host "Separating Application Gateway(s) for which NSG is already configured on associated subnet..." -ForegroundColor $([Constants]::MessageType.Info) + + $ApplicationGatewayDetails | ForEach-Object { + $ApplicationGateway = $_ + if($_.IsNSGConfigured -eq $false) + { + $ApplicationGatewaySubnetWithoutNSG += $ApplicationGateway + } + } + + $totalApplicationGatewaySubnetWithoutNSG = ($ApplicationGatewaySubnetWithoutNSG | Measure-Object).Count + + if ($totalApplicationGatewaySubnetWithoutNSG -eq 0) + { + Write-Host "No Application Gateway(s) found where NSG is not configured on associated subnets.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + return + } + + Write-Host "Found [$($totalApplicationGatewaySubnetWithoutNSG)] Application Gateway(s) found where NSG is not configured on associated Subnets." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + + $colsProperty = @{Expression={$_.ResourceName};Label="ResourceName";Width=30;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="ResourceGroupName";Width=30;Alignment="left"}, + @{Expression={$_.ResourceId};Label="ResourceId";Width=100;Alignment="left"}, + @{Expression={$_.ResourceSubNetId};Label="ResourceSubNetId";Width=100;Alignment="left"}, + @{Expression={$_.ResourceSubNetName};Label="ResourceSubNetName";Width=100;Alignment="left"}, + @{Expression={$_.ResourceVNetName};Label="ResourceVNetName";Width=100;Alignment="left"}, + @{Expression={$_.ResourceVNetRGName};Label="ResourceVNetRGName";Width=100;Alignment="left"}, + @{Expression={$_.IsNSGConfigured};Label="IsNSGConfigured";Width=100;Alignment="left"} + + if(-not $AutoRemediation) + { + Write-Host "Application Gateway(s) where NSG is not configured on associated subnet are as follows:" + $ApplicationGatewaySubnetWithoutNSG | 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'))\SetApplicationGatewaySubnetWithNSGConfigured" + + if (-not (Test-Path -Path $backupFolderPath)) + { + New-Item -ItemType Directory -Path $backupFolderPath | Out-Null + } + + Write-Host "[Step 3 of 4] Back up Application Gateway(s) details..." + Write-Host $([Constants]::SingleDashLine) + + if ([String]::IsNullOrWhiteSpace($FilePath)) + { + # Backing up Application Gateway Subnet(s) details. + $backupFile = "$($backupFolderPath)\ApplicationGatewaySubnetDetailsBackUp.csv" + $ApplicationGatewaySubnetWithoutNSG | Export-CSV -Path $backupFile -NoTypeInformation + Write-Host "Application Gateway(s) details have been backed up to [$($backupFile)]" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + + + Write-Host "Skipped as -DryRun switch is provided." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + Write-Host "Next steps:" -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host "Run the next command with -FilePath [$($backupFile)] and without -DryRun, Enable the NSG on the Subnet of Application Gateway(s) listed in the file." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + +} + + + + +# 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 = "------------------------------------------------------------------------------------------------------------------------" +} diff --git a/Scripts/RemediationScripts/Remediate-RetrieveVirtualNetworkOfAppGateway.ps1 b/Scripts/RemediationScripts/Remediate-RetrieveVirtualNetworkOfAppGateway.ps1 new file mode 100644 index 00000000..19cdd7a9 --- /dev/null +++ b/Scripts/RemediationScripts/Remediate-RetrieveVirtualNetworkOfAppGateway.ps1 @@ -0,0 +1,331 @@ +<### +# Overview: + This script is used to get the Virtual Network of Application Gateway where DDOS is disabled in a Subscription. + +# Control ID: + Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration_Trial + +# Display Name: + Get the Data of Virtual Network where DDOS is disabled. + +# Prerequisites: + Owner or higher priviliged role on the Application Gateway(s) is required. + +# Steps performed by the script: + To Retrieve the list of Virtual Network(s): + 1. Validating and installing the modules required to run the script and validating the user. + 2. Get the list of Application Gateway(s) in a Subscription that have Virtual Network where DDoS Protection Plan is not configured. + 3. Back up details of Application Gateway(s) that are to be remediated. + 4. Configure the DDoS Protection Plan on the Virtual Network of Application Gateway(s) in the Subscription. + +# 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 get the list of Virtual Network of Application Gateway(s) in the Subscription. Refer `Examples`, below. + +# Examples: + To remediate: + 1. To review the list of Virtual Network of Application Gateway(s) in a Subscription: + + Retrieve-ApplicationGatewayVirtualNetworkDDoSNotConfigured -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun + + To know more about the options supported by the remediation command, execute: + + Get-Help Retrieve-ApplicationGatewayVirtualNetworkDDoSNotConfigured -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.Network") + + 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) + } + } + Write-Host "$($_) module is not present." -ForegroundColor $([Constants]::MessageType.Warning) +} + + +function Retrieve-ApplicationGatewayVirtualNetworkDDoSNotConfigured +{ + <# + .SYNOPSIS + Remediates 'Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration_Trial' Control. + + .DESCRIPTION + Remediates 'Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration_Trial' Control. + Get the list of Virtual Network of Application Gateway(s) in the Subscription. + + .PARAMETER SubscriptionId + Specifies the ID of the Subscription to be remediated. + + .Parameter PerformPreReqCheck + Specifies validation of prerequisites for the command. + + .PARAMETER DryRun + Specifies a dry run of the actual remediation. + + .INPUTS + None. You cannot pipe objects to Retrieve-ApplicationGatewayVirtualNetworkDDoSNotConfigured. + + .OUTPUTS + None. Retrieve-ApplicationGatewayVirtualNetworkDDoSNotConfigured does return the list of Virtual Network that can be piped and used as an input to another script where DDoS would be configured for these Virtual Network(s). + + .EXAMPLE + PS> Retrieve-ApplicationGatewayVirtualNetworkDDoSNotConfigured -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck -DryRun + + .EXAMPLE + PS> Retrieve-ApplicationGatewayVirtualNetworkDDoSNotConfigured -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -PerformPreReqCheck + + .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 = "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 + ) + + 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." -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 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..." -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 get the data from Application Gateway(s) in a Subscription, Contributor or higher privileged role assignment on the Application Gateway(s) is required." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + + Write-Host "[Step 2 of 4] Fetch all Application Gateway(s)" + Write-Host $([Constants]::SingleDashLine) + + # list to store Container details. + $ApplicationGatewayDetails = @() + + # To keep track of remediated and skipped resources + $logRemediatedResources = @() + $logSkippedResources=@() + + # Control Id + $controlIds = "Azure_ApplicationGateway_NetSec_Enable_WAF_Configuration_Trial" + + + # No file path provided as input to the script. Fetch all Application Gateway(s) in the Subscription. + + + + Write-Host "Fetching all Application Gateway(s) in Subscription: [$($context.Subscription.SubscriptionId)]" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + + # Get all Application Gateway(s) in a Subscription + $ApplicationGatewayDetails = Get-AzApplicationGateway -ErrorAction Stop + + # Seperating required properties + $ApplicationGatewayDetails = $ApplicationGatewayDetails | Select-Object @{N='ResourceId';E={$_.Id}}, + @{N='ResourceGroupName';E={$_.Id.Split("/")[4]}}, + @{N='ResourceName';E={$_.Name}}, + @{N='ResourceSubNetId';E={$_.GatewayIPConfigurations.SubnetText}}, + @{N='ResourceVNetName';E={$_.GatewayIPConfigurations.SubnetText.Split('/')[8]}}, + @{N='ResourceVNetRGName';E={$_.GatewayIPConfigurations.SubnetText.Split('/')[4]}}, + @{N='IsDDOSEnabled';E= + { + $VnetDetails = Get-AzVirtualNetwork -Name $_.GatewayIPConfigurations.SubnetText.Split('/')[8] -ErrorAction Stop + $VnetDetails.EnableDdosProtection + }} + + + $totalApplicationGateways = ($ApplicationGatewayDetails| Measure-Object).Count + + if ($totalApplicationGateways -eq 0) + { + Write-Host "No Virtual Network of Application Gateway(s) found where DDoS Protection Plan is disabled. Exiting..." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::DoubleDashLine) + return + } + + Write-Host "Found [$($totalApplicationGateways)] Application Gateway(s)." -ForegroundColor $([Constants]::MessageType.Update) + + Write-Host $([Constants]::SingleDashLine) + + # list for storing Application Gateway(s) for which DDoS Protection Plan is not configured on associated Virtual Network. + $ApplicationGatewayVNetWithoutDDoS = @() + + Write-Host "Separating Application Gateway(s) for which DDoS Protection Plan is already enabled on associated VNet..." -ForegroundColor $([Constants]::MessageType.Info) + + $ApplicationGatewayDetails | ForEach-Object { + $ApplicationGateway = $_ + if($_.IsDDOSEnabled -eq $false) + { + $ApplicationGatewayVNetWithoutDDoS += $ApplicationGateway + } + } + + $totalApplicationGatewayVNetWithoutDDoS = ($ApplicationGatewayVNetWithoutDDoS | Measure-Object).Count + + if ($totalApplicationGatewayVNetWithoutDDoS -eq 0) + { + Write-Host "No Application Gateway(s) found where DDoS Protection Plan is disabled on associated Virtual Network.. Exiting..." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + return + } + + Write-Host "Found [$($totalApplicationGatewayVNetWithoutDDoS)] Application Gateway(s) found where DDoS Protection Plan is disabled on associated Virtual Network." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + + $colsProperty = @{Expression={$_.ResourceName};Label="ResourceName";Width=30;Alignment="left"}, + @{Expression={$_.ResourceGroupName};Label="ResourceGroupName";Width=30;Alignment="left"}, + @{Expression={$_.ResourceId};Label="ResourceId";Width=100;Alignment="left"}, + @{Expression={$_.ResourceSubNetId};Label="ResourceSubNetId";Width=100;Alignment="left"}, + @{Expression={$_.ResourceVNetName};Label="Resource Virtual Network Name";Width=100;Alignment="left"}, + @{Expression={$_.ResourceVNetRGName};Label="Resource Virtual Network RG Name";Width=100;Alignment="left"}, + @{Expression={$_.IsDDOSEnabled};Label="IsDDOSEnabled";Width=100;Alignment="left"} + @{Expression={$_.IsNSGConfigured};Label="IsNSGConfigured";Width=100;Alignment="left"} + + if(-not $AutoRemediation) + { + Write-Host "Application Gateway(s) where DDoS Protection Plan is disabled on associated Virtual Network are as follows:" + $ApplicationGatewayVNetWithoutDDoS | 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'))\VNetWithoutDDoSEnabled" + + if (-not (Test-Path -Path $backupFolderPath)) + { + New-Item -ItemType Directory -Path $backupFolderPath | Out-Null + } + + Write-Host "[Step 3 of 4] Back up Application Gateway(s) details..." + Write-Host $([Constants]::SingleDashLine) + + if ([String]::IsNullOrWhiteSpace($FilePath)) + { + # Backing up Application Gateway(s) details. + $backupFile = "$($backupFolderPath)\ApplicationGatewayDetailsBackUp.csv" + $ApplicationGatewayVNetWithoutDDoS | Export-CSV -Path $backupFile -NoTypeInformation + Write-Host "Application Gateway(s) details have been backed up to [$($backupFile)]" -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + } + + + Write-Host "Skipped as -DryRun switch is provided." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + Write-Host "Next steps:" -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host "Run the next command with -FilePath [$($backupFile)] and without -DryRun, Enable the DDOS on the Vnet of Application Gateway(s) listed in the file." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::DoubleDashLine) + +} + + + + +# 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 = "------------------------------------------------------------------------------------------------------------------------" +}