diff --git a/solutions/azure-hub-spoke-connected-group/README.md b/solutions/azure-hub-spoke-connected-group/README.md index e957c25b..7ef5f2ab 100644 --- a/solutions/azure-hub-spoke-connected-group/README.md +++ b/solutions/azure-hub-spoke-connected-group/README.md @@ -1,155 +1,3 @@ ---- -page_type: sample -languages: -- azurecli -- bicep -products: - - azure - - azure-firewall - - azure-virtual-network - - azure-bastion - - azure-vpn-gateway - - virtual-network-manager -name: Hub-and-Spoke Deployment with Connected Groups -urlFragment: hub-and-spoke-virtual-network-manager-connected-groups -description: This sample deploys Azure virtual networks in a hub and spoke configuration, using Azure Virtual Network Manager to manage Virtual Network connectivity and implement sample Security Admin Rules. An Azure Firewall and Bastion host are also deployed. Optionally, a VPN gateway and sample workload (virtual machines) can be deployed. -urlFragment: hub-and-spoke-deployment-with-connected-groups -azureDeploy: https://raw.githubusercontent.com/mspnp/samples/main/solutions/azure-hub-spoke-connected-group/azuredeploy.json ---- +# Hub and spoke deployment with Connected Groups -# Hub and Spoke Deployment with Connected Groups - -This sample deploys Azure virtual networks in a hub and spoke configuration, using Azure Virtual Network Manager to manage Virtual Network connectivity and implement sample Security Admin Rules. An Azure Firewall and Bastion host are also deployed. Optionally, a VPN gateway and sample workload (virtual machines) can be deployed. - -Where applicable, each resource is configured to send diagnostics to an Azure Log Analytics instance. - -![Hub and spoke connected group architectural diagram.](https://learn.microsoft.com/azure/architecture/reference-architectures/hybrid-networking/images/hub-spoke.png) - -For detailed information, see the Azure Hub and Spoke reference architecture in the Azure Architecture Center: - -> [!div class="nextstepaction"] -> [Hub-spoke network topology in Azure](https://learn.microsoft.com/azure/architecture/reference-architectures/hybrid-networking/hub-spoke) - -## Deploying Azure Virtual Network Manager with Infrastructure-as-Code - -### Registering Resource Providers - -If your Azure Virtual Network Manager's scopes includes Management Groups, you'll need to register the 'Microsoft.Network' Resource Provider at each Management Group scope with the REST API. See: [Register Resource Provider at Management Group Scope](https://learn.microsoft.com/rest/api/resources/providers/register-at-management-group-scope). - -### Deploying Configurations - -When deploying or managing Azure Virtual Network Manager using infrastructure-as-code, special consideration should be given to the fact that Azure Virtual Network Manager configuration involves a two step process: - -1. A configuration and configuration scope or target are defined, then -1. The configuration is deployed to the target resources (typically, Virtual Networks). - -To complete these steps using the Portal, you create a configuration then choose to deploy it in a separate action. For infrastructure code, after defining a configuration in code, the Azure Virtual Network Manager API must be called to perform a 'commit' action (mirroring the 'deploy' step in the Portal). - -Declarative infrastructure code on its own cannot call the API, requiring the use of a Deployment Script resource. The Deployment Script resource invokes a script in an Azure Container Instance to execute the `Deploy-AzNetworkManagerCommit` Azure PowerShell command. - -Because the PowerShell script runs within the Deployment Script resource, troubleshooting a failed deployment may require reviewing the script logs found on the Deployment Script resource if the Deployment Script resource deployment reports a failure. It is also possible to view the deployment in the Portal, but note that the Portal interface may take several minutes to update after a code deployment is run. - -## Deploy sample - -Clone repository - -```azurecli-interactive -git clone https://github.com/mspnp/samples.git -cd samples/solutions/azure-hub-spoke-connected-group/bicep -``` - -Create a resource group for the deployment. - -```azurecli-interactive -az group create --name rg-hub-spoke-eastus --location eastus -``` - -> The location for the deployed resources defaults to the location used for the target resource group. This deployment uses availability zones for all resources that support it, as hub networks are usually business critical. This means if the resource group's location does not support availability zones, you must provide an additional parameter to your chosen command below of `location=value` with a value that supports availability zones. See [Azure regions with availability zones](https://learn.microsoft.com/azure/availability-zones/az-overview#azure-regions-with-availability-zones). - -**Basic deployment** - -Run the following command to initiate the deployment. If you would like to also deploy this sample with virtual machines and / or an Azure VPN gateway, see the `az deployment group create` examples found later in this document. - -```azurecli-interactive -az deployment group create \ - --resource-group rg-hub-spoke-eastus \ - --template-file main.bicep -``` - -**Deploy with virtual machines** - -Run the following command to initiate the deployment with a Linux VM deployed to the first spoke network and a Windows VM deployed to the second spoke network. - -| :warning: | This deploys these VMs with basic configuration, they are not Internet facing, but security should always be top of mind. Please update the `adminUsername` and `adminPassword` to a value of your choosing. | -| --------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - -```azurecli-interactive -az deployment group create \ - --resource-group rg-hub-spoke-eastus \ - --template-file main.bicep \ - --parameters deployVirtualMachines=true adminUsername=azureadmin -``` - -**Deploy with VPN gateway** - -Run the following command to initiate the deployment with a virtual network gateway deployed into the hub virtual network. Note, VPN gateways take a significant time to deploy. - -```azurecli-interactive -az deployment group create \ - --resource-group rg-hub-spoke-eastus \ - --template-file main.bicep \ - --parameters deployVpnGateway=true -``` - -**Deploy with virtual machines and a VPN gateway** - -Run the following command to initiate the deployment with a Linux VM deployed to the first spoke network and a Windows VM deployed to the second spoke network. - -| :warning: | This deploys these VMs with basic configuration, they are not Internet facing, but security should always be top of mind. Please update the `adminUsername` and `adminPassword` to a value of your choosing. | -| --------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - -```azurecli-interactive -az deployment group create \ - --resource-group rg-hub-spoke-eastus \ - --template-file main.bicep \ - --parameters deployVirtualMachines=true adminUsername=azureadmin deployVpnGateway=true -``` - -## Solution deployment parameters - -| Parameter | Type | Description | Default | -| ------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------- | -------------------------- | -| `location` | string | Deployment location. Location must support availability zones. | `resourceGroup().location` | -| `deployVirtualMachines` | bool | If true, deploys one basic Linux virtual machine to spoke one and one basic Windows virtual machine to spoke two. | `false` | -| `adminUserName` | string | If deploying virtual machines, the admin user name for both VMs. | `azureadmin` | -| `adminPassword` | securestring | If deploying virtual machines, the admin password for both VMs. | `null` | -| `deployVpnGateway` | bool | If true, a virtual network gateway is deployed into the hub network (+30 min deployment). | `false` | -| `deployDefaultDenySecurityAdminRules` | bool | If false, the Azure Virtual Network Manager security rule collection is left empty. | `true` | - -## Diagnostic configurations - -The following resources are configured to send diagnostic logs to the included Log Analytics workspace. - -- All virtual networks -- All network security groups -- Azure VPN Gateway -- Azure Firewall -- Azure Bastion - -Note, this deployment includes optional basic virtual machines. These are not configured with a Log Analytics workspace, however, can be with the Log Analytics virtual machine extension for [Windows](https://learn.microsoft.com/azure/virtual-machines/extensions/oms-windows) and [Linux](https://learn.microsoft.com/azure/virtual-machines/extensions/oms-linux). - -## Step 5: Clean Up - -```azurecli-interactive -az group delete --name rg-hub-spoke-eastus --yes -``` - -## Microsoft Open Source Code of Conduct - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). - -Resources: - -- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) -- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) -- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns +The deployment sample that supports the [Hub-spoke network topology in Azure](https://learn.microsoft.com/azure/architecture/reference-architectures/hybrid-networking/hub-spoke) article has moved. You'll find it's new location on GitHub at [Hub and spoke deployment with Connected Groups](https://github.com/Azure-Samples/azure-hub-spoke/tree/main/hub-spoke-connected-group). diff --git a/solutions/azure-hub-spoke-connected-group/bicep/main.bicep b/solutions/azure-hub-spoke-connected-group/bicep/main.bicep deleted file mode 100644 index 76dd5ee2..00000000 --- a/solutions/azure-hub-spoke-connected-group/bicep/main.bicep +++ /dev/null @@ -1,301 +0,0 @@ -targetScope = 'resourceGroup' - -/*** PARAMETERS ***/ - -@description('The location of this regional hub. All resources, including spoke resources, will be deployed to this region. This region must support availability zones.') -@minLength(6) -/* Ideally we'd include this limitation, but since we want to default to the resource group's location, we cannot. -@allowed([ - 'brazilsouth' - 'canadacentral' - 'centralus' - 'eastus' - 'eastus2' - 'southcentralus' - 'westus2' - 'westus3' - 'francecentral' - 'germanywestcentral' - 'northeurope' - 'norwayeast' - 'uksouth' - 'westeurope' - 'sweedencentral' - 'switzerlandnorth' - 'uaenorth' - 'southafricanorth' - 'australiaeast' - 'centralindia' - 'japaneast' - 'koreacentral' - 'southeastasia' - 'eastasia' -])*/ -param location string = resourceGroup().location - -@description('Set to true to include a basic VPN Gateway deployment into the hub. Set to false to leave network space for a VPN Gateway, but do not deploy one. Default is false. Note deploying VPN gateways can take significant time.') -param deployVpnGateway bool = false - -@description('Set to true to include one Windows and one Linux virtual machine for you to experience peering, gateway transit, and bastion access. Default is false.') -param deployVirtualMachines bool = false - -@description('Set to true to deploy Azure Bastion. Default is true') -param deployAzureBastion bool = true - -@description('Set to false to disable the deployment of some provided default deny AVNM security admin rules. Default is true.') -param deployDefaultDenySecurityAdminRules bool = true - -@minLength(4) -@maxLength(20) -@description('Username for both the Linux and Windows VM. Must only contain letters, numbers, hyphens, and underscores and may not start with a hyphen or number. Only needed when providing deployVirtualMachines=true.') -param adminUsername string = 'azureadmin' - -@secure() -// @minLength(12) -- Ideally we'd have this here, but to support the multiple varients we will remove it. -@maxLength(70) -@description('Password for both the Linux and Windows VM. Password must have 3 of the following: 1 lower case character, 1 upper case character, 1 number, and 1 special character. Must be at least 12 characters. Only needed when providing deployVirtualMachines=true.') -param adminPassword string - -/*** RESOURCES (HUB) ***/ - -module hub 'modules/hub.bicep' = { - name: 'hub' - params: { - location: location - deployVpnGateway: deployVpnGateway - deployAzureBastion: deployAzureBastion - deployVirtualMachines: deployVirtualMachines - } -} - -/*** RESOURCES (ALL SPOKES) ***/ - -@description('Next hop to the regional hub\'s Azure Firewall') -resource routeNextHopToFirewall 'Microsoft.Network/routeTables@2024-05-01' = { - name: 'route-to-${location}-hub-fw' - location: location - properties: { - routes: [ - { - name: 'r-nexthop-to-fw' - properties: { - nextHopType: 'VirtualAppliance' - addressPrefix: '0.0.0.0/0' - nextHopIpAddress: hub.outputs.firewall.properties.ipConfigurations[0].properties.privateIPAddress - } - } - ] - } -} - -@description('NSG on the resource subnet (just using a common one for all as an example, but usually would be based on the specific needs of the spoke).') -resource nsgResourcesSubnet 'Microsoft.Network/networkSecurityGroups@2024-05-01' = { - name: 'nsg-${location}-spoke-resources' - location: location - properties: { - securityRules: [ - { - name: 'AllowBastionRdpFromHub' - properties: { - protocol: 'Tcp' - sourcePortRange: '*' - sourceAddressPrefix: hub.outputs.hubBastionSubnetAddressPrefix - destinationPortRanges: [ - '3389' - ] - destinationAddressPrefix: 'VirtualNetwork' - access: 'Allow' - priority: 200 - direction: 'Inbound' - } - } - { - name: 'AllowBastionSshFromHub' - properties: { - protocol: 'Tcp' - sourcePortRange: '*' - sourceAddressPrefix: hub.outputs.hubBastionSubnetAddressPrefix - destinationPortRanges: [ - '22' - ] - destinationAddressPrefix: 'VirtualNetwork' - access: 'Allow' - priority: 210 - direction: 'Inbound' - } - } - { - name: 'DenyAllInBound' - properties: { - protocol: '*' - sourcePortRange: '*' - sourceAddressPrefix: '*' - destinationPortRange: '*' - destinationAddressPrefix: '*' - access: 'Deny' - priority: 1000 - direction: 'Inbound' - } - } - // for demonstration purposes, this rule will be overridden by the 'AlwaysAllow' AVNM admin rule r-allowsql-${location} - { - name: 'DenyOutboundAzureSQL' - properties: { - protocol: '*' - sourcePortRange: '*' - sourceAddressPrefix: '*' - destinationPortRange: '*' - destinationAddressPrefix: 'Sql' - access: 'Deny' - priority: 300 - direction: 'Outbound' - } - } - ] - } -} - -@description('NSG on the Private Link subnet (just using a common one for all as an example, but usually would be based on the specific needs of the spoke).') -resource nsgPrivateLinkEndpointsSubnet 'Microsoft.Network/networkSecurityGroups@2024-05-01' = { - name: 'nsg-${location}-spoke-privatelinkendpoint' - location: location - properties: { - securityRules: [ - { - name: 'AllowAll443InFromVnet' - properties: { - protocol: 'Tcp' - sourcePortRange: '*' - sourceAddressPrefix: 'VirtualNetwork' - destinationPortRange: '443' - destinationAddressPrefix: 'VirtualNetwork' - access: 'Allow' - priority: 100 - direction: 'Inbound' - } - } - { - name: 'DenyAllInbound' - properties: { - protocol: '*' - sourcePortRange: '*' - sourceAddressPrefix: '*' - destinationPortRange: '*' - destinationAddressPrefix: '*' - access: 'Deny' - priority: 1000 - direction: 'Inbound' - } - } - { - name: 'DenyAllOutbound' - properties: { - protocol: '*' - sourcePortRange: '*' - sourceAddressPrefix: '*' - destinationPortRange: '*' - destinationAddressPrefix: '*' - access: 'Deny' - priority: 1000 - direction: 'Outbound' - } - } - ] - } -} - -/*** RESOURCES (SPOKE ONE) ***/ -module spokenonprod1 'modules/spoke.bicep' = { - name: 'spokenonprod1' - // hub vnets must be deployed before spoke vnets for nsg rules. Implicit dependency. - scope: resourceGroup() - params: { - location: location - deployVirtualMachines: deployVirtualMachines - adminUsername: adminUsername - adminPassword: adminPassword - routeTableId: routeNextHopToFirewall.id - spokeName: 'nonprod1' - spokeVnetPrefix: '10.100.0.0/22' - logAnalyticsWorkspaceId: hub.outputs.logAnalyticsWorkspaceId - nsgResourcesSubnetId: nsgResourcesSubnet.id - nsgPrivateLinkEndpointsSubnetId: nsgPrivateLinkEndpointsSubnet.id - } -} - -/*** RESOURCES (SPOKE TWO) ***/ -module spokenonprod2 'modules/spoke.bicep' = { - name: 'spokenonprod2' - // hub vnets must be deployed before spoke vnets for nsg rules. Implicit dependency. - scope: resourceGroup() - params: { - location: location - deployVirtualMachines: deployVirtualMachines - adminUsername: adminUsername - adminPassword: adminPassword - routeTableId: routeNextHopToFirewall.id - spokeName: 'nonprod2' - spokeVnetPrefix: '10.101.0.0/22' - logAnalyticsWorkspaceId: hub.outputs.logAnalyticsWorkspaceId - nsgResourcesSubnetId: nsgResourcesSubnet.id - nsgPrivateLinkEndpointsSubnetId: nsgPrivateLinkEndpointsSubnet.id - } -} - -/*** RESOURCES (SPOKE THREE) ***/ -module spokeprod1 'modules/spoke.bicep' = { - name: 'spokeprod1' - // hub vnets must be deployed before spoke vnets for nsg rules. Implicit dependency. - scope: resourceGroup() - params: { - location: location - deployVirtualMachines: deployVirtualMachines - adminUsername: adminUsername - adminPassword: adminPassword - routeTableId: routeNextHopToFirewall.id - spokeName: 'prod1' - spokeVnetPrefix: '10.200.0.0/22' - logAnalyticsWorkspaceId: hub.outputs.logAnalyticsWorkspaceId - nsgResourcesSubnetId: nsgResourcesSubnet.id - nsgPrivateLinkEndpointsSubnetId: nsgPrivateLinkEndpointsSubnet.id - } -} - -/*** RESOURCES (SPOKE FOUR) ***/ -module spokeprod2 'modules/spoke.bicep' = { - name: 'spokeprod2' - // hub vnets must be deployed before spoke vnets for nsg rules. Implicit dependency. - scope: resourceGroup() - params: { - location: location - deployVirtualMachines: deployVirtualMachines - adminUsername: adminUsername - adminPassword: adminPassword - routeTableId: routeNextHopToFirewall.id - spokeName: 'prod2' - spokeVnetPrefix: '10.201.0.0/22' - logAnalyticsWorkspaceId: hub.outputs.logAnalyticsWorkspaceId - nsgResourcesSubnetId: nsgResourcesSubnet.id - nsgPrivateLinkEndpointsSubnetId: nsgPrivateLinkEndpointsSubnet.id - } -} - -/*** AZURE VIRTUAL NETWORK MANAGER RESOURCES ***/ -module avnm 'modules/avnm.bicep' = { - name: 'avnm' - scope: resourceGroup() - params: { - location: location - hubVnetId: hub.outputs.hubVnetId - nonProdNetworkGroupMembers: [ - spokenonprod1.outputs.vnetId - spokenonprod2.outputs.vnetId - ] - prodNetworkGroupMembers: [ - spokeprod1.outputs.vnetId - spokeprod2.outputs.vnetId - ] - deployVpnGateway: deployVpnGateway - deployDefaultDenySecurityAdminRules: deployDefaultDenySecurityAdminRules - } -} diff --git a/solutions/azure-hub-spoke-connected-group/bicep/modules/avnm.bicep b/solutions/azure-hub-spoke-connected-group/bicep/modules/avnm.bicep deleted file mode 100644 index 3f9a3c27..00000000 --- a/solutions/azure-hub-spoke-connected-group/bicep/modules/avnm.bicep +++ /dev/null @@ -1,379 +0,0 @@ -param location string -param prodNetworkGroupMembers array -param nonProdNetworkGroupMembers array -param hubVnetId string -param deployVpnGateway bool -param deployDefaultDenySecurityAdminRules bool - -@description('This is the Azure Virtual Network Manager which will be used to implement the connected group for spoke-to-spoke connectivity.') -resource networkManager 'Microsoft.Network/networkManagers@2024-05-01' = { - name: 'avnm-${location}' - location: location - properties: { - networkManagerScopeAccesses: [ - 'Connectivity' - 'SecurityAdmin' - ] - networkManagerScopes: { - subscriptions: [ - '/subscriptions/${subscription().subscriptionId}' - ] - managementGroups: [] - } - } -} - - // static network group membership is used to avoid potential conflict in the test environment. - // for production deployments, consider using Azure Policy to dynamically bring VNETs under - // AVNM management. see https://learn.microsoft.com/azure/virtual-network-manager/concept-azure-policy-integration - @description('This is the static network group for the production spoke VNETs.') - resource networkGroupProd 'Microsoft.Network/networkManagers/networkGroups@2024-05-01' = { - name: 'ng-${location}-spokes-prod' - parent: networkManager - properties: { - description: 'Prod Spoke VNETs Network Group' - } - resource staticMembersSpokeOne 'staticMembers@2022-05-01' = { - name: 'sm-${location}-1' - properties: { - resourceId: prodNetworkGroupMembers[0] - } - } - resource staticMembersSpokeTwo 'staticMembers@2022-05-01' = { - name: 'sm-${location}-2' - properties: { - resourceId: prodNetworkGroupMembers[1] - } - } - } - @description('This is the static network group for the non-production spoke VNETs.') - resource networkGroupNonProd 'Microsoft.Network/networkManagers/networkGroups@2024-05-01' = { - name: 'ng-${location}-spokes-nonprod' - parent: networkManager - properties: { - description: 'Non-prod Spoke VNETs Network Group' - } - resource staticMembersSpokeOne 'staticMembers@2022-05-01' = { - name: 'sm-${location}-1' - properties: { - resourceId: nonProdNetworkGroupMembers[0] - } - } - resource staticMembersSpokeTwo 'staticMembers@2022-05-01' = { - name: 'sm-${location}-2' - properties: { - resourceId: nonProdNetworkGroupMembers[1] - } - } - } - @description('This is the static network group for all VNETs.') - resource networkGroupAll 'Microsoft.Network/networkManagers/networkGroups@2024-05-01' = { - name: 'ng-${location}-all' - parent: networkManager - properties: { - description: 'All VNETs Network Group (for Security Configurations)' - } - resource staticMembers1 'staticMembers@2022-05-01' = { - name: 'sm-${location}-0' - properties: { - resourceId: prodNetworkGroupMembers[0] - } - } - resource staticMembers2 'staticMembers@2022-05-01' = { - name: 'sm-${location}-1' - properties: { - resourceId: prodNetworkGroupMembers[1] - } - } - resource staticMembers3 'staticMembers@2022-05-01' = { - name: 'sm-${location}-2' - properties: { - resourceId: nonProdNetworkGroupMembers[0] - } - } - resource staticMembers4 'staticMembers@2022-05-01' = { - name: 'sm-${location}-3' - properties: { - resourceId: nonProdNetworkGroupMembers[1] - } - } - resource staticMembers5 'staticMembers@2022-05-01' = { - name: 'sm-${location}-4' - properties: { - resourceId: hubVnetId - } - } - } - -@description('This connectivity configuration defines the connectivity between the spokes.') -resource connectivityConfigurationNonProd 'Microsoft.Network/networkManagers/connectivityConfigurations@2024-05-01' = { - name: 'cc-${location}-spokesnonprod' - parent: networkManager - dependsOn: [ - networkGroupNonProd::staticMembersSpokeOne - networkGroupNonProd::staticMembersSpokeTwo - ] - properties: { - description: 'Non-prod poke-to-spoke connectivity configuration' - appliesToGroups: [ - { - networkGroupId: networkGroupNonProd.id - isGlobal: 'False' - useHubGateway: string(deployVpnGateway) - groupConnectivity: 'DirectlyConnected' - } - ] - connectivityTopology: 'HubAndSpoke' - deleteExistingPeering: 'True' - hubs: [ - { - resourceId: hubVnetId - resourceType: 'Microsoft.Network/virtualNetworks' - } - ] - isGlobal: 'False' - } -} - -@description('This connectivity configuration defines the connectivity between the spokes.') -resource connectivityConfigurationProd 'Microsoft.Network/networkManagers/connectivityConfigurations@2024-05-01' = { - name: 'cc-${location}-spokesprod' - parent: networkManager - dependsOn: [ - networkGroupProd::staticMembersSpokeOne - networkGroupProd::staticMembersSpokeTwo - ] - properties: { - description: 'Prod spoke-to-spoke connectivity configuration (through hub)' - appliesToGroups: [ - { - networkGroupId: networkGroupProd.id - isGlobal: 'False' - useHubGateway: string(deployVpnGateway) - groupConnectivity: 'None' - } - ] - connectivityTopology: 'HubAndSpoke' - deleteExistingPeering: 'True' - hubs: [ - { - resourceId: hubVnetId - resourceType: 'Microsoft.Network/virtualNetworks' - } - ] - isGlobal: 'False' - } -} - -@description('This user assigned identity is used by the Deployment Script resource to interact with Azure resources.') -resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2015-08-31-PREVIEW' = { - name: 'uai-${location}' - location: location -} - -@description('This role assignment grants the user assigned identity the Contributor role on the resource group.') -resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(resourceGroup().id, userAssignedIdentity.name) - properties: { - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') // Contributor: b24988ac-6180-42a0-ab88-20f7382dd24c - principalId: userAssignedIdentity.properties.principalId - principalType: 'ServicePrincipal' - } -} - -@description('This is the securityadmin configuration assigned to the AVNM') -resource securityConfig 'Microsoft.Network/networkManagers/securityAdminConfigurations@2024-05-01' = { - name: 'sg-${location}' - parent: networkManager - properties: { - applyOnNetworkIntentPolicyBasedServices: [ 'None' ] - description: 'Security Group for AVNM' - } -} - -@description('This is the rules collection for the security admin config assigned to the AVNM') -resource rulesCollection 'Microsoft.Network/networkManagers/securityAdminConfigurations/ruleCollections@2024-05-01' = { - name: 'rc-${location}' - parent: securityConfig - properties: { - appliesToGroups: [ - { - networkGroupId: networkGroupAll.id - } - ] - } -} - -@description('This example rule contains all denied inbound TCP ports') -resource rule1 'Microsoft.Network/networkManagers/securityAdminConfigurations/ruleCollections/rules@2024-05-01' = if (deployDefaultDenySecurityAdminRules) { - name: 'r-tcp-${location}' - kind: 'Custom' - parent: rulesCollection - properties: { - access: 'Deny' - description: 'Inbound TCP Deny Example Rule' - destinationPortRanges: [ '20', '21', '22', '23', '69', '119', '161', '445', '512', '514', '873', '3389', '5800', '5900' ] - destinations: [ - { - addressPrefix: '*' - addressPrefixType: 'IPPrefix' - } - ] - direction: 'Inbound' - priority: 100 - protocol: 'TCP' - sourcePortRanges: [ '0-65535' ] - sources: [ - { - addressPrefix: '*' - addressPrefixType: 'IPPrefix' - } - ] - } -} - -@description('This example rule contains all denied inbound TCP or UDP ports') -resource rule2 'Microsoft.Network/networkManagers/securityAdminConfigurations/ruleCollections/rules@2024-05-01' = { - name: 'r-tcp-udp-${location}' - kind: 'Custom' - parent: rulesCollection - properties: { - access: 'Deny' - description: 'Inbound TCP/UDP Deny Example Rule' - destinationPortRanges: [ '11', '135', '162', '593', '2049' ] - destinations: [ - { - addressPrefix: '*' - addressPrefixType: 'IPPrefix' - } - ] - direction: 'Inbound' - priority: 101 - protocol: 'TCP,UDP' - sourcePortRanges: [ '0-65535' ] - sources: [ - { - addressPrefix: '*' - addressPrefixType: 'IPPrefix' - } - ] - } -} - -@description('This example rule contains all denied inbound UDP ports') -resource rule3 'Microsoft.Network/networkManagers/securityAdminConfigurations/ruleCollections/rules@2024-05-01' = { - name: 'r-udp-${location}' - kind: 'Custom' - parent: rulesCollection - properties: { - access: 'Deny' - description: 'Inbound UDP Deny Example Rule' - destinationPortRanges: [ '69', '11211' ] - destinations: [ - { - addressPrefix: '*' - addressPrefixType: 'IPPrefix' - } - ] - direction: 'Inbound' - priority: 102 - protocol: 'UDP' - sourcePortRanges: [ '0-65535' ] - sources: [ - { - addressPrefix: '*' - addressPrefixType: 'IPPrefix' - } - ] - } -} - -@description('This example rule always allows outbound traffic to Microsoft Entra ID, overriding NSG outbound restrictions') -resource rule4 'Microsoft.Network/networkManagers/securityAdminConfigurations/ruleCollections/rules@2024-05-01' = { - name: 'r-alwaysallow-${location}' - kind: 'Custom' - parent: rulesCollection - properties: { - access: 'AlwaysAllow' - description: 'Always allow outbound traffic to Microsoft Entra ID' - destinationPortRanges: [ '0-65535' ] - destinations: [ - { - addressPrefix: 'AzureActiveDirectory' - addressPrefixType: 'ServiceTag' - } - ] - direction: 'Outbound' - priority: 103 - protocol: 'Any' - sourcePortRanges: [ '0-65535' ] - sources: [ - { - addressPrefix: '*' - addressPrefixType: 'IPPrefix' - } - ] - } -} - -@description('This example rule allows outbound traffic to Azure SQL, unless an NSG in the path denies it') -resource rule5 'Microsoft.Network/networkManagers/securityAdminConfigurations/ruleCollections/rules@2024-05-01' = { - name: 'r-allowsql-${location}' - kind: 'Custom' - parent: rulesCollection - properties: { - access: 'Allow' - description: 'Allow outbound traffic to Azure SQL' - destinationPortRanges: [ '0-65535' ] - destinations: [ - { - addressPrefix: 'Sql' - addressPrefixType: 'ServiceTag' - } - ] - direction: 'Outbound' - priority: 104 - protocol: 'Any' - sourcePortRanges: [ '0-65535' ] - sources: [ - { - addressPrefix: '*' - addressPrefixType: 'IPPrefix' - } - ] - } -} - -// -// In order to deploy a Connectivity or Security configruation, the /commit endpoint must be called or a Deployment created in the Portal. -// This DeploymentScript resource executes a PowerShell script which calls the /commit endpoint and monitors the status of the deployment. -// -module deploymentScriptConnectivityConfigs './avnmDeploymentScript.bicep' = { - name: 'ds-${location}-connectivityconfigs' - dependsOn: [ - roleAssignment - ] - params: { - location: location - userAssignedIdentityId: userAssignedIdentity.id - configurationIds: '${connectivityConfigurationProd.id},${connectivityConfigurationNonProd.id}' // each configuration separated by a comma - configType: 'Connectivity' - networkManagerName: networkManager.name - deploymentScriptName: 'ds-${location}-connectivityconfigs' - } -} - -module deploymentScriptSecurityConfigs './avnmDeploymentScript.bicep' = { - name: 'ds-${location}-securityconfigs' - dependsOn: [ - roleAssignment - ] - params: { - location: location - userAssignedIdentityId: userAssignedIdentity.id - configurationIds: securityConfig.id // each configuration separated by a semicolon - configType: 'SecurityAdmin' - networkManagerName: networkManager.name - deploymentScriptName: 'ds-${location}-securityconfigs' - } -} diff --git a/solutions/azure-hub-spoke-connected-group/bicep/modules/avnmDeploymentScript.bicep b/solutions/azure-hub-spoke-connected-group/bicep/modules/avnmDeploymentScript.bicep deleted file mode 100644 index bd363f09..00000000 --- a/solutions/azure-hub-spoke-connected-group/bicep/modules/avnmDeploymentScript.bicep +++ /dev/null @@ -1,74 +0,0 @@ -param location string -param userAssignedIdentityId string -param networkManagerName string -param configurationIds string -param deploymentScriptName string -@allowed([ - 'Connectivity' - 'SecurityAdmin' -]) -param configType string - -// the commit action is idempotent, so re-running the deployment will not cause any issues -@description('Create a Deployment Script resource to perform the commit/deployment of the Network Manager connectivity configuration.') -resource deploymentScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = { - name: deploymentScriptName - location: location - kind: 'AzurePowerShell' - identity: { - type: 'UserAssigned' - userAssignedIdentities: { - '${userAssignedIdentityId}': {} - } - } - properties: { - azPowerShellVersion: '11.0' - retentionInterval: 'PT1H' - timeout: 'PT1H' - arguments: '-networkManagerName "${networkManagerName}" -targetLocations ${location} -configIds ${configurationIds} -subscriptionId ${subscription().subscriptionId} -configType ${configType} -resourceGroupName ${resourceGroup().name}' - scriptContent: ''' - param ( - # AVNM subscription id - [parameter(mandatory=$true)][string]$subscriptionId, - - # AVNM resource name - [parameter(mandatory=$true)][string]$networkManagerName, - - # string with comma-separated list of config ids to deploy. ids must be of the same config type - [parameter(mandatory=$true)][string[]]$configIds, - - # string with comma-separated list of deployment target regions - [parameter(mandatory=$true)][string[]]$targetLocations, - - # configuration type to deploy. must be either connecticity or securityadmin - [parameter(mandatory=$true)][ValidateSet('Connectivity','SecurityAdmin')][string]$configType, - - # AVNM resource group name - [parameter(mandatory=$true)][string]$resourceGroupName - ) - - $null = Login-AzAccount -Identity -Subscription $subscriptionId - - [System.Collections.Generic.List[string]]$configIdList = @() - $configIdList.addRange($configIds) - [System.Collections.Generic.List[string]]$targetLocationList = @() # target locations for deployment - $targetLocationList.addRange($targetLocations) - - $deployment = @{ - Name = $networkManagerName - ResourceGroupName = $resourceGroupName - ConfigurationId = $configIdList - TargetLocation = $targetLocationList - CommitType = $configType - } - - try { - Deploy-AzNetworkManagerCommit @deployment -ErrorAction Stop - } - catch { - Write-Error "Deployment failed with error: $_" - exit 1 - } - ''' - } -} diff --git a/solutions/azure-hub-spoke-connected-group/bicep/modules/hub.bicep b/solutions/azure-hub-spoke-connected-group/bicep/modules/hub.bicep deleted file mode 100644 index 0c6d269f..00000000 --- a/solutions/azure-hub-spoke-connected-group/bicep/modules/hub.bicep +++ /dev/null @@ -1,671 +0,0 @@ -param location string -param deployAzureBastion bool -param deployVpnGateway bool -param deployVirtualMachines bool - -@description('This Log Analyics Workspace stores logs from the regional hub network, its spokes, and other related resources. Workspaces are regional resource, as such there would be one workspace per hub (region)') -resource laHub 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { - name: 'la-hub-${location}' - location: location - properties: { - sku: { - name: 'PerGB2018' - } - retentionInDays: 90 - forceCmkForQuery: false - publicNetworkAccessForIngestion: 'Enabled' - publicNetworkAccessForQuery: 'Enabled' - features: { - disableLocalAuth: false - enableLogAccessUsingOnlyResourcePermissions: true - } - workspaceCapping: { - dailyQuotaGb: -1 - } - } -} - -resource laHub_diagnosticsSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { - name: 'to-hub-la' - scope: laHub - properties: { - workspaceId: laHub.id - logs: [ - { - categoryGroup: 'allLogs' - enabled: true - } - ] - metrics: [ - { - category: 'AllMetrics' - enabled: true - } - ] - } -} - -@description('The NSG around the Azure Bastion subnet. Source: https://learn.microsoft.com/azure/bastion/bastion-nsg') -resource nsgBastionSubnet 'Microsoft.Network/networkSecurityGroups@2024-05-01' = { - name: 'nsg-${location}-bastion' - location: location - properties: { - securityRules: [ - { - name: 'AllowWebExperienceInbound' - properties: { - description: 'Allow our users in. Update this to be as restrictive as possible.' - protocol: 'Tcp' - sourcePortRange: '*' - destinationPortRange: '443' - sourceAddressPrefix: 'Internet' - destinationAddressPrefix: '*' - access: 'Allow' - priority: 100 - direction: 'Inbound' - } - } - { - name: 'AllowControlPlaneInbound' - properties: { - description: 'Service Requirement. Allow control plane access. Regional Tag not yet supported.' - protocol: 'Tcp' - sourcePortRange: '*' - destinationPortRange: '443' - sourceAddressPrefix: 'GatewayManager' - destinationAddressPrefix: '*' - access: 'Allow' - priority: 110 - direction: 'Inbound' - } - } - { - name: 'AllowHealthProbesInbound' - properties: { - description: 'Service Requirement. Allow Health Probes.' - protocol: 'Tcp' - sourcePortRange: '*' - destinationPortRange: '443' - sourceAddressPrefix: 'AzureLoadBalancer' - destinationAddressPrefix: '*' - access: 'Allow' - priority: 120 - direction: 'Inbound' - } - } - { - name: 'AllowBastionHostToHostInbound' - properties: { - description: 'Service Requirement. Allow Required Host to Host Communication.' - protocol: '*' - sourcePortRange: '*' - destinationPortRanges: [ - '8080' - '5701' - ] - sourceAddressPrefix: 'VirtualNetwork' - destinationAddressPrefix: 'VirtualNetwork' - access: 'Allow' - priority: 130 - direction: 'Inbound' - } - } - { - name: 'DenyAllInbound' - properties: { - description: 'No further inbound traffic allowed.' - protocol: '*' - sourcePortRange: '*' - destinationPortRange: '*' - sourceAddressPrefix: '*' - destinationAddressPrefix: '*' - access: 'Deny' - priority: 1000 - direction: 'Inbound' - } - } - { - name: 'AllowSshToVnetOutbound' - properties: { - description: 'Allow SSH out to the virtual network' - protocol: 'Tcp' - sourcePortRange: '*' - sourceAddressPrefix: '*' - destinationPortRange: '22' - destinationAddressPrefix: 'VirtualNetwork' - access: 'Allow' - priority: 100 - direction: 'Outbound' - } - } - { - name: 'AllowRdpToVnetOutbound' - properties: { - description: 'Allow RDP out to the virtual network' - protocol: 'Tcp' - sourcePortRange: '*' - sourceAddressPrefix: '*' - destinationPortRange: '3389' - destinationAddressPrefix: 'VirtualNetwork' - access: 'Allow' - priority: 110 - direction: 'Outbound' - } - } - { - name: 'AllowControlPlaneOutbound' - properties: { - description: 'Required for control plane outbound. Regional prefix not yet supported' - protocol: 'Tcp' - sourcePortRange: '*' - sourceAddressPrefix: '*' - destinationPortRange: '443' - destinationAddressPrefix: 'AzureCloud' - access: 'Allow' - priority: 120 - direction: 'Outbound' - } - } - { - name: 'AllowBastionHostToHostOutbound' - properties: { - description: 'Service Requirement. Allow Required Host to Host Communication.' - protocol: '*' - sourcePortRange: '*' - sourceAddressPrefix: 'VirtualNetwork' - destinationPortRanges: [ - '8080' - '5701' - ] - destinationAddressPrefix: 'VirtualNetwork' - access: 'Allow' - priority: 130 - direction: 'Outbound' - } - } - { - name: 'AllowBastionCertificateValidationOutbound' - properties: { - description: 'Service Requirement. Allow Required Session and Certificate Validation.' - protocol: '*' - sourcePortRange: '*' - sourceAddressPrefix: '*' - destinationPortRange: '80' - destinationAddressPrefix: 'Internet' - access: 'Allow' - priority: 140 - direction: 'Outbound' - } - } - { - name: 'DenyAllOutbound' - properties: { - description: 'No further outbound traffic allowed.' - protocol: '*' - sourcePortRange: '*' - destinationPortRange: '*' - sourceAddressPrefix: '*' - destinationAddressPrefix: '*' - access: 'Deny' - priority: 1000 - direction: 'Outbound' - } - } - ] - } -} - -resource nsgBastionSubnet_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { - scope: nsgBastionSubnet - name: 'to-hub-la' - properties: { - workspaceId: laHub.id - logs: [ - { - categoryGroup: 'allLogs' - enabled: true - } - ] - } -} - -@description('The regional hub network.') -resource vnetHub 'Microsoft.Network/virtualNetworks@2024-05-01' = { - name: 'vnet-${location}-hub' - location: location - properties: { - addressSpace: { - addressPrefixes: [ - '10.0.0.0/22' - ] - } - subnets: [ - { - name: 'AzureBastionSubnet' - properties: { - addressPrefix: '10.0.1.0/26' - defaultOutboundAccess: true - networkSecurityGroup: { - id: nsgBastionSubnet.id - } - } - } - { - name: 'GatewaySubnet' - properties: { - addressPrefix: '10.0.2.0/27' - defaultOutboundAccess: false - } - } - { - name: 'AzureFirewallSubnet' - properties: { - addressPrefix: '10.0.3.0/26' - defaultOutboundAccess: false - } - } - ] - } - - resource azureBastionSubnet 'subnets' existing = { - name: 'AzureBastionSubnet' - } - - resource gatewaySubnet 'subnets' existing = { - name: 'GatewaySubnet' - } - - resource azureFirewallSubnet 'subnets' existing = { - name: 'AzureFirewallSubnet' - } -} - -resource vnetHub_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { - name: 'to-hub-la' - scope: vnetHub - properties: { - workspaceId: laHub.id - metrics: [ - { - category: 'AllMetrics' - enabled: true - } - ] - } -} - -// Allocate three IP addresses to the firewall -var numFirewallIpAddressesToAssign = 3 -resource pipsAzureFirewall 'Microsoft.Network/publicIPAddresses@2024-05-01' = [for i in range(0, numFirewallIpAddressesToAssign): { - name: 'pip-fw-${location}-${padLeft(i, 2, '0')}' - location: location - sku: { - name: 'Standard' - } - zones: [ - '1' - '2' - '3' - ] - properties: { - publicIPAllocationMethod: 'Static' - idleTimeoutInMinutes: 4 - publicIPAddressVersion: 'IPv4' - } -}] - -resource pipsAzureFirewall_diagnosticSetting 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [for i in range(0, numFirewallIpAddressesToAssign): { - name: 'to-hub-la' - scope: pipsAzureFirewall[i] - properties: { - workspaceId: laHub.id - logs: [ - { - categoryGroup: 'allLogs' - enabled: true - } - ] - metrics: [ - { - category: 'AllMetrics' - enabled: true - } - ] - } -}] - -@description('Azure Firewall Policy') -resource fwPolicy 'Microsoft.Network/firewallPolicies@2024-05-01' = { - name: 'fw-policies-${location}' - location: location - properties: { - sku: { - tier: 'Basic' - } - insights: { - isEnabled: true - retentionDays: 30 - logAnalyticsResources: { - defaultWorkspaceId: { - id: laHub.id - } - } - } - intrusionDetection: null // Only valid on Premium tier sku - } - - // This network hub starts out with only supporting external DNS queries. This is only being done for - // simplicity in this deployment and is not guidance, please ensure all firewall rules are aligned with - // your security standards. - resource defaultNetworkRuleCollectionGroup 'ruleCollectionGroups@2024-05-01' = { - name: 'DefaultNetworkRuleCollectionGroup' - properties: { - priority: 200 - ruleCollections: [ - { - ruleCollectionType: 'FirewallPolicyFilterRuleCollection' - name: 'org-wide-allowed' - priority: 100 - action: { - type: 'Allow' - } - rules: [ - { - ruleType: 'NetworkRule' - name: 'DNS' - description: 'Allow DNS outbound (for simplicity, adjust as needed)' - ipProtocols: [ - 'UDP' - ] - sourceAddresses: [ - '*' - ] - sourceIpGroups: [] - destinationAddresses: [ - '*' - ] - destinationIpGroups: [] - destinationFqdns: [] - destinationPorts: [ - '53' - ] - } - ] - } - ] - } - } - - // Network hub starts out with no allowances for appliction rules - resource defaultApplicationRuleCollectionGroup 'ruleCollectionGroups@2024-05-01' = { - name: 'DefaultApplicationRuleCollectionGroup' - dependsOn: [ - defaultNetworkRuleCollectionGroup - ] - properties: { - priority: 300 - ruleCollections: [ - { - ruleCollectionType: 'FirewallPolicyFilterRuleCollection' - name: 'org-wide-allowed' - priority: 100 - action: { - type: 'Allow' - } - rules: deployVirtualMachines ? [ - { - ruleType: 'ApplicationRule' - name: 'WindowsVirtualMachineHealth' - description: 'Supports Windows Updates and Windows Diagnostics' - fqdnTags: [ - 'WindowsDiagnostics' - 'WindowsUpdate' - ] - protocols: [ - { - protocolType: 'Https' - port: 443 - } - ] - sourceAddresses: [ - '10.100.0.0/24' // The subnet that contains the Windows VMs - '10.101.0.0/24' // The subnet that contains the Windows VMs - '10.200.0.0/24' // The subnet that contains the Windows VMs - '10.201.0.0/24' // The subnet that contains the Windows VMs - ] - } - ] : [] - } - ] - } - } -} - -@description('This is the regional Azure Firewall that all regional spoke networks can egress through.') -resource fwHub 'Microsoft.Network/azureFirewalls@2024-05-01' = { - name: 'fw-${location}' - location: location - zones: [ - '1' - '2' - '3' - ] - dependsOn: [ - // This helps prevent multiple PUT updates happening to the firewall causing a CONFLICT race condition - // Ref: https://learn.microsoft.com/azure/firewall-manager/quick-firewall-policy - fwPolicy::defaultApplicationRuleCollectionGroup - fwPolicy::defaultNetworkRuleCollectionGroup - ] - properties: { - sku: { - name: 'AZFW_VNet' - tier: 'Standard' - } - firewallPolicy: { - id: fwPolicy.id - } - ipConfigurations: [for i in range(0, numFirewallIpAddressesToAssign): { - name: pipsAzureFirewall[i].name - properties: { - subnet: (0 == i) ? { - id: vnetHub::azureFirewallSubnet.id - } : null - publicIPAddress: { - id: pipsAzureFirewall[i].id - } - } - }] - } -} - -resource fwHub_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { - name: 'to-hub-la' - scope: fwHub - properties: { - workspaceId: laHub.id - logs: [ - { - categoryGroup: 'allLogs' - enabled: true - } - ] - metrics: [ - { - category: 'AllMetrics' - enabled: true - } - ] - } -} - - -@description('The public IP for the regional hub\'s Azure Bastion service.') -resource pipAzureBastion 'Microsoft.Network/publicIPAddresses@2024-05-01' = if (deployAzureBastion) { - name: 'pip-ab-${location}' - location: location - sku: { - name: 'Standard' - } - zones: [ - '1' - '2' - '3' - ] - properties: { - publicIPAllocationMethod: 'Static' - idleTimeoutInMinutes: 4 - publicIPAddressVersion: 'IPv4' - } -} - -resource pipAzureBastion_diagnosticSetting 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (deployAzureBastion) { - name: 'to-hub-la' - scope: pipAzureBastion - properties: { - workspaceId: laHub.id - logs: [ - { - categoryGroup: 'allLogs' - enabled: true - } - ] - metrics: [ - { - category: 'AllMetrics' - enabled: true - } - ] - } -} - -@description('This regional hub\'s Azure Bastion service. NSGs are configured to allow Bastion to reach any resource subnet in peered spokes.') -resource azureBastion 'Microsoft.Network/bastionHosts@2024-05-01' = if (deployAzureBastion) { - name: 'ab-${location}' - location: location - sku: { - name: 'Basic' - } - properties: { - ipConfigurations: [ - { - name: 'hub-subnet' - properties: { - privateIPAllocationMethod: 'Dynamic' - subnet: { - id: vnetHub::azureBastionSubnet.id - } - publicIPAddress: { - id: pipAzureBastion.id - } - } - } - ] - } -} - -resource azureBastion_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (deployAzureBastion) { - name: 'to-hub-la' - scope: azureBastion - properties: { - workspaceId: laHub.id - logs: [ - { - categoryGroup: 'allLogs' - enabled: true - } - ] - } -} - -@description('The public IPs for the regional VPN gateway. Only deployed if requested.') -resource pipVpnGateway 'Microsoft.Network/publicIPAddresses@2024-05-01' = if (deployVpnGateway) { - name: 'pip-vgw-${location}' - location: location - sku: { - name: 'Standard' - } - zones: [ - '1' - '2' - '3' - ] - properties: { - publicIPAllocationMethod: 'Static' - idleTimeoutInMinutes: 4 - publicIPAddressVersion: 'IPv4' - } -} - -resource pipVpnGateway_diagnosticSetting 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (deployVpnGateway) { - name: 'to-hub-la' - scope: pipVpnGateway - properties: { - workspaceId: laHub.id - logs: [ - { - categoryGroup: 'allLogs' - enabled: true - } - ] - metrics: [ - { - category: 'AllMetrics' - enabled: true - } - ] - } -} - -@description('The is the regional VPN gateway, configured with basic settings. Only deployed if requested.') -resource vgwHub 'Microsoft.Network/virtualNetworkGateways@2024-05-01' = if (deployVpnGateway) { - name: 'vgw-${location}-hub' - location: location - properties: { - sku: { - name: 'VpnGw2AZ' - tier: 'VpnGw2AZ' - } - gatewayType: 'Vpn' - vpnType: 'RouteBased' - vpnGatewayGeneration: 'Generation2' - ipConfigurations: [ - { - name: 'default' - properties: { - privateIPAllocationMethod: 'Dynamic' - publicIPAddress: { - id: pipVpnGateway.id - } - subnet: { - id: vnetHub::gatewaySubnet.id - } - } - } - ] - } -} - -resource vgwHub_diagnosticSetting 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (deployVpnGateway) { - name: 'to-hub-la' - scope: vgwHub - properties: { - workspaceId: laHub.id - logs: [ - { - categoryGroup: 'allLogs' - enabled: true - } - ] - metrics: [ - { - category: 'AllMetrics' - enabled: true - } - ] - } -} - -output hubBastionSubnetAddressPrefix string = vnetHub::azureBastionSubnet.properties.addressPrefix -output hubVnetId string = vnetHub.id -output firewall object = fwHub -output logAnalyticsWorkspaceId string = laHub.id diff --git a/solutions/azure-hub-spoke-connected-group/bicep/modules/spoke.bicep b/solutions/azure-hub-spoke-connected-group/bicep/modules/spoke.bicep deleted file mode 100644 index 32f7d8b8..00000000 --- a/solutions/azure-hub-spoke-connected-group/bicep/modules/spoke.bicep +++ /dev/null @@ -1,202 +0,0 @@ -param location string -param routeTableId string -param logAnalyticsWorkspaceId string -param deployVirtualMachines bool -param adminUsername string -param spokeName string -param spokeVnetPrefix string -param nsgResourcesSubnetId string -param nsgPrivateLinkEndpointsSubnetId string -@secure() -param adminPassword string - -resource hubNet 'Microsoft.Network/virtualNetworks@2024-05-01' existing = { - name: 'vnet-${location}-hub' - - resource azureBastionSubnet 'subnets' existing = { - name: 'AzureBastionSubnet' - } -} - -resource vnet 'Microsoft.Network/virtualNetworks@2024-05-01' = { - name: 'vnet-${location}-spoke-${spokeName}' - location: location - properties: { - addressSpace: { - addressPrefixes: [ - spokeVnetPrefix - ] - } - subnets: [ - { - name: 'snet-resources' - properties: { - addressPrefix: replace(spokeVnetPrefix, '.0.0/22','.0.0/24') - defaultOutboundAccess: false - privateEndpointNetworkPolicies: 'Disabled' - privateLinkServiceNetworkPolicies: 'Disabled' - networkSecurityGroup: { - id: nsgResourcesSubnetId - } - routeTable: { - id: routeTableId - } - } - } - { - name: 'snet-privatelinkendpoints' - properties: { - addressPrefix: replace(spokeVnetPrefix, '.0.0/22','.1.0/26') - defaultOutboundAccess: false - privateEndpointNetworkPolicies: 'Enabled' - privateLinkServiceNetworkPolicies: 'Enabled' - networkSecurityGroup: { - id: nsgPrivateLinkEndpointsSubnetId - } - routeTable: { - id: routeTableId - } - } - } - ] - } - - resource snetResources 'subnets' existing = { - name: 'snet-resources' - } -} - -resource vnet_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { - scope: vnet - name: 'to-hub-la' - properties: { - workspaceId: logAnalyticsWorkspaceId - metrics: [ - { - category: 'AllMetrics' - enabled: true - } - ] - } -} - -@description('The private Network Interface Card for the Windows VM in spoke.') -resource nic 'Microsoft.Network/networkInterfaces@2024-05-01' = if (deployVirtualMachines) { - name: 'nic-vm-${location}-${spokeName}-ubuntu' - location: location - properties: { - ipConfigurations: [ - { - name: 'default' - properties: { - subnet: { - id: vnet::snetResources.id - } - privateIPAllocationMethod: 'Dynamic' - } - } - ] - enableAcceleratedNetworking: true - } -} - -resource nic_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (deployVirtualMachines) { - scope: nic - name: 'to-hub-la' - properties: { - workspaceId: logAnalyticsWorkspaceId - metrics: [ - { - category: 'AllMetrics' - enabled: true - } - ] - } -} - -@description('A basic Ubuntu Linux virtual machine that will be attached to spoke.') -resource vm 'Microsoft.Compute/virtualMachines@2024-11-01' = if (deployVirtualMachines) { - name: 'vm-${location}-spoke-${spokeName}-ubuntu' - location: location - identity: { - // It is required by the Guest Configuration extension. - type: 'SystemAssigned' - } - properties: { - hardwareProfile: { - vmSize: 'Standard_DS1_v2' - } - storageProfile: { - osDisk: { - createOption: 'FromImage' - caching: 'ReadWrite' - managedDisk: { - storageAccountType: 'Standard_LRS' - } - deleteOption: 'Delete' - } - imageReference: { - publisher: 'canonical' - offer: 'ubuntu-24_04-lts' - sku: 'server' - version: 'latest' - } - dataDisks: [] - } - diagnosticsProfile: { - bootDiagnostics: { - enabled: true - storageUri: null - } - } - networkProfile: { - networkInterfaces: [ - { - id: nic.id - properties: { - deleteOption: 'Delete' - primary: true - } - } - ] - } - osProfile: { - computerName: 'examplevm' - adminUsername: adminUsername - adminPassword: adminPassword - linuxConfiguration: { - disablePasswordAuthentication: false - patchSettings: { - //Machines should be configured to periodically check for missing system updates - assessmentMode: 'AutomaticByPlatform' - patchMode: 'AutomaticByPlatform' - } - provisionVMAgent: true - } - } - securityProfile: { - // We recommend enabling encryption at host for virtual machines and virtual machine scale sets to harden security. - encryptionAtHost: false - } - priority: 'Regular' - } -} - -// The Guest Configuration extension supports Azure governance at cloud scale, and can be installed after ensuring that a system-assigned identity is added at the VM level. This enable Azure policies to audit and report on configuration settings inside machines. -@description('Install the Guest Configuration extension for Azure auto-manage machine configuration on top regulatory, security, and operational compliance.') -resource guestConfigExtension 'Microsoft.Compute/virtualMachines/extensions@2024-11-01' = { - parent: vm - name: 'Microsoft.GuestConfiguration' - location: location - properties: { - publisher: 'Microsoft.GuestConfiguration' - type: 'ConfigurationforLinux' // Use 'ConfigurationforWindows' if it's a Windows VM - typeHandlerVersion: '1.0' - autoUpgradeMinorVersion: true - enableAutomaticUpgrade: true - settings: {} - protectedSettings: {} - } -} - -output vnetId string = vnet.id diff --git a/solutions/azure-hub-spoke-connected-group/images/hub-spoke-connected-group.png b/solutions/azure-hub-spoke-connected-group/images/hub-spoke-connected-group.png deleted file mode 100644 index 5d466e9d..00000000 Binary files a/solutions/azure-hub-spoke-connected-group/images/hub-spoke-connected-group.png and /dev/null differ diff --git a/solutions/azure-hub-spoke/README.md b/solutions/azure-hub-spoke/README.md index ef223159..780591e1 100644 --- a/solutions/azure-hub-spoke/README.md +++ b/solutions/azure-hub-spoke/README.md @@ -1,128 +1,3 @@ ---- -page_type: sample -languages: -- azurepowershell -- azurecli -products: - - azure - - azure-firewall - - azure-virtual-network - - azure-bastion - - azure-vpn-gateway -description: This sample deploys Azure virtual networks in a hub and spoke configuration. An Azure Firewall and Bastion host are also deployed. Optionally, a VPN gateway and sample workload (virtual machines) can be deployed. ---- - # Hub and spoke deployment -This sample deploys Azure virtual networks in a hub and spoke configuration. An Azure Firewall and Bastion host are also deployed. Optionally, a VPN gateway and sample workload (virtual machines) can be deployed. - -Where applicable, each resource is configured to send diagnostics to an Azure Log Analytics instance. - -![Hub and spoke architectural diagram.](https://learn.microsoft.com/azure/architecture/reference-architectures/hybrid-networking/images/hub-spoke.png) - -For detailed information, see the Azure Hub and Spoke reference architecture in the Azure Architecture Center: - -> [!div class="nextstepaction"] -> [Hub-spoke network topology in Azure](https://learn.microsoft.com/azure/architecture/reference-architectures/hybrid-networking/hub-spoke) - -## Deploy sample - -Create a resource group for the deployment. - -```bash -LOCATION=eastus2 -RESOURCEGROUP_NAME=rg-hub-spoke-${LOCATION} - -az group create --name ${RESOURCEGROUP_NAME} --location ${LOCATION} - -curl -o main.bicep https://raw.githubusercontent.com/mspnp/samples/main/solutions/azure-hub-spoke/bicep/main.bicep -``` - -> The location for the deployed resources defaults to the location used for the target resource group. This deployment uses availability zones for all resources that support it, as hub networks are usually business critical. This means if the resource group's location does not support availability zones, you must provide an additional parameter to your chosen command below of `location=value` with a value supports availability zones. See [Azure regions with availability zones](https://learn.microsoft.com/azure/availability-zones/az-overview#azure-regions-with-availability-zones). - -**Basic deployment** - -Run the following command to initiate the deployment. If you would like to also deploy this sample with virtual machines and / or an Azure VPN gateway, see the `az deployment group create` examples found later in this document. - -```bash -az deployment group create \ - --resource-group ${RESOURCEGROUP_NAME} \ - --template-file main.bicep -``` - -**Deploy with virtual machines** - -Run the following command to initiate the deployment with a Linux VM deployed to the first spoke network and a Windows VM deployed to the second spoke network. - -| :warning: | This deploys these VMs with basic configuration, they are not Internet facing, but security should always be top of mind. Please update the `adminUsername` and `adminPassword` to a value of your choosing. | -|-----------|:--------------------------| - -```bash -az deployment group create \ - --resource-group ${RESOURCEGROUP_NAME} \ - --template-file main.bicep \ - --parameters deployVirtualMachines=true adminUsername=azureadmin -``` - -**Deploy with VPN gateway** - -Run the following command to initiate the deployment with a virtual network gateway deployed into the hub virtual network. Note, VPN gateways take a significant time to deploy. - -```bash -az deployment group create \ - --resource-group ${RESOURCEGROUP_NAME} \ - --template-file main.bicep \ - --parameters deployVpnGateway=true -``` - -**Deploy with virtual machines and a VPN gateway** - -Run the following command to initiate the deployment with a Linux VM deployed to the first spoke network and a Windows VM deployed to the second spoke network. - -| :warning: | This deploys these VMs with basic configuration, they are not Internet facing, but security should always be top of mind. Please update the `adminUsername` and `adminPassword` to a value of your choosing. | -|-----------|:--------------------------| - -```bash -az deployment group create \ - --resource-group ${RESOURCEGROUP_NAME} \ - --template-file main.bicep \ - --parameters deployVirtualMachines=true adminUsername=azureadmin deployVpnGateway=true -``` - -## Solution deployment parameters - -| Parameter | Type | Description | Default | -|---|---|---|--| -| `location` | string | Deployment location. Location must support availability zones. | `resourceGroup().location` | -| `deployVirtualMachines` | bool | If true, deploys one basic Linux virtual machine to spoke one and one basic Windows virtual machine to spoke two. | `false` | -| `adminUserName` | string | If deploying virtual machines, the admin user name for both VMs. | `azureadmin` | -| `adminPassword` | securestring | If deploying virtual machines, the admin password for both VMs. | `null` | -| `deployVpnGateway` | bool | If true, a virtual network gateway is deployed into the hub network (+30 min deployment). | `false` | - -## Diagnostic configurations - -The following resources are configured to send diagnostic logs to the included Log Analytics workspace. - -- All virtual networks -- All network security groups -- Azure VPN Gateway -- Azure Firewall -- Azure Bastion - -Note, this deployment includes optional basic virtual machines. These are not configured with a Log Analytics workspace, however, can be with the Log Analytics virtual machine extension for [Windows](https://learn.microsoft.com/azure/virtual-machines/extensions/oms-windows) and [Linux](https://learn.microsoft.com/azure/virtual-machines/extensions/oms-linux). - -## Clean up - -```bash -az group delete --name ${RESOURCEGROUP_NAME} --yes -``` - -## Microsoft Open Source Code of Conduct - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). - -Resources: - -- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) -- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) -- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns +The deployment sample that supports the [Hub-spoke network topology in Azure](https://learn.microsoft.com/azure/architecture/reference-architectures/hybrid-networking/hub-spoke) article has moved. You'll find it's new location on GitHub at [Hub and spoke deployment](https://github.com/Azure-Samples/azure-hub-spoke/tree/main/hub-spoke). diff --git a/solutions/azure-hub-spoke/azuredeploy.json b/solutions/azure-hub-spoke/azuredeploy.json deleted file mode 100644 index e79e9428..00000000 --- a/solutions/azure-hub-spoke/azuredeploy.json +++ /dev/null @@ -1,1437 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.41.2.15936", - "templateHash": "492807586996629292" - } - }, - "parameters": { - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "minLength": 6, - "metadata": { - "description": "The location of this regional hub. All resources, including spoke resources, will be deployed to this region. This region must support availability zones." - } - }, - "deployVpnGateway": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Set to true to include a basic VPN Gateway deployment into the hub. Set to false to leave network space for a VPN Gateway, but do not deploy one. Default is false. Note deploying VPN gateways can take significant time." - } - }, - "deployVirtualMachines": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Set to true to include one Windows and one Linux virtual machine for you to experience peering, gateway transit, and bastion access. Default is false." - } - }, - "adminUsername": { - "type": "string", - "defaultValue": "azureadmin", - "minLength": 4, - "maxLength": 20, - "metadata": { - "description": "Username for both the Linux and Windows VM. Must only contain letters, numbers, hyphens, and underscores and may not start with a hyphen or number. Only needed when providing deployVirtualMachines=true." - } - }, - "adminPassword": { - "type": "securestring", - "maxLength": 70, - "metadata": { - "description": "Password for both the Linux and Windows VM. Password must have 3 of the following: 1 lower case character, 1 upper case character, 1 number, and 1 special character. Must be at least 12 characters. Only needed when providing deployVirtualMachines=true." - } - } - }, - "variables": { - "suffix": "[uniqueString(subscription().subscriptionId, resourceGroup().id)]", - "numFirewallIpAddressesToAssign": 3 - }, - "resources": [ - { - "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", - "apiVersion": "2024-07-01", - "name": "[format('{0}/{1}', format('vnet-{0}-hub', parameters('location')), format('to_{0}', format('vnet-{0}-spoke-one', parameters('location'))))]", - "properties": { - "allowForwardedTraffic": false, - "allowGatewayTransit": false, - "allowVirtualNetworkAccess": true, - "useRemoteGateways": false, - "remoteVirtualNetwork": { - "id": "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-spoke-one', parameters('location')))]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-hub', parameters('location')))]", - "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-spoke-one', parameters('location')))]", - "[resourceId('Microsoft.Network/virtualNetworks/virtualNetworkPeerings', format('vnet-{0}-spoke-one', parameters('location')), format('to_{0}', format('vnet-{0}-hub', parameters('location'))))]" - ] - }, - { - "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", - "apiVersion": "2024-07-01", - "name": "[format('{0}/{1}', format('vnet-{0}-hub', parameters('location')), format('to_{0}', format('vnet-{0}-spoke-two', parameters('location'))))]", - "properties": { - "allowForwardedTraffic": false, - "allowGatewayTransit": false, - "allowVirtualNetworkAccess": true, - "useRemoteGateways": false, - "remoteVirtualNetwork": { - "id": "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-spoke-two', parameters('location')))]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-hub', parameters('location')))]", - "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-spoke-two', parameters('location')))]", - "[resourceId('Microsoft.Network/virtualNetworks/virtualNetworkPeerings', format('vnet-{0}-spoke-two', parameters('location')), format('to_{0}', format('vnet-{0}-hub', parameters('location'))))]" - ] - }, - { - "type": "Microsoft.Network/firewallPolicies/ruleCollectionGroups", - "apiVersion": "2024-07-01", - "name": "[format('{0}/{1}', format('fw-policies-{0}', parameters('location')), 'DefaultNetworkRuleCollectionGroup')]", - "properties": { - "priority": 200, - "ruleCollections": [ - { - "ruleCollectionType": "FirewallPolicyFilterRuleCollection", - "name": "org-wide-allowed", - "priority": 100, - "action": { - "type": "Allow" - }, - "rules": [ - { - "ruleType": "NetworkRule", - "name": "DNS", - "description": "Allow DNS outbound (for simplicity, adjust as needed)", - "ipProtocols": [ - "UDP" - ], - "sourceAddresses": [ - "*" - ], - "sourceIpGroups": [], - "destinationAddresses": [ - "*" - ], - "destinationIpGroups": [], - "destinationFqdns": [], - "destinationPorts": [ - "53" - ] - } - ] - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/firewallPolicies', format('fw-policies-{0}', parameters('location')))]" - ] - }, - { - "type": "Microsoft.Network/firewallPolicies/ruleCollectionGroups", - "apiVersion": "2024-07-01", - "name": "[format('{0}/{1}', format('fw-policies-{0}', parameters('location')), 'DefaultApplicationRuleCollectionGroup')]", - "properties": { - "priority": 300, - "ruleCollections": [ - { - "ruleCollectionType": "FirewallPolicyFilterRuleCollection", - "name": "org-wide-allowed", - "priority": 100, - "action": { - "type": "Allow" - }, - "rules": "[if(parameters('deployVirtualMachines'), createArray(createObject('ruleType', 'ApplicationRule', 'name', 'WindowsVirtualMachineHealth', 'description', 'Supports Windows Updates and Windows Diagnostics', 'fqdnTags', createArray('WindowsDiagnostics', 'WindowsUpdate'), 'protocols', createArray(createObject('protocolType', 'Https', 'port', 443)), 'sourceAddresses', createArray('10.200.0.0/24'))), createArray())]" - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/firewallPolicies', format('fw-policies-{0}', parameters('location')))]", - "[resourceId('Microsoft.Network/firewallPolicies/ruleCollectionGroups', format('fw-policies-{0}', parameters('location')), 'DefaultNetworkRuleCollectionGroup')]" - ] - }, - { - "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", - "apiVersion": "2024-07-01", - "name": "[format('{0}/{1}', format('vnet-{0}-spoke-one', parameters('location')), format('to_{0}', format('vnet-{0}-hub', parameters('location'))))]", - "properties": { - "allowForwardedTraffic": false, - "allowGatewayTransit": false, - "allowVirtualNetworkAccess": true, - "useRemoteGateways": false, - "remoteVirtualNetwork": { - "id": "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-hub', parameters('location')))]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-hub', parameters('location')))]", - "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-spoke-one', parameters('location')))]" - ] - }, - { - "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", - "apiVersion": "2024-07-01", - "name": "[format('{0}/{1}', format('vnet-{0}-spoke-two', parameters('location')), format('to_{0}', format('vnet-{0}-hub', parameters('location'))))]", - "properties": { - "allowForwardedTraffic": false, - "allowGatewayTransit": false, - "allowVirtualNetworkAccess": true, - "useRemoteGateways": false, - "remoteVirtualNetwork": { - "id": "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-hub', parameters('location')))]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-hub', parameters('location')))]", - "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-spoke-two', parameters('location')))]" - ] - }, - { - "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2022-10-01", - "name": "[format('la-hub-{0}-{1}', parameters('location'), variables('suffix'))]", - "location": "[parameters('location')]", - "properties": { - "sku": { - "name": "PerGB2018" - }, - "retentionInDays": 90, - "forceCmkForQuery": false, - "publicNetworkAccessForIngestion": "Enabled", - "publicNetworkAccessForQuery": "Enabled", - "features": { - "disableLocalAuth": false, - "enableLogAccessUsingOnlyResourcePermissions": true - }, - "workspaceCapping": { - "dailyQuotaGb": -1 - } - }, - "metadata": { - "description": "This Log Analyics Workspace stores logs from the regional hub network, its spokes, and other related resources. Workspaces are regional resource, as such there would be one workspace per hub (region)" - } - }, - { - "type": "Microsoft.Insights/diagnosticSettings", - "apiVersion": "2021-05-01-preview", - "scope": "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "name": "to-hub-la", - "properties": { - "workspaceId": "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "logs": [ - { - "categoryGroup": "allLogs", - "enabled": true - } - ], - "metrics": [ - { - "category": "AllMetrics", - "enabled": true - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]" - ] - }, - { - "type": "Microsoft.Network/networkSecurityGroups", - "apiVersion": "2024-07-01", - "name": "[format('nsg-{0}-bastion', parameters('location'))]", - "location": "[parameters('location')]", - "properties": { - "securityRules": [ - { - "name": "AllowWebExperienceInbound", - "properties": { - "description": "Allow our users in. Update this to be as restrictive as possible.", - "protocol": "Tcp", - "sourcePortRange": "*", - "destinationPortRange": "443", - "sourceAddressPrefix": "Internet", - "destinationAddressPrefix": "*", - "access": "Allow", - "priority": 100, - "direction": "Inbound" - } - }, - { - "name": "AllowControlPlaneInbound", - "properties": { - "description": "Service Requirement. Allow control plane access. Regional Tag not yet supported.", - "protocol": "Tcp", - "sourcePortRange": "*", - "destinationPortRange": "443", - "sourceAddressPrefix": "GatewayManager", - "destinationAddressPrefix": "*", - "access": "Allow", - "priority": 110, - "direction": "Inbound" - } - }, - { - "name": "AllowHealthProbesInbound", - "properties": { - "description": "Service Requirement. Allow Health Probes.", - "protocol": "Tcp", - "sourcePortRange": "*", - "destinationPortRange": "443", - "sourceAddressPrefix": "AzureLoadBalancer", - "destinationAddressPrefix": "*", - "access": "Allow", - "priority": 120, - "direction": "Inbound" - } - }, - { - "name": "AllowBastionHostToHostInbound", - "properties": { - "description": "Service Requirement. Allow Required Host to Host Communication.", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRanges": [ - "8080", - "5701" - ], - "sourceAddressPrefix": "VirtualNetwork", - "destinationAddressPrefix": "VirtualNetwork", - "access": "Allow", - "priority": 130, - "direction": "Inbound" - } - }, - { - "name": "DenyAllInbound", - "properties": { - "description": "No further inbound traffic allowed.", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "*", - "access": "Deny", - "priority": 1000, - "direction": "Inbound" - } - }, - { - "name": "AllowSshToVnetOutbound", - "properties": { - "description": "Allow SSH out to the virtual network", - "protocol": "Tcp", - "sourcePortRange": "*", - "sourceAddressPrefix": "*", - "destinationPortRange": "22", - "destinationAddressPrefix": "VirtualNetwork", - "access": "Allow", - "priority": 100, - "direction": "Outbound" - } - }, - { - "name": "AllowRdpToVnetOutbound", - "properties": { - "description": "Allow RDP out to the virtual network", - "protocol": "Tcp", - "sourcePortRange": "*", - "sourceAddressPrefix": "*", - "destinationPortRange": "3389", - "destinationAddressPrefix": "VirtualNetwork", - "access": "Allow", - "priority": 110, - "direction": "Outbound" - } - }, - { - "name": "AllowControlPlaneOutbound", - "properties": { - "description": "Required for control plane outbound. Regional prefix not yet supported", - "protocol": "Tcp", - "sourcePortRange": "*", - "sourceAddressPrefix": "*", - "destinationPortRange": "443", - "destinationAddressPrefix": "AzureCloud", - "access": "Allow", - "priority": 120, - "direction": "Outbound" - } - }, - { - "name": "AllowBastionHostToHostOutbound", - "properties": { - "description": "Service Requirement. Allow Required Host to Host Communication.", - "protocol": "*", - "sourcePortRange": "*", - "sourceAddressPrefix": "VirtualNetwork", - "destinationPortRanges": [ - "8080", - "5701" - ], - "destinationAddressPrefix": "VirtualNetwork", - "access": "Allow", - "priority": 130, - "direction": "Outbound" - } - }, - { - "name": "AllowBastionCertificateValidationOutbound", - "properties": { - "description": "Service Requirement. Allow Required Session and Certificate Validation.", - "protocol": "*", - "sourcePortRange": "*", - "sourceAddressPrefix": "*", - "destinationPortRange": "80", - "destinationAddressPrefix": "Internet", - "access": "Allow", - "priority": 140, - "direction": "Outbound" - } - }, - { - "name": "DenyAllOutbound", - "properties": { - "description": "No further outbound traffic allowed.", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "*", - "access": "Deny", - "priority": 1000, - "direction": "Outbound" - } - } - ] - }, - "metadata": { - "description": "The NSG around the Azure Bastion subnet. Source: https://learn.microsoft.com/azure/bastion/bastion-nsg" - } - }, - { - "type": "Microsoft.Insights/diagnosticSettings", - "apiVersion": "2021-05-01-preview", - "scope": "[resourceId('Microsoft.Network/networkSecurityGroups', format('nsg-{0}-bastion', parameters('location')))]", - "name": "to-hub-la", - "properties": { - "workspaceId": "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "logs": [ - { - "categoryGroup": "allLogs", - "enabled": true - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "[resourceId('Microsoft.Network/networkSecurityGroups', format('nsg-{0}-bastion', parameters('location')))]" - ] - }, - { - "type": "Microsoft.Network/ddosProtectionPlans", - "apiVersion": "2021-05-01", - "name": "[format('vnet-{0}-ddos', parameters('location'))]", - "location": "[parameters('location')]" - }, - { - "type": "Microsoft.Network/virtualNetworks", - "apiVersion": "2024-07-01", - "name": "[format('vnet-{0}-hub', parameters('location'))]", - "location": "[parameters('location')]", - "properties": { - "addressSpace": { - "addressPrefixes": [ - "10.0.0.0/22" - ] - }, - "subnets": [ - { - "name": "AzureBastionSubnet", - "properties": { - "addressPrefix": "10.0.1.0/26", - "defaultOutboundAccess": false, - "networkSecurityGroup": { - "id": "[resourceId('Microsoft.Network/networkSecurityGroups', format('nsg-{0}-bastion', parameters('location')))]" - } - } - }, - { - "name": "GatewaySubnet", - "properties": { - "addressPrefix": "10.0.2.0/27", - "defaultOutboundAccess": false - } - }, - { - "name": "AzureFirewallSubnet", - "properties": { - "addressPrefix": "10.0.3.0/26", - "defaultOutboundAccess": false - } - } - ], - "enableDdosProtection": true, - "ddosProtectionPlan": { - "id": "[resourceId('Microsoft.Network/ddosProtectionPlans', format('vnet-{0}-ddos', parameters('location')))]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/ddosProtectionPlans', format('vnet-{0}-ddos', parameters('location')))]", - "[resourceId('Microsoft.Network/networkSecurityGroups', format('nsg-{0}-bastion', parameters('location')))]" - ], - "metadata": { - "description": "The regional hub network." - } - }, - { - "type": "Microsoft.Insights/diagnosticSettings", - "apiVersion": "2021-05-01-preview", - "scope": "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-hub', parameters('location')))]", - "name": "to-hub-la", - "properties": { - "workspaceId": "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "metrics": [ - { - "category": "AllMetrics", - "enabled": true - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-hub', parameters('location')))]" - ] - }, - { - "copy": { - "name": "pipsAzureFirewall", - "count": "[length(range(0, variables('numFirewallIpAddressesToAssign')))]" - }, - "type": "Microsoft.Network/publicIPAddresses", - "apiVersion": "2024-07-01", - "name": "[format('pip-fw-{0}-{1}', parameters('location'), padLeft(range(0, variables('numFirewallIpAddressesToAssign'))[copyIndex()], 2, '0'))]", - "location": "[parameters('location')]", - "sku": { - "name": "Standard" - }, - "zones": [ - "1", - "2", - "3" - ], - "properties": { - "publicIPAllocationMethod": "Static", - "idleTimeoutInMinutes": 4, - "publicIPAddressVersion": "IPv4" - } - }, - { - "copy": { - "name": "pipsAzureFirewall_diagnosticSetting", - "count": "[length(range(0, variables('numFirewallIpAddressesToAssign')))]" - }, - "type": "Microsoft.Insights/diagnosticSettings", - "apiVersion": "2021-05-01-preview", - "scope": "[resourceId('Microsoft.Network/publicIPAddresses', format('pip-fw-{0}-{1}', parameters('location'), padLeft(range(0, variables('numFirewallIpAddressesToAssign'))[range(0, variables('numFirewallIpAddressesToAssign'))[copyIndex()]], 2, '0')))]", - "name": "to-hub-la", - "properties": { - "workspaceId": "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "logs": [ - { - "categoryGroup": "allLogs", - "enabled": true - } - ], - "metrics": [ - { - "category": "AllMetrics", - "enabled": true - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "[resourceId('Microsoft.Network/publicIPAddresses', format('pip-fw-{0}-{1}', parameters('location'), padLeft(range(0, variables('numFirewallIpAddressesToAssign'))[range(0, variables('numFirewallIpAddressesToAssign'))[copyIndex()]], 2, '0')))]" - ] - }, - { - "type": "Microsoft.Network/firewallPolicies", - "apiVersion": "2024-07-01", - "name": "[format('fw-policies-{0}', parameters('location'))]", - "location": "[parameters('location')]", - "properties": { - "sku": { - "tier": "Standard" - }, - "threatIntelMode": "Deny", - "insights": { - "isEnabled": true, - "retentionDays": 30, - "logAnalyticsResources": { - "defaultWorkspaceId": { - "id": "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]" - } - } - }, - "threatIntelWhitelist": { - "fqdns": [], - "ipAddresses": [] - }, - "intrusionDetection": null, - "dnsSettings": { - "servers": [], - "enableProxy": true - } - }, - "dependsOn": [ - "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]" - ], - "metadata": { - "description": "Azure Firewall Policy" - } - }, - { - "type": "Microsoft.Network/azureFirewalls", - "apiVersion": "2024-07-01", - "name": "[format('fw-{0}', parameters('location'))]", - "location": "[parameters('location')]", - "zones": [ - "1", - "2", - "3" - ], - "properties": { - "copy": [ - { - "name": "ipConfigurations", - "count": "[length(range(0, variables('numFirewallIpAddressesToAssign')))]", - "input": { - "name": "[format('pip-fw-{0}-{1}', parameters('location'), padLeft(range(0, variables('numFirewallIpAddressesToAssign'))[range(0, variables('numFirewallIpAddressesToAssign'))[copyIndex('ipConfigurations')]], 2, '0'))]", - "properties": { - "subnet": "[if(equals(0, range(0, variables('numFirewallIpAddressesToAssign'))[copyIndex('ipConfigurations')]), createObject('id', resourceId('Microsoft.Network/virtualNetworks/subnets', format('vnet-{0}-hub', parameters('location')), 'AzureFirewallSubnet')), null())]", - "publicIPAddress": { - "id": "[resourceId('Microsoft.Network/publicIPAddresses', format('pip-fw-{0}-{1}', parameters('location'), padLeft(range(0, variables('numFirewallIpAddressesToAssign'))[range(0, variables('numFirewallIpAddressesToAssign'))[copyIndex('ipConfigurations')]], 2, '0')))]" - } - } - } - } - ], - "sku": { - "name": "AZFW_VNet", - "tier": "Standard" - }, - "firewallPolicy": { - "id": "[resourceId('Microsoft.Network/firewallPolicies', format('fw-policies-{0}', parameters('location')))]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/firewallPolicies', format('fw-policies-{0}', parameters('location')))]", - "[resourceId('Microsoft.Network/firewallPolicies/ruleCollectionGroups', format('fw-policies-{0}', parameters('location')), 'DefaultApplicationRuleCollectionGroup')]", - "[resourceId('Microsoft.Network/firewallPolicies/ruleCollectionGroups', format('fw-policies-{0}', parameters('location')), 'DefaultNetworkRuleCollectionGroup')]", - "pipsAzureFirewall", - "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-hub', parameters('location')))]" - ], - "metadata": { - "description": "This is the regional Azure Firewall that all regional spoke networks can egress through." - } - }, - { - "type": "Microsoft.Insights/diagnosticSettings", - "apiVersion": "2021-05-01-preview", - "scope": "[resourceId('Microsoft.Network/azureFirewalls', format('fw-{0}', parameters('location')))]", - "name": "to-hub-la", - "properties": { - "workspaceId": "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "logs": [ - { - "categoryGroup": "allLogs", - "enabled": true - } - ], - "metrics": [ - { - "category": "AllMetrics", - "enabled": true - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/azureFirewalls', format('fw-{0}', parameters('location')))]", - "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]" - ] - }, - { - "type": "Microsoft.Network/publicIPAddresses", - "apiVersion": "2024-07-01", - "name": "[format('pip-ab-{0}', parameters('location'))]", - "location": "[parameters('location')]", - "sku": { - "name": "Standard" - }, - "zones": [ - "1", - "2", - "3" - ], - "properties": { - "publicIPAllocationMethod": "Static", - "idleTimeoutInMinutes": 4, - "publicIPAddressVersion": "IPv4" - }, - "metadata": { - "description": "The public IP for the regional hub's Azure Bastion service." - } - }, - { - "type": "Microsoft.Insights/diagnosticSettings", - "apiVersion": "2021-05-01-preview", - "scope": "[resourceId('Microsoft.Network/publicIPAddresses', format('pip-ab-{0}', parameters('location')))]", - "name": "to-hub-la", - "properties": { - "workspaceId": "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "logs": [ - { - "categoryGroup": "allLogs", - "enabled": true - } - ], - "metrics": [ - { - "category": "AllMetrics", - "enabled": true - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "[resourceId('Microsoft.Network/publicIPAddresses', format('pip-ab-{0}', parameters('location')))]" - ] - }, - { - "type": "Microsoft.Network/bastionHosts", - "apiVersion": "2024-07-01", - "name": "[format('ab-{0}-{1}', parameters('location'), variables('suffix'))]", - "location": "[parameters('location')]", - "sku": { - "name": "Basic" - }, - "properties": { - "ipConfigurations": [ - { - "name": "hub-subnet", - "properties": { - "privateIPAllocationMethod": "Dynamic", - "subnet": { - "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', format('vnet-{0}-hub', parameters('location')), 'AzureBastionSubnet')]" - }, - "publicIPAddress": { - "id": "[resourceId('Microsoft.Network/publicIPAddresses', format('pip-ab-{0}', parameters('location')))]" - } - } - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/publicIPAddresses', format('pip-ab-{0}', parameters('location')))]", - "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-hub', parameters('location')))]" - ], - "metadata": { - "description": "This regional hub's Azure Bastion service. NSGs are configured to allow Bastion to reach any resource subnet in peered spokes." - } - }, - { - "type": "Microsoft.Insights/diagnosticSettings", - "apiVersion": "2021-05-01-preview", - "scope": "[resourceId('Microsoft.Network/bastionHosts', format('ab-{0}-{1}', parameters('location'), variables('suffix')))]", - "name": "to-hub-la", - "properties": { - "workspaceId": "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "logs": [ - { - "categoryGroup": "allLogs", - "enabled": true - } - ], - "metrics": [ - { - "category": "AllMetrics", - "enabled": true - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/bastionHosts', format('ab-{0}-{1}', parameters('location'), variables('suffix')))]", - "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]" - ] - }, - { - "condition": "[parameters('deployVpnGateway')]", - "type": "Microsoft.Network/publicIPAddresses", - "apiVersion": "2024-07-01", - "name": "[format('pip-vgw-{0}', parameters('location'))]", - "location": "[parameters('location')]", - "sku": { - "name": "Standard" - }, - "zones": [ - "1", - "2", - "3" - ], - "properties": { - "publicIPAllocationMethod": "Static", - "idleTimeoutInMinutes": 4, - "publicIPAddressVersion": "IPv4" - }, - "metadata": { - "description": "The public IPs for the regional VPN gateway. Only deployed if requested." - } - }, - { - "condition": "[parameters('deployVpnGateway')]", - "type": "Microsoft.Insights/diagnosticSettings", - "apiVersion": "2021-05-01-preview", - "scope": "[resourceId('Microsoft.Network/publicIPAddresses', format('pip-vgw-{0}', parameters('location')))]", - "name": "to-hub-la", - "properties": { - "workspaceId": "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "logs": [ - { - "categoryGroup": "allLogs", - "enabled": true - } - ], - "metrics": [ - { - "category": "AllMetrics", - "enabled": true - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "[resourceId('Microsoft.Network/publicIPAddresses', format('pip-vgw-{0}', parameters('location')))]" - ] - }, - { - "condition": "[parameters('deployVpnGateway')]", - "type": "Microsoft.Network/virtualNetworkGateways", - "apiVersion": "2024-07-01", - "name": "[format('vgw-{0}-hub', parameters('location'))]", - "location": "[parameters('location')]", - "properties": { - "sku": { - "name": "VpnGw2AZ", - "tier": "VpnGw2AZ" - }, - "gatewayType": "Vpn", - "vpnType": "RouteBased", - "vpnGatewayGeneration": "Generation2", - "ipConfigurations": [ - { - "name": "default", - "properties": { - "privateIPAllocationMethod": "Dynamic", - "publicIPAddress": { - "id": "[resourceId('Microsoft.Network/publicIPAddresses', format('pip-vgw-{0}', parameters('location')))]" - }, - "subnet": { - "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', format('vnet-{0}-hub', parameters('location')), 'GatewaySubnet')]" - } - } - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/publicIPAddresses', format('pip-vgw-{0}', parameters('location')))]", - "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-hub', parameters('location')))]" - ], - "metadata": { - "description": "The is the regional VPN gateway, configured with basic settings. Only deployed if requested." - } - }, - { - "condition": "[parameters('deployVpnGateway')]", - "type": "Microsoft.Insights/diagnosticSettings", - "apiVersion": "2021-05-01-preview", - "scope": "[resourceId('Microsoft.Network/virtualNetworkGateways', format('vgw-{0}-hub', parameters('location')))]", - "name": "to-hub-la", - "properties": { - "workspaceId": "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "logs": [ - { - "categoryGroup": "allLogs", - "enabled": true - } - ], - "metrics": [ - { - "category": "AllMetrics", - "enabled": true - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "[resourceId('Microsoft.Network/virtualNetworkGateways', format('vgw-{0}-hub', parameters('location')))]" - ] - }, - { - "type": "Microsoft.Network/routeTables", - "apiVersion": "2024-07-01", - "name": "[format('route-to-{0}-hub-fw', parameters('location'))]", - "location": "[parameters('location')]", - "properties": { - "routes": [ - { - "name": "r-nexthop-to-fw", - "properties": { - "nextHopType": "VirtualAppliance", - "addressPrefix": "0.0.0.0/0", - "nextHopIpAddress": "[reference(resourceId('Microsoft.Network/azureFirewalls', format('fw-{0}', parameters('location'))), '2024-07-01').ipConfigurations[0].properties.privateIPAddress]" - } - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/azureFirewalls', format('fw-{0}', parameters('location')))]" - ], - "metadata": { - "description": "Next hop to the regional hub's Azure Firewall" - } - }, - { - "type": "Microsoft.Network/networkSecurityGroups", - "apiVersion": "2024-07-01", - "name": "nsg-spoke-resources", - "location": "[parameters('location')]", - "properties": { - "securityRules": [ - { - "name": "AllowBastionRdpFromHub", - "properties": { - "protocol": "Tcp", - "sourcePortRange": "*", - "sourceAddressPrefix": "[reference(resourceId('Microsoft.Network/virtualNetworks/subnets', format('vnet-{0}-hub', parameters('location')), 'AzureBastionSubnet'), '2024-07-01').addressPrefix]", - "destinationPortRanges": [ - "3389" - ], - "destinationAddressPrefix": "VirtualNetwork", - "access": "Allow", - "priority": 100, - "direction": "Inbound" - } - }, - { - "name": "AllowBastionSshFromHub", - "properties": { - "protocol": "Tcp", - "sourcePortRange": "*", - "sourceAddressPrefix": "[reference(resourceId('Microsoft.Network/virtualNetworks/subnets', format('vnet-{0}-hub', parameters('location')), 'AzureBastionSubnet'), '2024-07-01').addressPrefix]", - "destinationPortRanges": [ - "22" - ], - "destinationAddressPrefix": "VirtualNetwork", - "access": "Allow", - "priority": 110, - "direction": "Inbound" - } - }, - { - "name": "DenyAllInBound", - "properties": { - "protocol": "*", - "sourcePortRange": "*", - "sourceAddressPrefix": "*", - "destinationPortRange": "*", - "destinationAddressPrefix": "*", - "access": "Deny", - "priority": 1000, - "direction": "Inbound" - } - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-hub', parameters('location')))]" - ], - "metadata": { - "description": "NSG on the resource subnet (just using a common one for all as an example, but usually would be based on the specific needs of the spoke)." - } - }, - { - "type": "Microsoft.Insights/diagnosticSettings", - "apiVersion": "2021-05-01-preview", - "scope": "[resourceId('Microsoft.Network/networkSecurityGroups', 'nsg-spoke-resources')]", - "name": "to-hub-la", - "properties": { - "workspaceId": "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "logs": [ - { - "categoryGroup": "allLogs", - "enabled": true - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "[resourceId('Microsoft.Network/networkSecurityGroups', 'nsg-spoke-resources')]" - ] - }, - { - "type": "Microsoft.Network/networkSecurityGroups", - "apiVersion": "2024-07-01", - "name": "nsg-spoke-privatelinkendpoints", - "location": "[parameters('location')]", - "properties": { - "securityRules": [ - { - "name": "AllowAll443InFromVnet", - "properties": { - "protocol": "Tcp", - "sourcePortRange": "*", - "sourceAddressPrefix": "VirtualNetwork", - "destinationPortRange": "443", - "destinationAddressPrefix": "VirtualNetwork", - "access": "Allow", - "priority": 100, - "direction": "Inbound" - } - }, - { - "name": "DenyAllInbound", - "properties": { - "protocol": "*", - "sourcePortRange": "*", - "sourceAddressPrefix": "*", - "destinationPortRange": "*", - "destinationAddressPrefix": "*", - "access": "Deny", - "priority": 1000, - "direction": "Inbound" - } - }, - { - "name": "DenyAllOutbound", - "properties": { - "protocol": "*", - "sourcePortRange": "*", - "sourceAddressPrefix": "*", - "destinationPortRange": "*", - "destinationAddressPrefix": "*", - "access": "Deny", - "priority": 1000, - "direction": "Outbound" - } - } - ] - }, - "metadata": { - "description": "NSG on the Private Link subnet (just using a common one for all as an example, but usually would be based on the specific needs of the spoke)." - } - }, - { - "type": "Microsoft.Insights/diagnosticSettings", - "apiVersion": "2021-05-01-preview", - "scope": "[resourceId('Microsoft.Network/networkSecurityGroups', 'nsg-spoke-privatelinkendpoints')]", - "name": "to-hub-la", - "properties": { - "workspaceId": "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "logs": [ - { - "categoryGroup": "allLogs", - "enabled": true - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "[resourceId('Microsoft.Network/networkSecurityGroups', 'nsg-spoke-privatelinkendpoints')]" - ] - }, - { - "type": "Microsoft.Network/virtualNetworks", - "apiVersion": "2024-07-01", - "name": "[format('vnet-{0}-spoke-one', parameters('location'))]", - "location": "[parameters('location')]", - "properties": { - "addressSpace": { - "addressPrefixes": [ - "10.100.0.0/22" - ] - }, - "subnets": [ - { - "name": "snet-resources", - "properties": { - "addressPrefix": "10.100.0.0/24", - "networkSecurityGroup": { - "id": "[resourceId('Microsoft.Network/networkSecurityGroups', 'nsg-spoke-resources')]" - }, - "privateEndpointNetworkPolicies": "Disabled", - "privateLinkServiceNetworkPolicies": "Disabled", - "defaultOutboundAccess": false, - "routeTable": { - "id": "[resourceId('Microsoft.Network/routeTables', format('route-to-{0}-hub-fw', parameters('location')))]" - } - } - }, - { - "name": "snet-privatelinkendpoints", - "properties": { - "addressPrefix": "10.100.1.0/26", - "networkSecurityGroup": { - "id": "[resourceId('Microsoft.Network/networkSecurityGroups', 'nsg-spoke-privatelinkendpoints')]" - }, - "privateEndpointNetworkPolicies": "Enabled", - "privateLinkServiceNetworkPolicies": "Enabled", - "defaultOutboundAccess": false, - "routeTable": { - "id": "[resourceId('Microsoft.Network/routeTables', format('route-to-{0}-hub-fw', parameters('location')))]" - } - } - } - ], - "enableDdosProtection": true, - "ddosProtectionPlan": { - "id": "[resourceId('Microsoft.Network/ddosProtectionPlans', format('vnet-{0}-ddos', parameters('location')))]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/ddosProtectionPlans', format('vnet-{0}-ddos', parameters('location')))]", - "[resourceId('Microsoft.Network/networkSecurityGroups', 'nsg-spoke-privatelinkendpoints')]", - "[resourceId('Microsoft.Network/networkSecurityGroups', 'nsg-spoke-resources')]", - "[resourceId('Microsoft.Network/routeTables', format('route-to-{0}-hub-fw', parameters('location')))]" - ] - }, - { - "type": "Microsoft.Insights/diagnosticSettings", - "apiVersion": "2021-05-01-preview", - "scope": "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-spoke-one', parameters('location')))]", - "name": "to-hub-la", - "properties": { - "workspaceId": "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "metrics": [ - { - "category": "AllMetrics", - "enabled": true - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-spoke-one', parameters('location')))]" - ] - }, - { - "condition": "[parameters('deployVirtualMachines')]", - "type": "Microsoft.Network/networkInterfaces", - "apiVersion": "2024-07-01", - "name": "[format('nic-vm-{0}-spoke-one-linux', parameters('location'))]", - "location": "[parameters('location')]", - "properties": { - "ipConfigurations": [ - { - "name": "default", - "properties": { - "subnet": { - "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', format('vnet-{0}-spoke-one', parameters('location')), 'snet-resources')]" - }, - "privateIPAllocationMethod": "Dynamic" - } - } - ], - "enableAcceleratedNetworking": true - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-spoke-one', parameters('location')))]" - ], - "metadata": { - "description": "The private Network Interface Card for the linux VM in spoke one." - } - }, - { - "condition": "[parameters('deployVirtualMachines')]", - "type": "Microsoft.Insights/diagnosticSettings", - "apiVersion": "2021-05-01-preview", - "scope": "[resourceId('Microsoft.Network/networkInterfaces', format('nic-vm-{0}-spoke-one-linux', parameters('location')))]", - "name": "to-hub-la", - "properties": { - "workspaceId": "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "metrics": [ - { - "category": "AllMetrics", - "enabled": true - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "[resourceId('Microsoft.Network/networkInterfaces', format('nic-vm-{0}-spoke-one-linux', parameters('location')))]" - ] - }, - { - "condition": "[parameters('deployVirtualMachines')]", - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2023-03-01", - "name": "[format('vm-{0}-spoke-one-linux', parameters('location'))]", - "location": "[parameters('location')]", - "properties": { - "hardwareProfile": { - "vmSize": "Standard_D2ds_v4" - }, - "storageProfile": { - "osDisk": { - "createOption": "FromImage", - "managedDisk": { - "storageAccountType": "Standard_LRS" - }, - "caching": "ReadOnly", - "diffDiskSettings": { - "option": "Local", - "placement": "CacheDisk" - }, - "deleteOption": "Delete" - }, - "imageReference": { - "publisher": "Canonical", - "offer": "0001-com-ubuntu-server-focal", - "sku": "20_04-lts-gen2", - "version": "latest" - }, - "dataDisks": [] - }, - "diagnosticsProfile": { - "bootDiagnostics": { - "enabled": true, - "storageUri": null - } - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[resourceId('Microsoft.Network/networkInterfaces', format('nic-vm-{0}-spoke-one-linux', parameters('location')))]", - "properties": { - "deleteOption": "Delete", - "primary": true - } - } - ] - }, - "osProfile": { - "computerName": "examplevm", - "adminUsername": "[parameters('adminUsername')]", - "adminPassword": "[parameters('adminPassword')]", - "linuxConfiguration": { - "disablePasswordAuthentication": false, - "patchSettings": { - "patchMode": "ImageDefault", - "assessmentMode": "ImageDefault" - } - } - }, - "priority": "Regular" - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/networkInterfaces', format('nic-vm-{0}-spoke-one-linux', parameters('location')))]" - ], - "metadata": { - "description": "A basic Linux virtual machine that will be attached to spoke one." - } - }, - { - "type": "Microsoft.Network/virtualNetworks", - "apiVersion": "2024-07-01", - "name": "[format('vnet-{0}-spoke-two', parameters('location'))]", - "location": "[parameters('location')]", - "properties": { - "addressSpace": { - "addressPrefixes": [ - "10.200.0.0/22" - ] - }, - "subnets": [ - { - "name": "snet-resources", - "properties": { - "addressPrefix": "10.200.0.0/24", - "networkSecurityGroup": { - "id": "[resourceId('Microsoft.Network/networkSecurityGroups', 'nsg-spoke-resources')]" - }, - "privateEndpointNetworkPolicies": "Disabled", - "privateLinkServiceNetworkPolicies": "Disabled", - "defaultOutboundAccess": false, - "routeTable": { - "id": "[resourceId('Microsoft.Network/routeTables', format('route-to-{0}-hub-fw', parameters('location')))]" - } - } - }, - { - "name": "snet-privatelinkendpoints", - "properties": { - "addressPrefix": "10.200.1.0/26", - "networkSecurityGroup": { - "id": "[resourceId('Microsoft.Network/networkSecurityGroups', 'nsg-spoke-privatelinkendpoints')]" - }, - "privateEndpointNetworkPolicies": "Enabled", - "privateLinkServiceNetworkPolicies": "Enabled", - "defaultOutboundAccess": false, - "routeTable": { - "id": "[resourceId('Microsoft.Network/routeTables', format('route-to-{0}-hub-fw', parameters('location')))]" - } - } - } - ], - "enableDdosProtection": true, - "ddosProtectionPlan": { - "id": "[resourceId('Microsoft.Network/ddosProtectionPlans', format('vnet-{0}-ddos', parameters('location')))]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/ddosProtectionPlans', format('vnet-{0}-ddos', parameters('location')))]", - "[resourceId('Microsoft.Network/networkSecurityGroups', 'nsg-spoke-privatelinkendpoints')]", - "[resourceId('Microsoft.Network/networkSecurityGroups', 'nsg-spoke-resources')]", - "[resourceId('Microsoft.Network/routeTables', format('route-to-{0}-hub-fw', parameters('location')))]" - ] - }, - { - "type": "Microsoft.Insights/diagnosticSettings", - "apiVersion": "2021-05-01-preview", - "scope": "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-spoke-two', parameters('location')))]", - "name": "to-hub-la", - "properties": { - "workspaceId": "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "metrics": [ - { - "category": "AllMetrics", - "enabled": true - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-spoke-two', parameters('location')))]" - ] - }, - { - "condition": "[parameters('deployVirtualMachines')]", - "type": "Microsoft.Network/networkInterfaces", - "apiVersion": "2024-07-01", - "name": "[format('nic-vm-{0}-spoke-two-windows', parameters('location'))]", - "location": "[parameters('location')]", - "properties": { - "ipConfigurations": [ - { - "name": "default", - "properties": { - "subnet": { - "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', format('vnet-{0}-spoke-two', parameters('location')), 'snet-resources')]" - }, - "privateIPAllocationMethod": "Dynamic" - } - } - ], - "enableAcceleratedNetworking": true - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/virtualNetworks', format('vnet-{0}-spoke-two', parameters('location')))]" - ], - "metadata": { - "description": "The private Network Interface Card for the Windows VM in spoke two." - } - }, - { - "condition": "[parameters('deployVirtualMachines')]", - "type": "Microsoft.Insights/diagnosticSettings", - "apiVersion": "2021-05-01-preview", - "scope": "[resourceId('Microsoft.Network/networkInterfaces', format('nic-vm-{0}-spoke-two-windows', parameters('location')))]", - "name": "to-hub-la", - "properties": { - "workspaceId": "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "metrics": [ - { - "category": "AllMetrics", - "enabled": true - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.OperationalInsights/workspaces', format('la-hub-{0}-{1}', parameters('location'), variables('suffix')))]", - "[resourceId('Microsoft.Network/networkInterfaces', format('nic-vm-{0}-spoke-two-windows', parameters('location')))]" - ] - }, - { - "condition": "[parameters('deployVirtualMachines')]", - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2023-03-01", - "name": "[format('vm-{0}-spoke-two-windows', parameters('location'))]", - "location": "[parameters('location')]", - "properties": { - "hardwareProfile": { - "vmSize": "Standard_D2s_v3" - }, - "storageProfile": { - "osDisk": { - "createOption": "FromImage", - "caching": "ReadWrite", - "managedDisk": { - "storageAccountType": "Premium_LRS" - }, - "deleteOption": "Delete" - }, - "imageReference": { - "publisher": "MicrosoftWindowsServer", - "offer": "WindowsServer", - "sku": "2025-datacenter-azure-edition", - "version": "latest" - }, - "dataDisks": [] - }, - "diagnosticsProfile": { - "bootDiagnostics": { - "enabled": true, - "storageUri": null - } - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[resourceId('Microsoft.Network/networkInterfaces', format('nic-vm-{0}-spoke-two-windows', parameters('location')))]", - "properties": { - "deleteOption": "Delete", - "primary": true - } - } - ] - }, - "osProfile": { - "computerName": "examplevm", - "adminUsername": "[parameters('adminUsername')]", - "adminPassword": "[parameters('adminPassword')]", - "windowsConfiguration": { - "enableAutomaticUpdates": true, - "provisionVMAgent": true, - "patchSettings": { - "patchMode": "AutomaticByPlatform", - "assessmentMode": "AutomaticByPlatform" - } - } - }, - "priority": "Regular" - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/networkInterfaces', format('nic-vm-{0}-spoke-two-windows', parameters('location')))]" - ], - "metadata": { - "description": "A basic Windows virtual machine that will be attached to spoke two." - } - } - ] -} \ No newline at end of file diff --git a/solutions/azure-hub-spoke/bicep/main.bicep b/solutions/azure-hub-spoke/bicep/main.bicep deleted file mode 100644 index 09ed8427..00000000 --- a/solutions/azure-hub-spoke/bicep/main.bicep +++ /dev/null @@ -1,1281 +0,0 @@ -targetScope = 'resourceGroup' - -/*** PARAMETERS ***/ - -@description('The location of this regional hub. All resources, including spoke resources, will be deployed to this region. This region must support availability zones.') -@minLength(6) -/* Ideally we'd include this limitation, but since we want to default to the resource group's location, we cannot. -@allowed([ - 'brazilsouth' - 'canadacentral' - 'centralus' - 'eastus' - 'eastus2' - 'southcentralus' - 'westus2' - 'westus3' - 'francecentral' - 'germanywestcentral' - 'northeurope' - 'norwayeast' - 'uksouth' - 'westeurope' - 'sweedencentral' - 'switzerlandnorth' - 'uaenorth' - 'southafricanorth' - 'australiaeast' - 'centralindia' - 'japaneast' - 'koreacentral' - 'southeastasia' - 'eastasia' -])*/ -param location string = resourceGroup().location - -@description('Set to true to include a basic VPN Gateway deployment into the hub. Set to false to leave network space for a VPN Gateway, but do not deploy one. Default is false. Note deploying VPN gateways can take significant time.') -param deployVpnGateway bool = false - -@description('Set to true to include one Windows and one Linux virtual machine for you to experience peering, gateway transit, and bastion access. Default is false.') -param deployVirtualMachines bool = false - -@minLength(4) -@maxLength(20) -@description('Username for both the Linux and Windows VM. Must only contain letters, numbers, hyphens, and underscores and may not start with a hyphen or number. Only needed when providing deployVirtualMachines=true.') -param adminUsername string = 'azureadmin' - -@secure() -// @minLength(12) -- Ideally we'd have this here, but to support the multiple varients we will remove it. -@maxLength(70) -@description('Password for both the Linux and Windows VM. Password must have 3 of the following: 1 lower case character, 1 upper case character, 1 number, and 1 special character. Must be at least 12 characters. Only needed when providing deployVirtualMachines=true.') -param adminPassword string - -/*** VARIABLES ***/ - -var suffix = uniqueString(subscription().subscriptionId, resourceGroup().id) - -/*** RESOURCES (HUB) ***/ - -@description('This Log Analyics Workspace stores logs from the regional hub network, its spokes, and other related resources. Workspaces are regional resource, as such there would be one workspace per hub (region)') -resource laHub 'Microsoft.OperationalInsights/workspaces@2022-10-01' = { - name: 'la-hub-${location}-${suffix}' - location: location - properties: { - sku: { - name: 'PerGB2018' - } - retentionInDays: 90 - forceCmkForQuery: false - publicNetworkAccessForIngestion: 'Enabled' - publicNetworkAccessForQuery: 'Enabled' - features: { - disableLocalAuth: false - enableLogAccessUsingOnlyResourcePermissions: true - } - workspaceCapping: { - dailyQuotaGb: -1 - } - } -} - -resource laHub_diagnosticsSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { - name: 'to-hub-la' - scope: laHub - properties: { - workspaceId: laHub.id - logs: [ - { - categoryGroup: 'allLogs' - enabled: true - } - ] - metrics: [ - { - category: 'AllMetrics' - enabled: true - } - ] - } -} - -@description('The NSG around the Azure Bastion subnet. Source: https://learn.microsoft.com/azure/bastion/bastion-nsg') -resource nsgBastionSubnet 'Microsoft.Network/networkSecurityGroups@2024-07-01' = { - name: 'nsg-${location}-bastion' - location: location - properties: { - securityRules: [ - { - name: 'AllowWebExperienceInbound' - properties: { - description: 'Allow our users in. Update this to be as restrictive as possible.' - protocol: 'Tcp' - sourcePortRange: '*' - destinationPortRange: '443' - sourceAddressPrefix: 'Internet' - destinationAddressPrefix: '*' - access: 'Allow' - priority: 100 - direction: 'Inbound' - } - } - { - name: 'AllowControlPlaneInbound' - properties: { - description: 'Service Requirement. Allow control plane access. Regional Tag not yet supported.' - protocol: 'Tcp' - sourcePortRange: '*' - destinationPortRange: '443' - sourceAddressPrefix: 'GatewayManager' - destinationAddressPrefix: '*' - access: 'Allow' - priority: 110 - direction: 'Inbound' - } - } - { - name: 'AllowHealthProbesInbound' - properties: { - description: 'Service Requirement. Allow Health Probes.' - protocol: 'Tcp' - sourcePortRange: '*' - destinationPortRange: '443' - sourceAddressPrefix: 'AzureLoadBalancer' - destinationAddressPrefix: '*' - access: 'Allow' - priority: 120 - direction: 'Inbound' - } - } - { - name: 'AllowBastionHostToHostInbound' - properties: { - description: 'Service Requirement. Allow Required Host to Host Communication.' - protocol: '*' - sourcePortRange: '*' - destinationPortRanges: [ - '8080' - '5701' - ] - sourceAddressPrefix: 'VirtualNetwork' - destinationAddressPrefix: 'VirtualNetwork' - access: 'Allow' - priority: 130 - direction: 'Inbound' - } - } - { - name: 'DenyAllInbound' - properties: { - description: 'No further inbound traffic allowed.' - protocol: '*' - sourcePortRange: '*' - destinationPortRange: '*' - sourceAddressPrefix: '*' - destinationAddressPrefix: '*' - access: 'Deny' - priority: 1000 - direction: 'Inbound' - } - } - { - name: 'AllowSshToVnetOutbound' - properties: { - description: 'Allow SSH out to the virtual network' - protocol: 'Tcp' - sourcePortRange: '*' - sourceAddressPrefix: '*' - destinationPortRange: '22' - destinationAddressPrefix: 'VirtualNetwork' - access: 'Allow' - priority: 100 - direction: 'Outbound' - } - } - { - name: 'AllowRdpToVnetOutbound' - properties: { - description: 'Allow RDP out to the virtual network' - protocol: 'Tcp' - sourcePortRange: '*' - sourceAddressPrefix: '*' - destinationPortRange: '3389' - destinationAddressPrefix: 'VirtualNetwork' - access: 'Allow' - priority: 110 - direction: 'Outbound' - } - } - { - name: 'AllowControlPlaneOutbound' - properties: { - description: 'Required for control plane outbound. Regional prefix not yet supported' - protocol: 'Tcp' - sourcePortRange: '*' - sourceAddressPrefix: '*' - destinationPortRange: '443' - destinationAddressPrefix: 'AzureCloud' - access: 'Allow' - priority: 120 - direction: 'Outbound' - } - } - { - name: 'AllowBastionHostToHostOutbound' - properties: { - description: 'Service Requirement. Allow Required Host to Host Communication.' - protocol: '*' - sourcePortRange: '*' - sourceAddressPrefix: 'VirtualNetwork' - destinationPortRanges: [ - '8080' - '5701' - ] - destinationAddressPrefix: 'VirtualNetwork' - access: 'Allow' - priority: 130 - direction: 'Outbound' - } - } - { - name: 'AllowBastionCertificateValidationOutbound' - properties: { - description: 'Service Requirement. Allow Required Session and Certificate Validation.' - protocol: '*' - sourcePortRange: '*' - sourceAddressPrefix: '*' - destinationPortRange: '80' - destinationAddressPrefix: 'Internet' - access: 'Allow' - priority: 140 - direction: 'Outbound' - } - } - { - name: 'DenyAllOutbound' - properties: { - description: 'No further outbound traffic allowed.' - protocol: '*' - sourcePortRange: '*' - destinationPortRange: '*' - sourceAddressPrefix: '*' - destinationAddressPrefix: '*' - access: 'Deny' - priority: 1000 - direction: 'Outbound' - } - } - ] - } -} - -resource nsgBastionSubnet_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { - scope: nsgBastionSubnet - name: 'to-hub-la' - properties: { - workspaceId: laHub.id - logs: [ - { - categoryGroup: 'allLogs' - enabled: true - } - ] - } -} - -// Azure DDoS Protection Standard should be enabled -resource ddosProtectionPlan 'Microsoft.Network/ddosProtectionPlans@2021-05-01' = { - name: 'vnet-${location}-ddos' - location: location -} - -@description('The regional hub network.') -resource vnetHub 'Microsoft.Network/virtualNetworks@2024-07-01' = { - name: 'vnet-${location}-hub' - location: location - properties: { - addressSpace: { - addressPrefixes: [ - '10.0.0.0/22' - ] - } - subnets: [ - { - name: 'AzureBastionSubnet' - properties: { - addressPrefix: '10.0.1.0/26' - defaultOutboundAccess: false - networkSecurityGroup: { - id: nsgBastionSubnet.id - } - } - } - { - name: 'GatewaySubnet' - properties: { - addressPrefix: '10.0.2.0/27' - defaultOutboundAccess: false - } - } - { - name: 'AzureFirewallSubnet' - properties: { - addressPrefix: '10.0.3.0/26' - defaultOutboundAccess: false - } - } - ] - enableDdosProtection: true - ddosProtectionPlan: { - id: ddosProtectionPlan.id - } - } - - resource azureBastionSubnet 'subnets' existing = { - name: 'AzureBastionSubnet' - } - - resource gatewaySubnet 'subnets' existing = { - name: 'GatewaySubnet' - } - - resource azureFirewallSubnet 'subnets' existing = { - name: 'AzureFirewallSubnet' - } - - // Connect regional hub back to spoke one (created later below). This could also - // be handled via Azure Policy or Portal. How virtual networks are peered might - // vary from organization to organization. This example simply does it in the most - // direct way to simplify ease of deployment. - resource peerToSpokeOne 'virtualNetworkPeerings@2024-07-01' = { - name: 'to_${vnetSpokeOne.name}' - dependsOn: [ - vnetSpokeOne::peerToHub // This artificially waits until the spoke peers with the hub first to control order of operations. - ] - properties: { - allowForwardedTraffic: false - allowGatewayTransit: false - allowVirtualNetworkAccess: true - useRemoteGateways: false - remoteVirtualNetwork: { - id: vnetSpokeOne.id - } - } - } - - // Connect regional hub back to spoke one (created later below). - resource peerToSpokeTwo 'virtualNetworkPeerings@2024-07-01' = { - name: 'to_${vnetSpokeTwo.name}' - dependsOn: [ - vnetSpokeTwo::peerToHub // This artificially waits until the spoke peers with the hub first to control order of operations. - ] - properties: { - allowForwardedTraffic: false - allowGatewayTransit: false - allowVirtualNetworkAccess: true - useRemoteGateways: false - remoteVirtualNetwork: { - id: vnetSpokeTwo.id - } - } - } -} - -resource vnetHub_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { - name: 'to-hub-la' - scope: vnetHub - properties: { - workspaceId: laHub.id - metrics: [ - { - category: 'AllMetrics' - enabled: true - } - ] - } -} - -// Allocate three IP addresses to the firewall -var numFirewallIpAddressesToAssign = 3 -resource pipsAzureFirewall 'Microsoft.Network/publicIPAddresses@2024-07-01' = [for i in range(0, numFirewallIpAddressesToAssign): { - name: 'pip-fw-${location}-${padLeft(i, 2, '0')}' - location: location - sku: { - name: 'Standard' - } - zones: [ - '1' - '2' - '3' - ] - properties: { - publicIPAllocationMethod: 'Static' - idleTimeoutInMinutes: 4 - publicIPAddressVersion: 'IPv4' - } -}] - -resource pipsAzureFirewall_diagnosticSetting 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [for i in range(0, numFirewallIpAddressesToAssign): { - name: 'to-hub-la' - scope: pipsAzureFirewall[i] - properties: { - workspaceId: laHub.id - logs: [ - { - categoryGroup: 'allLogs' - enabled: true - } - ] - metrics: [ - { - category: 'AllMetrics' - enabled: true - } - ] - } -}] - -@description('Azure Firewall Policy') -resource fwPolicy 'Microsoft.Network/firewallPolicies@2024-07-01' = { - name: 'fw-policies-${location}' - location: location - properties: { - sku: { - tier: 'Standard' - } - threatIntelMode: 'Deny' - insights: { - isEnabled: true - retentionDays: 30 - logAnalyticsResources: { - defaultWorkspaceId: { - id: laHub.id - } - } - } - threatIntelWhitelist: { - fqdns: [] - ipAddresses: [] - } - intrusionDetection: null // Only valid on Premium tier sku - dnsSettings: { - servers: [] - enableProxy: true - } - } - - // This network hub starts out with only supporting external DNS queries. This is only being done for - // simplicity in this deployment and is not guidance, please ensure all firewall rules are aligned with - // your security standards. - resource defaultNetworkRuleCollectionGroup 'ruleCollectionGroups@2024-07-01' = { - name: 'DefaultNetworkRuleCollectionGroup' - properties: { - priority: 200 - ruleCollections: [ - { - ruleCollectionType: 'FirewallPolicyFilterRuleCollection' - name: 'org-wide-allowed' - priority: 100 - action: { - type: 'Allow' - } - rules: [ - { - ruleType: 'NetworkRule' - name: 'DNS' - description: 'Allow DNS outbound (for simplicity, adjust as needed)' - ipProtocols: [ - 'UDP' - ] - sourceAddresses: [ - '*' - ] - sourceIpGroups: [] - destinationAddresses: [ - '*' - ] - destinationIpGroups: [] - destinationFqdns: [] - destinationPorts: [ - '53' - ] - } - ] - } - ] - } - } - - // Network hub starts out with no allowances for appliction rules - resource defaultApplicationRuleCollectionGroup 'ruleCollectionGroups@2024-07-01' = { - name: 'DefaultApplicationRuleCollectionGroup' - dependsOn: [ - defaultNetworkRuleCollectionGroup - ] - properties: { - priority: 300 - ruleCollections: [ - { - ruleCollectionType: 'FirewallPolicyFilterRuleCollection' - name: 'org-wide-allowed' - priority: 100 - action: { - type: 'Allow' - } - rules: deployVirtualMachines ? [ - { - ruleType: 'ApplicationRule' - name: 'WindowsVirtualMachineHealth' - description: 'Supports Windows Updates and Windows Diagnostics' - fqdnTags: [ - 'WindowsDiagnostics' - 'WindowsUpdate' - ] - protocols: [ - { - protocolType: 'Https' - port: 443 - } - ] - sourceAddresses: [ - '10.200.0.0/24' // The subnet that contains the Windows VMs - ] - } - ] : [] - } - ] - } - } -} - -@description('This is the regional Azure Firewall that all regional spoke networks can egress through.') -resource fwHub 'Microsoft.Network/azureFirewalls@2024-07-01' = { - name: 'fw-${location}' - location: location - zones: [ - '1' - '2' - '3' - ] - dependsOn: [ - // This helps prevent multiple PUT updates happening to the firewall causing a CONFLICT race condition - // Ref: https://learn.microsoft.com/azure/firewall-manager/quick-firewall-policy - fwPolicy::defaultApplicationRuleCollectionGroup - fwPolicy::defaultNetworkRuleCollectionGroup - ] - properties: { - sku: { - name: 'AZFW_VNet' - tier: 'Standard' - } - firewallPolicy: { - id: fwPolicy.id - } - ipConfigurations: [for i in range(0, numFirewallIpAddressesToAssign): { - name: pipsAzureFirewall[i].name - properties: { - subnet: (0 == i) ? { - id: vnetHub::azureFirewallSubnet.id - } : null - publicIPAddress: { - id: pipsAzureFirewall[i].id - } - } - }] - } -} - -resource fwHub_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { - name: 'to-hub-la' - scope: fwHub - properties: { - workspaceId: laHub.id - logs: [ - { - categoryGroup: 'allLogs' - enabled: true - } - ] - metrics: [ - { - category: 'AllMetrics' - enabled: true - } - ] - } -} - -@description('The public IP for the regional hub\'s Azure Bastion service.') -resource pipAzureBastion 'Microsoft.Network/publicIPAddresses@2024-07-01' = { - name: 'pip-ab-${location}' - location: location - sku: { - name: 'Standard' - } - zones: [ - '1' - '2' - '3' - ] - properties: { - publicIPAllocationMethod: 'Static' - idleTimeoutInMinutes: 4 - publicIPAddressVersion: 'IPv4' - } -} - -resource pipAzureBastion_diagnosticSetting 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { - name: 'to-hub-la' - scope: pipAzureBastion - properties: { - workspaceId: laHub.id - logs: [ - { - categoryGroup: 'allLogs' - enabled: true - } - ] - metrics: [ - { - category: 'AllMetrics' - enabled: true - } - ] - } -} - -@description('This regional hub\'s Azure Bastion service. NSGs are configured to allow Bastion to reach any resource subnet in peered spokes.') -resource azureBastion 'Microsoft.Network/bastionHosts@2024-07-01' = { - name: 'ab-${location}-${suffix}' - location: location - sku: { - name: 'Basic' - } - properties: { - ipConfigurations: [ - { - name: 'hub-subnet' - properties: { - privateIPAllocationMethod: 'Dynamic' - subnet: { - id: vnetHub::azureBastionSubnet.id - } - publicIPAddress: { - id: pipAzureBastion.id - } - } - } - ] - } -} - -resource azureBastion_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { - name: 'to-hub-la' - scope: azureBastion - properties: { - workspaceId: laHub.id - logs: [ - { - categoryGroup: 'allLogs' - enabled: true - } - ] - metrics: [ - { - category: 'AllMetrics' - enabled: true - } - ] - } -} - -@description('The public IPs for the regional VPN gateway. Only deployed if requested.') -resource pipVpnGateway 'Microsoft.Network/publicIPAddresses@2024-07-01' = if (deployVpnGateway) { - name: 'pip-vgw-${location}' - location: location - sku: { - name: 'Standard' - } - zones: [ - '1' - '2' - '3' - ] - properties: { - publicIPAllocationMethod: 'Static' - idleTimeoutInMinutes: 4 - publicIPAddressVersion: 'IPv4' - } -} - -resource pipVpnGateway_diagnosticSetting 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (deployVpnGateway) { - name: 'to-hub-la' - scope: pipVpnGateway - properties: { - workspaceId: laHub.id - logs: [ - { - categoryGroup: 'allLogs' - enabled: true - } - ] - metrics: [ - { - category: 'AllMetrics' - enabled: true - } - ] - } -} - -@description('The is the regional VPN gateway, configured with basic settings. Only deployed if requested.') -resource vgwHub 'Microsoft.Network/virtualNetworkGateways@2024-07-01' = if (deployVpnGateway) { - name: 'vgw-${location}-hub' - location: location - properties: { - sku: { - name: 'VpnGw2AZ' - tier: 'VpnGw2AZ' - } - gatewayType: 'Vpn' - vpnType: 'RouteBased' - vpnGatewayGeneration: 'Generation2' - ipConfigurations: [ - { - name: 'default' - properties: { - privateIPAllocationMethod: 'Dynamic' - publicIPAddress: { - id: pipVpnGateway.id - } - subnet: { - id: vnetHub::gatewaySubnet.id - } - } - } - ] - } -} - -resource vgwHub_diagnosticSetting 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (deployVpnGateway) { - name: 'to-hub-la' - scope: vgwHub - properties: { - workspaceId: laHub.id - logs: [ - { - categoryGroup: 'allLogs' - enabled: true - } - ] - metrics: [ - { - category: 'AllMetrics' - enabled: true - } - ] - } -} - -/*** RESOURCES (ALL SPOKES) ***/ - -@description('Next hop to the regional hub\'s Azure Firewall') -resource routeNextHopToFirewall 'Microsoft.Network/routeTables@2024-07-01' = { - name: 'route-to-${location}-hub-fw' - location: location - properties: { - routes: [ - { - name: 'r-nexthop-to-fw' - properties: { - nextHopType: 'VirtualAppliance' - addressPrefix: '0.0.0.0/0' - nextHopIpAddress: fwHub.properties.ipConfigurations[0].properties.privateIPAddress - } - } - ] - } -} - -@description('NSG on the resource subnet (just using a common one for all as an example, but usually would be based on the specific needs of the spoke).') -resource nsgResourcesSubnet 'Microsoft.Network/networkSecurityGroups@2024-07-01' = { - name: 'nsg-spoke-resources' - location: location - properties: { - securityRules: [ - { - name: 'AllowBastionRdpFromHub' - properties: { - protocol: 'Tcp' - sourcePortRange: '*' - sourceAddressPrefix: vnetHub::azureBastionSubnet.properties.addressPrefix - destinationPortRanges: [ - '3389' - ] - destinationAddressPrefix: 'VirtualNetwork' - access: 'Allow' - priority: 100 - direction: 'Inbound' - } - } - { - name: 'AllowBastionSshFromHub' - properties: { - protocol: 'Tcp' - sourcePortRange: '*' - sourceAddressPrefix: vnetHub::azureBastionSubnet.properties.addressPrefix - destinationPortRanges: [ - '22' - ] - destinationAddressPrefix: 'VirtualNetwork' - access: 'Allow' - priority: 110 - direction: 'Inbound' - } - } - { - name: 'DenyAllInBound' - properties: { - protocol: '*' - sourcePortRange: '*' - sourceAddressPrefix: '*' - destinationPortRange: '*' - destinationAddressPrefix: '*' - access: 'Deny' - priority: 1000 - direction: 'Inbound' - } - } - // No outbound restrictions. - ] - } -} - -resource nsgResourcesSubnet_diagnosticsSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { - scope: nsgResourcesSubnet - name: 'to-hub-la' - properties: { - workspaceId: laHub.id - logs: [ - { - categoryGroup: 'allLogs' - enabled: true - } - ] - } -} - -@description('NSG on the Private Link subnet (just using a common one for all as an example, but usually would be based on the specific needs of the spoke).') -resource nsgPrivateLinkEndpointsSubnet 'Microsoft.Network/networkSecurityGroups@2024-07-01' = { - name: 'nsg-spoke-privatelinkendpoints' - location: location - properties: { - securityRules: [ - { - name: 'AllowAll443InFromVnet' - properties: { - protocol: 'Tcp' - sourcePortRange: '*' - sourceAddressPrefix: 'VirtualNetwork' - destinationPortRange: '443' - destinationAddressPrefix: 'VirtualNetwork' - access: 'Allow' - priority: 100 - direction: 'Inbound' - } - } - { - name: 'DenyAllInbound' - properties: { - protocol: '*' - sourcePortRange: '*' - sourceAddressPrefix: '*' - destinationPortRange: '*' - destinationAddressPrefix: '*' - access: 'Deny' - priority: 1000 - direction: 'Inbound' - } - } - { - name: 'DenyAllOutbound' - properties: { - protocol: '*' - sourcePortRange: '*' - sourceAddressPrefix: '*' - destinationPortRange: '*' - destinationAddressPrefix: '*' - access: 'Deny' - priority: 1000 - direction: 'Outbound' - } - } - ] - } -} - -resource nsgPrivateLinkEndpointsSubnet_diagnosticsSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { - scope: nsgPrivateLinkEndpointsSubnet - name: 'to-hub-la' - properties: { - workspaceId: laHub.id - logs: [ - { - categoryGroup: 'allLogs' - enabled: true - } - ] - } -} - -/*** RESOURCES (SPOKE ONE) ***/ - -resource vnetSpokeOne 'Microsoft.Network/virtualNetworks@2024-07-01' = { - name: 'vnet-${location}-spoke-one' - location: location - properties: { - addressSpace: { - addressPrefixes: [ - '10.100.0.0/22' - ] - } - subnets: [ - { - name: 'snet-resources' - properties: { - addressPrefix: '10.100.0.0/24' - networkSecurityGroup: { - id: nsgResourcesSubnet.id - } - privateEndpointNetworkPolicies: 'Disabled' - privateLinkServiceNetworkPolicies: 'Disabled' - defaultOutboundAccess: false - routeTable: { - id: routeNextHopToFirewall.id - } - } - } - { - name: 'snet-privatelinkendpoints' - properties: { - addressPrefix: '10.100.1.0/26' - networkSecurityGroup: { - id: nsgPrivateLinkEndpointsSubnet.id - } - privateEndpointNetworkPolicies: 'Enabled' - privateLinkServiceNetworkPolicies: 'Enabled' - defaultOutboundAccess: false - routeTable: { - id: routeNextHopToFirewall.id - } - } - } - ] - enableDdosProtection: true - ddosProtectionPlan: { - id: ddosProtectionPlan.id - } - } - - resource snetResources 'subnets' existing = { - name: 'snet-resources' - } - - // Peer to regional hub (hub to spoke peering is in the hub resource) - resource peerToHub 'virtualNetworkPeerings@2024-07-01' = { - name: 'to_${vnetHub.name}' - properties: { - allowForwardedTraffic: false - allowGatewayTransit: false - allowVirtualNetworkAccess: true - useRemoteGateways: false - remoteVirtualNetwork: { - id: vnetHub.id - } - } - } -} - -resource vnetSpokeOne_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { - scope: vnetSpokeOne - name: 'to-hub-la' - properties: { - workspaceId: laHub.id - metrics: [ - { - category: 'AllMetrics' - enabled: true - } - ] - } -} - -@description('The private Network Interface Card for the linux VM in spoke one.') -resource nicVmSpokeOneLinux 'Microsoft.Network/networkInterfaces@2024-07-01' = if (deployVirtualMachines) { - name: 'nic-vm-${location}-spoke-one-linux' - location: location - properties: { - ipConfigurations: [ - { - name: 'default' - properties: { - subnet: { - id: vnetSpokeOne::snetResources.id - } - privateIPAllocationMethod: 'Dynamic' - } - } - ] - enableAcceleratedNetworking: true - } -} - -resource nicVmSpokeOneLinux_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (deployVirtualMachines) { - scope: nicVmSpokeOneLinux - name: 'to-hub-la' - properties: { - workspaceId: laHub.id - metrics: [ - { - category: 'AllMetrics' - enabled: true - } - ] - } -} - -@description('A basic Linux virtual machine that will be attached to spoke one.') -resource vmSpokeOneLinux 'Microsoft.Compute/virtualMachines@2023-03-01' = if (deployVirtualMachines) { - name: 'vm-${location}-spoke-one-linux' - location: location - properties: { - hardwareProfile: { - vmSize: 'Standard_D2ds_v4' - } - storageProfile: { - osDisk: { - createOption: 'FromImage' - managedDisk: { - storageAccountType: 'Standard_LRS' - } - caching: 'ReadOnly' - diffDiskSettings: { - option: 'Local' - placement: 'CacheDisk' - } - deleteOption: 'Delete' - } - imageReference: { - publisher: 'Canonical' - offer: '0001-com-ubuntu-server-focal' - sku: '20_04-lts-gen2' - version: 'latest' - } - dataDisks: [] - } - diagnosticsProfile: { - bootDiagnostics: { - enabled: true - storageUri: null - } - } - networkProfile: { - networkInterfaces: [ - { - id: nicVmSpokeOneLinux.id - properties: { - deleteOption: 'Delete' - primary: true - } - } - ] - } - osProfile: { - computerName: 'examplevm' - adminUsername: adminUsername - adminPassword: adminPassword - linuxConfiguration: { - disablePasswordAuthentication: false - patchSettings: { - patchMode: 'ImageDefault' - assessmentMode: 'ImageDefault' - } - } - } - priority: 'Regular' - } -} - -/*** RESOURCES (SPOKE TWO) ***/ - -resource vnetSpokeTwo 'Microsoft.Network/virtualNetworks@2024-07-01' = { - name: 'vnet-${location}-spoke-two' - location: location - properties: { - addressSpace: { - addressPrefixes: [ - '10.200.0.0/22' - ] - } - subnets: [ - { - name: 'snet-resources' - properties: { - addressPrefix: '10.200.0.0/24' - networkSecurityGroup: { - id: nsgResourcesSubnet.id - } - privateEndpointNetworkPolicies: 'Disabled' - privateLinkServiceNetworkPolicies: 'Disabled' - defaultOutboundAccess: false - routeTable: { - id: routeNextHopToFirewall.id - } - } - } - { - name: 'snet-privatelinkendpoints' - properties: { - addressPrefix: '10.200.1.0/26' - networkSecurityGroup: { - id: nsgPrivateLinkEndpointsSubnet.id - } - privateEndpointNetworkPolicies: 'Enabled' - privateLinkServiceNetworkPolicies: 'Enabled' - defaultOutboundAccess: false - routeTable: { - id: routeNextHopToFirewall.id - } - } - } - ] - enableDdosProtection: true - ddosProtectionPlan: { - id: ddosProtectionPlan.id - } - } - - resource snetResources 'subnets' existing = { - name: 'snet-resources' - } - - // Peer to regional hub (hub to spoke peering is in the hub resource) - resource peerToHub 'virtualNetworkPeerings@2024-07-01' = { - name: 'to_${vnetHub.name}' - properties: { - allowForwardedTraffic: false - allowGatewayTransit: false - allowVirtualNetworkAccess: true - useRemoteGateways: false - remoteVirtualNetwork: { - id: vnetHub.id - } - } - } -} - -resource vnetSpokeTwo_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { - scope: vnetSpokeTwo - name: 'to-hub-la' - properties: { - workspaceId: laHub.id - metrics: [ - { - category: 'AllMetrics' - enabled: true - } - ] - } -} - -@description('The private Network Interface Card for the Windows VM in spoke two.') -resource nicVmSpokeTwoLinux 'Microsoft.Network/networkInterfaces@2024-07-01' = if (deployVirtualMachines) { - name: 'nic-vm-${location}-spoke-two-windows' - location: location - properties: { - ipConfigurations: [ - { - name: 'default' - properties: { - subnet: { - id: vnetSpokeTwo::snetResources.id - } - privateIPAllocationMethod: 'Dynamic' - } - } - ] - enableAcceleratedNetworking: true - } -} - -resource nicVmSpokeTwoLinux_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (deployVirtualMachines) { - scope: nicVmSpokeTwoLinux - name: 'to-hub-la' - properties: { - workspaceId: laHub.id - metrics: [ - { - category: 'AllMetrics' - enabled: true - } - ] - } -} - -@description('A basic Windows virtual machine that will be attached to spoke two.') -resource vmSpokeTwoWindows 'Microsoft.Compute/virtualMachines@2023-03-01' = if (deployVirtualMachines) { - name: 'vm-${location}-spoke-two-windows' - location: location - properties: { - hardwareProfile: { - vmSize: 'Standard_D2s_v3' - } - storageProfile: { - osDisk: { - createOption: 'FromImage' - caching: 'ReadWrite' - managedDisk: { - storageAccountType: 'Premium_LRS' - } - deleteOption: 'Delete' - } - imageReference: { - publisher: 'MicrosoftWindowsServer' - offer: 'WindowsServer' - sku: '2025-datacenter-azure-edition' - version: 'latest' - } - dataDisks: [] - } - diagnosticsProfile: { - bootDiagnostics: { - enabled: true - storageUri: null - } - } - networkProfile: { - networkInterfaces: [ - { - id: nicVmSpokeTwoLinux.id - properties: { - deleteOption: 'Delete' - primary: true - } - } - ] - } - osProfile: { - computerName: 'examplevm' - adminUsername: adminUsername - adminPassword: adminPassword - windowsConfiguration: { - enableAutomaticUpdates: true - provisionVMAgent: true - patchSettings: { - patchMode: 'AutomaticByPlatform' - assessmentMode: 'AutomaticByPlatform' - } - } - } - priority: 'Regular' - } -} diff --git a/solutions/azure-hub-spoke/images/hub-spoke.png b/solutions/azure-hub-spoke/images/hub-spoke.png deleted file mode 100644 index eb8a1b32..00000000 Binary files a/solutions/azure-hub-spoke/images/hub-spoke.png and /dev/null differ