In this post, I will explain how you can deploy Azure Bastion into a spoke in a hub & spoke architecture with a Virtual WAN hub – and use that Bastion to securely log into virtual machines in other spokes using RDP or SSH. I will also explain why this has limitations in a hub & spoke architecture with a VNet-based hub.
Even organisations that opt for a PaaS-only Azure implementation need virtual machines. Once you do network security, you need virtual machines for those DevOps build agents or those GitHub runners. And realistically, migrated legacy workloads need VMs. And things like AKS and HPC are based on VMs that you build and troubleshoot. So you need VMs. And therefore, you need a secure way to log into those VMs.
Those of us who want an air gap between the PC and the servers have tried things like RD Gateway and Guacamole. Neither is perfect. Ideally, we want Azure AD integration (for Premium security features) and a platform resource (to minimise maintenance).
And along came Azure Bastion. At first reading, it seemed ideal. And then we started to discover warts. Many of those warts were cleaned up. Bastion got support for a desktop client through a CLI login. A hub deployment was possible – if you use a VNet-based hub – but it gave Bastion users (including external support) staff a map of your entire Azure network because they read access to the hub VNet – and all its peering connections. For many of us, that left us with deploying Bastion in every subnet – both costly and a waste of IP space.
We needed an Azure Bastion that we could deploy once in a spoke. We could log into it, route through the firewall in the hub, and log into VMs running in other spokes.
Microsoft announced a new feature in the Standard tier of Azure Bastion called IP-Based Connection. With this feature, you can log into a virtual machine across an IP network from your Bastion. That means you can log into:
- Virtual machines in the same subnet or virtual network
- Virtual machines in other Azure virtual networks
- On-premises computers across site-to-site network connections such as VPN or ExpressRoute
The assumptions are that:
- The NSG protecting the Azure virtual machine allows SSH/RDP from AzureBastionSubnet o the virtual machine.
- The hub firewall (if you have one) allows SSH/RDP from AzureBastionSubnet to the virtual machine.
- The on-premises firewall, if the virtual machine is on-premises, allows RDP/SSH from AzureBastionSubnet to the computer.
- The OS of the virtual machine or computer allows RDP/SSH rom AzureBastionSubnet.
In my first experiment, I tried deploying a shared Azure Bastion in a spoke with a VNet-based hub. I could deploy it no problem but Azure Bastion could not route to other spokes sharing the hub. Why?
There were no routes from the spoke subnets to other spoke subnets. But that’s OK – I know how to fix that.
Let’s say my entire Azure network is in the 10.1.0.0/16 address space. Well, if I want to route to any spoke in that address space, I can:
- Create a route table and *cough* associate it with the AzureBastionSubnet
- Add a user-defined route (UDR) for 10.1.0.0/16 with a next hop of the hub firewall (or a routing appliance).
Azure Bastion needs to have a route of 0.0.0.0/0 to Internet so you can’t do the usual spoke thing with the UDR so I could leave the default Internet route in place.
Did it work? No – that’s because AzureBastionSubnet has a hard-coded rule to prevent the association of a route table. I guess that Microsoft had too many support calls with people doing bad things with a route table associated with AzureBastionSubnet.
It turns out that the only way to get a route from one spoke to another with a VNet-based hub is:
- Use Azure Virtual Network Manager (AVNM – currently in preview) to peer your spokes with transitive peering.
- Do not expect spoke-to-spoke traffic to flow through a hub firewall – AVNM does not support configuring a next-hop.
That means the only shared Azure Bastion option for VNet-based hubs is to deploy Bastion in the hub and leave all your peering connections visible. Ick!
I really like using Azure Virtual WAN (vWAN) for my hub and spoke – and almost none of my customers use it for SD-WAN (the primary use case). The reasons I like it are:
- It pushes the hub into the platform, reducing administrative efforts
- Routing becomes something that you push from the hub using eBGP
“Ah – what’s that you say about eBGP, Aidan?”
You can create route tables in Virtual WAN Hubs – let’s call them hub route tables. Then in the properties of a spoke virtual network connection you can configure:
- Propagation: Have the route table learn routes from the spoke virtual network using eBGP.
- Association: Share routes from the route table to the spoke virtual network.
And you can put static routes into a hub route table.
When I deploy a Virtual WAN Hub, I choose the Secured Virtual WAN Hub option. This places an Azure Firewall in the hub. I then add a static route for the 0.0.0.0/0 and private IP address spaces to route via the Azure Firewall in the build-in Default hub route table.
- Propagate to the built-in None hub route table, so the routes of the spoke are forgotten.
- Associate with the built-in Default hub route table, so they learn the next hop to 0.0.0.0/0 and the private IP address spaces is via the firewall.
I can deploy Azure Bastion into a spoke but this spoke will require a different route configuration. That is because I use a firewall to isolate the spokes. If I had open spoke-to-spoke traffic then Bastion would probably just work. My scenario is actually simple to fix:
- Create a new hub route table, maybe called Bastion.
- Add a static route to the rest of the hub and spoke (10.1.0.0/16 in my example) with a next-hop of the hub firewall.
- Configure the Bastion spoke connection to associate with the Bastion hub route table and propagate to none.
- The Bastion will use the firewall as the next hop to all other spokes.
- Go directly to Internet for the control plane traffic, using the default route in the subnet.
- Other spokes will have the same route back to the Bastion using the firewall as the next-hop.
Finally, you need to ensure that Firewall and NSG rules allow RDP/SSH from AzureBastionSubnet in the Bastion spoke to the VMs in other spokes. And it works! All an operator/developer/support staff member needs now is:
- An Azure AD account with read access to the Azure Bastion resource – no need for read to the hub or even the spoke with the VM!
- The IP address for the machine they want to sign into – my design is limited to Azure VMs but on-premises static routes could be added to the Bastion hub route table.
- A username and password with login rights to the virtual machine or computer.