Managed and secured hub-spoke architecture using Azure WAN and Firewall Manager

10 minute read

Azure Virtual WAN is a managed hub-spoke architecture, that supports public (VPN) and private (Express Route) connectivity. The hub enables transitive connectivity between endpoints that may be distributed across different types of spokes. Traffic from the branches enters Microsoft’s network at the Microsoft edge, or Point of Presence (PoP) sites closest to a given branch office and traverse Microsoft backbone to reach the virtual hub. In this architecture, there is a single hub per region, and all sites from that region connect to the closest hub. You can also connect to a hub in a different region, but for latency-sensitive connections, this probably is not a good idea. Hub-to-hub connection (in preview at the time of writing) enables full mesh between the hubs enabling, for example, vnet-to-vnet or branch-to-branch connectivity through the hubs.
There are two types of SKUs for Azure Virtual WAN:

  • Basic: supports only Site-to-site VPN connectivity
  • Standard: supports ExpressRoute, Point-to-site VPN, Site-to-site VPN, Inter-hub and vnet-to-vnet connectivity

Desktop View

Secured virtual hub (in public preview) is an Azure Virtual WAN Hub with associated security and routing policies configured by Azure Firewall Manager. It enables the easy creation of hub-and-spoke architectures with cloud-native security services for traffic governance and protection. A secured virtual hub can also be used as a managed central virtual network with no on-prem connectivity.

Desktop View


Several components of the architecture are involved in the pricing (prices below are for the West Europe region):

Connectivity unit

  • Site-to-site VPN / Express Route: €0.043/hour
  • User VPN (Point-to-site) : €0.011/hour

Scale unit (Aggregate throughput of a hub gateway)

  • 1 VPN Scale Unit = €0.305/hour (500 Mbps)
  • 1 ExpressRoute Scale Unit = €0.355/hour (2 Gbps)

Virtual Hub

  • Basic Virtual WAN Hub : No charge
  • Standard Virtual WAN Hub : €0.211/hour,
  • Secure Virtual WAN Hub: €0.528/hour (preview price)
  • Virtual WAN Hub data processing: €0.014/GB
  • Secured Virtual WAN Hub data processing: €0.007/GB (preview price)


For the demo, we are going to create a Virtual WAN with a secured virtual hub. We will create two spokes (Shared Services and WebApp spoke),deploy an Application Gateway in the Shared Services spoke and publish the nginx from the WebApp spoke through the previously created Application Gateway. To enable communication between the spokes, we will create a Firewall Policy. You can find the full script here.

Desktop View

Note: If we want to route Internet traffic from the spokes through the Azure Firewall deployed in the hub, we will lose the connectivity to the services that are using public IP in the spokes (in our case Application Gateway). Probably, this is some limitation of the public preview of Azure Firewall Manager.

Let’s start.

1. Set the value of the variables we are going to use during the deployment


2. Install virtual-wan and azure-firewall cli extensions

az extension add --name virtual-wan
az extension add --name azure-firewall

3. Create the resource group

az group create --name $resourceGroupName --location $location

4. Create Azure WAN

az network vwan create --name $wanName \
--resource-group $resourceGroupName --type Standard \
--location $location --vnet-to-vnet-traffic --branch-to-branch-traffic 

5. Create virtual hub

az network vhub create --name $virtualHubName \
--address-prefix $virtulHubAddressPrefix \
--resource-group $resourceGroupName \
--vwan $wanName --location $location \
--sku Standard

6. Convert the virtual hub into secured virtual hub

az network firewall create --name virtulHubFirewall \
--resource-group $resourceGroupName \
--location $location --sku AZFW_Hub \
--vhub $virtualHubName

7. Create shared services spoke vnet

az network vnet create --name $sharedServicesVnetName \
--resource-group $resourceGroupName \
--address-prefixes $sharedServicesVnetAddressPrefix \
--location $location \
--subnet-name $sharedServicesVnetSubnet1Name \
--subnet-prefixes $sharedServicesVnetSubnet1AddressPrefix

8. Crete a spoke vnet

az network vnet create --name $spokeVnetName \
--resource-group $resourceGroupName \
--address-prefixes $spokeVnetAddressPrefix \
--location $location \
--subnet-name $spokeVnetSubnet1Name \
--subnet-prefixes $spokeVnetSubnet1AddressPrefix

