Connect to an Azure Bastion using native tools

Connect to an Azure Bastion using native tools

I recently engaged in a conversation with a customer about securely granting their geographically dispersed contractors access to selected Azure resources. The contractors needed to use SQL Server Management Studio (SSMS), so access via an internet browser was insufficient.

To address this, I assisted a client in providing a secure means for multiple remote contract workers to access their environment. The contractors required the use of SQL Server Management Studio (SSMS), necessitating a solution beyond browser-based access.

The aim of this article is to demonstrate two methods for securely accessing a trusted internal network:

>Deploying Azure Bastion hosts to facilitate local endpoint native RDP and SSH tools.

>Using browser-based shareable link access.


  1. What is Azure Bastion?
  2. Azure Bastion SKUs
  3. RBAC Roles
  4. Subnet Architecture
  5. Deployment Steps
  6. Monitoring


1. What is Azure Bastion?

RDP and SSH are fundamental means to connect to your trusted workloads running in Azure. Azure Bastion is a PaaS service that can be deployed at the public side of your perimeter network. These Bastion host servers are designed to withstand attacks while providing RDP and SSH connectivity to your internal workloads behind the Bastion servers. Azure Bastion enables connectivity to virtual machines using your internet browser or native SSH/RDP client tools.


2. Azure Bastion SKUs

Azure Bastion is available in four SKU types. You can upgrade your SKU post deployment but downgrading to a lower SKU requires you to delete and recreate a new Azure Bastion instance which will induce downtime and business disruption.

In this article I will discussing the deploying of the Standard SKU which enables the option of connecting to virtual machines using native SSH/ RDP tools.

The following table shows the availability of features per corresponding SKU.


3. RBAC Roles

To be able to access your internal servers via the Azure Bastion server, you need to provision the following roles at your choice of scope level, resource | resource group | subscription level. Apply the Zero Trust Models micro segmentation and least-privilege access principles to minimize lateral movement. The PowerShell script is below.


4. Subnet Architecture

Subnet architecture:

#1 The virtual network requires a dedicated subnet called AzureBastionSubnet.

#2 The AzureBastionSubnet size must be /26 at a minimum to accommodate host scaling.

#3 The AzureBastionSubnet cannot contain other resources.

#4 Create the AzureBastionSubnet without any route tables.


5. Deployment Steps:

Step 1 - Provision the Resource Group Container

#The PowerShell script below provisions a resource group, a virtual network and an AzureBastionSubnet

# Variables
$resourceGroupName = "allen-sandbox-bastion"
$location = "southafricanorth"

$vnetName = "allensan"
$addressPrefix = "10.0.0.0/16"

$subnet1Name = "AzureBastionSubnet"
$subnet1Prefix = "10.0.0.0/26"

$subnet2Name = "Application"
$subnet2Prefix = "10.0.0.64/26"

$tag = @{CustomerName="Allen"; AutoShutdownSchedule="None"; Environment="Sandbox";}

####################
#Step 1 - Create a resource group container:
New-AzResourceGroup -Name $resourceGroupName -Location $location -Tag $tag
####################
#Step 2 - Create a virtual network:
$vnet = New-AzVirtualNetwork `
-ResourceGroupName $resourceGroupName `
-Location $location `
-Name $vnetName `
-AddressPrefix $addressPrefix `
-Tag $tag

# Add the AzureBastionSubnet subnet to the virtual network:
Add-AzVirtualNetworkSubnetConfig `
-Name $subnet1Name -VirtualNetwork $vnet -AddressPrefix $subnet1Prefix
# Apply the Application subnet configuration to the virtual network
$vnet | Set-AzVirtualNetwork

# Apply the Application subnet configuration to the virtual network
$vnet | Set-AzVirtualNetwork
# Add the Application subnet to the virtual network:
Add-AzVirtualNetworkSubnetConfig `
-Name $subnet2Name -VirtualNetwork $vnet -AddressPrefix $subnet2Prefix
# Apply the subnet configuration to the virtual network
$vnet | Set-AzVirtualNetwork        


Step 2 - RBAC roles

The PowerShell script below will provision the required RBAC roles at the resource group level.

#Step 1 - Provision RBAC roles:

# Variables
$resourceGroupName = "allen-sandbox-bastion"
$signInName = "[email protected]"
$roleDefinitionName1 = "Reader"
$roleDefinitionName2 = "Virtual Machine User Login"
$principalId = $principalId

# Get the principal ID (Object ID) of the user:
$user = Get-AzADUser -UserPrincipalName $signInName
$principalId = $user.Id

# Output the principal ID
$principalId

# Assign the READER role to the principal on the resource group:
New-AzRoleAssignment `
-ObjectId $principalId `
-RoleDefinitionName $roleDefinitionName1 `
-ResourceGroupName $resourceGroupName `
-Description "Bastion - READER"

# Assign the VIRTUAL MACHINE USER LOGIN role to the principal on the resource group:
New-AzRoleAssignment `
-ObjectId $principalId `
-RoleDefinitionName $roleDefinitionName2 `
-ResourceGroupName $resourceGroupName `
-Description "Bastion - VIRTUAL MACHINE USER LOGIN"        

Step 3 – Provision the Network Security Groups (NSG)

The following NSG resource must be provisioned and linked to the AzureBastionSubnet subnet. This NSG is mandatory for the Azure Bastion to operate.

I recommend pre-creating the Bastion NSG with the 4 ingress and 4 egress rules that must be attached to the Bastion subnet. Without this NSG, Bastion will not work.

I have written out a PowerShell script below that will provision the NSG with all the required ingress and egress rules as well as associate it to your pre created AzureBastionSubnet.

The PowerShell script will:

#1 Provision the empty NSG,

#2 Populate 4 ingress rules,

#4 Populate 4 egress rules,

#5 Apply a no delete lock to the NSG,

#6 Associate the NSG to the AzureBastionSubnet

$NSGname = 'Bastion-NSG'

###Step 1 - Provision the Network Security Group:
$Variables = @{
  'Name'              = $NSGname
  'ResourceGroupName' = $resourceGroupName
  'Location'          = $location
  'Tag'	 	   = $tag
}
$AzNSG = New-AzNetworkSecurityGroup @Variables

###Step 2 - Provision the NSG Access Control Entries:
##Create the 4 inbound rules:

$AzNSG = Get-AzNetworkSecurityGroup `
-Name $NSGname `
-ResourceGroupName $resourceGroupName

$Variables1 = @{
'Name' = 'Bastion-Internet-Ingress'
'NetworkSecurityGroup' = $AzNSG
'Protocol' = 'TCP'
'Direction' = 'Inbound'
'Priority' = 200
'SourceAddressPrefix' = 'internet'
'SourcePortRange' = '*'
'DestinationAddressPrefix' = '*'
'DestinationPortRange' = 443
'Access' = 'Allow'
}
Add-AzNetworkSecurityRuleConfig @Variables1 | Set-AzNetworkSecurityGroup `

$Variables2 = @{
'Name' = 'Bastion-GatwayManager-Ingress'
'NetworkSecurityGroup' = $AzNSG
'Protocol' = 'TCP'
'Direction' = 'Inbound'
'Priority' = 300
'SourceAddressPrefix' = 'gatewaymanager'
'SourcePortRange' = '*'
'DestinationAddressPrefix' = '*'
'DestinationPortRange' = 443
'Access' = 'Allow'
}
Add-AzNetworkSecurityRuleConfig @Variables2 | Set-AzNetworkSecurityGroup `

$Variables3 = @{
'Name' = 'Bastion-AzureLoadBalancer-Ingress'
'NetworkSecurityGroup' = $AzNSG
'Protocol' = 'TCP'
'Direction' = 'Inbound'
'Priority' = 400
'SourceAddressPrefix' = 'azureloadbalancer'
'SourcePortRange' = '*'
'DestinationAddressPrefix' = '*'
'DestinationPortRange' = 443
'Access' = 'Allow'
}
Add-AzNetworkSecurityRuleConfig @Variables3 | Set-AzNetworkSecurityGroup `

$Variables4 = @{
'Name' = 'Bastion-HostCommunication-Ingress'
'NetworkSecurityGroup' = $AzNSG
'Protocol' = '*'
'Direction' = 'Inbound'
'Priority' = 500
'SourceAddressPrefix' = 'virtualnetwork'
'SourcePortRange' = '*'
'DestinationAddressPrefix' = 'virtualnetwork'
'DestinationPortRange' = 8080,5701
'Access' = 'Allow'
}
Add-AzNetworkSecurityRuleConfig @Variables4 | Set-AzNetworkSecurityGroup `


###Step 3 - Create 4 outbound rules:

$Variables5 = @{
'Name' = 'Bastion-Internet-Egress'
'NetworkSecurityGroup' = $AzNSG
'Protocol' = '*'
'Direction' = 'Outbound'
'Priority' = 200
'SourceAddressPrefix' = '*'
'SourcePortRange' = '*'
'DestinationAddressPrefix' = 'internet'
'DestinationPortRange' = '80'
'Access' = 'Allow'
}

Add-AzNetworkSecurityRuleConfig @Variables5 | Set-AzNetworkSecurityGroup `

