Posted: Wednesday, January 11, 2023

Word Count: 2749

Reading Time: 12 minutes


Summary

The following Azure vWAN deployment includes the following:

  • 3 Connected spokes each configured with 2 subnets
  • 3 desktops each connected to a subnet in each spoke
  • 1 Firewall Enabled Azure Virtual WAN with a single virtual hub
  • Bastion Hosts have been deployed to offer direct remote access to the virtual desktops.

Tools

Azure VWAN Bicep Code: Click Here
Visual Studio Code: Click Here
AZ Cli Download: Click Here

Estimated Lab Costs

ResourceCost per hour
Virtual Machines x 3$0.28
Virtual WAN$0.35
Virtual Networks x 3 $0.02
Azure Bastion x 3$0.57
Azure Firewall$1.57

Additional Notes

Bastion hosts can be leveraged to connect to any of the virtual machines by leveraging the password that was set during deployments. The admin account is the <machinename>-Admin. Two network rules have been created for intranet communication: RDP and ICMP. ICMP will not respond until the virtual machine’s local firewall is either adjusted or disabled. RDP should be immediately available.

To deploy, run the command below:

az deployment sub create --name TestDeploy --location eastus --template-file 'main.bicep' --parameters 'parameters.json' --what-if

Note: Remove the –what-if switch to deploy the lab.

Summary of Modules

ModulesDescription
AzureFirewall.bicepProvisions Azure Firewall to secure the virtual hub
AzureFirewallPolicy.bicepCreates a simple application rule that allows outbound http/https communication.
Compute.bicepProvisions azure virtual compute resources
ResourceGroup.bicepProvisions resource groups to the desired subscription
virtualnetwork.bicepProvisions the virtual networks that will support the subnets
subnet.bicepdeploys subnets within the designated virtual network
routetable.bicepbasic route table to allow communication between all subnets / vnets
subnet-nsg.bicepcreates default nsg for provisioned subnets
virtualhub.bicepprovisions virtual hub within designated virtual WAN
vhubnetworkconnection.bicepestablishes connections between vhub and vnets
bastionhost.bicepdeploys bastion host remote capability into the designated spokes
bastionhostnsg.bicepNSG rules that are provisioned to the bastionhost subnet.

Module Detail

AzureFirewall.bicep

The firewall sku ‘AZFW_Hub’ is leveraged to secure a virtual hub.

param location string
@allowed([
  'AZFW_Hub'
])
param azfwskuname string
@allowed([
  'Basic'
  'Premium'
  'Standard'
])
param azfwskutier string
param azfwname string
param vhubname string
param azfwpoliyname string

resource azurefirewall 'Microsoft.Network/azureFirewalls@2021-08-01' = {
  name: azfwname
  location: location
  properties:{
    sku:{
      name:  azfwskuname
      tier:  azfwskutier
    }
    hubIPAddresses:{
      publicIPs:{
       count: 1 
      }
    }
    virtualHub: {
      id: resourceId('Microsoft.Network/virtualHubs',vhubname)
    }
    firewallPolicy:{
      id: resourceId('Microsoft.Network/firewallPolicies', azfwpoliyname)
    }
    
  }
}

output ipaddress string = azurefirewall.properties.hubIPAddresses.privateIPAddress
output firewallPIP string = azurefirewall.properties.hubIPAddresses.publicIPs.addresses[0].address
output firewallID string = azurefirewall.id
AzureFirewallPolicy.bicep
param azfwpolname string 
param location string 
param destinationAddresses array
param sourceAddresses array


@allowed([
'Alert'
'Deny'
'Off'
])
param fwpolthreatintelmode string
//param azfwrcgrpname string
param azfwrcgrppriority int
//param azfwrctype string
//param azfwrulename string
//param azfwruleType string
//param azfwsource string