9. Get the ids of the created network resources

sharedServicesVnetId=$(az network vnet show --name $sharedServicesVnetName \
--resource-group $resourceGroupName --query id -o tsv )
spokeVnetId=$(az network vnet show --name $spokeVnetName \
--resource-group $resourceGroupName --query id -o tsv)
spokeSubnet1Id=$(az network vnet subnet show --name $spokeVnetSubnet1Name \
--resource-group $resourceGroupName --vnet-name $spokeVnetName \
--query id -o tsv)

10. Peer the shared services vnet with the secured hub

az network vhub connection create \
--name sharedServicesConnectionPeer \
--remote-vnet $sharedServicesVnetId \
--resource-group $resourceGroupName \
--internet-security true \
--vhub-name $virtualHubName

11. Peer the spoke vnet with the secured hub

az network vhub connection create \
--name spokeConnectionPeer \
--remote-vnet $spokeVnetId \
--resource-group $resourceGroupName \
--internet-security true \
--vhub-name $virtualHubName

12. Send the traffic between vnets and branches through Azure Firewall

az network vhub route-table create \
--connections All_Branches All_Vnets \
--destination-type CIDR \
--destinations $sharedServicesVnetAddressPrefix $spokeVnetAddressPrefix \
--name VirtualNetworkAndBranchRouteTable \
--next-hop-type IPAddress \
--next-hops \
--resource-group $resourceGroupName \
--vhub-name $virtualHubName \
--location $location

13. Create a cloud-init file for the Linux VM to auto install nginx on provisioning

cat <<EOF > cloud-init.yaml
package_upgrade: true
  - nginx

14. Deploy Linux VM in spoke vnet

az vm create --name $spokeVMName \
--resource-group $resourceGroupName \
--image UbuntuLTS --authentication-type password \
--admin-username $spokeVMUserName \
--admin-password $spokeVMPassword \
--location $location \
--private-ip-address $spokeVMIPAddress \
--public-ip-address "" \
--size Standard_A1_v2 \
--subnet $spokeSubnet1Id \
--generate-ssh-keys \
--custom-data cloud-init.yaml

15. Create public IP address for the Application Gateway

az network public-ip create \
  --resource-group $resourceGroupName \
  --name myAGPublicIPAddress \
  --allocation-method Static \
  --sku Standard

16. Create the ApplicationGateway

az network application-gateway create \
  --name aksAppGateway \
  --location $location \
  --resource-group $resourceGroupName \
  --capacity 1 \
  --sku Standard_v2 \
  --public-ip-address myAGPublicIPAddress \
  --vnet-name $sharedServicesVnetName \
  --subnet $sharedServicesVnetSubnet1Name \
  --http-settings-port 80 \
  --http-settings-protocol Http \
  --servers $spokeVMIPAddress

17. Create a firewall policy

az network firewall policy create \
  --name WanBasePolicy \
  --resource-group $resourceGroupName \
  --location $location

18. Create a firewall rule collection group

az network firewall policy rule-collection-group create \
--name spokeRules --policy-name WanBasePolicy \
--priority 201 --resource-group $resourceGroupName

19. Add filter collection into the rule collection group

az network firewall policy rule-collection-group collection add-filter-collection \
--collection-priority 201 \
--name spokeCollection \
--policy-name WanBasePolicy \
--resource-group $resourceGroupName \
--rule-collection-group-name spokeRules \
--action Allow \
--rule-type NetworkRule \
--destination-addresses $spokeVMIPAddress \
--destination-ports 80 \
--ip-protocols TCP \
--source-addresses $sharedServicesVnetSubnet1AddressPrefix

20. Assing the policy to the firewall

az network firewall update \
--name virtulHubFirewall \
--resource-group $resourceGroupName \
--firewall-policy WanBasePolicy

21. Access the Application Gateway listener

curl http://$(az network public-ip show \
--name myAGPublicIPAddress --resource-group $resourceGroupName \
--query ipAddress -o tsv)

If you do not see the “Welcome to nginx!” page, wait for 30 seconds for the backend health to refresh in Application Gateway. If still not successful, debug debug debug …

Grab a beer. You did it!