Azure Azure Bastion Azure PowerShell

Azure Bastion: Set Azure Bastion NSG Inbound security rules on the Target VM Subnet with Azure PowerShell


This blog post will show you how to set the required NSG Inbound security rules on a Target VM subnet for Azure Bastion connectivity with an Azure PowerShell script.

Azure Bastion is an Azure PaaS service that you provision inside a virtual network (VNet), recommendable the HUB VNet. It allows you to securely connect to your Azure virtual machines (VMs) via RDP or SSH and this directly from the Azure portal over SSL. When you use Azure Bastion, your VMs do not need a Public IP Address (PIP)agent or special client software to be able to connect to them.

If you want to read some more about Azure Bastion, you can do so via the following Microsoft Docs link: Azure Bastion documentation

If you want, you can also look at my previous blog posts which can help you to securely deploy and configure Azure Bastion and all associated resources: Azure Bastion: Azure PowerShell deployment script and Azure Bastion: Set the minimum required roles to access a virtual machine


When using Azure Bastion in a production environment, you should always apply a Network Security Group (NSG) on the AzureBastionSubnet, with all the required inbound and outbound security rules, like shown in the screenshots below.



Next to that, and especially when you want to implement a good defense-in-depth strategy, you should also apply an NSG on all the subnets on which a virtual machine (VM) needs to be accessed by Azure Bastion.

On that NSG the following inbound security rule(s) should be provided:

  • Inbound (ingress) traffic from Azure Bastion on ports 3389 (RDP) or 22 (SSH). This because Azure Bastion will reach the targeted VM over its private IP and depending on the type of VM (Windows or Linux) port 3389 or 22 will be used. Therefore, as a best practice, you should only add the Azure Bastion subnet (AzureBastionSubnet CIDR IP address in IPv4) as the source in this rule.
  • Block all other inbound traffic with a lower priority number then the default inbound rules (for example with a priority of 900).

Because configuring this NSG rule manually can be quite a long and error prone task, I wrote the below Azure PowerShell script which does all of the following:

  • Check if the PowerShell window is running as Administrator (when not running from Cloud Shell), otherwise the Azure PowerShell script will be exited.
  • Suppress breaking change warning messages.
  • Create the Target VM Subnet NSG if it does not exist.
  • Store the Target VM Subnet NSG in a variable.
  • Add inbound rule 1 to allow ingress RDP traffic from AzureBastionSubnet to the Target VM Subnet NSG, if it not already exists.
  • Add inbound rule 2 to allow ingress SSH traffic from AzureBastionSubnet to the Target VM Subnet NSG, if it not already exists.
  • Add inbound rule 3 to deny all other inbound virtual network traffic to the Target VM Subnet NSG, if it not already exists.
  • Update the NSG with the new inbound rules.


To use the script copy and save it as Set-AzureBastion-NSG-Inbound-security-rules-on-Target-VM-Subnet or download it from GitHub. Then before using the script, adjust all variables to your use and then run the customized script with Administrator privileges from Windows TerminalVisual Studio Code, or Windows PowerShell. Or you can simply run it from Cloud Shell.


Prerequisites

  • An Azure subscription.
  • An Azure Administrator account with the necessary RBAC roles.
  • An existing HUB VNet with the AzureBastionSubnet
  • An existing spoke VNet with some subnets.
  • An existing Azure Bastion host.
  • At least Azure Az PowerShell module version 5.9.0 and Az.Network module version 4.7.0
  • Change all the variables in the script where needed to fit your needs (you can find an adjusted example in one of the screenshots below).






Azure PowerShell script

If you are not running the script from Cloud Shell, don’t forget to sign in with the Connect-AzAccount cmdlet to connect your Azure account. And if you are using multiple Azure subscriptions, select the proper subscription with the Set-AzContext cmdlet before running the script.


<#
.SYNOPSIS

A script used to set the required NSG Inbound security rules on a Target VM subnet for Azure Bastion connectivity.

.DESCRIPTION

A script used to set the required NSG Inbound security rules on a Target VM subnet for Azure Bastion connectivity.
The script will do all of the following:

Check if the PowerShell window is running as Administrator (when not running from Cloud Shell), otherwise the Azure PowerShell script will be exited.
Suppress breaking change warning messages.
Create the Target VM Subnet NSG if it does not exist.
Store the Target VM Subnet NSG in a variable.
Add inbound rule 1 to allow ingress RDP traffic from AzureBastionSubnet to the Target VM Subnet NSG, if it not already exists.
Add inbound rule 2 to allow ingress SSH traffic from AzureBastionSubnet to the Target VM Subnet NSG, if it not already exists.
Add inbound rule 3 to deny all other inbound virtual network traffic to the Target VM Subnet NSG, if it not already exists.
Update the NSG with the new inbound rules.

.NOTES

Filename:       Set-AzureBastion-NSG-Inbound-security-rules-on-Target-VM-Subnet.ps1
Created:        10/08/2022
Last modified:  10/08/2022
Author:         Wim Matthyssen
Version:        1.0
PowerShell:     Azure Cloud Shell or Azure PowerShell
Requires:       PowerShell Az (v5.9.0) and Az.Network (v4.7.0)
Action:         Change variables were needed to fit your needs. 
Disclaimer:     This script is provided "As Is" with no warranties.

