Perform bulk NSG rule rollout across multiple target NSGs
Published Apr 22 2024 08:59 PM 1,703 Views
Microsoft

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.

 

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) {
  
  $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 $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) {
  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 ($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

        # 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 bulkAzure Portal: Manage your NSG rules at bulk

Stay tuned for more articles on how to automate and simplify your Azure networking tasks!

Version history
Last update:
‎Apr 22 2024 11:40 PM
Updated by: