diff --git a/telco/referenceImplementation/afoArm.json b/telco/referenceImplementation/afoArm.json index 33078e22..b9afc0bb 100644 --- a/telco/referenceImplementation/afoArm.json +++ b/telco/referenceImplementation/afoArm.json @@ -655,6 +655,20 @@ "Audit", "No" ] + }, + "subnetMaskForDnsResolverInbound": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Provide subnet for DNS Resolver Inbound Endpoint." + } + }, + "subnetMaskForDnsResolverOutbound": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Provide subnet for DNS Resolver Outbound Endpoint." + } } }, "variables": { @@ -1738,6 +1752,12 @@ }, "enableAvnm": { "value": "[parameters('enableAvnm')]" + }, + "subnetMaskForDnsResolverInbound": { + "value": "[parameters('subnetMaskForDnsResolverInbound')]" + }, + "subnetMaskForDnsResolverOutbound": { + "value": "[parameters('subnetMaskForDnsResolverOutbound')]" } } } diff --git a/telco/referenceImplementation/industrySpecific/subscriptionTemplates/hubspoke-connectivity.json b/telco/referenceImplementation/industrySpecific/subscriptionTemplates/hubspoke-connectivity.json index f698d703..b1242d27 100644 --- a/telco/referenceImplementation/industrySpecific/subscriptionTemplates/hubspoke-connectivity.json +++ b/telco/referenceImplementation/industrySpecific/subscriptionTemplates/hubspoke-connectivity.json @@ -158,6 +158,20 @@ "ddosPlanResourceId": { "type": "string", "defaultValue": "" + }, + "subnetMaskForDnsResolverInbound": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Provide subnet for DNS Resolver Inbound Endpoint." + } + }, + "subnetMaskForDnsResolverOutbound": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Provide subnet for DNS Resolver Outbound Endpoint." + } } }, "variables": { @@ -166,6 +180,9 @@ "rgName": "[concat(parameters('topLevelManagementGroupPrefix'), '-vnethub-', parameters('location'))]", "azFwPolicyName": "[concat(parameters('topLevelManagementGroupPrefix'), '-azfwpolicy-', parameters('location'))]", "hubName": "[concat(parameters('topLevelManagementGroupPrefix'), '-hub-', parameters('location'))]", + "dnsResolverName": "[concat(parameters('topLevelManagementGroupPrefix'), '-dnsr-', parameters('location'))]", + "dnsResolverInboundEndpointName": "DefaultInboundEndpoint", + "dnsResolverOutboundEndpointName": "DefaultOutboundEndpoint", "avnmName": "[concat(parameters('topLevelManagementGroupPrefix'), '-vnm-', parameters('location'))]", "avnmRgName": "[concat(parameters('topLevelManagementGroupPrefix'), '-netmanager-', parameters('location'))]", "azVpnGwIpName": "[concat(variables('vpngwname'), '-pip')]", @@ -178,8 +195,11 @@ "azErGwPipId": "[concat('/subscriptions/', parameters('connectivitySubscriptionId'), '/resourceGroups/', variables('rgName'), '/providers/Microsoft.Network/publicIPAddresses/', variables('azErGwIpName'))]", "azFwSubnetId": "[concat('/subscriptions/', parameters('connectivitySubscriptionId'), '/resourceGroups/', variables('rgName'),'/providers/Microsoft.Network/virtualNetworks/', variables('hubname'), '/subnets/AzureFirewallSubnet')]", "azFwPipId": "[concat('/subscriptions/', parameters('connectivitySubscriptionId'), '/resourceGroups/', variables('rgName'), '/providers/Microsoft.Network/publicIPAddresses/', variables('azFwIpName'))]", + "hubId": "[concat('/subscriptions/', parameters('connectivitySubscriptionId'), '/resourceGroups/', variables('rgName'), '/providers/Microsoft.Network/virtualNetworks/', variables('hubName'))]", "resourceDeploymentName": "[take(concat(deployment().name, '-hubspoke', parameters('location')), 64)]", "avnmDeploymentName": "[take(concat(deployment().name, '-avnetmanager', parameters('location')), 64)]", + "dnsResolverInboundSubnetId": "[concat('/subscriptions/', parameters('connectivitySubscriptionId'), '/resourceGroups/', variables('rgName'),'/providers/Microsoft.Network/virtualNetworks/', variables('hubname'), '/subnets/', first(variables('dnsResolverInboundSubnet')).name)]", + "dnsResolverOutboundSubnetId": "[concat('/subscriptions/', parameters('connectivitySubscriptionId'), '/resourceGroups/', variables('rgName'),'/providers/Microsoft.Network/virtualNetworks/', variables('hubname'), '/subnets/', first(variables('dnsResolverOutboundSubnet')).name)]", // Creating variable that later will be used in conjunction with the union() function to cater for conditional subnet creation while ensuring idempotency "gwSubnet": [ { @@ -197,6 +217,22 @@ } } ], + "dnsResolverInboundSubnet": [ + { + "name": "DnsResolverInboundSubnet", + "properties": { + "addressPrefix": "[parameters('subnetMaskForDnsResolverInbound')]" + } + } + ], + "dnsResolverOutboundSubnet": [ + { + "name": "DnsResolverOutboundSubnet", + "properties": { + "addressPrefix": "[parameters('subnetMaskForDnsResolverOutbound')]" + } + } + ], "ddosProtectionPlanId": { "id": "[parameters('ddosPlanResourceId')]" }, @@ -305,13 +341,21 @@ ] }, "subnets": "[ - union( - if( - not( - empty(parameters('subnetMaskForGw'))), variables('gwSubnet'), json('[]')), - if( - not( - empty(parameters('subnetMaskForAzFw'))), variables('fwSubnet'), json('[]')))]", + union( + if( + not(empty(parameters('subnetMaskForGw'))), variables('gwSubnet'), json('[]') + ), + if( + not(empty(parameters('subnetMaskForAzFw'))), variables('fwSubnet'), json('[]') + ), + if( + not(empty(parameters('subnetMaskForDnsResolverInbound'))), variables('dnsResolverInboundSubnet'), json('[]') + ), + if( + not(empty(parameters('subnetMaskForDnsResolverOutbound'))), variables('dnsResolverOutboundSubnet'), json('[]') + ) + ) + ]", "enableDdosProtection": "[if(equals(parameters('enableDdoS'), 'Yes'), 'true', 'false')]", "ddosProtectionPlan": "[if(equals(parameters('enableDdoS'), 'Yes'), variables('ddosProtectionPlanId'), json('null'))]" } @@ -496,6 +540,56 @@ ], "firewallPolicy": "[variables('azFirewallPolicyId')]" } + }, + { + "condition": "[not(parameters('enableAzFwDnsProxy'))]", + "apiVersion": "2020-04-01-preview", + "type": "Microsoft.Network/dnsResolvers", + "name": "[variables('dnsResolverName')]", + "location": "[parameters('location')]", + "dependsOn": [ + "[concat('Microsoft.Network/virtualNetworks/', variables('hubName'))]" + ], + "properties": { + "virtualNetwork": { + "id": "[variables('hubId')]" + } + } + }, + { + "condition": "[not(parameters('enableAzFwDnsProxy'))]", + "apiVersion": "2020-04-01-preview", + "type": "Microsoft.Network/dnsResolvers/inboundEndpoints", + "name": "[variables('dnsResolverInboundEndpointName')]", + "location": "[parameters('location')]", + "dependsOn": [ + "[concat('Microsoft.Network/dnsResolvers/', variables('dnsResolverName'))]" + ], + "properties": { + "ipConfigurations": [ + { + "privateIpAllocationMethod": "Static", + "subnet": { + "id": "[variables('dnsResolverInboundSubnetId')]" + } + } + ] + } + }, + { + "condition": "[not(parameters('enableAzFwDnsProxy'))]", + "apiVersion": "2020-04-01-preview", + "type": "Microsoft.Network/dnsResolvers/outboundEndpoints", + "name": "[variables('dnsResolverOutboundEndpointName')]", + "location": "[parameters('location')]", + "dependsOn": [ + "[concat('Microsoft.Network/dnsResolvers/', variables('dnsResolverName'))]" + ], + "properties": { + "subnet": { + "id": "[variables('dnsResolverOutboundSubnetId')]" + } + } } ] } diff --git a/telco/referenceImplementation/telco-portal.json b/telco/referenceImplementation/telco-portal.json index 10848355..249f66a5 100644 --- a/telco/referenceImplementation/telco-portal.json +++ b/telco/referenceImplementation/telco-portal.json @@ -1478,6 +1478,72 @@ ] } }, + { + "name": "subnetMaskForDnsResolverInbound", + "type": "Microsoft.Common.TextBox", + "label": "Subnet for DNS Resolver (inbound)", + "toolTip": "Provide address prefix in CIDR notation (e.g 10.200.0.0/28)", + "defaultValue": "10.200.0.0/28", + "visible": "[and(equals(steps('esConnectivityGoalState').esAzFw, 'Yes'), not(equals(steps('esConnectivityGoalState').esHub, 'vwan')), not(equals(steps('esConnectivityGoalState').esAzFwDns, 'Yes')))]", + "constraints": { + "required": true, + "validations": [ + { + "regex": "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:/(2[0-8]))$", + "message": "Invalid CIDR range. The address prefix must be in the range [20,28]." + }, + { + "isValid": "[if(greaterOrEquals(last(split(steps('esConnectivityGoalState').esAddressHubHS, '/')), 8), equals(last(take(split(first(split(steps('esConnectivityGoalState').esAddressHubHS, '/')), '.'), 1)), last(take(split(first(split(steps('esConnectivityGoalState').subnetMaskForDnsResolverInbound, '/')), '.'), 1))), true)]", + "message": "CIDR range not within virtual network CIDR range (first octet)." + }, + { + "isValid": "[if(greaterOrEquals(last(split(steps('esConnectivityGoalState').esAddressHubHS, '/')), 16), equals(last(take(split(first(split(steps('esConnectivityGoalState').esAddressHubHS, '/')), '.'), 2)), last(take(split(first(split(steps('esConnectivityGoalState').subnetMaskForDnsResolverInbound, '/')), '.'), 2))), true)]", + "message": "CIDR range not within virtual network CIDR range (second octet)." + }, + { + "isValid": "[if(greaterOrEquals(last(split(steps('esConnectivityGoalState').esAddressHubHS, '/')), 24), equals(last(take(split(first(split(steps('esConnectivityGoalState').esAddressHubHS, '/')), '.'), 3)), last(take(split(first(split(steps('esConnectivityGoalState').subnetMaskForDnsResolverInbound, '/')), '.'), 3))), true)]", + "message": "CIDR range not within virtual network CIDR range (third octet)." + }, + { + "isValid": "[lessOrEquals(last(split(steps('esConnectivityGoalState').esAddressHubHS, '/')), last(split(steps('esConnectivityGoalState').subnetMaskForDnsResolverInbound, '/')))]", + "message": "CIDR range not within virtual network CIDR range (subnet mask)." + } + ] + } + }, + { + "name": "subnetMaskForDnsResolverInbound", + "type": "Microsoft.Common.TextBox", + "label": "Subnet for DNS Resolver (outbound)", + "toolTip": "Provide address prefix in CIDR notation (e.g 10.200.0.16/28)", + "defaultValue": "10.200.0.16/28", + "visible": "[and(equals(steps('esConnectivityGoalState').esAzFw, 'Yes'), not(equals(steps('esConnectivityGoalState').esHub, 'vwan')), not(equals(steps('esConnectivityGoalState').esAzFwDns, 'Yes')))]", + "constraints": { + "required": true, + "validations": [ + { + "regex": "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:/(2[0-8]))$", + "message": "Invalid CIDR range. The address prefix must be in the range [20,28]." + }, + { + "isValid": "[if(greaterOrEquals(last(split(steps('esConnectivityGoalState').esAddressHubHS, '/')), 8), equals(last(take(split(first(split(steps('esConnectivityGoalState').esAddressHubHS, '/')), '.'), 1)), last(take(split(first(split(steps('esConnectivityGoalState').subnetMaskForDnsResolverInbound, '/')), '.'), 1))), true)]", + "message": "CIDR range not within virtual network CIDR range (first octet)." + }, + { + "isValid": "[if(greaterOrEquals(last(split(steps('esConnectivityGoalState').esAddressHubHS, '/')), 16), equals(last(take(split(first(split(steps('esConnectivityGoalState').esAddressHubHS, '/')), '.'), 2)), last(take(split(first(split(steps('esConnectivityGoalState').subnetMaskForDnsResolverInbound, '/')), '.'), 2))), true)]", + "message": "CIDR range not within virtual network CIDR range (second octet)." + }, + { + "isValid": "[if(greaterOrEquals(last(split(steps('esConnectivityGoalState').esAddressHubHS, '/')), 24), equals(last(take(split(first(split(steps('esConnectivityGoalState').esAddressHubHS, '/')), '.'), 3)), last(take(split(first(split(steps('esConnectivityGoalState').subnetMaskForDnsResolverInbound, '/')), '.'), 3))), true)]", + "message": "CIDR range not within virtual network CIDR range (third octet)." + }, + { + "isValid": "[lessOrEquals(last(split(steps('esConnectivityGoalState').esAddressHubHS, '/')), last(split(steps('esConnectivityGoalState').subnetMaskForDnsResolverInbound, '/')))]", + "message": "CIDR range not within virtual network CIDR range (subnet mask)." + } + ] + } + }, { "name": "nwSecurity", "type": "Microsoft.Common.Section", @@ -2637,6 +2703,8 @@ "parameters": { "subnetMaskForGw": "[steps('esConnectivityGoalState').esAddressVpnOrEr]", "subnetMaskForAzFw": "[steps('esConnectivityGoalState').esAddressFw]", + "subnetMaskForDnsResolverInbound": "[steps('esConnectivityGoalState').subnetMaskForDnsResolverInbound]", + "subnetMaskForDnsResolverOutbound": "[steps('esConnectivityGoalState').subnetMaskForDnsResolverInbound]", "enableErGw": "[steps('esConnectivityGoalState').esErGw]", "enableVpnGw": "[steps('esConnectivityGoalState').esVpnGw]", "enableHub": "[steps('esConnectivityGoalState').esHub]",