diff --git a/src/bicep/add-ons/virtual-network-gateway/README.md b/src/bicep/add-ons/virtual-network-gateway/README.md new file mode 100644 index 00000000..bb8105ce --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/README.md @@ -0,0 +1,199 @@ +# VPN Gateway MLZ Add-On + +## Introduction + +This document provides details on a Bicep script that deploys a VPN Gateway, Local Network Gateway, VPN connection, and related resources in Azure, integrating into an existing MLZ network deployment. It includes descriptions of all parameters, required parameters, instructions on building and deploying the ARM template, and steps to create a template specification from the Bicep script. + +The deployment is intended to provide an on-prem VPN gateway to connect into the MLZ network through the Hub vNet and route to all spokes. A route table is created for the vpn gateway and attached to the GatewaySubnet in the Hub network, and firewall rules added to allow connectivity. + +This allows for the firewall to serve as a protection between the on-prem internal network and the Azure spoke networks. + +Additionally, it covers the modules used within the script and their roles in the deployment process. + +--- + +## Parameters + +### 1. **vgwName** (string) - Required + +- **Description:** The name of the VPN Gateway. Provided as an input parameter to the solution when deployed. + +### 2. **vgwLocation** (string) - Optional (default: location of the resource group) + +- **Description:** The Azure region for deploying the VPN Gateway. If not provided, it defaults to the location of the resource group. Provided as an input parameter to the solution when deployed. + +### 3. **vgwPublicIpAddressNames** (array) - Required + +- **Description:** The names of the public IP addresses associated with the VPN Gateway. Requires two for redundancy. Provided as an input parameter to the solution when deployed. + +### 4. **vgwSku** (string) - Optional (default: `'VpnGw2'`) + +- **Description:** The SKU (size) of the VPN Gateway. Allowed values: `VpnGw2`, `VpnGw3`, `VpnGw4`, `VpnGw5`. The default can be changed in the "solution.bicep" file. + +### 5. **localNetworkGatewayName** (string) - Required + +- **Description:** The name of the Local Network Gateway. Provided as an input parameter to the solution when deployed. + +### 6. **localGatewayIpAddress** (string) - Required + +- **Description:** The IP address of the Local Network Gateway. This must be a public IP address or a reachable IP from the Azure environment. Provided as an input parameter to the solution when deployed. + +### 7. **allowedAzureAddressPrefixes** (array) - Required + +- **Description:** A list of address prefixes of the peered spoke networks that will be allowed to access the networks through the VPN gateway. This is used in an Azure firewall rule. Provided as an input parameter to the solution when deployed. + +### 8. **localAddressPrefixes** (array) - Required + +- **Description:** A list of address prefixes of the local network routable through the VPN Gateway. This controls what networks can be accessed from Azure through the VPN Gateway. This is also used in an Azure firewall rule. Provided as an input parameter to the solution when deployed. + +### 9. **useSharedKey** (bool) - Required + +- **Description:** Indicates whether to use a shared key or a Key Vault certificate URI for the VPN connection. If false, a URL to a pre-existing keyvault stored certificate must be used instead. Provided as an input parameter to the solution when deployed. + +### 10. **sharedKey** (string) - Required if `useSharedKey = true` + +- **Description:** The shared key for the VPN connection. This parameter is secured. A "true" value uses shared key which is provided in the portal or command prompt at deployment. A "false" value requires that a keyVaultCertificateUri is provided. Remove this from the parameters file before deployment to ensure the deployment will prompt for the value to avoid storing the secret in the file. + +### 11. **keyVaultCertificateUri** (string) - Optional (default: `''`) + +- **Description:** The URI of the Key Vault certificate for the VPN connection. Only used if `useSharedKey = false`. Must be a valid URI starting with `https://` and containing `/secrets/`. Provided as an input parameter to the solution when deployed. + +### 12. **hubVirtualNetworkResourceId** (string) - Required + +- **Description:** The resource ID of the hub virtual network. Can be found on the "Properties" blade on the vNet in the Azure portal. Provided as an input parameter to the solution when deployed. + +### 13. **vnetResourceIdList** (array) - Required + +- **Description:** A list of peered virtual networks that will use the VPN Gateway. The peerings will be updated to allow gateway transit and use. Can be found on the "Properties" blade on the vNet in the Azure portal. Provided as an input parameter to the solution when deployed. + +### 14. **azureFirewallName** (string) - Required + +- **Description:** The name of the Azure firewall in the hub network used to control all traffic through the VPN gateway and all spoke networks. Provided as an input parameter to the solution when deployed. + +### 14. **routeTableName** (string) - Required + +- **Description:** The name of the VPN Gateway route table that is used to control the gateway subnet routing overrides necessary to push all traffic through the Azure firewall. Provided as an input parameter to the solution when deployed. + +--- + +## Modules Used in the Script + +This Bicep script calls several external modules to deploy resources efficiently and modularly. Here's an overview of each module and what it does: + +### 1. **VPN Gateway Module** + +- **File:** `modules/vpn-gateway.bicep` +- **Description:** This module deploys the Virtual Network Gateway (VPN Gateway) in a specified resource group. The VPN Gateway enables secure cross-premises connectivity. +- **Parameters:** + - `vgwName`: The name of the VPN Gateway. Provided as an input parameter to the solution when deployed. + - `vgwLocation`: The location where the VPN Gateway will be deployed. Provided as an input parameter to the solution when deployed. + - `publicIpAddressNames`: The names of the public IP addresses associated with the VPN Gateway. Provided as an input parameter to the solution when deployed. + - `vgwsku`: The SKU of the VPN to be deployed. Provided as an input parameter to the solution when deployed. + - `vnetName`: The name of the hub virtual network to which the VPN Gateway will be connected. Derived from the hub virtual network resource id provided as an input parameter when deployed. + +### 2. **Local Network Gateway Module** + +- **File:** `modules/local-network-gateway.bicep` +- **Description:** This module deploys the Local Network Gateway, which defines the on-premises network's configuration and connectivity to Azure. It includes the on-premises gateway's public IP address and the network address ranges to route through the VPN connection. +- **Parameters:** + - `vgwlocation`: The location of the Local Network Gateway. Provided as an input parameter to the solution when deployed. + - `localNetworkGatewayName`: The name of the Local Network Gateway. Provided as an input parameter to the solution when deployed. + - `gatewayIpAddress`: The public IP address of the Local Network Gateway. Provided as an input parameter to the solution when deployed. + - `addressPrefixes`: The local address prefixes (network ranges) of the on-premises network. Provided as an input parameter to the solution when deployed. + +### 3. **VPN Connection Module** + +The VPN connection module contains these most commonly used IPSEC configuration settings: +`` + saLifeTimeSeconds: 3600 + saDataSizeKilobytes: 102400000 + ipsecEncryption: 'AES256' + ipsecIntegrity: 'SHA256' + ikeEncryption: 'AES256' + ikeIntegrity: 'SHA256' + dhGroup: 'DHGroup2' + pfsGroup: 'PFS2' +`` +Change these in the module file directly to modify connection settings for deployment. + +- **File:** `modules/vpn-connection.bicep` +- **Description:** This module creates the VPN connection between the VPN Gateway in Azure and the Local Network Gateway (on-premises network). It can use either a shared key or a Key Vault certificate for secure authentication. +- **Parameters:** + - `vpnConnectionName`: The name of the VPN connection. Provided as an input parameter to the solution when deployed. + - `vgwlocation`: The location of the VPN Gateway. Provided as an input parameter to the solution when deployed. + - `vpnGatewayName`: The name of the VPN Gateway. Provided as an input parameter to the solution when deployed. + - `vpnGatewayResourceGroupName`: The resource group where the VPN Gateway is deployed. Gateway is placed in the hub virtual network resource group, the name is extracted from the hub virtual network resource group id provided in the parameters when deployed. + - `sharedKey`: The shared key for the VPN connection (if using shared key authentication). Provided as an input parameter to the solution when deployed. Ensure the shared key and value are not provided in the parameters file before deployment to ensure prompting for the value at deployment time. + - `keyVaultCertificateUri`: The URI of the Key Vault certificate (if using certificate-based authentication). Provided as an input parameter to the solution when deployed, if shared key is not used. + - `localNetworkGatewayName`: The name of the Local Network Gateway. Provided as an input parameter to the solution when deployed. + +### 4. **Retrieve Existing Module** + +- **File:** `modules/retrieve-existing.bicep` +- **Description:** This module retrieves the list of virtual network peerings associated with a virtual network. The peerings allow networks to communicate securely with each other within the same Azure region or across regions. This module is also used to retrieve information from other existing resources depending on the parameters used. +- **Parameters:** + - `vnetResourceId`: The resource ID of the virtual network for which peerings are being retrieved. Provided as an input parameter to the solution when deployed. + +### 5. **VNet Peerings Module** + +- **File:** `modules/vnet-peerings.bicep` +- **Description:** After retrieving the peerings for a virtual network, this module updates the peerings to reflect the new VPN Gateway configuration. This allows peered networks to utilize the VPN Gateway for cross-premises connectivity. +- **Parameters:** + - `vnetResourceId`: The resource ID of the virtual network. Provided as an input parameter to the solution when deployed. + - `peeringsList`: The list of virtual network peerings to be updated. Returned values from the retrieve-existing.bicep module. + +### 6. **Route Table Module** + +- **File:** `modules/route-table.bicep` +- **Description:** This module creates the route table for the VPN gateway. +- **Parameters:** + - `routeTableName`: The route table name. Provided as an input parameter to the solution when deployed. + +### 7. **Route Definition** + +- **File:** `modules/route-definition.bicep` +- **Description:** This module builds the route construct to be used when adding the route, as multiple routes need to be added. Virtual appliance is hard coded as the next hop type. +- **Parameters:** + - `firewallIpAddress`: The IP address of the firewall, used as the next hop IP address. Returned value from the retrieve-existing.bicep module. + - `addressPrefixes`: The address prefixes used in the route being built. Provided as an input parameter to the solution when deployed. + +### 8. **Routes Module** + +- **File:** `modules/routes.bicep` +- **Description:** This module creates the routes in a route table. +- **Parameters:** + - `routeTableName`: The route table name. Provided as an input parameter to the solution when deployed. + - `routeName`: The name of the route. Value defaulted in the solution.bicep file, the value is: "route-#" with an increment number depending on the number of routes being added. + - `addressSpace`: The CIDR address prefix being routed. Provided as an input parameter to the solution when deployed. + - `nextHopType`: The type of next hop, defaulted to appliance in the solution.bicep file. + - `nextHopIpAddress`: The IP address of the next hop. In this implementation, the firewall IP address. Provided as an input parameter to the solution when deployed. + +### 8. **Firewall Rules Module** + +- **File:** `modules/firewall-rules.bicep ` +- **Description:** This module creates the firewall rules to allow spoke and vpn address prefixes access to eachother. +- **Parameters:** + - `allowVnetAddressSpaces`: The CIDR address prefixes of peered Azure spoke vnets. Provided as an input parameter to the solution when deployed. + - `onPremAddressSpaces`: The CIDR address prefixes of the onprem networks to be allowed. Provided as an input parameter to the solution when deployed. + - `firewallPolicyId`: The firewall policy attached to the hub firewall. The solution will dynamically retrieve this value. + - `priorityValue`: The priority value for the firewall rule. The solution defines this at a value of 300 in the main bicep named solution.bicep. + + The rule is hardcoded to allow any protocol to any address in the rule. Customize firewall-rules.bicep to change behavior. + +## Removal of VPN Gateway + +1. Delete the VPN Connection in the MLZ Hub resource group. +2. Delete the Local Network Gateway in the MLZ Hub resource group. +3. Delete the VPN Gateway in the MLZ Hub resource group. +4. Navigate to the Hub vNet, and go to Peerings: + a. Open each peering in the list. + b. Uncheck "Allow gateway or route server in '' to forward traffic to ''. + c. Click "save". +5. Navigate to each spoke vNet represented in the peerings list. + a. Open the peering to the Hub network. + b. Uncheck "Enable '' to use '' remote gateway. + c. Click "save". +6. Navigate to each spoke network resource group. + a. Open the Route table in the group. + b. Choose "Routes". + c. Delete the VPN routes in the list. diff --git a/src/bicep/add-ons/virtual-network-gateway/modules/associate-route-table.bicep b/src/bicep/add-ons/virtual-network-gateway/modules/associate-route-table.bicep new file mode 100644 index 00000000..abee9df6 --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/modules/associate-route-table.bicep @@ -0,0 +1,29 @@ + +@description('virtual network resource ID that holds the subnet') +param vnetResourceId string + +@description('route table resource ID to associate with the subnet') +param routeTableResourceId string + +@description('name of the subnet to associate with the route table') +param subnetName string + +@description('address prefix of the gateway subnet') +param subnetAddressPrefix string + +// Reference the existing Virtual Network +resource existingVnet 'Microsoft.Network/virtualNetworks@2023-11-01' existing = { + name: last(split(vnetResourceId, '/')) +} + +// Update the GatewaySubnet to associate the existing Route Table +resource gatewaySubnet 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' = { + parent: existingVnet + name: subnetName + properties: { + addressPrefix: subnetAddressPrefix + routeTable: { + id: resourceId('Microsoft.Network/routeTables', last(split(routeTableResourceId, '/'))) + } + } +} diff --git a/src/bicep/add-ons/virtual-network-gateway/modules/firewall-rules.bicep b/src/bicep/add-ons/virtual-network-gateway/modules/firewall-rules.bicep new file mode 100644 index 00000000..3785a198 --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/modules/firewall-rules.bicep @@ -0,0 +1,70 @@ +@description('The list of virtual network resource IDs to be used as the source IP groups') +param allowVnetAddressSpaces array + +@description('Address prefixes of the on-premises network') +param onPremAddressSpaces array + +@description('Name of the firewall policy') +param firewallPolicyId string + +@description('The priority value for the rule collection') +@minValue(300) +@maxValue(65000) +param priorityValue int + + +// Define the firewall policy reference +resource firewallPolicy 'Microsoft.Network/firewallPolicies@2023-04-01' existing = { + name: last(split(firewallPolicyId, '/')) +} + +// First rule set: Source = allowedIpGroup, Destination = onPremIpGroup +var outboundRules = { + name: 'AllowAzureToOnPremRule' // Unique rule name using index + ruleType: 'NetworkRule' + sourceAddresses: allowVnetAddressSpaces + destinationAddresses: onPremAddressSpaces + destinationPorts: [ + '*' // Modify this as needed + ] + ipProtocols: [ + 'Any' // Modify this as needed + ] +} + +// Second rule set (reverse): Source = onPremIpGroup, Destination = allowedIpGroup +var inboundRules = { + name: 'AllowOnPremToAzureRule' // Unique rule name using index + ruleType: 'NetworkRule' + sourceAddresses: onPremAddressSpaces + destinationAddresses: allowVnetAddressSpaces + destinationPorts: [ + '*' // Modify this as needed + ] + ipProtocols: [ + 'Any' // Modify this as needed + ] +} + +// Define the rule collection group, referencing existing IP groups for source and destination +resource allowVgwCollection 'Microsoft.Network/firewallPolicies/ruleCollectionGroups@2023-04-01' = { + name: 'VgwNetworkRuleCollectionGroup' + parent: firewallPolicy + properties: { + priority: priorityValue + ruleCollections: [ + { + name: 'AllowVgw' + priority: priorityValue + ruleCollectionType: 'FirewallPolicyFilterRuleCollection' + action: { + type: 'Allow' + } + rules: [ + outboundRules + inboundRules + ] + } + ] + } +} diff --git a/src/bicep/add-ons/virtual-network-gateway/modules/local-network-gateway.bicep b/src/bicep/add-ons/virtual-network-gateway/modules/local-network-gateway.bicep new file mode 100644 index 00000000..37c8d33c --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/modules/local-network-gateway.bicep @@ -0,0 +1,17 @@ +param vgwlocation string = resourceGroup().location +param localNetworkGatewayName string +param gatewayIpAddress string +param addressPrefixes array + + +// Local Network Gateway configuration +resource localNetworkGateway 'Microsoft.Network/localNetworkGateways@2023-02-01' = { + name: localNetworkGatewayName + location: vgwlocation + properties: { + gatewayIpAddress: gatewayIpAddress + localNetworkAddressSpace: { + addressPrefixes: addressPrefixes + } + } +} diff --git a/src/bicep/add-ons/virtual-network-gateway/modules/retrieve-existing.bicep b/src/bicep/add-ons/virtual-network-gateway/modules/retrieve-existing.bicep new file mode 100644 index 00000000..7dccaff7 --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/modules/retrieve-existing.bicep @@ -0,0 +1,58 @@ +@description('Name of the Azure Firewall (optional)') +param azureFirewallName string = '' + +@description('Name of the subnet (optional)') +param subnetName string = '' + +@description('The resource ID of the existing spoke virtual network (optional)') +param vnetResourceId string = '' + +@description('The name of the route table associated with the hub virtual network (optional)') +param routeTableName string = '' + +resource vnetRouteTable 'Microsoft.Network/routeTables@2020-11-01' existing = if (!empty(routeTableName) && !empty(vnetResourceId)) { + scope: resourceGroup() + name: routeTableName +} + +// Retrieve internal address of the firewall, conditionally +resource azureFirewall 'Microsoft.Network/azureFirewalls@2020-11-01' existing = if (!empty(azureFirewallName) && !empty(vnetResourceId)) { + scope: resourceGroup(split(vnetResourceId, '/')[2], split(vnetResourceId, '/')[4]) + name: azureFirewallName +} + +// Reference the existing Virtual Network using its resource ID, conditionally +resource vnetInfo 'Microsoft.Network/virtualNetworks@2020-11-01' existing = if (!empty(vnetResourceId)) { + scope: resourceGroup() + name: last(split(vnetResourceId, '/')) // Extract the VNet name from the resource ID +} + +// Loop through the subnets to find the specified subnet, conditionally +resource subnet 'Microsoft.Network/virtualNetworks/subnets@2020-11-01' existing = if (!empty(subnetName) && !empty(vnetResourceId)) { + parent: vnetInfo + name: subnetName +} + +// Output the route table ID of the hub virtual network, if the route table name is provided +output routeTableId string = !empty(routeTableName) ? vnetRouteTable.id : 'N/A' + +// Output the internal IP address of the firewall, if firewall parameters are provided +output firewallPrivateIp string = (!empty(azureFirewallName) && !empty(vnetResourceId)) ? azureFirewall.properties.ipConfigurations[0].properties.privateIPAddress : 'N/A' + +// Output the firewall policy id attached to the firewall +output firewallPolicyId string = !empty(azureFirewallName) ? azureFirewall.properties.firewallPolicy.id : 'N/A' + +// Output the address prefix of the GatewaySubnet, if the parameters are provided +output subnetAddressPrefix string = (!empty(subnetName) && !empty(vnetResourceId)) ? subnet.properties.addressPrefix : 'N/A' + +// Output the address space of the VNet, if the VNet resource ID is provided +output vnetAddressSpace array = !empty(vnetResourceId) ? vnetInfo.properties.addressSpace.addressPrefixes : [] + +// Output the list of peerings from the VNet, if the VNet resource ID is provided +output peeringsData object = !empty(vnetResourceId) ? { + vnetResourceId: vnetResourceId + peeringsList: vnetInfo.properties.virtualNetworkPeerings +} : { + vnetResourceId: 'N/A' + peeringsList: [] +} diff --git a/src/bicep/add-ons/virtual-network-gateway/modules/route-definition.bicep b/src/bicep/add-ons/virtual-network-gateway/modules/route-definition.bicep new file mode 100644 index 00000000..a0cdc6e6 --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/modules/route-definition.bicep @@ -0,0 +1,18 @@ +// Assume hubVnetAddressSpace is a parameter or variable from another part of your script +@description('Address space prefixes of the virtual network') +param addressPrefixes array + +@description('Private IP address of the Azure Firewall') +param firewallPrivateIp string + +// Create a variable with the route definitions +output routes array = [ + for i in range(0, length(addressPrefixes)): { + name: 'mlzToOnPrem-${i}' // Ensure unique route names + addressPrefix: addressPrefixes[i] + nextHopType: 'VirtualAppliance' + nextHopIpAddress: firewallPrivateIp + } +] + + diff --git a/src/bicep/add-ons/virtual-network-gateway/modules/route-table.bicep b/src/bicep/add-ons/virtual-network-gateway/modules/route-table.bicep new file mode 100644 index 00000000..38112a97 --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/modules/route-table.bicep @@ -0,0 +1,12 @@ +@description('Name of the route table to create') +param routeTableName string + +resource routeTable 'Microsoft.Network/routeTables@2021-02-01' = { + name: routeTableName + location: resourceGroup().location + properties: { + disableBgpRoutePropagation: true + } +} + +output routeTableId string = routeTable.id diff --git a/src/bicep/add-ons/virtual-network-gateway/modules/routes.bicep b/src/bicep/add-ons/virtual-network-gateway/modules/routes.bicep new file mode 100644 index 00000000..6da4f8f6 --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/modules/routes.bicep @@ -0,0 +1,30 @@ +@description('Name of the route table to create') +param routeTableName string + +@description('Name of the route') +param routeName string + +@description('CIDR prefixes for the route') +param addressSpace array + +@description('The next hop type for the route') +param nextHopType string + +@description('The next hop IP address for the route') +param nextHopIpAddress string + +resource routeTable 'Microsoft.Network/routeTables@2021-02-01' existing = { + name: routeTableName + scope: resourceGroup() +} + +// Loop over the address spaces and create routes +resource routes 'Microsoft.Network/routeTables/routes@2023-04-01' = [for (cidr, i) in addressSpace: { + parent: routeTable + name: '${routeName}-${i}' + properties: { + addressPrefix: cidr + nextHopType: nextHopType + nextHopIpAddress: nextHopIpAddress != '' ? nextHopIpAddress : null + } +}] diff --git a/src/bicep/add-ons/virtual-network-gateway/modules/vnet-peerings.bicep b/src/bicep/add-ons/virtual-network-gateway/modules/vnet-peerings.bicep new file mode 100644 index 00000000..a094975c --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/modules/vnet-peerings.bicep @@ -0,0 +1,35 @@ +@description('The list of peerings to update') +param peeringsList array + +@description('The resource ID of the existing virtual network') +param vnetResourceId string + +// Extract the virtual network name from the resource ID +var vnetName = last(split(vnetResourceId, '/')) + +// Generate the list of updated peerings +var updatedPeerings = [for peering in peeringsList: { + name: last(split(peering.id, '/')) // Extract the peering name from the ID + properties: { + allowGatewayTransit: contains(vnetName, '-hub-') ? true : peering.properties.allowGatewayTransit + useRemoteGateways: !contains(vnetName, '-hub-') ? true : peering.properties.useRemoteGateways + // allowGatewayTransit: contains(split(peering.id, '/')[8], '-hub-') ? true : peering.properties.allowGatewayTransit + // useRemoteGateways: !contains(split(peering.id, '/')[8], '-hub-') ? true : peering.properties.useRemoteGateways + allowForwardedTraffic: peering.properties.allowForwardedTraffic == null ? true : peering.properties.allowForwardedTraffic // Preserve existing value or set to true + remoteVirtualNetwork: peering.properties.remoteVirtualNetwork + } +}] + +// Define the parent virtual network resource +resource vnet 'Microsoft.Network/virtualNetworks@2022-07-01' existing = { + name: vnetName +} + +// Create or update the peerings within the virtual network context +resource peeringUpdates 'Microsoft.Network/virtualNetworks/virtualNetworkPeerings@2021-02-01' = [for (peering, i) in updatedPeerings: { + parent: vnet + name: peering.name + properties: peering.properties +}] + + diff --git a/src/bicep/add-ons/virtual-network-gateway/modules/vpn-connection.bicep b/src/bicep/add-ons/virtual-network-gateway/modules/vpn-connection.bicep new file mode 100644 index 00000000..64179e6b --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/modules/vpn-connection.bicep @@ -0,0 +1,52 @@ +param vpnConnectionName string +param vgwlocation string +param vpnGatewayName string +param vpnGatewayResourceGroupName string +param sharedKey string +param keyVaultCertificateUri string +param localNetworkGatewayName string + +// Determine if either sharedKey or keyVaultCertificateUri is provided +var useSharedKey = !empty(sharedKey) +var useKeyVaultCertificate = !empty(keyVaultCertificateUri) + +// Conditional validation through variables +var errorMsg = (useSharedKey && useKeyVaultCertificate) ? 'Cannot provide both sharedKey and keyVaultCertificateUri' : '' +var connectionSharedKey = useSharedKey ? sharedKey : null +var connectionIpsecPolicies = useKeyVaultCertificate ? [ + { + saLifeTimeSeconds: 3600 + saDataSizeKilobytes: 102400000 + ipsecEncryption: 'AES256' + ipsecIntegrity: 'SHA256' + ikeEncryption: 'AES256' + ikeIntegrity: 'SHA256' + dhGroup: 'DHGroup2' + pfsGroup: 'PFS2' + } +] : null + +// Deploy the VPN connection only if the conditions are met +resource vpnConnection 'Microsoft.Network/connections@2023-02-01' = if (empty(errorMsg)) { + name: vpnConnectionName + location: vgwlocation + properties: { + virtualNetworkGateway1: { + id: resourceId(vpnGatewayResourceGroupName, 'Microsoft.Network/virtualNetworkGateways', vpnGatewayName) + } + localNetworkGateway2: { + id: resourceId(vpnGatewayResourceGroupName, 'Microsoft.Network/localNetworkGateways', localNetworkGatewayName) + } + connectionType: 'IPsec' + routingWeight: 10 + + sharedKey: connectionSharedKey + + // Use ipsecPolicies if Key Vault certificate URI is provided + ipsecPolicies: connectionIpsecPolicies + + // Additional properties as required + enableBgp: false + usePolicyBasedTrafficSelectors: false + } +} diff --git a/src/bicep/add-ons/virtual-network-gateway/modules/vpn-gateway.bicep b/src/bicep/add-ons/virtual-network-gateway/modules/vpn-gateway.bicep new file mode 100644 index 00000000..661f9ea3 --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/modules/vpn-gateway.bicep @@ -0,0 +1,81 @@ +param vgwname string +param vgwlocation string = resourceGroup().location +param publicIpAddressNames array +param vgwsku string +param vnetName string + +// Existing Virtual Network and Subnet +resource vnet 'Microsoft.Network/virtualNetworks@2023-02-01' existing = { + name: vnetName +} + +// Reference the existing subnet within the specified Virtual Network +resource gatewaySubnet 'Microsoft.Network/virtualNetworks/subnets@2023-02-01' existing = { + parent: vnet + name: 'GatewaySubnet' +} + +var gatewaySubnetId = gatewaySubnet.id + +// Public IP Addresses +resource publicIpAddresses 'Microsoft.Network/publicIPAddresses@2023-02-01' = [for (pipname, index) in publicIpAddressNames: { + name: pipname + location: vgwlocation + sku: { + name: 'Standard' + } + properties: { + publicIPAllocationMethod: 'Static' + } +}] + +var firstPublicIpAddressId = publicIpAddresses[0].id +var secondPublicIpAddressId = publicIpAddresses[1].id + +// VPN Gateway +resource vpnGateway 'Microsoft.Network/virtualNetworkGateways@2023-02-01' = { + name: vgwname + location: vgwlocation + properties: { + gatewayType: 'Vpn' + ipConfigurations: [ + { + name: 'first' + properties: { + privateIPAllocationMethod: 'Dynamic' + subnet: { + id: gatewaySubnetId + } + publicIPAddress: { + id: firstPublicIpAddressId + } + } + } + { + name: 'second' + properties: { + privateIPAllocationMethod: 'Dynamic' + publicIPAddress: { + id: secondPublicIpAddressId + } + subnet: { + id: gatewaySubnetId + } + } + } + ] + activeActive: true + vpnType: 'RouteBased' + vpnGatewayGeneration: 'Generation2' + enableBgp: false + enablePrivateIpAddress: false + sku: { + name: vgwsku + tier: vgwsku + } + } +} + + + + diff --git a/src/bicep/add-ons/virtual-network-gateway/sample.solution.parameters.json b/src/bicep/add-ons/virtual-network-gateway/sample.solution.parameters.json new file mode 100644 index 00000000..8364d4a0 --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/sample.solution.parameters.json @@ -0,0 +1,60 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "vgwName": { + "value": "" + }, + "vgwLocation": { + "value": "usgovvirginia" + }, + "vgwPublicIpAddressNames": { + "value": [ + "", + "" + }, + "localGatewayIpAddress": { + "value": "" + }, + "allowedAzureAddressPrefixes": { + "value": [ + "10.0.130.0/24", + "10.0.131.0/24", + "10.0.132.0/24", + "10.0.128.0/23" + ] + }, + "localAddressPrefixes": { + "value": [ + "192.168.0.0/24", + "192.168.1.0/24" + ] + }, + "useSharedKey": { + "value": true + }, + "hubVirtualNetworkResourceId": { + "value": "/subscriptions//resourceGroups//providers/Microsoft.Network/virtualNetworks/" + }, + "vnetResourceIdList": { + "value": [ + "/subscriptions//resourceGroups//providers/Microsoft.Network/virtualNetworks//resourceGroups//providers/Microsoft.Network/virtualNetworks//resourceGroups//providers/Microsoft.Network/virtualNetworks/" + }, + "vgwRouteTableName": { + "value": "" + }, + "hubVnetRouteTableName": { + "value": "" + } + } +} \ No newline at end of file diff --git a/src/bicep/add-ons/virtual-network-gateway/solution.bicep b/src/bicep/add-ons/virtual-network-gateway/solution.bicep new file mode 100644 index 00000000..f356e044 --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/solution.bicep @@ -0,0 +1,276 @@ +targetScope = 'subscription' + +@description('The name of the VPN Gateway.') +param vgwName string + +@description('The Azure region location of the VPN Gateway.') +param vgwLocation string + +@description('The names of the public IP addresses to use for the VPN Gateway.') +param vgwPublicIpAddressNames array + +@description('The SKU of the VPN Gateway.') +@allowed(['VpnGw2', 'VpnGw3', 'VpnGw4', 'VpnGw5']) +param vgwSku string + +@description('Local Network Gateway Name') +param localNetworkGatewayName string + +@description('IP Address of the Local Network Gateway, must be a public IP address or be able to be connected to from MLZ network') +param localGatewayIpAddress string + +@description('Azure address prefixes allowed to communicate to VPN Gateway to on-premises network') +param allowedAzureAddressPrefixes array + +@description('Address prefixes of the Local Network which will be routable through the VPN Gateway') +param localAddressPrefixes array + +@description('Choose whether to use a shared key or Key Vault certificate URI for the VPN connection.') +param useSharedKey bool + +@description('The shared key to use for the VPN connection. If using the shared key, this must be provided.') +@secure() +param sharedKey string + +@description('The URI of the Key Vault certificate to use for the VPN connection. If using a Key Vault certificate, this must be a valid URI.') +param keyVaultCertificateUri string = '' + +@description('A suffix to use for naming deployments uniquely.') +param deploymentNameSuffix string = utcNow() + +@description('The resource ID of the hub virtual network.') +param hubVirtualNetworkResourceId string +// Extracting the resource group name and virtual network name from the hub virtual network resource ID +var hubResourceGroupName = split(hubVirtualNetworkResourceId, '/')[4] +var hubVnetName = split(hubVirtualNetworkResourceId, '/')[8] + +@description('List of peered networks that should use the VPN Gateway once configured.') +param vnetResourceIdList array + +@description('The name of the Azure Firewall to retrieve the internal IP address from.') +param azureFirewallResourceId string +var azureFirewallName = split(azureFirewallResourceId, '/')[8] + +@description('The name of the vgw route table to create') +param vgwRouteTableName string + +@description('The name of the gateway subnet') +param gatewaySubnetName string = 'GatewaySubnet' + +@description('The name of the hub virtual network route table') +param hubVnetRouteTableResourceId string +var hubVnetRouteTableName = split(hubVnetRouteTableResourceId, '/')[8] + +// Conditional parameter assignment for VPN connection module +var vpnSharedKey = useSharedKey ? sharedKey : '' +var vpnKeyVaultUri = !useSharedKey ? keyVaultCertificateUri : '' + +// Parameter validation +var isValidUri = contains(keyVaultCertificateUri, 'https://') && contains(keyVaultCertificateUri, '/secrets/') + +// Conditional validation to ensure either sharedKey or keyVaultCertificateUri is used correctly +resource validateKeyOrUri 'Microsoft.Resources/deployments@2021-04-01' = if (!useSharedKey && !isValidUri) { + name: 'InvalidKeyVaultCertificateUri-${deploymentNameSuffix}' + properties: { + mode: 'Incremental' + parameters: { + message: { + value: 'Invalid Key Vault Certificate URI. It must start with "https://" and contain "/secrets/".' + } + } + templateLink: { + uri: 'https://validatemessage.com' // Placeholder for validation message, replace if needed + } + } +} + +// calling Virtual Network Gateway Module +module vpnGatewayModule 'modules/vpn-gateway.bicep' = { + name: 'vpnGateway-${deploymentNameSuffix}' + scope: resourceGroup(hubResourceGroupName) + params: { + vgwname: vgwName + vgwlocation: vgwLocation + publicIpAddressNames: vgwPublicIpAddressNames + vgwsku: vgwSku + vnetName: hubVnetName + } +} + +// calling Local Network Gateway Module +module localNetworkGatewayModule 'modules/local-network-gateway.bicep' = { + name: 'localNetworkGateway-${deploymentNameSuffix}' + scope: resourceGroup(hubResourceGroupName) + params: { + vgwlocation: vgwLocation + localNetworkGatewayName: localNetworkGatewayName + gatewayIpAddress: localGatewayIpAddress + addressPrefixes: localAddressPrefixes + } +} + +// calling VPN Connection Module +module vpnConnectionModule 'modules/vpn-connection.bicep' = { + name: 'vpnConnection-${deploymentNameSuffix}' + scope: resourceGroup(hubResourceGroupName) + params: { + vpnConnectionName: '${vgwName}-to-${localNetworkGatewayName}' + vgwlocation: vgwLocation + vpnGatewayName: vgwName + vpnGatewayResourceGroupName: hubResourceGroupName + sharedKey: vpnSharedKey + keyVaultCertificateUri: vpnKeyVaultUri + localNetworkGatewayName: localNetworkGatewayName + } + dependsOn: [ + vpnGatewayModule + localNetworkGatewayModule + validateKeyOrUri + ] +} + +// Loop through the vnetResourceIdList and to retrieve the peerings for each VNet +module retrieveVnetInfo 'modules/retrieve-existing.bicep' = [for (vnetId, i) in vnetResourceIdList: { + name: 'retrieveVnetPeerings-${deploymentNameSuffix}-${i}' + scope: resourceGroup(split(vnetId, '/')[2], split(vnetId, '/')[4]) + params: { + vnetResourceId: vnetId + } + dependsOn: [ + vpnConnectionModule + ] +}] + +// Get the hub virtual network peerings +module retrieveHubVnetInfo 'modules/retrieve-existing.bicep' = { + name: 'retrieveHubVnetPeerings-${deploymentNameSuffix}' + scope: resourceGroup(split(hubVirtualNetworkResourceId, '/')[2], split(hubVirtualNetworkResourceId, '/')[4]) + params: { + vnetResourceId: hubVirtualNetworkResourceId + } + dependsOn: [ + vpnConnectionModule + ] +} + +// retrieve the route table information for the hub vnet including the firewall private IP and gateway subnet address space info to be used for the new vgw route table and routes +module retrieveRouteTableInfo 'modules/retrieve-existing.bicep' = { + name: 'retrieveRouteTableInfo-${deploymentNameSuffix}' + scope: resourceGroup(split(hubVirtualNetworkResourceId, '/')[2], split(hubVirtualNetworkResourceId, '/')[4]) + params: { + vnetResourceId: hubVirtualNetworkResourceId + azureFirewallName: azureFirewallName + subnetName: gatewaySubnetName + } + dependsOn: [ + updatePeerings + ] +} + +// Call update the Hub peerings first to enable spokes to use the VPN Gateway, if not done first, spokes will fail their update +module updateHubPeerings 'modules/vnet-peerings.bicep' = { + name: 'updateHubPeerings-${deploymentNameSuffix}' + scope: resourceGroup(split(hubVirtualNetworkResourceId, '/')[2], split(hubVirtualNetworkResourceId, '/')[4]) + params: { + vnetResourceId: retrieveHubVnetInfo.outputs.peeringsData.vnetResourceId + peeringsList: retrieveHubVnetInfo.outputs.peeringsData.peeringsList + } + dependsOn: [ + retrieveHubVnetInfo + retrieveVnetInfo + ] +} + + +// Update the peerings for each spoke VNet to use the VPN Gateway +module updatePeerings 'modules/vnet-peerings.bicep' = [for (vnetId, i) in vnetResourceIdList: { + name: 'updatePeerings-${deploymentNameSuffix}-${i}' + scope: resourceGroup(split(vnetId, '/')[2], split(vnetId, '/')[4]) + params: { + vnetResourceId: retrieveVnetInfo[i].outputs.peeringsData.vnetResourceId + peeringsList: retrieveVnetInfo[i].outputs.peeringsData.peeringsList + } + dependsOn: [ + retrieveVnetInfo + updateHubPeerings + ] +}] + +// Create the route table for the VPN Gateway subnet, will route spoke vnets to through the firewall, overriding default behavior +module createRouteTable 'modules/route-table.bicep' = { + name: 'createVgwRouteTable-${deploymentNameSuffix}' + scope: resourceGroup(split(hubVirtualNetworkResourceId, '/')[2], split(hubVirtualNetworkResourceId, '/')[4]) + params: { + routeTableName: vgwRouteTableName + } + dependsOn: [ + retrieveVnetInfo + retrieveRouteTableInfo + updateHubPeerings + updatePeerings + ] +} + +// Create the routes to the firewall for the spoke vnets, if vnet is not provided in the "allowedAzureAddressPrefixes" then the spoke will not be able to use the VPN Gateway +module createRoutes 'modules/routes.bicep' = [for (vnetResourceId, i) in vnetResourceIdList: { + name: 'createRoute-${i}-${deploymentNameSuffix}' + scope: resourceGroup(split(hubVirtualNetworkResourceId, '/')[2], split(hubVirtualNetworkResourceId, '/')[4]) + params: { + routeTableName: vgwRouteTableName + addressSpace: retrieveVnetInfo[i].outputs.vnetAddressSpace + routeName: 'route-${i}' + nextHopType: 'VirtualAppliance' + nextHopIpAddress: retrieveRouteTableInfo.outputs.firewallPrivateIp + } + dependsOn: [ + createRouteTable + ] +}] + +// Create the routes to the firewall for the hub vnet as and override to the onprem networks +module createHubRoutesToOnPrem 'modules/routes.bicep' = { + name: 'createOverrideRoutes-${deploymentNameSuffix}' + scope: resourceGroup(split(hubVirtualNetworkResourceId, '/')[2], split(hubVirtualNetworkResourceId, '/')[4]) + params: { + routeTableName: hubVnetRouteTableName + addressSpace: localAddressPrefixes + routeName: 'route-onprem-override' + nextHopType: 'VirtualAppliance' + nextHopIpAddress: retrieveRouteTableInfo.outputs.firewallPrivateIp + } + dependsOn: [ + createRouteTable + ] +} + + +// Associate the vgw route table with the gateway subnet so the gateway routes traffic destined for spokes through the firewall +module associateRouteTable 'modules/associate-route-table.bicep' = { + name: 'associateRouteTable-${deploymentNameSuffix}' + scope: resourceGroup(split(hubVirtualNetworkResourceId, '/')[2], split(hubVirtualNetworkResourceId, '/')[4]) + params: { + vnetResourceId: hubVirtualNetworkResourceId + routeTableResourceId: createRouteTable.outputs.routeTableId + subnetName: gatewaySubnetName + subnetAddressPrefix: retrieveRouteTableInfo.outputs.subnetAddressPrefix + } + dependsOn: [ + createRouteTable + ] +} + + +// Create the firewall rules +module firewallRules 'modules/firewall-rules.bicep' = { + name: 'firewallRules-${deploymentNameSuffix}' + scope: resourceGroup(split(hubVirtualNetworkResourceId, '/')[2], split(hubVirtualNetworkResourceId, '/')[4]) + params: { + allowVnetAddressSpaces: allowedAzureAddressPrefixes + onPremAddressSpaces: localAddressPrefixes + firewallPolicyId: retrieveRouteTableInfo.outputs.firewallPolicyId + priorityValue: 300 + } + dependsOn: [ + associateRouteTable + ] +} diff --git a/src/bicep/add-ons/virtual-network-gateway/solution.json b/src/bicep/add-ons/virtual-network-gateway/solution.json new file mode 100644 index 00000000..92531d82 --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/solution.json @@ -0,0 +1,1443 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "7891133147346503241" + } + }, + "parameters": { + "vgwName": { + "type": "string", + "metadata": { + "description": "The name of the VPN Gateway." + } + }, + "vgwLocation": { + "type": "string", + "metadata": { + "description": "The Azure region location of the VPN Gateway." + } + }, + "vgwPublicIpAddressNames": { + "type": "array", + "metadata": { + "description": "The names of the public IP addresses to use for the VPN Gateway." + } + }, + "vgwSku": { + "type": "string", + "allowedValues": [ + "VpnGw2", + "VpnGw3", + "VpnGw4", + "VpnGw5" + ], + "metadata": { + "description": "The SKU of the VPN Gateway." + } + }, + "localNetworkGatewayName": { + "type": "string", + "metadata": { + "description": "Local Network Gateway Name" + } + }, + "localGatewayIpAddress": { + "type": "string", + "metadata": { + "description": "IP Address of the Local Network Gateway, must be a public IP address or be able to be connected to from MLZ network" + } + }, + "allowedAzureAddressPrefixes": { + "type": "array", + "metadata": { + "description": "Azure address prefixes allowed to communicate to VPN Gateway to on-premises network" + } + }, + "localAddressPrefixes": { + "type": "array", + "metadata": { + "description": "Address prefixes of the Local Network which will be routable through the VPN Gateway" + } + }, + "useSharedKey": { + "type": "bool", + "metadata": { + "description": "Choose whether to use a shared key or Key Vault certificate URI for the VPN connection." + } + }, + "sharedKey": { + "type": "securestring", + "metadata": { + "description": "The shared key to use for the VPN connection. If using the shared key, this must be provided." + } + }, + "keyVaultCertificateUri": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The URI of the Key Vault certificate to use for the VPN connection. If using a Key Vault certificate, this must be a valid URI." + } + }, + "deploymentNameSuffix": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "A suffix to use for naming deployments uniquely." + } + }, + "hubVirtualNetworkResourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the hub virtual network." + } + }, + "vnetResourceIdList": { + "type": "array", + "metadata": { + "description": "List of peered networks that should use the VPN Gateway once configured." + } + }, + "azureFirewallResourceId": { + "type": "string", + "metadata": { + "description": "The name of the Azure Firewall to retrieve the internal IP address from." + } + }, + "vgwRouteTableName": { + "type": "string", + "metadata": { + "description": "The name of the vgw route table to create" + } + }, + "gatewaySubnetName": { + "type": "string", + "defaultValue": "GatewaySubnet", + "metadata": { + "description": "The name of the gateway subnet" + } + }, + "hubVnetRouteTableResourceId": { + "type": "string", + "metadata": { + "description": "The name of the hub virtual network route table" + } + } + }, + "variables": { + "hubResourceGroupName": "[split(parameters('hubVirtualNetworkResourceId'), '/')[4]]", + "hubVnetName": "[split(parameters('hubVirtualNetworkResourceId'), '/')[8]]", + "azureFirewallName": "[split(parameters('azureFirewallResourceId'), '/')[8]]", + "hubVnetRouteTableName": "[split(parameters('hubVnetRouteTableResourceId'), '/')[8]]", + "vpnSharedKey": "[if(parameters('useSharedKey'), parameters('sharedKey'), '')]", + "vpnKeyVaultUri": "[if(not(parameters('useSharedKey')), parameters('keyVaultCertificateUri'), '')]", + "isValidUri": "[and(contains(parameters('keyVaultCertificateUri'), 'https://'), contains(parameters('keyVaultCertificateUri'), '/secrets/'))]" + }, + "resources": [ + { + "condition": "[and(not(parameters('useSharedKey')), not(variables('isValidUri')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2021-04-01", + "name": "[format('InvalidKeyVaultCertificateUri-{0}', parameters('deploymentNameSuffix'))]", + "properties": { + "mode": "Incremental", + "parameters": { + "message": { + "value": "Invalid Key Vault Certificate URI. It must start with \"https://\" and contain \"/secrets/\"." + } + }, + "templateLink": { + "uri": "https://validatemessage.com" + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('vpnGateway-{0}', parameters('deploymentNameSuffix'))]", + "resourceGroup": "[variables('hubResourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "vgwname": { + "value": "[parameters('vgwName')]" + }, + "vgwlocation": { + "value": "[parameters('vgwLocation')]" + }, + "publicIpAddressNames": { + "value": "[parameters('vgwPublicIpAddressNames')]" + }, + "vgwsku": { + "value": "[parameters('vgwSku')]" + }, + "vnetName": { + "value": "[variables('hubVnetName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "3589752526565849732" + } + }, + "parameters": { + "vgwname": { + "type": "string" + }, + "vgwlocation": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "publicIpAddressNames": { + "type": "array" + }, + "vgwsku": { + "type": "string" + }, + "vnetName": { + "type": "string" + } + }, + "variables": { + "gatewaySubnetId": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), 'GatewaySubnet')]", + "firstPublicIpAddressId": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressNames')[0])]", + "secondPublicIpAddressId": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressNames')[1])]" + }, + "resources": [ + { + "copy": { + "name": "publicIpAddresses", + "count": "[length(parameters('publicIpAddressNames'))]" + }, + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2023-02-01", + "name": "[parameters('publicIpAddressNames')[copyIndex()]]", + "location": "[parameters('vgwlocation')]", + "sku": { + "name": "Standard" + }, + "properties": { + "publicIPAllocationMethod": "Static" + } + }, + { + "type": "Microsoft.Network/virtualNetworkGateways", + "apiVersion": "2023-02-01", + "name": "[parameters('vgwname')]", + "location": "[parameters('vgwlocation')]", + "properties": { + "gatewayType": "Vpn", + "ipConfigurations": [ + { + "name": "first", + "properties": { + "privateIPAllocationMethod": "Dynamic", + "subnet": { + "id": "[variables('gatewaySubnetId')]" + }, + "publicIPAddress": { + "id": "[variables('firstPublicIpAddressId')]" + } + } + }, + { + "name": "second", + "properties": { + "privateIPAllocationMethod": "Dynamic", + "publicIPAddress": { + "id": "[variables('secondPublicIpAddressId')]" + }, + "subnet": { + "id": "[variables('gatewaySubnetId')]" + } + } + } + ], + "activeActive": true, + "vpnType": "RouteBased", + "vpnGatewayGeneration": "Generation2", + "enableBgp": false, + "enablePrivateIpAddress": false, + "sku": { + "name": "[parameters('vgwsku')]", + "tier": "[parameters('vgwsku')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressNames')[1])]", + "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressNames')[0])]" + ] + } + ] + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('localNetworkGateway-{0}', parameters('deploymentNameSuffix'))]", + "resourceGroup": "[variables('hubResourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "vgwlocation": { + "value": "[parameters('vgwLocation')]" + }, + "localNetworkGatewayName": { + "value": "[parameters('localNetworkGatewayName')]" + }, + "gatewayIpAddress": { + "value": "[parameters('localGatewayIpAddress')]" + }, + "addressPrefixes": { + "value": "[parameters('localAddressPrefixes')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "17093542793626818819" + } + }, + "parameters": { + "vgwlocation": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "localNetworkGatewayName": { + "type": "string" + }, + "gatewayIpAddress": { + "type": "string" + }, + "addressPrefixes": { + "type": "array" + } + }, + "resources": [ + { + "type": "Microsoft.Network/localNetworkGateways", + "apiVersion": "2023-02-01", + "name": "[parameters('localNetworkGatewayName')]", + "location": "[parameters('vgwlocation')]", + "properties": { + "gatewayIpAddress": "[parameters('gatewayIpAddress')]", + "localNetworkAddressSpace": { + "addressPrefixes": "[parameters('addressPrefixes')]" + } + } + } + ] + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('vpnConnection-{0}', parameters('deploymentNameSuffix'))]", + "resourceGroup": "[variables('hubResourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "vpnConnectionName": { + "value": "[format('{0}-to-{1}', parameters('vgwName'), parameters('localNetworkGatewayName'))]" + }, + "vgwlocation": { + "value": "[parameters('vgwLocation')]" + }, + "vpnGatewayName": { + "value": "[parameters('vgwName')]" + }, + "vpnGatewayResourceGroupName": { + "value": "[variables('hubResourceGroupName')]" + }, + "sharedKey": { + "value": "[variables('vpnSharedKey')]" + }, + "keyVaultCertificateUri": { + "value": "[variables('vpnKeyVaultUri')]" + }, + "localNetworkGatewayName": { + "value": "[parameters('localNetworkGatewayName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "17808641870371981197" + } + }, + "parameters": { + "vpnConnectionName": { + "type": "string" + }, + "vgwlocation": { + "type": "string" + }, + "vpnGatewayName": { + "type": "string" + }, + "vpnGatewayResourceGroupName": { + "type": "string" + }, + "sharedKey": { + "type": "string" + }, + "keyVaultCertificateUri": { + "type": "string" + }, + "localNetworkGatewayName": { + "type": "string" + } + }, + "variables": { + "useSharedKey": "[not(empty(parameters('sharedKey')))]", + "useKeyVaultCertificate": "[not(empty(parameters('keyVaultCertificateUri')))]", + "errorMsg": "[if(and(variables('useSharedKey'), variables('useKeyVaultCertificate')), 'Cannot provide both sharedKey and keyVaultCertificateUri', '')]", + "connectionSharedKey": "[if(variables('useSharedKey'), parameters('sharedKey'), null())]", + "connectionIpsecPolicies": "[if(variables('useKeyVaultCertificate'), createArray(createObject('saLifeTimeSeconds', 3600, 'saDataSizeKilobytes', 102400000, 'ipsecEncryption', 'AES256', 'ipsecIntegrity', 'SHA256', 'ikeEncryption', 'AES256', 'ikeIntegrity', 'SHA256', 'dhGroup', 'DHGroup2', 'pfsGroup', 'PFS2')), null())]" + }, + "resources": [ + { + "condition": "[empty(variables('errorMsg'))]", + "type": "Microsoft.Network/connections", + "apiVersion": "2023-02-01", + "name": "[parameters('vpnConnectionName')]", + "location": "[parameters('vgwlocation')]", + "properties": { + "virtualNetworkGateway1": { + "id": "[resourceId(parameters('vpnGatewayResourceGroupName'), 'Microsoft.Network/virtualNetworkGateways', parameters('vpnGatewayName'))]" + }, + "localNetworkGateway2": { + "id": "[resourceId(parameters('vpnGatewayResourceGroupName'), 'Microsoft.Network/localNetworkGateways', parameters('localNetworkGatewayName'))]" + }, + "connectionType": "IPsec", + "routingWeight": 10, + "sharedKey": "[variables('connectionSharedKey')]", + "ipsecPolicies": "[variables('connectionIpsecPolicies')]", + "enableBgp": false, + "usePolicyBasedTrafficSelectors": false + } + } + ] + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('hubResourceGroupName')), 'Microsoft.Resources/deployments', format('localNetworkGateway-{0}', parameters('deploymentNameSuffix')))]", + "[subscriptionResourceId('Microsoft.Resources/deployments', format('InvalidKeyVaultCertificateUri-{0}', parameters('deploymentNameSuffix')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('hubResourceGroupName')), 'Microsoft.Resources/deployments', format('vpnGateway-{0}', parameters('deploymentNameSuffix')))]" + ] + }, + { + "copy": { + "name": "retrieveVnetInfo", + "count": "[length(parameters('vnetResourceIdList'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('retrieveVnetPeerings-{0}-{1}', parameters('deploymentNameSuffix'), copyIndex())]", + "subscriptionId": "[split(parameters('vnetResourceIdList')[copyIndex()], '/')[2]]", + "resourceGroup": "[split(parameters('vnetResourceIdList')[copyIndex()], '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "vnetResourceId": { + "value": "[parameters('vnetResourceIdList')[copyIndex()]]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "8229271046057649788" + } + }, + "parameters": { + "azureFirewallName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the Azure Firewall (optional)" + } + }, + "subnetName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the subnet (optional)" + } + }, + "vnetResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The resource ID of the existing spoke virtual network (optional)" + } + }, + "routeTableName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the route table associated with the hub virtual network (optional)" + } + } + }, + "resources": [], + "outputs": { + "routeTableId": { + "type": "string", + "value": "[if(not(empty(parameters('routeTableName'))), resourceId('Microsoft.Network/routeTables', parameters('routeTableName')), 'N/A')]" + }, + "firewallPrivateIp": { + "type": "string", + "value": "[if(and(not(empty(parameters('azureFirewallName'))), not(empty(parameters('vnetResourceId')))), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('vnetResourceId'), '/')[2], split(parameters('vnetResourceId'), '/')[4]), 'Microsoft.Network/azureFirewalls', parameters('azureFirewallName')), '2020-11-01').ipConfigurations[0].properties.privateIPAddress, 'N/A')]" + }, + "firewallPolicyId": { + "type": "string", + "value": "[if(not(empty(parameters('azureFirewallName'))), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('vnetResourceId'), '/')[2], split(parameters('vnetResourceId'), '/')[4]), 'Microsoft.Network/azureFirewalls', parameters('azureFirewallName')), '2020-11-01').firewallPolicy.id, 'N/A')]" + }, + "subnetAddressPrefix": { + "type": "string", + "value": "[if(and(not(empty(parameters('subnetName'))), not(empty(parameters('vnetResourceId')))), reference(resourceId('Microsoft.Network/virtualNetworks/subnets', last(split(parameters('vnetResourceId'), '/')), parameters('subnetName')), '2020-11-01').addressPrefix, 'N/A')]" + }, + "vnetAddressSpace": { + "type": "array", + "value": "[if(not(empty(parameters('vnetResourceId'))), reference(resourceId('Microsoft.Network/virtualNetworks', last(split(parameters('vnetResourceId'), '/'))), '2020-11-01').addressSpace.addressPrefixes, createArray())]" + }, + "peeringsData": { + "type": "object", + "value": "[if(not(empty(parameters('vnetResourceId'))), createObject('vnetResourceId', parameters('vnetResourceId'), 'peeringsList', reference(resourceId('Microsoft.Network/virtualNetworks', last(split(parameters('vnetResourceId'), '/'))), '2020-11-01').virtualNetworkPeerings), createObject('vnetResourceId', 'N/A', 'peeringsList', createArray()))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('hubResourceGroupName')), 'Microsoft.Resources/deployments', format('vpnConnection-{0}', parameters('deploymentNameSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('retrieveHubVnetPeerings-{0}', parameters('deploymentNameSuffix'))]", + "subscriptionId": "[split(parameters('hubVirtualNetworkResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('hubVirtualNetworkResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "vnetResourceId": { + "value": "[parameters('hubVirtualNetworkResourceId')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "8229271046057649788" + } + }, + "parameters": { + "azureFirewallName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the Azure Firewall (optional)" + } + }, + "subnetName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the subnet (optional)" + } + }, + "vnetResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The resource ID of the existing spoke virtual network (optional)" + } + }, + "routeTableName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the route table associated with the hub virtual network (optional)" + } + } + }, + "resources": [], + "outputs": { + "routeTableId": { + "type": "string", + "value": "[if(not(empty(parameters('routeTableName'))), resourceId('Microsoft.Network/routeTables', parameters('routeTableName')), 'N/A')]" + }, + "firewallPrivateIp": { + "type": "string", + "value": "[if(and(not(empty(parameters('azureFirewallName'))), not(empty(parameters('vnetResourceId')))), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('vnetResourceId'), '/')[2], split(parameters('vnetResourceId'), '/')[4]), 'Microsoft.Network/azureFirewalls', parameters('azureFirewallName')), '2020-11-01').ipConfigurations[0].properties.privateIPAddress, 'N/A')]" + }, + "firewallPolicyId": { + "type": "string", + "value": "[if(not(empty(parameters('azureFirewallName'))), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('vnetResourceId'), '/')[2], split(parameters('vnetResourceId'), '/')[4]), 'Microsoft.Network/azureFirewalls', parameters('azureFirewallName')), '2020-11-01').firewallPolicy.id, 'N/A')]" + }, + "subnetAddressPrefix": { + "type": "string", + "value": "[if(and(not(empty(parameters('subnetName'))), not(empty(parameters('vnetResourceId')))), reference(resourceId('Microsoft.Network/virtualNetworks/subnets', last(split(parameters('vnetResourceId'), '/')), parameters('subnetName')), '2020-11-01').addressPrefix, 'N/A')]" + }, + "vnetAddressSpace": { + "type": "array", + "value": "[if(not(empty(parameters('vnetResourceId'))), reference(resourceId('Microsoft.Network/virtualNetworks', last(split(parameters('vnetResourceId'), '/'))), '2020-11-01').addressSpace.addressPrefixes, createArray())]" + }, + "peeringsData": { + "type": "object", + "value": "[if(not(empty(parameters('vnetResourceId'))), createObject('vnetResourceId', parameters('vnetResourceId'), 'peeringsList', reference(resourceId('Microsoft.Network/virtualNetworks', last(split(parameters('vnetResourceId'), '/'))), '2020-11-01').virtualNetworkPeerings), createObject('vnetResourceId', 'N/A', 'peeringsList', createArray()))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('hubResourceGroupName')), 'Microsoft.Resources/deployments', format('vpnConnection-{0}', parameters('deploymentNameSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix'))]", + "subscriptionId": "[split(parameters('hubVirtualNetworkResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('hubVirtualNetworkResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "vnetResourceId": { + "value": "[parameters('hubVirtualNetworkResourceId')]" + }, + "azureFirewallName": { + "value": "[variables('azureFirewallName')]" + }, + "subnetName": { + "value": "[parameters('gatewaySubnetName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "8229271046057649788" + } + }, + "parameters": { + "azureFirewallName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the Azure Firewall (optional)" + } + }, + "subnetName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the subnet (optional)" + } + }, + "vnetResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The resource ID of the existing spoke virtual network (optional)" + } + }, + "routeTableName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the route table associated with the hub virtual network (optional)" + } + } + }, + "resources": [], + "outputs": { + "routeTableId": { + "type": "string", + "value": "[if(not(empty(parameters('routeTableName'))), resourceId('Microsoft.Network/routeTables', parameters('routeTableName')), 'N/A')]" + }, + "firewallPrivateIp": { + "type": "string", + "value": "[if(and(not(empty(parameters('azureFirewallName'))), not(empty(parameters('vnetResourceId')))), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('vnetResourceId'), '/')[2], split(parameters('vnetResourceId'), '/')[4]), 'Microsoft.Network/azureFirewalls', parameters('azureFirewallName')), '2020-11-01').ipConfigurations[0].properties.privateIPAddress, 'N/A')]" + }, + "firewallPolicyId": { + "type": "string", + "value": "[if(not(empty(parameters('azureFirewallName'))), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('vnetResourceId'), '/')[2], split(parameters('vnetResourceId'), '/')[4]), 'Microsoft.Network/azureFirewalls', parameters('azureFirewallName')), '2020-11-01').firewallPolicy.id, 'N/A')]" + }, + "subnetAddressPrefix": { + "type": "string", + "value": "[if(and(not(empty(parameters('subnetName'))), not(empty(parameters('vnetResourceId')))), reference(resourceId('Microsoft.Network/virtualNetworks/subnets', last(split(parameters('vnetResourceId'), '/')), parameters('subnetName')), '2020-11-01').addressPrefix, 'N/A')]" + }, + "vnetAddressSpace": { + "type": "array", + "value": "[if(not(empty(parameters('vnetResourceId'))), reference(resourceId('Microsoft.Network/virtualNetworks', last(split(parameters('vnetResourceId'), '/'))), '2020-11-01').addressSpace.addressPrefixes, createArray())]" + }, + "peeringsData": { + "type": "object", + "value": "[if(not(empty(parameters('vnetResourceId'))), createObject('vnetResourceId', parameters('vnetResourceId'), 'peeringsList', reference(resourceId('Microsoft.Network/virtualNetworks', last(split(parameters('vnetResourceId'), '/'))), '2020-11-01').virtualNetworkPeerings), createObject('vnetResourceId', 'N/A', 'peeringsList', createArray()))]" + } + } + } + }, + "dependsOn": [ + "updatePeerings" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('updateHubPeerings-{0}', parameters('deploymentNameSuffix'))]", + "subscriptionId": "[split(parameters('hubVirtualNetworkResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('hubVirtualNetworkResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "vnetResourceId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveHubVnetPeerings-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.peeringsData.value.vnetResourceId]" + }, + "peeringsList": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveHubVnetPeerings-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.peeringsData.value.peeringsList]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "10372783504892384694" + } + }, + "parameters": { + "peeringsList": { + "type": "array", + "metadata": { + "description": "The list of peerings to update" + } + }, + "vnetResourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the existing virtual network" + } + } + }, + "variables": { + "copy": [ + { + "name": "updatedPeerings", + "count": "[length(parameters('peeringsList'))]", + "input": { + "name": "[last(split(parameters('peeringsList')[copyIndex('updatedPeerings')].id, '/'))]", + "properties": { + "allowGatewayTransit": "[if(contains(variables('vnetName'), '-hub-'), true(), parameters('peeringsList')[copyIndex('updatedPeerings')].properties.allowGatewayTransit)]", + "useRemoteGateways": "[if(not(contains(variables('vnetName'), '-hub-')), true(), parameters('peeringsList')[copyIndex('updatedPeerings')].properties.useRemoteGateways)]", + "allowForwardedTraffic": "[if(equals(parameters('peeringsList')[copyIndex('updatedPeerings')].properties.allowForwardedTraffic, null()), true(), parameters('peeringsList')[copyIndex('updatedPeerings')].properties.allowForwardedTraffic)]", + "remoteVirtualNetwork": "[parameters('peeringsList')[copyIndex('updatedPeerings')].properties.remoteVirtualNetwork]" + } + } + } + ], + "vnetName": "[last(split(parameters('vnetResourceId'), '/'))]" + }, + "resources": [ + { + "copy": { + "name": "peeringUpdates", + "count": "[length(variables('updatedPeerings'))]" + }, + "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", + "apiVersion": "2021-02-01", + "name": "[format('{0}/{1}', variables('vnetName'), variables('updatedPeerings')[copyIndex()].name)]", + "properties": "[variables('updatedPeerings')[copyIndex()].properties]" + } + ] + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveHubVnetPeerings-{0}', parameters('deploymentNameSuffix')))]", + "retrieveVnetInfo" + ] + }, + { + "copy": { + "name": "updatePeerings", + "count": "[length(parameters('vnetResourceIdList'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('updatePeerings-{0}-{1}', parameters('deploymentNameSuffix'), copyIndex())]", + "subscriptionId": "[split(parameters('vnetResourceIdList')[copyIndex()], '/')[2]]", + "resourceGroup": "[split(parameters('vnetResourceIdList')[copyIndex()], '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "vnetResourceId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('vnetResourceIdList')[copyIndex()], '/')[2], split(parameters('vnetResourceIdList')[copyIndex()], '/')[4]), 'Microsoft.Resources/deployments', format('retrieveVnetPeerings-{0}-{1}', parameters('deploymentNameSuffix'), copyIndex())), '2022-09-01').outputs.peeringsData.value.vnetResourceId]" + }, + "peeringsList": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('vnetResourceIdList')[copyIndex()], '/')[2], split(parameters('vnetResourceIdList')[copyIndex()], '/')[4]), 'Microsoft.Resources/deployments', format('retrieveVnetPeerings-{0}-{1}', parameters('deploymentNameSuffix'), copyIndex())), '2022-09-01').outputs.peeringsData.value.peeringsList]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "10372783504892384694" + } + }, + "parameters": { + "peeringsList": { + "type": "array", + "metadata": { + "description": "The list of peerings to update" + } + }, + "vnetResourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the existing virtual network" + } + } + }, + "variables": { + "copy": [ + { + "name": "updatedPeerings", + "count": "[length(parameters('peeringsList'))]", + "input": { + "name": "[last(split(parameters('peeringsList')[copyIndex('updatedPeerings')].id, '/'))]", + "properties": { + "allowGatewayTransit": "[if(contains(variables('vnetName'), '-hub-'), true(), parameters('peeringsList')[copyIndex('updatedPeerings')].properties.allowGatewayTransit)]", + "useRemoteGateways": "[if(not(contains(variables('vnetName'), '-hub-')), true(), parameters('peeringsList')[copyIndex('updatedPeerings')].properties.useRemoteGateways)]", + "allowForwardedTraffic": "[if(equals(parameters('peeringsList')[copyIndex('updatedPeerings')].properties.allowForwardedTraffic, null()), true(), parameters('peeringsList')[copyIndex('updatedPeerings')].properties.allowForwardedTraffic)]", + "remoteVirtualNetwork": "[parameters('peeringsList')[copyIndex('updatedPeerings')].properties.remoteVirtualNetwork]" + } + } + } + ], + "vnetName": "[last(split(parameters('vnetResourceId'), '/'))]" + }, + "resources": [ + { + "copy": { + "name": "peeringUpdates", + "count": "[length(variables('updatedPeerings'))]" + }, + "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", + "apiVersion": "2021-02-01", + "name": "[format('{0}/{1}', variables('vnetName'), variables('updatedPeerings')[copyIndex()].name)]", + "properties": "[variables('updatedPeerings')[copyIndex()].properties]" + } + ] + } + }, + "dependsOn": [ + "retrieveVnetInfo", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('updateHubPeerings-{0}', parameters('deploymentNameSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('createVgwRouteTable-{0}', parameters('deploymentNameSuffix'))]", + "subscriptionId": "[split(parameters('hubVirtualNetworkResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('hubVirtualNetworkResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "routeTableName": { + "value": "[parameters('vgwRouteTableName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "13855829966458352" + } + }, + "parameters": { + "routeTableName": { + "type": "string", + "metadata": { + "description": "Name of the route table to create" + } + } + }, + "resources": [ + { + "type": "Microsoft.Network/routeTables", + "apiVersion": "2021-02-01", + "name": "[parameters('routeTableName')]", + "location": "[resourceGroup().location]", + "properties": { + "disableBgpRoutePropagation": true + } + } + ], + "outputs": { + "routeTableId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/routeTables', parameters('routeTableName'))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix')))]", + "retrieveVnetInfo", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('updateHubPeerings-{0}', parameters('deploymentNameSuffix')))]", + "updatePeerings" + ] + }, + { + "copy": { + "name": "createRoutes", + "count": "[length(parameters('vnetResourceIdList'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('createRoute-{0}-{1}', copyIndex(), parameters('deploymentNameSuffix'))]", + "subscriptionId": "[split(parameters('hubVirtualNetworkResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('hubVirtualNetworkResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "routeTableName": { + "value": "[parameters('vgwRouteTableName')]" + }, + "addressSpace": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('vnetResourceIdList')[copyIndex()], '/')[2], split(parameters('vnetResourceIdList')[copyIndex()], '/')[4]), 'Microsoft.Resources/deployments', format('retrieveVnetPeerings-{0}-{1}', parameters('deploymentNameSuffix'), copyIndex())), '2022-09-01').outputs.vnetAddressSpace.value]" + }, + "routeName": { + "value": "[format('route-{0}', copyIndex())]" + }, + "nextHopType": { + "value": "VirtualAppliance" + }, + "nextHopIpAddress": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.firewallPrivateIp.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "280047033181845191" + } + }, + "parameters": { + "routeTableName": { + "type": "string", + "metadata": { + "description": "Name of the route table to create" + } + }, + "routeName": { + "type": "string", + "metadata": { + "description": "Name of the route" + } + }, + "addressSpace": { + "type": "array", + "metadata": { + "description": "CIDR prefixes for the route" + } + }, + "nextHopType": { + "type": "string", + "metadata": { + "description": "The next hop type for the route" + } + }, + "nextHopIpAddress": { + "type": "string", + "metadata": { + "description": "The next hop IP address for the route" + } + } + }, + "resources": [ + { + "copy": { + "name": "routes", + "count": "[length(parameters('addressSpace'))]" + }, + "type": "Microsoft.Network/routeTables/routes", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('routeTableName'), format('{0}-{1}', parameters('routeName'), copyIndex()))]", + "properties": { + "addressPrefix": "[parameters('addressSpace')[copyIndex()]]", + "nextHopType": "[parameters('nextHopType')]", + "nextHopIpAddress": "[if(not(equals(parameters('nextHopIpAddress'), '')), parameters('nextHopIpAddress'), null())]" + } + } + ] + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('createVgwRouteTable-{0}', parameters('deploymentNameSuffix')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('vnetResourceIdList')[copyIndex()], '/')[2], split(parameters('vnetResourceIdList')[copyIndex()], '/')[4]), 'Microsoft.Resources/deployments', format('retrieveVnetPeerings-{0}-{1}', parameters('deploymentNameSuffix'), copyIndex()))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('createOverrideRoutes-{0}', parameters('deploymentNameSuffix'))]", + "subscriptionId": "[split(parameters('hubVirtualNetworkResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('hubVirtualNetworkResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "routeTableName": { + "value": "[variables('hubVnetRouteTableName')]" + }, + "addressSpace": { + "value": "[parameters('localAddressPrefixes')]" + }, + "routeName": { + "value": "route-onprem-override" + }, + "nextHopType": { + "value": "VirtualAppliance" + }, + "nextHopIpAddress": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.firewallPrivateIp.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "280047033181845191" + } + }, + "parameters": { + "routeTableName": { + "type": "string", + "metadata": { + "description": "Name of the route table to create" + } + }, + "routeName": { + "type": "string", + "metadata": { + "description": "Name of the route" + } + }, + "addressSpace": { + "type": "array", + "metadata": { + "description": "CIDR prefixes for the route" + } + }, + "nextHopType": { + "type": "string", + "metadata": { + "description": "The next hop type for the route" + } + }, + "nextHopIpAddress": { + "type": "string", + "metadata": { + "description": "The next hop IP address for the route" + } + } + }, + "resources": [ + { + "copy": { + "name": "routes", + "count": "[length(parameters('addressSpace'))]" + }, + "type": "Microsoft.Network/routeTables/routes", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('routeTableName'), format('{0}-{1}', parameters('routeName'), copyIndex()))]", + "properties": { + "addressPrefix": "[parameters('addressSpace')[copyIndex()]]", + "nextHopType": "[parameters('nextHopType')]", + "nextHopIpAddress": "[if(not(equals(parameters('nextHopIpAddress'), '')), parameters('nextHopIpAddress'), null())]" + } + } + ] + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('createVgwRouteTable-{0}', parameters('deploymentNameSuffix')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('associateRouteTable-{0}', parameters('deploymentNameSuffix'))]", + "subscriptionId": "[split(parameters('hubVirtualNetworkResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('hubVirtualNetworkResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "vnetResourceId": { + "value": "[parameters('hubVirtualNetworkResourceId')]" + }, + "routeTableResourceId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('createVgwRouteTable-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.routeTableId.value]" + }, + "subnetName": { + "value": "[parameters('gatewaySubnetName')]" + }, + "subnetAddressPrefix": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.subnetAddressPrefix.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "17439321293785150273" + } + }, + "parameters": { + "vnetResourceId": { + "type": "string", + "metadata": { + "description": "virtual network resource ID that holds the subnet" + } + }, + "routeTableResourceId": { + "type": "string", + "metadata": { + "description": "route table resource ID to associate with the subnet" + } + }, + "subnetName": { + "type": "string", + "metadata": { + "description": "name of the subnet to associate with the route table" + } + }, + "subnetAddressPrefix": { + "type": "string", + "metadata": { + "description": "address prefix of the gateway subnet" + } + } + }, + "resources": [ + { + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', last(split(parameters('vnetResourceId'), '/')), parameters('subnetName'))]", + "properties": { + "addressPrefix": "[parameters('subnetAddressPrefix')]", + "routeTable": { + "id": "[resourceId('Microsoft.Network/routeTables', last(split(parameters('routeTableResourceId'), '/')))]" + } + } + } + ] + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('createVgwRouteTable-{0}', parameters('deploymentNameSuffix')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('createHubRoute-{0}', parameters('deploymentNameSuffix'))]", + "subscriptionId": "[split(parameters('hubVirtualNetworkResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('hubVirtualNetworkResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "routeTableName": { + "value": "[variables('hubVnetRouteTableName')]" + }, + "addressSpace": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.vnetAddressSpace.value]" + }, + "routeName": { + "value": "route-hubvnet" + }, + "nextHopType": { + "value": "VirtualAppliance" + }, + "nextHopIpAddress": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.firewallPrivateIp.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "280047033181845191" + } + }, + "parameters": { + "routeTableName": { + "type": "string", + "metadata": { + "description": "Name of the route table to create" + } + }, + "routeName": { + "type": "string", + "metadata": { + "description": "Name of the route" + } + }, + "addressSpace": { + "type": "array", + "metadata": { + "description": "CIDR prefixes for the route" + } + }, + "nextHopType": { + "type": "string", + "metadata": { + "description": "The next hop type for the route" + } + }, + "nextHopIpAddress": { + "type": "string", + "metadata": { + "description": "The next hop IP address for the route" + } + } + }, + "resources": [ + { + "copy": { + "name": "routes", + "count": "[length(parameters('addressSpace'))]" + }, + "type": "Microsoft.Network/routeTables/routes", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('routeTableName'), format('{0}-{1}', parameters('routeName'), copyIndex()))]", + "properties": { + "addressPrefix": "[parameters('addressSpace')[copyIndex()]]", + "nextHopType": "[parameters('nextHopType')]", + "nextHopIpAddress": "[if(not(equals(parameters('nextHopIpAddress'), '')), parameters('nextHopIpAddress'), null())]" + } + } + ] + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('createVgwRouteTable-{0}', parameters('deploymentNameSuffix')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('firewallRules-{0}', parameters('deploymentNameSuffix'))]", + "subscriptionId": "[split(parameters('hubVirtualNetworkResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('hubVirtualNetworkResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "allowVnetAddressSpaces": { + "value": "[parameters('allowedAzureAddressPrefixes')]" + }, + "onPremAddressSpaces": { + "value": "[parameters('localAddressPrefixes')]" + }, + "firewallPolicyId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.firewallPolicyId.value]" + }, + "priorityValue": { + "value": 300 + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "17390602903085657580" + } + }, + "parameters": { + "allowVnetAddressSpaces": { + "type": "array", + "metadata": { + "description": "The list of virtual network resource IDs to be used as the source IP groups" + } + }, + "onPremAddressSpaces": { + "type": "array", + "metadata": { + "description": "Address prefixes of the on-premises network" + } + }, + "firewallPolicyId": { + "type": "string", + "metadata": { + "description": "Name of the firewall policy" + } + }, + "priorityValue": { + "type": "int", + "minValue": 300, + "maxValue": 65000, + "metadata": { + "description": "The priority value for the rule collection" + } + } + }, + "variables": { + "outboundRules": { + "name": "AllowAzureToOnPremRule", + "ruleType": "NetworkRule", + "sourceAddresses": "[parameters('allowVnetAddressSpaces')]", + "destinationAddresses": "[parameters('onPremAddressSpaces')]", + "destinationPorts": [ + "*" + ], + "ipProtocols": [ + "Any" + ] + }, + "inboundRules": { + "name": "AllowOnPremToAzureRule", + "ruleType": "NetworkRule", + "sourceAddresses": "[parameters('onPremAddressSpaces')]", + "destinationAddresses": "[parameters('allowVnetAddressSpaces')]", + "destinationPorts": [ + "*" + ], + "ipProtocols": [ + "Any" + ] + } + }, + "resources": [ + { + "type": "Microsoft.Network/firewallPolicies/ruleCollectionGroups", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', last(split(parameters('firewallPolicyId'), '/')), 'VgwNetworkRuleCollectionGroup')]", + "properties": { + "priority": "[parameters('priorityValue')]", + "ruleCollections": [ + { + "name": "AllowVgw", + "priority": "[parameters('priorityValue')]", + "ruleCollectionType": "FirewallPolicyFilterRuleCollection", + "action": { + "type": "Allow" + }, + "rules": [ + "[variables('outboundRules')]", + "[variables('inboundRules')]" + ] + } + ] + } + } + ] + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('associateRouteTable-{0}', parameters('deploymentNameSuffix')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix')))]" + ] + } + ] +} \ No newline at end of file diff --git a/src/bicep/mlz.json b/src/bicep/mlz.json index 0dc43b61..105425e4 100644 --- a/src/bicep/mlz.json +++ b/src/bicep/mlz.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "15343776706780459746" + "version": "0.31.92.45157", + "templateHash": "7670256562437793548" } }, "parameters": { @@ -887,8 +887,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "10924801470287352076" + "version": "0.31.92.45157", + "templateHash": "4114664614478275863" } }, "parameters": { @@ -967,8 +967,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "11992572431768878515" + "version": "0.31.92.45157", + "templateHash": "6411408920489493501" } }, "parameters": { @@ -1561,8 +1561,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "2502930168536032010" + "version": "0.31.92.45157", + "templateHash": "17619606927129129347" } }, "parameters": { @@ -1706,8 +1706,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "12782100486021815060" + "version": "0.31.92.45157", + "templateHash": "5311912111839888762" } }, "parameters": { @@ -1766,8 +1766,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "17445726037807437290" + "version": "0.31.92.45157", + "templateHash": "9371138343009436350" } }, "parameters": { @@ -1907,8 +1907,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "11630019985682391796" + "version": "0.31.92.45157", + "templateHash": "6531207997218247037" } }, "parameters": { @@ -2084,8 +2084,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "12984958392421507149" + "version": "0.31.92.45157", + "templateHash": "17606032368841347839" } }, "parameters": { @@ -2352,8 +2352,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "2196017082128829477" + "version": "0.31.92.45157", + "templateHash": "3681943409502537301" } }, "parameters": { @@ -2431,8 +2431,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "2196017082128829477" + "version": "0.31.92.45157", + "templateHash": "3681943409502537301" } }, "parameters": { @@ -2512,8 +2512,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "8417416771003518918" + "version": "0.31.92.45157", + "templateHash": "18425321023142226965" } }, "parameters": { @@ -2613,8 +2613,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "4071285799602638747" + "version": "0.31.92.45157", + "templateHash": "15002544166394392504" } }, "parameters": { @@ -2682,8 +2682,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "3465065949976146403" + "version": "0.31.92.45157", + "templateHash": "16817025486402215719" } }, "parameters": { @@ -2796,8 +2796,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "1730170030487163899" + "version": "0.31.92.45157", + "templateHash": "782319697243698272" } }, "parameters": { @@ -2886,8 +2886,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "1730170030487163899" + "version": "0.31.92.45157", + "templateHash": "782319697243698272" } }, "parameters": { @@ -3000,8 +3000,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "5808375390237466838" + "version": "0.31.92.45157", + "templateHash": "4750034338253477937" } }, "parameters": { @@ -3401,8 +3401,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "11567108053836979012" + "version": "0.31.92.45157", + "templateHash": "11818136489056939588" } }, "parameters": { @@ -3519,8 +3519,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "2196017082128829477" + "version": "0.31.92.45157", + "templateHash": "3681943409502537301" } }, "parameters": { @@ -3602,8 +3602,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "8417416771003518918" + "version": "0.31.92.45157", + "templateHash": "18425321023142226965" } }, "parameters": { @@ -3705,8 +3705,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "4071285799602638747" + "version": "0.31.92.45157", + "templateHash": "15002544166394392504" } }, "parameters": { @@ -3782,8 +3782,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "3465065949976146403" + "version": "0.31.92.45157", + "templateHash": "16817025486402215719" } }, "parameters": { @@ -3930,8 +3930,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "6489616383757058493" + "version": "0.31.92.45157", + "templateHash": "11553909803736438916" } }, "parameters": { @@ -3983,8 +3983,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "5031620623183573702" + "version": "0.31.92.45157", + "templateHash": "5574526676512163672" } }, "parameters": { @@ -4062,8 +4062,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "15799890372401066181" + "version": "0.31.92.45157", + "templateHash": "10267893616110384815" } }, "parameters": { @@ -4115,8 +4115,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "5031620623183573702" + "version": "0.31.92.45157", + "templateHash": "5574526676512163672" } }, "parameters": { @@ -4192,8 +4192,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "16841222955467860758" + "version": "0.31.92.45157", + "templateHash": "17171440191267536743" } }, "parameters": { @@ -4269,8 +4269,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "12405780209119797551" + "version": "0.31.92.45157", + "templateHash": "16077950968688123011" } }, "parameters": { @@ -4435,8 +4435,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "6386888682917235118" + "version": "0.31.92.45157", + "templateHash": "12787329163785242553" } }, "parameters": { @@ -4524,8 +4524,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "419730844167715947" + "version": "0.31.92.45157", + "templateHash": "11761568940379970751" } }, "parameters": { @@ -4782,8 +4782,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "7946295394028911325" + "version": "0.31.92.45157", + "templateHash": "4207798980384159491" } }, "parameters": { @@ -4862,8 +4862,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "967013811257719495" + "version": "0.31.92.45157", + "templateHash": "1966035938992047983" } }, "parameters": { @@ -4957,8 +4957,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "9115514582672423063" + "version": "0.31.92.45157", + "templateHash": "7930493629995578222" } }, "parameters": { @@ -5102,8 +5102,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "366255573709691198" + "version": "0.31.92.45157", + "templateHash": "17900321188332105834" } }, "parameters": { @@ -5188,8 +5188,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "11337913680813675353" + "version": "0.31.92.45157", + "templateHash": "17298378299072098272" } }, "parameters": { @@ -5356,8 +5356,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "6648745810362038848" + "version": "0.31.92.45157", + "templateHash": "12087616562036012055" } }, "parameters": { @@ -5484,8 +5484,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "18318483669505051648" + "version": "0.31.92.45157", + "templateHash": "2081045465267717136" } }, "parameters": { @@ -5712,8 +5712,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "12373038860746323248" + "version": "0.31.92.45157", + "templateHash": "15042212833055960396" } }, "parameters": { @@ -5889,8 +5889,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "10895428597231985214" + "version": "0.31.92.45157", + "templateHash": "2760766953842709390" } }, "parameters": { @@ -6049,8 +6049,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "13001914044795164689" + "version": "0.31.92.45157", + "templateHash": "10850741356290813493" } }, "parameters": { @@ -6371,8 +6371,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "8788916623054227962" + "version": "0.31.92.45157", + "templateHash": "2785700700806650182" } }, "parameters": { @@ -6538,8 +6538,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "16352081091904770478" + "version": "0.31.92.45157", + "templateHash": "10838080267928735788" } }, "parameters": { @@ -6846,8 +6846,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "8788916623054227962" + "version": "0.31.92.45157", + "templateHash": "2785700700806650182" } }, "parameters": { @@ -6991,8 +6991,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "15180554823445581333" + "version": "0.31.92.45157", + "templateHash": "15042903713059976655" } }, "parameters": { @@ -7108,8 +7108,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "10245709494871874056" + "version": "0.31.92.45157", + "templateHash": "1625826941635729014" } }, "parameters": { @@ -7392,8 +7392,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "11013785870428129557" + "version": "0.31.92.45157", + "templateHash": "11341470403202647858" } }, "parameters": { @@ -7478,8 +7478,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "5964906331561259426" + "version": "0.31.92.45157", + "templateHash": "4687229436121899773" } }, "parameters": { @@ -7565,8 +7565,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "3200577753830159376" + "version": "0.31.92.45157", + "templateHash": "6315472047633861096" } }, "parameters": { @@ -7652,8 +7652,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "5533529544253509904" + "version": "0.31.92.45157", + "templateHash": "2073766618455932098" } }, "parameters": { @@ -7734,8 +7734,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "169088610601729285" + "version": "0.31.92.45157", + "templateHash": "9546260853018527046" } }, "parameters": { @@ -7816,8 +7816,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "5104130163491279218" + "version": "0.31.92.45157", + "templateHash": "16372121177996394493" } }, "parameters": { @@ -7894,8 +7894,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "8958046244399156747" + "version": "0.31.92.45157", + "templateHash": "3821176451773778831" } }, "parameters": { @@ -7975,8 +7975,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "13721817451936402949" + "version": "0.31.92.45157", + "templateHash": "1721966359516622278" } }, "parameters": { @@ -8047,8 +8047,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "16505356842938617427" + "version": "0.31.92.45157", + "templateHash": "8510408576984746573" } }, "parameters": { @@ -8133,8 +8133,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "7145459344230861166" + "version": "0.31.92.45157", + "templateHash": "3495057416767671634" } }, "parameters": { @@ -8190,8 +8190,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "4206571908443996627" + "version": "0.31.92.45157", + "templateHash": "14528983897386416653" } }, "parameters": { @@ -8366,8 +8366,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "967013811257719495" + "version": "0.31.92.45157", + "templateHash": "1966035938992047983" } }, "parameters": { @@ -8464,8 +8464,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "4104174025531546645" + "version": "0.31.92.45157", + "templateHash": "17309660590425732791" } }, "parameters": { @@ -8528,8 +8528,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.31.34.60546", - "templateHash": "2575529544484247921" + "version": "0.31.92.45157", + "templateHash": "17047820191891552534" } }, "parameters": {