$Variables6 = @{
'Name' = 'Bastion-HostCommunication-Egress'
'NetworkSecurityGroup' = $AzNSG
'Protocol' = '*'
'Direction' = 'Outbound'
'Priority' = 300
'SourceAddressPrefix' = 'virtualnetwork'
'SourcePortRange' = '*'
'DestinationAddressPrefix' = 'virtualnetwork'
'DestinationPortRange' = 8080,5701
'Access' = 'Allow'
}
Add-AzNetworkSecurityRuleConfig @Variables6 | Set-AzNetworkSecurityGroup `

$Variables7 = @{
'Name' = 'Bastion-AzureCloud-Egress'
'NetworkSecurityGroup' = $AzNSG
'Protocol' = 'TCP'
'Direction' = 'Outbound'
'Priority' = 400
'SourceAddressPrefix' = '*'
'SourcePortRange' = '*'
'DestinationAddressPrefix' = 'azurecloud'
'DestinationPortRange' = '443'
'Access' = 'Allow'
}

Add-AzNetworkSecurityRuleConfig @Variables7 | Set-AzNetworkSecurityGroup `

$Variables8 = @{
'Name' = 'Bastion-RDPSSH-Egress'
'NetworkSecurityGroup' = $AzNSG
'Protocol' = '*'
'Direction' = 'Outbound'
'Priority' = 500
'SourceAddressPrefix' = '*'
'SourcePortRange' = '*'
'DestinationAddressPrefix' = 'virtualnetwork'
'DestinationPortRange' = 22,3389
'Access' = 'Allow'
}

Add-AzNetworkSecurityRuleConfig @Variables8 | Set-AzNetworkSecurityGroup


###Step 4 - Lock the NSG:
$NSG1 = $NSGname

New-AzResourceLock `
-LockLevel CanNotDelete `
-LockNotes "This Bastion NSG has been locked" `
-LockName "Locked by Allen" `
-ResourceName $NSG1 `
-ResourceType "Microsoft.Network/networkSecurityGroups" `
-ResourceGroupName $resourceGroupName `
-Force 

###Step 5 - Associate the Bastion NSG to the AzureBastionSubnet:

# Get the virtual network:
$vnet = Get-AzVirtualNetwork -Name $vnetName -ResourceGroupName $resourceGroupName

# Get the subnet:
$subnet = Get-AzVirtualNetworkSubnetConfig -Name $subnet1Name -VirtualNetwork $vnet

# Get the NSG:
$nsg = Get-AzNetworkSecurityGroup -Name $nsgName -ResourceGroupName $resourceGroupName

# Associate the NSG with the subnet:
Set-AzVirtualNetworkSubnetConfig -VirtualNetwork $vnet -Name $subnet1Name -AddressPrefix $subnet1Prefix -NetworkSecurityGroup $nsg

# Update the virtual network
$vnet | Set-AzVirtualNetwork        

Verification shows the 4 inbound and 4 outbound rules linked to the 1 subnet.



Step 4 - Create the public ip address

Use the PowerShell script below to provision the public ip address for the Azure Bastion Host.

The public IP address must be in the same region as the Bastion host = $location,

$resourceGroupName = "allen-sandbox-bastion"
$location = "southafricanorth"
$BastionPIPName = "Bastion-Public-ip"
$tag = @{CustomerName="Allen"; AutoShutdownSchedule="None"; Environment="Sandbox";}

$BastionName = "Allen-Azure-Bastion"
$NSGname = 'Bastion-NSG'

$Bastionpublicip = New-AzPublicIpAddress `
-ResourceGroupName $resourceGroupName `
-name $BastionPIPName `
-location $location `
-AllocationMethod Static `
-Sku Standard `
-Tag $tag        


Step 5 – Provision the Azure Bastion Host

The PowerShell script below will provision your Bastion Host which will take about 10 minutes to deploy.

#-ScaleUnit min 2 > each instance can support 20 concurrent RDP connections and 40 concurrent SSH connections,

#-EnableIpConnect > only required if you define a source IP eg on premise location,

$resourceGroupName = "allen-sandbox-bastion"
$vnetName = "allensan"
$tag = @{CustomerName="Allen"; AutoShutdownSchedule="None"; Environment="Sandbox";}
$BastionPIPName = "Bastion-Public-ip"
$BastionName = "Allen-Azure-Bastion"

$Bastion = New-AzBastion `
-ResourceGroupName $resourceGroupName `
-Name $BastionName `
-PublicIpAddressRgName $resourceGroupName `
-PublicIpAddressName $BastionPIPName `
-VirtualNetworkRgName $resourceGroupName `
-VirtualNetworkName $vnetName `
-Sku "Standard" `
-DisableCopyPaste $false `
-EnableTunneling $true `
-EnableIpConnect $false `
-EnableShareableLink $true `
-ScaleUnit 2 `
-Tag $tag        