resource azfwpolicy 'Microsoft.Network/firewallPolicies@2021-08-01' = {
  name: azfwpolname
  location: location

  tags:{
    
  }
  properties:{
    threatIntelMode: fwpolthreatintelmode
  }

  resource azfwrcs 'ruleCollectionGroups' = {
    name: 'DefaultNetworkRuleCollectionGroup'
    dependsOn: [
      
    ]
    properties: {
    priority: azfwrcgrppriority
    ruleCollections: [
      {
        ruleCollectionType: 'FirewallPolicyFilterRuleCollection'
        action:{
          type: 'Allow'
        }
                name: '${azfwpolname}Internet'
        priority:500
        rules:[
          {
            ruleType: 'ApplicationRule'
            name: 'InetOutBound'
            sourceAddresses: [
              '*'
            ]
            protocols: [
              {
                  port: 80
                  protocolType: 'Http'

              }
              {
                protocolType: 'Https'
                port: 443
              }

            ]
            targetFqdns: [
              '*'
            ]
            

          }
        ]
      }
      {
        ruleCollectionType: 'FirewallPolicyFilterRuleCollection'
        action: {
          type: 'Allow'
        }
        name: 'RDP'
        priority:100
        rules: [
          {
            ruleType: 'NetworkRule'
            description: 'RDP Access to Remote PCs'
            sourceAddresses: sourceAddresses
            destinationAddresses: destinationAddresses
            
            destinationPorts: [
              '3389'
            ]
            ipProtocols: [
              'TCP'
            ]

          }
          {
            ruleType: 'NetworkRule'
            description:'Pinging the PCs'
            sourceAddresses: sourceAddresses
            destinationAddresses: destinationAddresses
            destinationPorts: [
              '*'
            ]
            ipProtocols: [
              'ICMP'
            ]
            name:'Pings'
          }
        ]

      }

    ]

      
    }
   
  }

}

BastionHost.bicep

This module deploys a bastion host for remote management. A bastion host must be deployed to every vnet where the services are required.


// Parameters


param bastionHostName string = 'Bastionhost'
param ipConfname string = 'bastionIpConf'
param location string 
param subnetid string
param domainNameLabel string 
param publicIPAddressName string


//  Resources

resource StandardStaticPip 'Microsoft.Network/publicIPAddresses@2021-02-01' =  {
  name: '${publicIPAddressName}-Std'
  location: location
    sku:{
    name: 'Standard'
  }
  properties: {
    publicIPAllocationMethod: 'Static'
    dnsSettings: {
      domainNameLabel: domainNameLabel
    }
  }

}

resource bastionHostres 'Microsoft.Network/bastionHosts@2020-07-01' = {
  name: bastionHostName
  location: location
  properties:{
    ipConfigurations:[
      {
        name: ipConfname
        properties: {
          subnet: {
            id: subnetid
          }
          publicIPAddress: {
            id: StandardStaticPip.id
          }
        }
      }
    ]
  }
}


output IPaddress string = StandardStaticPip.properties.ipAddress
Compute.bicep

Admittedly, I went overboard on the imageOSSku parameter. Outside of that it provisions a virtual machine. In its current state, it will only provision ubuntu and windows servers.

//Some Helpful Commands at the bottom
@description(' Name of the vnet the vmnic will be hosted')
param vNetName string 
@description(' Name of the subnet the vmnic will be hosted')
param subnetName string
@description(' Name of the resourcegroup hosting the vnet')
param vnetrgname string

param vmName string

param location string 

//virtual Machine Variables

@description('To obtain vmsize run the following command: Get-AzVMSize -Location <replace with desired location>')
@allowed([
  'Standard_A3'
  'Standard_F4'
  'Standard_B2ms'
])
param vmSize string
@allowed([
  'MicrosoftWindowsServer'
  'Canonical'
])
param imagePublisher string

@allowed([
  'WindowsServer'
  'UbuntuServer'
])
param imageOffer string