.EXAMPLE

Connect-AzAccount
Get-AzTenant (if not using the default tenant)
Set-AzContext -tenantID "<xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx>" (if not using the default tenant)
Set-AzContext -Subscription "<SubscriptionName>" (if not using the default subscription)
.\Set-AzureBastion-NSG-Inbound-security-rules-on-Target-VM-Subnet <"your NSG name here"> <"your NSG resource group name here"> 

-> .\Set-AzureBastion-NSG-Inbound-security-rules-on-Target-VM-Subnet nsg-tst-myh-app-01 rg-tst-myh-networking-01

.LINK

https://wmatthyssen.com/2022/08/11/azure-bastion-set-azure-bastion-nsg-inbound-security-rules-on-the-target-vm-subnet-with-azure-powershell/
#>

## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

## Parameters

param(
    # $nsgNameTargetVMSubnet -> Name of the NSG associated to the Target VM Subnet
    [parameter(Mandatory =$true)][ValidateNotNullOrEmpty()] [string] $nsgNameTargetVMSubnet,
    # $rgNameNetworking -> Name of the resource group holding the NSG
    [parameter(Mandatory =$true)][ValidateNotNullOrEmpty()] [string] $rgNameNetworking
)

## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

## Variables

$inboundRule1Name = #<your RDP inbound rule here> The name of the RDP inbound rule. Example: "Allow_RDP_3389_AzureBastionSubnet_Inbound"
$inboundRule2Name = #<your SSH inbound rule here> The name of the SSH inbound rule. Example: "Allow_SSH_22_AzureBastionSubnet_Inbound"
$inboundRule3Name = #<your deny all other traffic inbound rule here> The name of the deny all other traffic inbound rule. Example: "Deny_Any_Other_Inbound_Traffic_Inbound"

$inboundRule1Priority = #<your RDP inbound rule priority here> The priority of the RDP inbound rule. Example: "100"
$inboundRule2Priority = #<your SSH inbound rule priority here> The priority of the SSH inbound rule. Example: "110"
$inboundRule3Priority = #<your deny all other traffic inbound rule priority here> The priority of the deny all other traffic inbound rule. Example: "900"

$bastionSubnetAddressRange = "10.1.1.128/26"

$global:currenttime= Set-PSBreakpoint -Variable currenttime -Mode Read -Action {$global:currenttime= Get-Date -UFormat "%A %m/%d/%Y %R"}
$foregroundColor1 = "Red"
$foregroundColor2 = "Yellow"
$writeEmptyLine = "`n"
$writeSeperatorSpaces = " - "

## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

## Check if PowerShell runs as Administrator (when not running from Cloud Shell), otherwise exit the script

if ($PSVersionTable.Platform -eq "Unix") {
    Write-Host ($writeEmptyLine + "# Running in Cloud Shell" + $writeSeperatorSpaces + $currentTime)`
    -foregroundcolor $foregroundColor1 $writeEmptyLine
    
    ## Start script execution    
    Write-Host ($writeEmptyLine + "# Script started. Without any errors, it will need around 1 minute to complete" + $writeSeperatorSpaces + $currentTime)`
    -foregroundcolor $foregroundColor1 $writeEmptyLine 
} else {
    $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
    $isAdministrator = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)

        ## Check if running as Administrator, otherwise exit the script
        if ($isAdministrator -eq $false) {
        Write-Host ($writeEmptyLine + "# Please run PowerShell as Administrator" + $writeSeperatorSpaces + $currentTime)`
        -foregroundcolor $foregroundColor1 $writeEmptyLine
        Start-Sleep -s 3
        exit
        }
        else {

        ## If running as Administrator, start script execution    
        Write-Host ($writeEmptyLine + "# Script started. Without any errors, it will need around 1 minute to complete" + $writeSeperatorSpaces + $currentTime)`
        -foregroundcolor $foregroundColor1 $writeEmptyLine 
        }
}

## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

## Suppress breaking change warning messages

Set-Item Env:\SuppressAzurePowerShellBreakingChangeWarnings "true"

## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

# Create the Target VM Subnet NSG if it does not exist

try {
    Get-AzNetworkSecurityGroup -Name $nsgNameTargetVMSubnet -ResourceGroupName $rgNameNetworking -ErrorAction Stop | Out-Null 
} catch {
    New-AzNetworkSecurityGroup -Name $nsgNameTargetVMSubnet -ResourceGroupName $rgNameNetworking -Location $region -Force | Out-Null 
}

Write-Host ($writeEmptyLine + "# NSG $nsgNameTargetVMSubnet available" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor2 $writeEmptyLine

## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

## Store the Target VM Subnet NSG in a variable

$nsg = Get-AzNetworkSecurityGroup -Name $nsgNameTargetVMSubnet -ResourceGroupName $rgNameNetworking

Write-Host ($writeEmptyLine + "# NSG $nsgNameTargetVMSubnet stored in a variable" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor2 $writeEmptyLine

## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

## Add inbound rule 1 to allow Ingress RDP traffic from AzureBastionSubnet to the Target VM Subnet NSG, if it not already exists

$inboundRule1Exists = $nsg | Get-AzNetworkSecurityRuleConfig -Name $inboundRule1Name -ErrorAction SilentlyContinue

if ($inboundRule1Exists) {
    Write-Host ($writeEmptyLine + "# Inbound security rule $inboundRule1Name already exists" + $writeSeperatorSpaces + $currentTime) `
    -foregroundcolor $foregroundColor2 $writeEmptyLine
} else {
    $nsg | Add-AzNetworkSecurityRuleConfig -Name $inboundRule1Name -Description $inboundRule1Name -Access Allow -Protocol TCP -Direction Inbound -Priority $inboundRule1Priority `
    -SourceAddressPrefix $bastionSubnetAddressRange -SourcePortRange * -DestinationAddressPrefix * -DestinationPortRange 3389 | Out-Null 

    Write-Host ($writeEmptyLine + "# Inbound security rule added to allow RDP from AzureBastionSubnet to NSG $nsgNameTargetVMSubnet" + $writeSeperatorSpaces + $currentTime) `
    -foregroundcolor $foregroundColor2 $writeEmptyLine
}

## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

## Add inbound rule 2 to allow Ingress SSH traffic from AzureBastionSubnet to the Target VM Subnet NSG, if it not already exists

$inboundRule2Exists = $nsg | Get-AzNetworkSecurityRuleConfig -Name $inboundRule2Name -ErrorAction SilentlyContinue

if ($inboundRule2Exists) {
    Write-Host ($writeEmptyLine + "# Inbound security rule $inboundRule2Name already exists" + $writeSeperatorSpaces + $currentTime) `
    -foregroundcolor $foregroundColor2 $writeEmptyLine
} else {
    $nsg | Add-AzNetworkSecurityRuleConfig -Name $inboundRule2Name -Description $inboundRule2Name -Access Allow -Protocol TCP -Direction Inbound -Priority $inboundRule2Priority `
    -SourceAddressPrefix $bastionSubnetAddressRange -SourcePortRange * -DestinationAddressPrefix * -DestinationPortRange 22 | Out-Null 
    
    Write-Host ($writeEmptyLine + "# Inbound security rule added to allow SSH from AzureBastionSubnet to NSG $nsgNameTargetVMSubnet" + $writeSeperatorSpaces + $currentTime) `
    -foregroundcolor $foregroundColor2 $writeEmptyLine
}

## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

## Add inbound rule 3 to deny all other inbound virtual network traffic to the Target VM Subnet NSG, if it not already exists

$inboundRule3Exists = $nsg | Get-AzNetworkSecurityRuleConfig -Name $inboundRule3Name -ErrorAction SilentlyContinue

if ($inboundRule3Exists) {
    Write-Host ($writeEmptyLine + "# Inbound security rule $inboundRule3Name already exists" + $writeSeperatorSpaces + $currentTime) `
    -foregroundcolor $foregroundColor2 $writeEmptyLine
} else {
    $nsg | Add-AzNetworkSecurityRuleConfig -Name $inboundRule3Name -Description $inboundRule3Name -Access Deny -Protocol * -Direction Inbound -Priority $inboundRule3Priority `
    -SourceAddressPrefix * -SourcePortRange * -DestinationAddressPrefix * -DestinationPortRange * | Out-Null 
        
    Write-Host ($writeEmptyLine + "# Inbound security rule added to deny any other traffic to NSG $nsgNameTargetVMSubnet" + $writeSeperatorSpaces + $currentTime) `
    -foregroundcolor $foregroundColor2 $writeEmptyLine
}

## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

## Update the NSG with the new inbound rules

$nsg | Set-AzNetworkSecurityGroup | Out-Null 

Write-Host ($writeEmptyLine + "# NSG $nsgNameTargetVMSubnet updated" + $writeSeperatorSpaces + $currentTime) `
-foregroundcolor $foregroundColor2 $writeEmptyLine

## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

## Write script completed

Write-Host ($writeEmptyLine + "# Script completed" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor1 $writeEmptyLine 

## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


When downloaded or copied run the script.


## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Connect-AzAccount
Get-AzSubscription -SubscriptionId <"your Azure Subscirption ID here"> -TenantId <"your Tenant ID here"> | Set-AzContext
.\Set-AzureBastion-NSG-Inbound-security-rules-on-Target-VM-Subnet <"your NSG name here"> <"your NSG resource group name here"> 

Example -> .\Set-AzureBastion-NSG-Inbound-security-rules-on-Target-VM-Subnet nsg-tst-myh-app-01 rg-tst-myh-networking-01

## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------




I hope this Azure PowerShell script is useful for you and provides you with a good starting point to use Azure Bastion into your Azure environment.

If you have any questions or recommendations about it, feel free to contact me through my Twitter handle (@wmatthyssen) or to just leave a comment.


%d bloggers like this: