Network Security Groups (NSGs) are a fundamental aspect of Azure networking, providing a layer of security to control traffic flow within virtual networks. However, managing NSG rules across multiple NSGs can be a daunting task, especially when done manually. This article introduces a powerful PowerShell script that allows you to perform bulk NSG rule rollouts across multiple target NSGs, saving you time and ensuring consistency across your network.
The Challenge
In a large-scale Azure environment, you may have hundreds or even thousands of NSGs, each with its own set of rules. Updating these rules manually is not only time-consuming but also prone to errors. Furthermore, it's difficult to ensure consistency across all NSGs, which is crucial for maintaining a secure and reliable network.
Scenario
The requirements were to empower the customers using an automation that could perform the mass assignment, updating, or deletion of the NSG rules across a set of NSGs. The NSG rules would have a common definition across all the NSGs except the source address prefix range and the destination address prefix range that would change based on the NSGs subnet address range, and if the rule's direction is outbound or inbound.
The Solution
The solution consists of three PowerShell scripts that perform add, update, and delete operations on Network Security Group (NSG) rules in Azure.
Add Operation
Our PowerShell script, nsg_rule_add_csv.ps1
, simplifies the process of adding NSG rules in bulk. The script reads from a CSV file, nsg_rule_definitions.csv
, which contains the definitions of the rules to be added. It then applies these additions to the target NSGs specified in another CSV file, target_nsgs.csv
. If the rule already exists in the NSG, the script skips the addition. Otherwise, it adds the rule to the NSG. Find the code for nsg_rule_add_csv.ps1
.
function Add-NsgRule ($SubscriptionId, $ResourceGroup, $NsgName, $ruleDefinition, $SubnetAddressPrefix) {
if ($ruleDefinition.Direction -ieq 'Inbound') {
$rule = New-AzNetworkSecurityRuleConfig -Name $ruleDefinition.Name -Description $ruleDefinition.Description -Priority $ruleDefinition.Priority -Direction $ruleDefinition.Direction -Access $ruleDefinition.Access -SourceAddressPrefix $ruleDefinition.SourceAddressPrefix -SourcePortRange $ruleDefinition.SourcePortRange -DestinationAddressPrefix $subnetAddressPrefix -DestinationPortRange $ruleDefinition.DestinationPortRange -Protocol $ruleDefinition.Protocol
} elseif ($ruleDefinition.Direction -ieq 'Outbound') {
$rule = New-AzNetworkSecurityRuleConfig -Name $ruleDefinition.Name -Description $ruleDefinition.Description -Priority $ruleDefinition.Priority -Direction $ruleDefinition.Direction -Access $ruleDefinition.Access -SourceAddressPrefix $subnetAddressPrefix -SourcePortRange $ruleDefinition.SourcePortRange -DestinationAddressPrefix $ruleDefinition.DestinationAddressPrefix -DestinationPortRange $ruleDefinition.DestinationPortRange -Protocol $ruleDefinition.Protocol
}
Try {
# Switch the context to the appropriate subscription
Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop
$nsg = Get-AzNetworkSecurityGroup -Name $NsgName -ResourceGroupName $ResourceGroup -ErrorAction Stop
$nsg.SecurityRules.Add($rule)
Set-AzNetworkSecurityGroup -NetworkSecurityGroup $nsg -ErrorAction Stop
Write-Host "Rule added to NSG: $NsgName ($ResourceGroup)"
$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
$logEntry = "$timestamp, $($ruleDefinition.Name), $nsgName, $ResourceGroup, Added Successfully"
$logFileName = "$($run_time)_$($ruleDefinition.Name)_add_success.log"
Add-Content -Path ".\$logFileName" -Value $logEntry
} Catch {
$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
$logEntry = "$timestamp, $($NsgName), $($_.Exception.Message)"
$logFileName = "$($run_time)_$($ruleDefinition.Name)_add_fail.log"
Add-Content -Path ".\$logFileName" -Value $logEntry
Write-Error "Failed to add rule to NSG: $NsgName ($ResourceGroup) - $($_.Exception.Message)"
}
}
Update Operation
Our PowerShell script, nsg_rule_update_csv.ps1
, simplifies the process of updating NSG rules in bulk. The script reads from a CSV file, nsg_rule_definitions_update.csv
, which contains the rule definitions to be updated. It then applies these updates to the target NSGs specified in another CSV file, target_nsgs.csv
. If the rule exists in the NSG, and the rule name (immutable) and old rule priority (additional input in the nsg_rule_definitions_update.csv
for validating the rule before update operation) matches, the script updates the rule to the new rule definition. If the rule doesn't exist, it logs an error message. Find the code for nsg_rule_update_csv.ps1
.
function Update-NsgRule ($SubscriptionId, $ResourceGroup, $NsgName, $ruleDefinition, $subnetaddrprefix) {
Try {
# Switch the context to the appropriate subscription
Set-AzContext -SubscriptionId $SubscriptionId
# Get the Network Security Group
$nsg = Get-AzNetworkSecurityGroup -Name $NsgName -ResourceGroupName $ResourceGroup -ErrorAction Stop
$existingRule = Get-AzNetworkSecurityRuleConfig -Name $ruleDefinition.Name -NetworkSecurityGroup $nsg -ErrorAction Stop
if ($existingRule) {
# if existing PN= OLd PN-> Update
# existing PN != OLd PN -> throw error
if ($existingRule.Priority -eq $ruleDefinition.OldPriority) {
$existingRule.Priority = $ruleDefinition.Priority
$existingRule.Direction = $ruleDefinition.Direction
$existingRule.SourceAddressPrefix = [System.Collections.Generic.List[string]]@($ruleDefinition.SourceAddressPrefix)
$existingRule.DestinationAddressPrefix = [System.Collections.Generic.List[string]]@($ruleDefinition.DestinationAddressPrefix)
$existingRule.SourcePortRange = [System.Collections.Generic.List[string]]@($ruleDefinition.SourcePortRange)
$existingRule.DestinationPortRange = [System.Collections.Generic.List[string]]@($ruleDefinition.DestinationPortRange)
$existingRule.Description = $ruleDefinition.Description
$existingRule.Protocol = $ruleDefinition.Protocol
$existingRule.Access = $ruleDefinition.Access
if ($ruleDefinition.Direction -ieq 'Inbound' ) {
$existingRule.DestinationAddressPrefix = [System.Collections.Generic.List[string]]@($subnetaddrprefix)
} elseif ($ruleDefinition.Direction -ieq 'Outbound') {
$existingRule.SourceAddressPrefix = [System.Collections.Generic.List[string]]@($subnetaddrprefix)
}
# Update the existing rule
$nsg | Set-AzNetworkSecurityRuleConfig -Name $existingRule.Name -Description $existingRule.Description -Access $existingRule.Access -Protocol $existingRule.Protocol -Direction $existingRule.Direction -Priority $existingRule.Priority -SourceAddressPrefix $existingRule.SourceAddressPrefix -SourcePortRange $existingRule.SourcePortRange -DestinationAddressPrefix $existingRule.DestinationAddressPrefix -DestinationPortRange $existingRule.DestinationPortRange -ErrorAction Stop
# Save the changes to the Network Security Group
$nsg | Set-AzNetworkSecurityGroup -ErrorAction Stop
$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
$logEntry = "$timestamp, $($ruleDefinition.Name), $nsgName, $ResourceGroup, Updated Successfully"
$logFileName = "$($run_time)_$($ruleDefinition.Name)_update_success.log"
Add-Content -Path ".\$logFileName" -Value $logEntry
Write-Host "Rule updated in NSG: $NsgName ($ResourceGroup)"
}
else{
$errorMessage = "Cannot update the rule in NSG: $NsgName ($ResourceGroup) - Priority does not match for rule in NSG."
Write-Error $errorMessage
$logFileName = "$($run_time)_$($ruleDefinition.Name)_update_fail.log"
Add-Content -Path ".\$logFileName" -Value $logEntry
}
}
else {
Write-Error "Rule '$($ruleDefinition.Name)' not found in NSG: $NsgName ($ResourceGroup)"
$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
$logEntry = "$timestamp, $($NsgName), Rule not found in NSG."
$logFileName = "$($run_time)_$($ruleDefinition.Name)_update_fail.log"
Add-Content -Path ".\$logFileName" -Value $logEntry
}
}
Catch {
$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
$logEntry = "$timestamp, $($NsgName), $($_.Exception.Message)"
$logFileName = "$($run_time)_$($ruleDefinition.Name)_update_fail.log"
Add-Content -Path ".\$logFileName" -Value $logEntry
Write-Error "Failed to update rule in NSG: $NsgName ($ResourceGroup) - $($_.Exception.Message)"
}
}
Delete Operation
Our PowerShell script, nsg_rule_delete_csv.ps1
, simplifies the process of deleting NSG rules in bulk. The script reads from a CSV file, nsg_rule_definitions.csv
, which contains the definitions of the rules to be deleted. It then applies these deletions to the target NSGs specified in another CSV file, target_nsgs.csv
. The script verifies if the existing rule's name and priority matches the priority specified in the rule definition. If they match, the script removes the rule. If they don't match, it logs an error message. Find the code for nsg_rule_delete_csv.ps1
.
function Remove-NsgRule ($SubscriptionId, $ResourceGroup, $NsgName, $ruleDefinition) {
Try {
# Switch the context to the appropriate subscription
Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop
# Get the Network Security Group
$nsg = Get-AzNetworkSecurityGroup -Name $NsgName -ResourceGroupName $ResourceGroup -ErrorAction Stop
# Get the rule
$rule = Get-AzNetworkSecurityRuleConfig -Name $ruleDefinition.Name -NetworkSecurityGroup $nsg -ErrorAction Stop
# Check if the priority matches the user-provided priority
if ($rule.Priority -eq $ruleDefinition.Priority) {
# Remove the rule
$nsg | Remove-AzNetworkSecurityRuleConfig -Name $ruleDefinition.Name -ErrorAction Stop
$nsg | Set-AzNetworkSecurityGroup -ErrorAction Stop
$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
$dateOnly = $timestamp.Substring(0, 10)
$logEntry = "$timestamp, $($ruleDefinition.Name), $nsgName, $ResourceGroup, Updated Successfully"
$logFileName = "$($run_time)_$($ruleDefinition.Name)_delete_success.log"
$logFileName = $logFileName -replace '[\/:*?"<>|]', '_' # replace invalid characters
Add-Content -Path ".\$logFileName" -Value $logEntry
Write-Host "Rule removed from NSG: $NsgName ($ResourceGroup)"
} else {
Write-Error "Priority does not match for rule in NSG: $NsgName ($ResourceGroup)"
$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
$logEntry = "$timestamp, $($NsgName), Priority does not match for rule in NSG."
$logFileName = "$($run_time)_$($ruleDefinition.Name)_delete_fail.log"
Add-Content -Path ".\$logFileName" -Value $logEntry
}
} Catch {
$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
$logEntry = "$timestamp, $($NsgName), $($_.Exception.Message)"
$logFileName = "$($run_time)_$($ruleDefinition.Name)_delete_fail.log"
Add-Content -Path ".\$logFileName" -Value $logEntry
Write-Error "Failed to remove rule from NSG: $NsgName ($ResourceGroup) - $($_.Exception.Message)"
}
}
In all three scripts, the operations are performed using the Azure PowerShell module. The scripts also handle errors and log them to a file. The scripts prompt the user for confirmation before performing the operations. This way, you can easily manage NSG rules across multiple target NSGs, saving time and ensuring consistency across your network.
How It Works
The script works on the principle that the rules to be added, updated or modified must be consistent across all the target NSGs. For this, the script validates the rules for mass rollout on two baseline rule definition parameters i.e. rule name and rule priority, only on successful validation of these baseline, the script performs the required operations. For instance, the general flow of the nsg_rule_delete_csv.ps1
begins by prompting the user for confirmation, as the operation is irreversible. Once the user confirms, the script loops through each target NSG. For each NSG, it checks if the required variables (subscription ID, resource group, and NSG name) are null or empty. If they are, it logs an error message to a log file and skips to the next NSG.
Next, the script prints a message indicating that it's deleting the rule from the NSG. It then loops through each rule definition and calls the Remove-NsgRule
function to delete the rule.
If the user chooses not to proceed with the deletion, the script prints a message indicating that the operation was cancelled by the user.
Finally, the script prints a message indicating that it has completed. (Source: code repository)
Conclusion
With our PowerShell script, you can easily perform bulk NSG rule rollouts across multiple target NSGs. This not only saves you time but also ensures consistency across your network, enhancing its security and reliability.
Azure Portal: Manage your NSG rules at bulk
Stay tuned for more articles on how to automate and simplify your Azure networking tasks!