Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion solutions/secure-hybrid-network/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,21 @@ cd samples/solutions/secure-hybrid-network
Run the following commands to initiate the deployment. When prompted, enter values for an admin username and password. These values are used to log into the included virtual machines.

```azurecli-interactive
# Resources will be created on deployment region
# 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 mocOnPremResourceGroup=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:

```azurecli-interactive
# 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**
Expand Down Expand Up @@ -105,6 +116,42 @@ az deployment sub create -n secure-hybrid-network --location eastus2 --template-
| localNetworkGateway | string | Name of the mock on-prem local network gateway. | local-gateway-moc-prem |
| 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

```azurecli-interactive
# 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

```azurecli-interactive
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Once an on-premises site joins the network, this template updates the hub
// firewall with a DNAT rule so inbound traffic can reach the spoke workloads.

param location string = resourceGroup().location

@description('Name of the Azure Firewall')
param firewallName string

@description('Private IP address of the firewall')
param firewallPrivateIp string

@description('Private IP address of the internal load balancer')
param internalLoadBalancerPrivateIp string

@description('Name of the hub virtual network')
param hubVnetName string = 'vnet-hub'

@description('Name of the firewall public IP')
param firewallPublicIpName string = 'pip-firewall'

@description('Spoke network address prefix for source filtering')
param spokeAddressPrefix string = '10.100.0.0/16'

resource hubVnet 'Microsoft.Network/virtualNetworks@2024-05-01' existing = {
name: hubVnetName
}

resource firewallPublicIp 'Microsoft.Network/publicIPAddresses@2024-05-01' existing = {
name: firewallPublicIpName
}

resource firewallDnat 'Microsoft.Network/azureFirewalls@2024-05-01' = {
name: firewallName
location: location
properties: {
sku: {
name: 'AZFW_VNet'
tier: 'Standard'
}
threatIntelMode: 'Alert'
ipConfigurations: [
{
name: firewallName
properties: {
publicIPAddress: {
id: firewallPublicIp.id
}
subnet: {
id: resourceId('Microsoft.Network/virtualNetworks/subnets', hubVnet.name, 'AzureFirewallSubnet')
}
}
}
]
applicationRuleCollections: [
{
name: 'spoke-outbound'
properties: {
priority: 100
action: {
type: 'Allow'
}
rules: [
{
name: 'windows-update'
protocols: [
{
protocolType: 'Https'
port: 443
}
]
targetFqdns: [
'*.update.microsoft.com'
'*.windowsupdate.com'
'*.download.windowsupdate.com'
]
sourceAddresses: [
spokeAddressPrefix
]
}
]
}
}
]
natRuleCollections: [
{
name: 'dnat-onprem-to-spoke'
properties: {
priority: 100
action: {
type: 'Dnat'
}
rules: [
{
name: 'onprem-to-web'
protocols: [
'TCP'
]
sourceAddresses: [
'192.168.0.0/16'
]
destinationAddresses: [
firewallPrivateIp
]
destinationPorts: [
'80'
]
translatedAddress: internalLoadBalancerPrivateIp
translatedPort: '80'
}
]
}
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"_generator": {
"name": "bicep",
"version": "0.39.26.7824",
"templateHash": "11434533286408150086"
}
},
"parameters": {
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]"
},
"firewallName": {
"type": "string",
"metadata": {
"description": "Name of the Azure Firewall"
}
},
"firewallPrivateIp": {
"type": "string",
"metadata": {
"description": "Private IP address of the firewall"
}
},
"internalLoadBalancerPrivateIp": {
"type": "string",
"metadata": {
"description": "Private IP address of the internal load balancer"
}
},
"hubVnetName": {
"type": "string",
"defaultValue": "vnet-hub",
"metadata": {
"description": "Name of the hub virtual network"
}
},
"firewallPublicIpName": {
"type": "string",
"defaultValue": "pip-firewall",
"metadata": {
"description": "Name of the firewall public IP"
}
},
"spokeAddressPrefix": {
"type": "string",
"defaultValue": "10.100.0.0/16",
"metadata": {
"description": "Spoke network address prefix for source filtering"
}
}
},
"resources": [
{
"type": "Microsoft.Network/azureFirewalls",
"apiVersion": "2024-05-01",
"name": "[parameters('firewallName')]",
"location": "[parameters('location')]",
"properties": {
"sku": {
"name": "AZFW_VNet",
"tier": "Standard"
},
"threatIntelMode": "Alert",
"ipConfigurations": [
{
"name": "[parameters('firewallName')]",
"properties": {
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('firewallPublicIpName'))]"
},
"subnet": {
"id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hubVnetName'), 'AzureFirewallSubnet')]"
}
}
}
],
"applicationRuleCollections": [
{
"name": "spoke-outbound",
"properties": {
"priority": 100,
"action": {
"type": "Allow"
},
"rules": [
{
"name": "windows-update",
"protocols": [
{
"protocolType": "Https",
"port": 443
}
],
"targetFqdns": [
"*.update.microsoft.com",
"*.windowsupdate.com",
"*.download.windowsupdate.com"
],
"sourceAddresses": [
"[parameters('spokeAddressPrefix')]"
]
}
]
}
}
],
"natRuleCollections": [
{
"name": "dnat-onprem-to-spoke",
"properties": {
"priority": 100,
"action": {
"type": "Dnat"
},
"rules": [
{
"name": "onprem-to-web",
"protocols": [
"TCP"
],
"sourceAddresses": [
"192.168.0.0/16"
],
"destinationAddresses": [
"[parameters('firewallPrivateIp')]"
],
"destinationPorts": [
"80"
],
"translatedAddress": "[parameters('internalLoadBalancerPrivateIp')]",
"translatedPort": "80"
}
]
}
}
]
}
}
]
}
Loading