Verification:

Copy and Paste?> enables users to copy and paste text between their local device clipboard and the remote session host.

Native client support?> A Standard and Premium Bastion Tier allowing users to use native SSH / RDP tools to connect to remote virtual machine instead of only using their internet browsers.

Shareable Link?– Standard and Premium Bastion feature that provisions a random Bastion URL that you send to the remote user to use to connect to the destination virtual machine.

A minimum of 2 hosts can be deployed, for high availability.



6. Monitoring:

Bastion can log diagnostics of the remote sessions. You can then use the diagnostics to view which users connected to which workloads, at what time, from where, and other such relevant logging information. In order to use the diagnostics, you must enable diagnostics logs on Azure Bastion. This article helps you enable diagnostics logs, and then view the logs.

https://learn.microsoft.com/en-us/azure/bastion/diagnostic-logs


End user Login:

Option #1 – Native RDP / SSH Tools:

Requirements:

#1 Azure Bastion Standard or Premium,

#2 Entra ID account,

If you have deployed the Azure Bastion Standard (or Premium) Tier and you have an Entra ID account, then you can use the RDP /SSH native tool on your local pc to logon to the destination virtual machine.

#1 Download and install the Azure CLI msi installer file onto your local endpoint https://aka.ms/installazurecliwindows or use winget in (Admin) console:

winget install -e --id Microsoft.AzureCLI

#2 Once the Azure CLI MSI is installed, save the following PowerShell script into a PowerShell file,

#Tenant ID which will prompt you to verify to login via browser https://microsoft.com/devicelogin with MFA code
$tenantID = "0000-0000-0000-0000-0000"
az login --tenant $tenantID

#tenant subscriptionID
$SubsID = "0000-0000-0000-0000-0000"
az account set --subscription $SubsID

#sets the extension without a prompt
az config set extension.use_dynamic_install=yes_without_prompt

#get Bastion details:
$BastionName = "Allen-Azure-Bastion"
$resourceGroupName = "allen-sandbox-bastion"

#get destination INTERNAL vm resourceID from vm > properties > resourceID:
$VMResourceID = "/subscriptions/0000-0000-0000-0000-0000/resourceGroups/allen-sandbox-bastion/providers/Microsoft.Compute/virtualMachines/app01"
$VMResourceID

#provision the RDP session:
az network bastion rdp --name $BastionName `
--resource-group $resourceGroupName `
--target-resource-id $VMResourceID        





Run the PowerShell script, you will be prompted with an RDP console,

Select Connect to logon via RDP,


Connect to the target virtual machine



Option #2 – Internet browser connectivity via the URL:


Create a Shareable Link:

The Bastion Shareable Link feature lets remote users connect directly to an internal destination virtual machine via a Azure Bastion generated URL.

When a user without Azure credentials clicks a shareable link, a browser webpage will open that prompts the user to sign in to the target resource via RDP or SSH. Users can authenticate using local virtual machine usernames and passwords or private keys. Multiple users can connect to the same resource concurrently using Azure Bastion.

Setup:

Go to Shareable Links > Add >

Select the destination internal virtual machine subscription, resource group and resource > Apply


A unique URL will be created for each virtual machine. I prefer this approach to minimize lateral movement between resources.

Copy the shareable link provisioned by Bastion and paste this into your browser:

(If you have enabled copy/paste, you can select Allow)



Post Deployment:

Scaling:

Azure Bastion hosts can support 20 concurrent RDP sessions and 40 SSH sessions.

Azure Bastion host scaling allows you to manually adjust the number of host instances post deployment to manage the load of concurrent RDP/SSH connections that Azure Bastion can accommodate.

Troubleshooting Azure Issues

Peering:

If you are connecting across multiple vnets, then peering must be implemented between the source Bastion virtual network and the remote virtual network hosting the destination virtual machines.

Without peering, end users will receive the following error message.

If after provisioning peering > If you ever get this message, delete and recreate your peering! Simple quick fix)



Connection Error:

This error indicates that your local username or password is incorrect. You can reset the password as a quick fix.












Manish Moothedath

Digital Architect, Microsoft platform & Business applications (Dynamics 365, Power Platform, Azure)

3 个月

Pls excuse my stupid question- Since bastion is working directly on the Azure portal, is it still necessary to have a public IP address??

回复
Otto Beekman

Azure Consultant | Cloud Journey Enablement Expert

3 个月

Good topic ????

要查看或添加评论,请登录

社区洞察

其他会员也浏览了