@description('The Windows version for the VM. This will pick a fully patched image of this given Windows version.')
@allowed([
'2008-R2-SP1'
'2008-R2-SP1-smalldisk'
'2012-Datacenter'
'2012-datacenter-gensecond'
'2012-Datacenter-smalldisk'
'2012-datacenter-smalldisk-g2'
'2012-Datacenter-zhcn'
'2012-datacenter-zhcn-g2'
'2012-R2-Datacenter'
'2012-r2-datacenter-gensecond'
'2012-R2-Datacenter-smalldisk'
'2012-r2-datacenter-smalldisk-g2'
'2012-R2-Datacenter-zhcn'
'2012-r2-datacenter-zhcn-g2'
'2016-Datacenter'
'2016-datacenter-gensecond'
'2016-datacenter-gs'
'2016-Datacenter-Server-Core'
'2016-datacenter-server-core-g2'
'2016-Datacenter-Server-Core-smalldisk'
'2016-datacenter-server-core-smalldisk-g2'
'2016-Datacenter-smalldisk'
'2016-datacenter-smalldisk-g2'
'2016-Datacenter-with-Containers'
'2016-datacenter-with-containers-g2'
'2016-datacenter-with-containers-gs'
'2016-Datacenter-zhcn'
'2016-datacenter-zhcn-g2'
'2019-Datacenter'
'2019-Datacenter-Core'
'2019-datacenter-core-g2'
'2019-Datacenter-Core-smalldisk'
'2019-datacenter-core-smalldisk-g2'
'2019-Datacenter-Core-with-Containers'
'2019-datacenter-core-with-containers-g2'
'2019-Datacenter-Core-with-Containers-smalldisk'
'2019-datacenter-core-with-containers-smalldisk-g2'
'2019-datacenter-gensecond'
'2019-datacenter-gs'
'2019-Datacenter-smalldisk'
'2019-datacenter-smalldisk-g2'
'2019-Datacenter-with-Containers'
'2019-datacenter-with-containers-g2'
'2019-datacenter-with-containers-gs'
'2019-Datacenter-with-Containers-smalldisk'
'2019-datacenter-with-containers-smalldisk-g2'
'2019-Datacenter-zhcn'
'2019-datacenter-zhcn-g2'
'2022-datacenter'
'2022-datacenter-azure-edition'
'2022-datacenter-azure-edition-core'
'2022-datacenter-azure-edition-core-smalldisk'
'2022-datacenter-azure-edition-smalldisk'
'2022-datacenter-core'
'2022-datacenter-core-g2'
'2022-datacenter-core-smalldisk'
'2022-datacenter-core-smalldisk-g2'
'2022-datacenter-g2'
'2022-datacenter-smalldisk'
'2022-datacenter-smalldisk-g2'
'UbuntuServer'
])
param imageOSsku string
param imageVersion string

@secure()
param adminPassword string 
param adminUsername string = '${vmName}-Admin'

// Storage Account Variables

@allowed([
  'Standard_LRS'
  'Premium_ZRS'
  'Standard_RAGRS'
  'Standard_RAGZRS'
  'Standard_GRS'
  'Standard_GZRS'
  'Premium_LRS' 
  'Standard_ZRS'
])
param storageskuname string

@maxLength(8)
param storageAccountPrefix string



@allowed([
  'StorageV2'
  'FileStorage'
  'BlockBlobStorage'
])
param sakind string
var saname = '${toLower(storageAccountPrefix)}${uniqueString(resourceGroup().id)}'

///////////////////////////
// RESOURCES AND MODULES//
/////////////////////////

resource vmStorage 'Microsoft.Storage/storageAccounts@2021-09-01' = {
  name: saname
  location: location
  sku: {
    name: storageskuname
  }
  kind: sakind
}

resource vmnic 'Microsoft.Network/networkInterfaces@2021-08-01' = {
  name: '${vmName}-NIC'
  location: location
  dependsOn: [
  ]
  properties:{
    ipConfigurations:[
      {
        name: '${vmName}-IPconfig'
        properties: {
          subnet:{
            id: resourceId(vnetrgname,'Microsoft.Network/virtualNetworks/subnets',vNetName, subnetName)
          }
        }
      }
    ]
  }
}

resource vm 'Microsoft.Compute/virtualMachines@2021-11-01' = {
  name: vmName
  location: location
  dependsOn:[
    
  ]
  properties:{
     hardwareProfile:{
      vmSize: vmSize
    }

    osProfile:{
      adminPassword: adminPassword
      adminUsername: adminUsername
      computerName: vmName
    }
    storageProfile:{
      imageReference:{
        publisher: imagePublisher
        offer: imageOffer
        sku: imageOSsku
        version: imageVersion
      }
      osDisk: {
        createOption: 'FromImage'
        managedDisk: {
          storageAccountType: 'StandardSSD_LRS'
        }
      }
      dataDisks: [
      {
        diskSizeGB: 1023
        lun: 0
        createOption: 'Empty'
      }
    ]
    }
  networkProfile:{

    networkInterfaces:[
      {
        id: resourceId('Microsoft.Network/networkInterfaces',vmnic.name)
      }
    ]
     
  }
  diagnosticsProfile:{
    bootDiagnostics:{
      enabled: true
      storageUri: vmStorage.properties.primaryEndpoints.blob
    }
  }
  } 

} 

output ipaddress string = vmnic.properties.ipConfigurations[0].properties.privateIPAddress
output adminUserName string = adminUsername
ResourceGroup
targetScope = 'subscription'

param resourceGroupName string
param location string

resource ResourceGroup 'Microsoft.Resources/resourceGroups@2022-09-01' = {
  name: resourceGroupName
  location: location
}
routetable.bicep

This is a basic module. The destination is hard-coded to the default route.



param routetTblname string
param vhubname string
param firewallID string
@description('Next hop resource ID (Azure Firewall or VNet Connection')

param destinations array

@allowed([
  'CIDR'
  'ResourceId'
  'Service'
])
param destinationtype string 

@allowed([
  'CIDR'
  'ResourceId'
  'Service'
])

param nextHoptype string

resource vhub 'Microsoft.Network/virtualHubs@2021-08-01' existing = {
  name: vhubname
}

resource vWANhubRouteTable 'Microsoft.Network/virtualHubs/hubrouteTables@2021-08-01'  =   {
  name: routetTblname
  parent: vhub
  properties: {
      labels: [
    'default'
  ]
   routes:[
     {
       name: 'InternettoFirewall'
       nextHop: firewallID
       nextHopType: nextHoptype
       destinationType: destinationtype
       destinations: [
        '0.0.0.0/0'
      ]
     }
    
    {
    name: 'InternalTraffic'
    nextHop: firewallID
    nextHopType: nextHoptype
    destinationType: destinationtype
    destinations: destinations
    } 

    ]
  }
}
virtualhub.bicep

param location string
param vWanname string
param vhubname string
param allowBranchToBranchTraffic bool
param vhubprefix string

resource vhub 'Microsoft.Network/virtualHubs@2021-08-01' = {
  name: vhubname
  location:location
  properties: {
   addressPrefix: vhubprefix 
   allowBranchToBranchTraffic: allowBranchToBranchTraffic
   virtualWan:{
     id: resourceId('Microsoft.Network/virtualWans',vWanname)
   }

  
   
  }

}
output vhubID string = vhub.id
output addressPrefix string = vhub.properties.addressPrefix
vhubnetworkconnection.bicep

param location string
param vWanname string
param vhubname string
param allowBranchToBranchTraffic bool
param vhubprefix string

resource vhub 'Microsoft.Network/virtualHubs@2021-08-01' = {
  name: vhubname
  location:location
  properties: {
   addressPrefix: vhubprefix 
   allowBranchToBranchTraffic: allowBranchToBranchTraffic
   virtualWan:{
     id: resourceId('Microsoft.Network/virtualWans',vWanname)
   }

  
   
  }

}
output vhubID string = vhub.id
output addressPrefix string = vhub.properties.addressPrefix
VirtualNetwork.bicep

The virtual network module is fairly vanilla and easy to interpret. The code I’ve designed only provisions a single prefix per virtual network.

targetScope = 'resourceGroup'

param location string
param vnetName string 
param vnetAddress string

//subnet variables

resource vnet 'Microsoft.Network/virtualNetworks@2021-08-01' = {
  name: vnetName
  location: location
  properties:{
    addressSpace: {
      addressPrefixes: [
        vnetAddress
      ]
    } 
  }
}
vWAN.bicep

There’s nothing tricky about the virtual WAN module either. The properties in the params are all set to true in the main.bicep file.


param location string
param vWanname string
param allowBranchToBranchTraffic bool
param allowVnetToVnetTraffic bool
param disableVpnEncryption bool

resource vWan 'Microsoft.Network/virtualWans@2021-08-01' = {
  name: vWanname
  location: location
  properties: {
     allowBranchToBranchTraffic: allowBranchToBranchTraffic
     allowVnetToVnetTraffic: allowVnetToVnetTraffic
     disableVpnEncryption: disableVpnEncryption

  }
  
}
Main.tf

Calls all previous modules to build the environment.

targetScope = 'subscription' 

param stringData string
var base64String = base64(stringData)
// PARAMETERS

param location string
param Spoke01Name string
param Spoke02Name string
param Spoke03Name string
param NetworkRGName string
param ComputeRGName string
param firewallName string
param Spoke01Address string
param Spoke02Address string
param Spoke03Address string
param vhubAddress string
param Subnet01 string
param Subnet02 string
param BastionSN string
param vHubName string
param vWANname string
param destinationType string
param nextHopType string
param routetTblname string
param vhubConnectionName01 string
param vhubConnectionName02 string
param vhubConnectionName03 string

// Compute Parameters
param imageOffer string
param imageOSsku string
param imagePublisher string 
param imageVersion string
param sakind string
param storageAccountPrefix string
param storageskuname string
param vmName string
param vmSize string 
@secure()
param adminPassword string

// BastionHost Name
param bastionHostName string
param publicIPAddressName string

// Firewall Parameters
param firewallPolicyName string
param azfwskuname string
param azfwskutier string

// vHubConnections Parameters

param allowHubToRemoteVnetTransit bool = true
param allowRemoteVnetToUseHubVnetGateways bool = true
param enableInternetSecurity bool = true

// Virtual WAN Parameters
param allowBranchToBranchTraffic bool = true
param allowVnetToVnetTraffic bool = true
param disableVpnEncryption bool = false


// VARIABLES

// Vnet Addressing
var Spoke01CIDR = '${Spoke01Address}0.0/16'
var Spoke02CIDR = '${Spoke02Address}0.0/16'
var Spoke03CIDR = '${Spoke03Address}0.0/16'
var vhubCIDR = '${vhubAddress}0.0/16'


// Spoke01 Subnets
var S01Subnet1Prefix = '${Spoke01Address}${Subnet01}'
var S01Subnet2Prefix = '${Spoke01Address}${Subnet02}'
var S01BastionPrefix = '${Spoke01Address}${BastionSN}'
// Spoke02 Subnets
var S02Subnet1Prefix = '${Spoke02Address}${Subnet01}'
var S02Subnet2Prefix = '${Spoke02Address}${Subnet02}'
var S02BastionPrefix = '${Spoke02Address}${BastionSN}'
// Spoke03 Subnets
var S03Subnet1Prefix = '${Spoke03Address}${Subnet01}'
var S03Subnet2Prefix = '${Spoke03Address}${Subnet02}'
var S03BastionPrefix = '${Spoke03Address}${BastionSN}'

// Default Route Table
var destinations = [
  S01Subnet1Prefix
  S01Subnet2Prefix
  S02Subnet1Prefix
  S02Subnet2Prefix
  S03Subnet1Prefix
  S03Subnet2Prefix
]

// Firewall Policies
@allowed([
  'Alert' 
  'Deny' 
  'Off'
])
param fwpolthreatintelmode string
param azfwrcgrppriority int


var sourceAddresses = [
  Spoke01CIDR
  Spoke02CIDR
  Spoke03CIDR
]

var destinationAddresses = [
  Spoke01CIDR
  Spoke02CIDR
  Spoke03CIDR
]

//Resource Groups
module computeRG 'modules/ResourceGroup.bicep' = {
  name: ComputeRGName
  scope: subscription()
  params: {
    location: location
    resourceGroupName: ComputeRGName
  }
}

module NetworkRG 'modules/ResourceGroup.bicep' = {
  name: NetworkRGName
  scope: subscription()
  params: {
    location: location
    resourceGroupName: NetworkRGName
  }
}

// Spoke01 Virtual Network
module Spoke01 'modules/VirtualNetwork.bicep' = {
  name: Spoke01Name
  scope: resourceGroup(NetworkRGName)
  params: {
    location: location
    vnetAddress: Spoke01CIDR
    vnetName: Spoke01Name
  }
  dependsOn: [
    NetworkRG
  ]
 }

 module Spoke01S01 'modules/subnet.bicep' = {
  name: '${Spoke01Name}-S01'
  scope: resourceGroup(NetworkRGName)
  params: {
    addressprefix: S01Subnet1Prefix
    subnetname: '${Spoke01Name}/${Spoke01Name}-S01'
  }
  dependsOn: [
    Spoke01
  ]
 }

 module Spoke01S02 'modules/subnet.bicep' = {
  name: '${Spoke01Name}-S02'
  scope: resourceGroup(NetworkRGName)
  params: {
    addressprefix: S01Subnet2Prefix
    subnetname: '${Spoke01Name}/${Spoke01Name}-S02'
  }
  dependsOn: [
    Spoke01
    Spoke01S01
  ]
 }

 module spoke01Bastion 'modules/subnet-nsg.bicep' = {
  name: '${Spoke01Name}-BastionSN'
  scope: resourceGroup(NetworkRGName)
  params: {
    addressprefix: S01BastionPrefix
    subnetname: '${Spoke01Name}/AzureBastionSubnet'
    nsgid: bastionnsg.outputs.bastionHostNSGId
  }
  dependsOn: [
    Spoke01S02
    bastionnsg
  ]
 }

// Spoke 2 Virtual Network
 module Spoke02 'modules/VirtualNetwork.bicep' = {
  name: Spoke02Name
  scope: resourceGroup(NetworkRGName)
  params: {
    location: location
    vnetAddress: Spoke02CIDR
    vnetName: Spoke02Name
  }
  dependsOn: [
    NetworkRG
  ]
 }



 module Spoke02S01 'modules/subnet.bicep' = {
  name: '${Spoke02Name}-S01'
  scope: resourceGroup(NetworkRGName)
  params: {
    addressprefix: S02Subnet1Prefix
    subnetname: '${Spoke02Name}/${Spoke02Name}-S01'
  }
  dependsOn: [
    Spoke02
  ]
 }

 module Spoke02S02 'modules/subnet.bicep' = {
  name: '${Spoke02Name}-S02'
  scope: resourceGroup(NetworkRGName)
  params: {
    addressprefix: S02Subnet2Prefix
    subnetname: '${Spoke02Name}/${Spoke02Name}-S02'
  }
  dependsOn: [
    Spoke02
    Spoke02S01
  ]
 }

 module spoke02Bastion 'modules/subnet-nsg.bicep' = {
  name: '${Spoke02Name}-BastionSN'
  scope: resourceGroup(NetworkRGName)
  params: {
    addressprefix: S02BastionPrefix
    subnetname: '${Spoke02Name}/AzureBastionSubnet'
    nsgid: bastionnsg.outputs.bastionHostNSGId
  }
  dependsOn: [
    Spoke02S02
    bastionnsg
  ]
 }

 // Spoke 3 Virtual Network
 module Spoke03 'modules/VirtualNetwork.bicep' = {
  name: Spoke03Name
  scope: resourceGroup(NetworkRGName)
  params: {
    location: location
    vnetAddress: Spoke03CIDR
    vnetName: Spoke03Name
  }
  dependsOn: [
    NetworkRG
  ]
 }

 module Spoke03S01 'modules/subnet.bicep' = {
  name: '${Spoke03Name}-S01'
  scope: resourceGroup(NetworkRGName)
  params: {
    addressprefix: S03Subnet1Prefix
    subnetname: '${Spoke03Name}/${Spoke03Name}-S01'
  }
  dependsOn: [
    Spoke03
  ]
 }

 module Spoke03S02 'modules/subnet.bicep' = {
  name: '${Spoke03Name}-S02'
  scope: resourceGroup(NetworkRGName)
  params: {
    addressprefix: S03Subnet2Prefix
    subnetname: '${Spoke03Name}/${Spoke03Name}-S02'
  }
  dependsOn: [
    Spoke03
    Spoke03S01
  ]
 }

 module bastionnsg 'modules/bastionnsg.bicep' = {
  scope: resourceGroup(NetworkRGName)
  name: '${Spoke03Name}-BastionNSG'
  params: {
    bastionHostName: bastionHostName
    location: location
  }
  dependsOn: [
    NetworkRG
  ]
 }

 module spoke03Bastion 'modules/subnet-nsg.bicep' = {
  name: '${Spoke03Name}-BastionSN'
  scope: resourceGroup(NetworkRGName)
  params: {
    addressprefix: S03BastionPrefix
    subnetname: '${Spoke03Name}/AzureBastionSubnet'
    nsgid: bastionnsg.outputs.bastionHostNSGId
  }
  dependsOn: [
    Spoke03S02
    bastionnsg
  ]
 }

module vWAN 'modules/vWAN.bicep' = {
  scope: resourceGroup(NetworkRGName)
  name: vWANname
  params: {
    allowBranchToBranchTraffic: allowBranchToBranchTraffic
    allowVnetToVnetTraffic: allowVnetToVnetTraffic
    disableVpnEncryption: disableVpnEncryption
    location: location
    vWanname: vWANname
  }
  dependsOn:[
    Spoke02
    Spoke01
    Spoke03
  ]
}

module vhub 'modules/virtualhub.bicep' = {
  scope: resourceGroup(NetworkRGName)
  name: vHubName
  params: {
    allowBranchToBranchTraffic: allowBranchToBranchTraffic
    location: location
    vhubname: vHubName 
    vhubprefix: vhubCIDR
    vWanname: vWAN.name
  }
  dependsOn:[
    vWAN
  ]
}

module firewall 'modules/AzureFirewall.bicep' = {
  scope: resourceGroup(NetworkRGName)
  name: firewallName
  params: {
    azfwname: firewallName
    azfwpoliyname: FWPolicy01.name
    azfwskuname: azfwskuname
    azfwskutier: azfwskutier
    location: location
    vhubname: vhub.name
  }
dependsOn: [
  vhub
  FWPolicy01
]
}

module vWanRouteTable 'modules/routetable.bicep' = {
  scope: resourceGroup(NetworkRGName)
  name: routetTblname
  params: {
    firewallID: firewall.outputs.firewallID
    destinations: destinations
    destinationtype: destinationType
    nextHoptype: nextHopType
    vhubname: vhub.name
    routetTblname: routetTblname
  }
  dependsOn: [
    vhub
    firewall
  ]
}

module vhubConnection01 'modules/vhubnetworkconnection.bicep' = {
  scope: resourceGroup(NetworkRGName)
  name: vhubConnectionName01
  params: {
    allowHubToRemoteVnetTransit: allowHubToRemoteVnetTransit
    allowRemoteVnetToUseHubVnetGateways: allowRemoteVnetToUseHubVnetGateways
    enableInternetSecurity: enableInternetSecurity
    labels: Spoke01.name
    RouteTableName: vWanRouteTable.name
    SpokeName: Spoke01.name
    vhubconnectionname: vhubConnectionName01
    vhubname: vhub.name
  }
  dependsOn: [
    Spoke01
    vhub
    vWanRouteTable
  ]
}

module vhubConnection03 'modules/vhubnetworkconnection.bicep' = {
  scope: resourceGroup(NetworkRGName)
  name: vhubConnectionName03
  params: {
    allowHubToRemoteVnetTransit: allowHubToRemoteVnetTransit
    allowRemoteVnetToUseHubVnetGateways: allowRemoteVnetToUseHubVnetGateways
    enableInternetSecurity: enableInternetSecurity
    labels: Spoke03.name
    RouteTableName: vWanRouteTable.name
    SpokeName: Spoke03.name
    vhubconnectionname: vhubConnectionName03
    vhubname: vhub.name
  }
  dependsOn: [
    vhubConnection02
  ]
}

module vhubConnection02 'modules/vhubnetworkconnection.bicep' = {
  scope: resourceGroup(NetworkRGName)
  name: vhubConnectionName02
  params: {
    allowHubToRemoteVnetTransit: allowHubToRemoteVnetTransit
    allowRemoteVnetToUseHubVnetGateways: allowRemoteVnetToUseHubVnetGateways
    enableInternetSecurity: enableInternetSecurity
    labels: Spoke02.name
    RouteTableName: vWanRouteTable.name
    SpokeName: Spoke02.name
    vhubconnectionname: vhubConnectionName02
    vhubname: vhub.name
  }
  dependsOn: [
    vhubConnection01
  ]
}

module FWPolicy01 'modules/AzureFirewallPolicy.bicep' = {
  scope: resourceGroup(NetworkRGName)
  name:  firewallPolicyName
  params: {
    azfwpolname: firewallPolicyName
    azfwrcgrppriority: azfwrcgrppriority
    fwpolthreatintelmode: fwpolthreatintelmode
    location: location
    destinationAddresses: destinationAddresses
    sourceAddresses: sourceAddresses
  }
  dependsOn: [
    NetworkRG
    Desktop3
  ]
}

module Desktop1 'modules/Compute.bicep' = {
  scope: resourceGroup(ComputeRGName)
  name: '${vmName}S01S01'
  params: {
    adminPassword: adminPassword
    location: location
    imageOffer: imageOffer
    imageOSsku: imageOSsku
    imagePublisher: imagePublisher
    imageVersion: imageVersion
    sakind: sakind
    storageAccountPrefix: storageAccountPrefix
    storageskuname: storageskuname
    subnetName: Spoke01S01.name
    vmName: '${vmName}S01S01'
    vmSize: vmSize
    vNetName: Spoke01.name
    vnetrgname: NetworkRG.name
  }
  dependsOn: [
    computeRG
    Spoke01S01
    Spoke01
  ]
}

module Desktop2 'modules/Compute.bicep' = {
  scope: resourceGroup(ComputeRGName)
  name: '${vmName}S02S02'
  params: {
    adminPassword: adminPassword
    location: location
    imageOffer: imageOffer
    imageOSsku: imageOSsku
    imagePublisher: imagePublisher
    imageVersion: imageVersion
    sakind: sakind
    storageAccountPrefix: storageAccountPrefix
    storageskuname: storageskuname
    subnetName: Spoke02S02.name
    vmName: '${vmName}S02S02'
    vmSize: vmSize
    vNetName: Spoke02.name
    vnetrgname: NetworkRG.name
  }
  dependsOn: [
    computeRG
    Spoke02S02
    Spoke02
    Desktop1
  ]
}

module Desktop3 'modules/Compute.bicep' = {
  scope: resourceGroup(ComputeRGName)
  name: '${vmName}S03S01'
  params: {
    adminPassword: adminPassword
    location: location
    imageOffer: imageOffer
    imageOSsku: imageOSsku
    imagePublisher: imagePublisher
    imageVersion: imageVersion
    sakind: sakind
    storageAccountPrefix: storageAccountPrefix
    storageskuname: storageskuname
    subnetName: Spoke03S01.name
    vmName: '${vmName}S03S01'
    vmSize: vmSize
    vNetName: Spoke03.name
    vnetrgname: NetworkRG.name
  }
  dependsOn: [
    computeRG
    Spoke03S01
    Spoke03
    Desktop2
  ]
}

module spoke03BastionHost 'modules/bastionhost.bicep' = {
  scope: resourceGroup(ComputeRGName)
  name: '${bastionHostName}03'
  params: {
    domainNameLabel: toLower('${bastionHostName}${base64String}03')
    publicIPAddressName: '${publicIPAddressName}bh03'
    subnetid: spoke03Bastion.outputs.subnetid
    location: location
    bastionHostName: '${bastionHostName}03'
     ipConfname: '${publicIPAddressName}bh03'
  }
  dependsOn: [
    spoke03Bastion
    spoke02BastionHost
  ]
}

module spoke02BastionHost 'modules/bastionhost.bicep' = {
  scope: resourceGroup(ComputeRGName)
  name: '${bastionHostName}02'
  params: {
    domainNameLabel: toLower('${bastionHostName}${base64String}02')
    publicIPAddressName: '${publicIPAddressName}bh02'
    subnetid: spoke02Bastion.outputs.subnetid
    location: location
    bastionHostName: '${bastionHostName}02'
    ipConfname:  '${publicIPAddressName}bh02'
  }
  dependsOn: [
    spoke02Bastion
    spoke01BastionHost
  ]
}

module spoke01BastionHost 'modules/bastionhost.bicep' = {
  scope: resourceGroup(ComputeRGName)
  name: '${bastionHostName}01'
  params: {
    domainNameLabel: toLower('${bastionHostName}${base64String}01')
    publicIPAddressName: '${publicIPAddressName}bh01'
    subnetid: spoke01Bastion.outputs.subnetid
    location: location
    bastionHostName: '${bastionHostName}01'
    ipConfname: '${publicIPAddressName}bh01'
  }
  dependsOn: [
    spoke01Bastion
  ]
}


output firewallpublicIP string = firewall.outputs.ipaddress
output desktop3UserName string = Desktop3.outputs.adminUserName
output desktop2UserName string = Desktop2.outputs.adminUserName
output desktop1UserName string = Desktop1.outputs.adminUserName

Architectural Diagram

Azure vWAN Lab Diagram