Secure hybrid network
This sample deploys a hub and spoke network, a mock on-premises network, and connects both with a site-to-site VPN connection.
Where applicable, each resource is configured to send diagnostics to an Azure Log Analytics instance.

For detailed information, see the Implement a secure hybrid network:
Deploy sample
Clone this repo
git clone https://github.com/mspnp/samples.git
cd samples/solutions/secure-hybrid-network
Run the following commands to initiate the deployment. When prompted, enter values for an admin username, password, and VPN shared key. These values are used to log into the included virtual machines and establish the site-to-site VPN connection.
# Deploy the base infrastructure: hub, spoke, firewall, VPN, and VMSS workload
az deployment sub create -n secure-hybrid-network --location eastus2 --template-file azuredeploy.bicep -p mockOnPremResourceGroup=rg-site-to-site-mock-prem-eastus2 azureNetworkResourceGroup=rg-site-to-site-azure-network-eastus2
Now that the on-premises site has joined the network, update the hub firewall with a DNAT rule so it can reach the spoke workloads:
# Get the firewall and load balancer private IPs
FW_IP=$(az network firewall show -g rg-site-to-site-azure-network-eastus2 -n AzureFirewall --query "ipConfigurations[0].privateIPAddress" -o tsv)
LB_IP=$(az network lb frontend-ip list -g rg-site-to-site-azure-network-eastus2 --lb-name lb-internal --query "[0].privateIPAddress" -o tsv)
# Add DNAT rules for on-premises to spoke traffic
az deployment group create -n firewallDnat -g rg-site-to-site-azure-network-eastus2 --template-file nestedtemplates/azure-network-azuredeploy-v2.bicep -p firewallName=AzureFirewall firewallPrivateIp=$FW_IP internalLoadBalancerPrivateIp=$LB_IP
Solution deployment parameters
azuredeploy.bicep
| Parameter | Type | Description | Default and properties |
|---|---|---|---|
| mockOnPremResourceGroup | string | The name of the mock on-prem resource group. | null |
| azureNetworkResourceGroup | string | The name of the Azure network resource group. | null |
| adminUserName | string | The admin user name for the virtual machines. | null |
| adminPassword | securestring | The admin password for the virtual machines. | null |
| sharedKey | securestring | The shared key used for VPN site-to-site connections. | null |
nestedtemplates/azure-network-azuredeploy.bicep
| Parameter | Type | Description | Default and properties |
|---|---|---|---|
| adminUserName | string | The admin user name for the virtual machines. | azureadmin |
| adminPassword | securestring | The admin password for the virtual machines. | null |
| vmSize | string | Size of the load-balanced virtual machines. | Standard_D2s_v3 |
| configureSitetosite | bool | Condition for configuring a site-to-site VPN connection. | true |
| hubNetwork | object | Object representing the configuration of the hub network. | name, addressPrefix |
| spokeNetwork | object | Object representing the configuration of the spoke network. | name, addressPrefix, subnetName, subnetPrefix, subnetNsgName |
| vpnGateway | object | Object representing the configuration of the VPN gateway. | name, subnetName, subnetPrefix, publicIPAddressName |
| bastionHost | object | Object representing the configuration of the Bastion host. | name, subnetName, subnetPrefix, publicIPAddressName, nsgName |
| azureFirewall | object | Object representing the configuration of the Azure Firewall. | name, subnetName, subnetPrefix, publicIPAddressName |
| spokeRoutes | object | Object representing user-defined routes for the spoke subnet. | tableName, routeNameFirewall |
| gatewayRoutes | object | Object representing user-defined routes for the gateway network. | tableName, routeNameFirewall |
| internalLoadBalancer | object | Object representing the configuration of the application load balancer. | name, backendName, frontendName, probeName |
| location | string | Location to be used for all resources. | rg location |
nestedtemplates/azure-network-local-gateway.bicep
| Parameter | Type | Description | Default and properties |
|---|---|---|---|
| connectionName | string | Name of the Azure connection resource. | hub-to-mock-prem |
| gatewayIpAddress | string | Public IP address of the mock on-prem virtual network gateway. | null |
| azureCloudVnetPrefix | string | Address prefix of the hub network. | null |
| azureNetworkGatewayName | string | Name of the Azure virtual network gateway. | null |
| localNetworkGatewayName | string | Name of the Azure local network gateway. | local-gateway-azure-network |
| sharedKey | securestring | The shared key for the VPN connection. | null |
nestedtemplates/mock-onprem-azuredeploy.bicep
| Parameter | Type | Description | Default |
|---|---|---|---|
| adminUserName | string | The admin user name for the virtual machine. | null |
| adminPassword | securestring | The admin password for the virtual machine. | null |
| mocOnpremNetwork | object | Object representing the configuration of the mock on-prem network. | name, addressPrefix, mgmt, subnetPrefix |
| mocOnpremGateway | object | Object representing the configuration of the VPN gateway. | name, subnetName, subnetPrefix, publicIPAddressName |
| bastionHost | object | Object representing the configuration of the Bastion host. | name, subnetName, subnetPrefix, publicIPAddressName, nsgName |
| vmSize | string | Size of the virtual machine. | Standard_D2s_v3 |
| configureSitetosite | bool | Condition for configuring a site-to-site VPN connection. | true |
| location | string | Location to be used for all resources. | rg location |
nestedtemplates/mock-onprem-local-gateway.bicep
| Parameter | Type | Description | Default |
|---|---|---|---|
| connectionName | string | Name of the mock on-prem connection resource. | mock-prem-to-hub |
| azureCloudVnetPrefix | string | Address prefix of the hub network. | null |
| spokeNetworkAddressPrefix | string | Address prefix of the spoke network. | null |
| gatewayIpAddress | string | Public IP address of the Azure virtual network gateway. | null |
| mocOnpremGatewayName | string | Name of the mock on-prem virtual network gateway. | null |
| localNetworkGateway | string | Name of the mock on-prem local network gateway. | local-gateway-moc-prem |
| sharedKey | securestring | The shared key for the VPN connection. | null |
| location | string | Location to be used for all resources. | rg location |
nestedtemplates/azure-network-azuredeploy-v2.bicep
| Parameter | Type | Description | Default |
|---|---|---|---|
| firewallName | string | Name of the Azure Firewall. | null |
| firewallPrivateIp | string | Private IP address of the firewall. | null |
| internalLoadBalancerPrivateIp | string | Private IP address of the internal load balancer. | null |
| location | string | Location for the resource. | rg location |
Validate deployment
After the deployment completes, verify end-to-end connectivity by accessing the IIS web server from the mock on-premises VM through the VPN tunnel. Traffic flows through the Azure Firewall via a DNAT rule that translates requests to the internal load balancer.
Option 1: Azure Bastion
Connect to the mock on-premises virtual machine using the included Azure Bastion host, open a web browser, and navigate to the Azure Firewall's private IP address (http://<firewall-private-ip>). The firewall translates the request to the application's internal load balancer.
Option 2: CLI
# Get the Azure Firewall private IP (DNAT entry point)
FW_IP=$(az network firewall show \
-g rg-site-to-site-azure-network-eastus2 \
-n AzureFirewall \
--query "ipConfigurations[0].privateIPAddress" -o tsv)
# From the mock on-prem VM, reach IIS through the VPN tunnel via firewall DNAT
az vm run-command invoke \
-g rg-site-to-site-mock-prem-eastus2 \
-n vm-windows \
--command-id RunPowerShellScript \
--scripts "Invoke-WebRequest -Uri http://$FW_IP -UseBasicParsing | Select-Object -Property StatusCode"
A successful response returns StatusCode: 200, confirming the full path: on-prem VM → VPN → hub → firewall (DNAT) → spoke → load balancer → VMSS (IIS).
Clean Up
az group delete --name rg-site-to-site-mock-prem-eastus2 --yes
az group delete --name rg-site-to-site-azure-network-eastus2 --yes
Microsoft Open Source Code of Conduct
This project has adopted the Microsoft Open Source Code of Conduct.
Resources:
- Microsoft Open Source Code of Conduct
- Microsoft Code of Conduct FAQ
- Contact opencode@microsoft.com with questions or concerns