Search
StarWind is a hyperconverged (HCI) vendor with focus on Enterprise ROBO, SMB & Edge

A centralized, secured & restricted Azure Bastion deployment in an Azure Virtual WAN spoke

  • April 13, 2023
  • 40 min read
Cloud and Virtualization Architect. Didier is an IT veteran with over 20 years of expertise in Microsoft technologies, storage, virtualization, and networking. Didier primarily works as an expert advisor and infrastructure architect.
Cloud and Virtualization Architect. Didier is an IT veteran with over 20 years of expertise in Microsoft technologies, storage, virtualization, and networking. Didier primarily works as an expert advisor and infrastructure architect.

Introduction

Since its inception, Azure Bastion has been a practical, affordable, and secure way to offer connectivity to Azure virtual machines without setting up another secured solution. In addition, since it is a platform resource, you don’t need to worry about maintaining it beyond the actual deployment, and there is minimal configuration involved. That made up for its drawbacks.

Azure Bastion - secured access to your VMs

Figure 1: Azure Bastion – secured access to your VMs (Photo by Richard Clark on Unsplash)

A highly available Remote Desktop Gateway (RDGW) solution protected via a redundant RADIUS/Network Policy Server (NPS) deployment that runs the NPS Extension for Azure MFA to provide multi-factor authentication (MFA) does the job excellently. It has long been my favorite, providing a more user-friendly and complete solution for RDP than Azure Bastion. And yes, you can also use the NPS extension to provide MFA for SSH. In addition, you can isolate this solution from the rest of the network while sending traffic over a firewall for granular access control. Read more about this solution in these two articles: Transition a Highly Available RD Gateway to Use the NPS Extension for Azure MFA – Phase I and Transition a highly available RD Gateway to use the NPS Extension for Azure MFA – Phase II.

But it requires Windows Server, RDGW, Radius/NPS, load balancer skills, and time and effort to design, deploy, maintain, and support it. Tell me, when was the last time you upgraded such a solution from, let’s say, Windows Server 2016 to Windows Server 2019 or Windows 2022? How often do you upgrade the NPS extension or renew the certificates? I’ve done it a lot and still do that. My on-premises skills remain and are maintained next to my Azure skill set. But of the new talent entering the IT world, few are trained in on-prem skills. On top of that, you need much exposure, which means time, to gain a better understanding and more profound knowledge required for troubleshooting when needed.

Azure Bastion was not perfect from day one but has improved significantly. We now even have Kerberos support in the works! The biggest challenge in the early days with Azure Bastion was that it seemed to be a point solution which forced us to deploy it many times per environment and forced many people to the RDGW/NPS solution mentioned above. That was addressed over time via VNet peering support and IP-based connection support. Those two features, combined with native client support, allow Azure Bastion to become better at providing a full-fledged alternative to the RDGW/NPS solution. But it is not complete or perfect yet, and RDGW/NPS remains the most complete and especially the more end user-friendly solution.

Today we look at building a central and secured Bastion solution in an Azure VWAN environment. How? Read on! When you have it working, it is up to you to decide if it is sufficient for your needs and if you can tear down the existing RDGW/NPS solution.

The case for a centralized, secured & restricted Azure Bastion deployment

Let’s make a case for a centralized, secured & restricted Azure Bastion deployment in an Azure Virtual WAN spoke.

  • Centralized is because we deploy a single Azure Bastion deployment for all our needs, not multiple deployments as we sometimes needed in the past.
  • Secured means we control the traffic over the firewall of the secured Hub.
  • Restricted refers to the fact that we do not allow routing from our Azure Bastion network to our Azure Virtual WAN spoke bar via the firewall. We do not expose unneeded routing information and, as such, network insights via Azure Bastion whatsoever.

While some quirks and limits of Azure VWAN, especially with a secure virtual hub, bother me at times, it also does some things very well and offloads the burden of VNet peering at any scale of my shoulders. And then, sometimes, it allows for an elegant solution to particular challenges, which is the case with Azure Bastion. Let’s discuss why this is.

Azure Hub-and-spoke network

First, I do not want to deploy Azure Bastion into a network environment shared with other workloads. I can deploy Azure Bastion in the Hub of a hub-and-spoke network. While that works, it potentially leaves all the peered VNET information visible for people with read access to the Hub VNet. When you want to have the bastion traffic pass over a firewall, things become even more interesting, as you can read below.

Azure Bastion in the Hub of a hub-and-spoke network works, but no thanks!

Figure 2: Azure Bastion in the Hub of a hub-and-spoke network works, but no thanks!

Can even we achieve what I want? I cannot accomplish this goal with a traditional hub and spoke model, as we just saw when we discussed deploying Azure Bastion in the Hub. That exposes too much of our network, which we dislike. But if we deploy it in a dedicated spoke VNet of a hub-and-spoke network, our plan to restrict routing falls apart because the Azure Bastion subnet does not support User Defined Routing (UDR). As a result, we cannot send the traffic to the firewall and do the other things we expect using UDR. So, keep this in mind. It is a restriction you cannot ignore. So sending traffic over a firewall would require Route Server. Since Azure firewall does not support BGP, you’ll need either a 3rd party firewall or some creative trickery. Using a Firewall in a Hub deployment won’t work since Route Server cannot override the routes learned via VNet peering. Sigh!

Nice try, but no, this will not work

Figure 3: Nice try, but no, this will not work.

The second restriction to be aware of is that you must not override the default route (0.0.0.0/0) of the Azure Bastion subnet to reach the Internet directly. If you do, public connectivity to Azure Bastion breaks. Clients need Bastion to have direct Internet access to connect to Azure Bastion over the Internet. But Bastion also requires it to manage the service correctly. This requirement also reflects in the network security group (NSG) settings we’ll discuss later. So, don’t worry about direct access to the Internet. It is secured access managed by the platform.

Azure Virtual WAN

When using Azure Virtual WAN, you cannot deploy Azure Bastion into the Virtual WAN hub. That is perfectly fine. I prefer it isolated in a dedicated VNet/subnet with minimal exposure to and disclosure of the rest of the network. I also don’t want “any to any routing” for this use case. I want to route all the traffic from Azure Bastion via the firewall.

Can we build what we want, considering the two restrictions and knowing we use an Azure VWAN with a secured virtual hub?

  1. No routing all over the place (any-to-any) by the Azure Bastion spoke VNet. As a result, we do not provide insights into the network via the routing table of the Azure Bastion network.
  2. Azure Bastion is to reach all intended VM targets via the secured virtual hub firewall
  3. Azure Bastion requires a direct connection to the Internet to function correctly.

Requirement one leads to requirement two. Ultimately, my need to limit the routing information for the Azure Bastion subnet to the minimum required forces me to provide connectivity via the firewall in the secured virtual Hub, which allows for granular control of what traffic from Azure Bastion can reach. If you want to do the exercise for another setup, go ahead and try, but remember that the Bastion subnet does not support user-defined routing (UDR) and see how far you get without very creative constructions.

You cannot deploy Azure Bastion into the Azure Virtual WAN hub when using Azure Virtual WAN. That is perfectly fine. I prefer it isolated in a dedicated VNet/subnet with minimal exposure to and disclosure of the rest of the network. I also don’t want “any to any routing” for this use case. I want to route all the traffic from Azure Bastion via the firewall.

Am I paranoid? Think about it. No matter who it is, power users or external or internal support staff, there is no reason for them to gain any information about the network infrastructure to which they connect via Azure Bastion. Preventing such insights adheres to a strict “least privilege” principle, as they only need to have read access to the Azure Bastion resource.

Sure, I can deploy Azure Bastion “Quick & Dirty” if I allow routing from anywhere to anywhere, which is the default, in Azure VWAN when you click it together, as peering is transitive. I could even build a solution without a secured virtual hub (firewall) in my VWAN. But why would you want to give that much access and, potentially, insight into the Azure network layout to anyone connecting to the Azure Bastion network? Also, remember that as you cannot use a UDR, you cannot use it to block BGP on the Bastion subnet to prevent it from learning the routes.

Routing is often customized, and not just that, most or at least much network traffic passes via the Azure Firewall. Therefore, I treat my Azure Bastion network as a highly secure and isolated environment. Next to minimizing (or removing) direct routing, we control network traffic flow via the Azure Firewall in the secured virtual Hub. That gives me precise control over what VMs are reachable via Azure Bastion.

Design and configuration

As stated above, Azure Bastion support for VNET peering was instrumental in making IP-based connections possible. The latter makes deploying a central bastion solution in a secure yet, routing-wise, restricted spoke VNet possible. As a result, we can connect to any virtual machine in a peered VNET or on-premises!

Sounds easy enough, right? But how to configure this in our desired centralized, secured & restricted Azure Bastion deployment in an Azure VWAN spoke?

Given our requirements, let’s explain what we need to make Azure Bastion work.

  • The Azure Bastion network reaches the Internet directly via 0.0.0.0/0 Internet. That is a requirement for the Bastion control plane traffic.
  • The Azure Bastion network reaches all internal, private IP ranges via the Azure Firewall private IP as their next hop, where you control where it can go very granularly.
  • The target spokes, where the VMs you target with RDP or SSH reside, must have a route return route to the Azure Bastion network via the Azure Firewall private IP to send response traffic.
  • On the Azure Firewall and all NSGs involved, you must allow RDP (TCP/UPD/3389)/SSH(TCP/22) from the Bastion subnet to the VM subnets or IP addresses in the other VWAN spokes.
  • Configure the Azure Bastion NSG explicitly for its role and nothing more.
  • The on-premises firewall must also allow connectivity to virtual machines in branches.
  • Any virtual machine must allow RDP/SSH in the host firewall and enable RDP/SSH access.

I use a dedicated VNet for Azure Bastion. That way, I prevent any impact this might have on other workloads if we were to share the VNET. Restricting the routing and sending all connectivity to virtual machines over the firewall cannot impact different workloads. That, combined with a custom routing table in the Azure VWAN hub dedicated to Azure Bastion, isolates the bastion network as much as possible.

The custom Azure Bastion routing table

In Azure VWAN, we create a custom routing table called rt-bastion.

  • We add a route for the private ranges where the VMs we connect to reside. In this example, 10.16.0.0/16 covers all of them.
  • We do not secure the internet traffic for the Bastion VNet in Azure Firewall Manager or VWAN rout table (it requires direct Internet connectivity).
  • We associate our Bastion VNet connection with the rt-bastion
  • We propagate our Bastion VNet connection to NONE, so no one learns a route to it. As a result, the other connected VNets do not know about a path to the Bastion VNet.

The other connected VNets

Since the other connected VNets do not know about a route to the bastion network, how does return traffic reach it? In the default routing table or custom routing tables, a path exists for 0.0.0.0/0 to the firewall.

They associate with the desired routing table and propagate to whatever routing table is needed but not to the rt-bastion route table. As such, the rt-bastion route table learns nothing of the route to the other connected VNets. Therefore, everything heading to the internal private range must go via the Azure Firewall.

Example of a centralized Bastion deployment in Azure VWAN

Figure 4: Example of a centralized Bastion deployment in Azure VWAN

In the same spirit, we can even reach on-premises virtual and physical machines via their IP address if we ensure the on-premises router/firewall knows where to send the return traffic to the bastion host.

As you can see in the image, this works whether you use the default route table, custom route table, or a mix of both.

Do not secure internet traffic on the Bastion VNet via the Azure Firewall. It needs to connect directly to the Internet

Figure 5: Do not secure internet traffic on the Bastion VNet via the Azure Firewall. It needs to connect directly to the Internet.

You can let connected VNets route directly to each other, route them via the firewall (securing private traffic), or a mixture of both. There are some things to be aware of. First, you cannot manage all configurations via the Azure Firewall Manager in the portal. The configuration settings in your custom route tables, especially those for private traffic, do not always reflect in the Azure Portal user interface. Beware of this. The Internet traffic from on-premises does not flow over Azure Firewall but passes over the on-premises firewall to the public IP address of the Bastion host.

Azure Bastion Subnet Network Security Group (NSG)

We want to be as restrictive as possible and only allow what Azure Bastion needs to function correctly.

Inbound rules

Add the following rules to make sure Azure Bastion functions correctly:

  1. Azure Bastion has a public IP with port 443 enabled for ingress traffic. IMPORTANT: Port 3389/22 is NOT required to be opened on the AzureBastionSubnet. The source can be the Internet or a set of public IP addresses that you specify to restrict access, Only from behind your corporate firewall.
  2. The Azure Bastion control plane requires connectivity over TCP/443 inbound from the GatewayManager service tag. That way, the control plane, Gateway Manager, can talk to Azure Bastion.
  3. The Azure Bastion data plane needs communication between the underlying components of Azure Bastion over ports 8080 and 5701. The inbound traffic comes from the VirtualNetwork service tag and goes to the VirtualNetwork service tag.
  4. Azure Bastion needs ingress traffic enabled on port 443 from the AzureLoadBalancer service tag for health probes.
  5. I also block all the default rules that an NSG contains and that I cannot delete or change via an inbound rule with a higher priority that overrides them all.

NSG inbound rules for the Azure Bastion subnet

Figure 6: NSG inbound rules for the Azure Bastion subnet

Outbound rules

Add the following rules to make sure Azure Bastion functions correctly:

  1. Outbound traffic for Azure Bastion to the target VMs private Ips requires the Azure Bastion NSG to allow egress traffic to the target VM subnets for ports 3389 and 22. I split up the rules so that I could manage them separately. While RDP supports using UDP as an optional transport protocol, Azure Bastion does not currently use UDP for RDP connections, but one day I hope UDP support becomes available.
  2. The Azure Bastion NSG also needs to allow data plane communication between the underlying components of Azure Bastion. To do so, enable ports 8080 and 5701 outbound from the VirtualNetwork service tag to the VirtualNetwork service tag.
  3. Azure Bastion must connect to various public endpoints within Azure (for example, for storing diagnostics and metering logs). Therefore, we allow outbound traffic to 443 to the AzureCloud service tag.
  4. Azure Bastion needs to communicate with the Internet for session and certificate validation. To do so, it requires outbound traffic over port 80 outbound to the Internet.
  5. Finally, I also block all the default rules that an NSG contains, which I cannot delete or change via a rule with a higher priority that overrides them all.

NSG outbound rules for the Azure Bastion subnet.

Figure 7: NSG outbound rules for the Azure Bastion subnet.

Subnets where the virtual machine reside Network Security Group

I allow inbound RDP/SSH traffic only from the Azure Bastion subnet. That ensures no other network sources can reach the VMs via TCP/3389 or TCP/22.

Please test it out!

If you have this in place, anyone with the following minimal permission requirements fulfilled can connect to a VM VNet connected to the VWAN, aka a spoke, via RDP or SSH.

Using the native RDP client with the VM resource ID

Required permissions

When connecting to a virtual machine using Azure Bastion via the virtual machine resource id, an AD user will at least need the below roles:

  • Reader role on the Azure Bastion resource.
  • Reader role on the target VM.
  • Reader role on the network interface (NIC) with private IP of the VM.

Since we are most definitely connecting to virtual machines over peered networks (Azure Virtual WAN), we also need the following role:

  • Reader tole on the virtual network of the target virtual machine

Now it might be more practical to assign permission using the predefined role “Virtual Machine User Login” at the subscription or resource group level. No matter what, you need the reader role on the Azure Bastion host resource. As always, use groups to assign rights to, not individual users. Nothing more, remember the aim of the exercise here is to lock down Bastion as tight as we can.

Example

Open up a PowerShell console. I like to use Windows Terminal for this. Make sure you have the latest version of Azure CLI installed. Use the MSI installer interactively or do so via PowerShell. For more information, see Install Azure CLI on Windows. You also need the bastion extension, which installs for you when required. You can also install it manually via az extension add –name Bastion

Run the following commands (note that the Bastion subscription ID in the code example is fake)

You should get an RDP client prompt if the permissions are correct

Figure 8: You should get an RDP client prompt if the permissions are correct.

You should notice that in the Advanced tab under “RD Gateway server settings,” the FQDN for the bastion host is filled out for you.

Notice the FQDN for your bastion host

Figure 9: Notice the FQDN for your bastion host

That is fine, but how do you log in to it with the azure credentials? When we save that RDP config to a file and look at it with a text editor, you look for “gatewayaccesstoken” and “signature.”

Note the “gatewayaccestoken” and signature settings

Figure 10: Note the “gatewayaccestoken” and signature settings

Using Bastion IP-based connection in the Azure portal

Required permissions

Connectivity via IP is the use case where the right can be completely minimal. The Azure AD user needs only read rights on the Azure Bastion resource and nothing else! It can’t get more restricted than that. The drawback of such minimal permissions is that the user needs to know the URL to the bastion resource. They cannot navigate through the portal due to a lack of rights. You must have more read rights on the resource group for this.

However, such restricted user rights make IP-based connectivity a prime use case when you need to allow external support personnel to log into a virtual machine in your environment.

Example

Log in to the Azure portal and navigate to your bastion deployment. Select “connect” in the left pane, fill out the IP address, the username, and provide the password. Click connect and watch the magic happen. Beware of any popup blockers.

Using Bastion IP-based connection in the Azure portal

Figure 11: Using Bastion IP-based connection in the Azure portal

Logged in to a VM without a public IP using Bastion IP-based connection via the Azure portal

Figure 12: Logged in to a VM without a public IP using Bastion IP-based connection via the Azure portal

Using Bastion connection with the native RDP and the VM IP address

This option is still a bit experimental and does not always work. However, it does with the bastion extension for az (version 2.45) network RDP with version 0.2.3. The –configure parameter seems to be ignored when combined with –target-ip-address at the time of writing. But this is a work in progress. So when testing, your mileage may vary.

In version 0.2.3, the bastion extension ignores the --configure parameter when combined with the --target-ip-address parameter

Figure 13: In version 0.2.3, the bastion extension ignores the –configure parameter when combined with the –target-ip-address parameter.

What does a user need for all this magic to work?

In terms of Azure rights, very little:

  1. The user needs an Azure AD account with read access to the Azure Bastion resource. No other Azure user rights are required! The dependency on an Azure AD account on which you enforce MFA helps secure RDP/SSH access to the VMs.
  2. The users need to know the resource ID or the IP address of the VM. If you want to be able to click through the portal, you need extra read-rights to get there (resource group)
  3. Note that the VMs can reside in Azure or a Branch. Note that in a branch, this can even be a physical host.For connectivity to the virtual machine:
  4. Finally, the user needs a user/password to log in to the virtual machine.
  5. That can be a local account or an Azure AD account. At the time of writing, Kerberos support is in preview.

To make the entire az CLI experience more comfortable for end users, we can wrap it in PowerShell and minimize the parameters they need to fill out. Depending on what we can provide for the client, we can limit what they need to know to the server name or IP address. That is if we can fill out their subscription and resource group. For inspiration on how to do this, I have an example script of mine on GitHub. With some work, we can add ssh as well.

Conclusion

That’s it. We have deployed and tested an Azure Bastion deployment in a centralized spoke VNet. It is shared across our Azure network and secured since traffic passes via Bastion over the firewall. Finally, it is also restricted, meaning we have no routing info for the rest of our network exposed to the Azure Bastion VNet. While native client support with Azure Bastion is still in preview, it is improving, and I hope we’ll see a release of version 1.0 this year.

The above article shared the most satisfying setup for Azure Bastion I can build right now. Unfortunately, it is not yet on par with the experience of a self-deployed RDGW setup. But we are getting closer. However, if we could make the client experience with the native RDP client a bit more user-friendly, it would put the icing on the cake!

Hey! Found Didier’s insights useful? Looking for a cost-effective, high-performance, and easy-to-use hyperconverged platform?
Taras Shved
Taras Shved StarWind HCI Appliance Product Manager
Look no further! StarWind HCI Appliance (HCA) is a plug-and-play solution that combines compute, storage, networking, and virtualization software into a single easy-to-use hyperconverged platform. It's designed to significantly trim your IT costs and save valuable time. Interested in learning more? Book your StarWind HCA demo now to see it in action!