Kirk Evans Blog

.NET From a Markup Perspective

Connecting Virtual Networks in ARM Templates

This post will demonstrate creating two virtual networks, gateways for each, and connecting them together using an ARM template.

Background

I am working on a project that requires two Azure virtual networks to connect together.  I have done this so many times, typically through CLI scripts or PowerShell, but didn’t find the time to create an ARM template to demonstrate how to do this until today.  The result is very simple:

image

Note that I use this for quick and dirty deployments since everything is in a single resource group.  For a production deployment, I would split each VNet into a separate resource group and I would apply network security group rules to each subnet to control the traffic allowed in or out.  I’d script the connection between the VNets rather than put it in an ARM template.  In other words, this is not a complete and representative example of a production deployment.  For the recommended “golden path” to follow for a production scenario, see the Patterns & Practices guidance on Implementing a secure hybrid network architecture in Azure

Show Me the Code

Again, this is a quick and dirty example, for a production scenario see Implementing a secure hybrid network architecture in Azure

Connect Two VNets
  1. {
  2.   "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  3.   "contentVersion": "1.0.0.0",
  4.   "parameters":
  5.   {
  6.     "VPNSharedKey":
  7.     {
  8.  
  9.       "type": "securestring",
  10.       "metadata":
  11.       {
  12.         "Description": "Shared key between VNets"
  13.       }
  14.     },
  15.     "Location":
  16.     {
  17.       "type": "string",
  18.       "defaultValue": "centralus"
  19.     }
  20.   },
  21.   "variables":
  22.   {
  23.     "primaryvnetname": "primaryvnet",
  24.     "primaryvnetPrefix": "10.0.0.0/16",
  25.     "primaryvnetSubnet1Name": "Subnet-1",
  26.     "primaryvnetSubnet1Prefix": "10.0.0.0/24",
  27.     "primaryvnetSubnet2Name": "Subnet-2",
  28.     "primaryvnetSubnet2Prefix": "10.0.1.0/24",
  29.     "primaryvnetGatewaySubnetPrefix": "10.0.255.224/27",
  30.     "secondaryvnetname": "secondaryvnet",
  31.     "secondaryvnetPrefix": "192.168.0.0/16",
  32.     "secondaryvnetSubnet1Name": "Subnet-1",
  33.     "secondaryvnetSubnet1Prefix": "192.168.0.0/24",
  34.     "secondaryvnetSubnet2Name": "Subnet-2",
  35.     "secondaryvnetSubnet2Prefix": "192.168.1.0/24",
  36.     "secondaryvnetGatewaySubnetPrefix": "192.168.255.224/27"
  37.   },
  38.   "resources":
  39.   [
  40.     {
  41.       "name": "[variables('primaryvnetname')]",
  42.       "type": "Microsoft.Network/virtualNetworks",
  43.       "location": "[resourceGroup().location]",
  44.       "apiVersion": "2015-06-15",
  45.       "dependsOn":
  46.       [
  47.       ],
  48.       "tags":
  49.       {
  50.         "displayName": "primaryvnet"
  51.       },
  52.       "properties":
  53.       {
  54.         "addressSpace":
  55.         {
  56.           "addressPrefixes":
  57.           [
  58.             "[variables('primaryvnetPrefix')]"
  59.           ]
  60.         },
  61.         "subnets":
  62.         [
  63.           {
  64.             "name": "[variables('primaryvnetSubnet1Name')]",
  65.             "properties":
  66.             {
  67.               "addressPrefix": "[variables('primaryvnetSubnet1Prefix')]"
  68.             }
  69.           },
  70.           {
  71.             "name": "[variables('primaryvnetSubnet2Name')]",
  72.             "properties":
  73.             {
  74.               "addressPrefix": "[variables('primaryvnetSubnet2Prefix')]"
  75.             }
  76.           },
  77.           {
  78.             "name": "GatewaySubnet",
  79.             "properties":
  80.             {
  81.               "addressPrefix": "[variables('primaryvnetGatewaySubnetPrefix')]"
  82.             }
  83.           }
  84.         ]
  85.       }
  86.     },
  87.     {
  88.       "apiVersion": "2015-06-15",
  89.       "type": "Microsoft.Network/publicIPAddresses",
  90.       "name": "primaryGatewayPIP",
  91.       "location": "[parameters('Location')]",
  92.       "tags":
  93.       {
  94.         "displayName": "Primary Gateway PIP"
  95.       },
  96.       "properties":
  97.       {
  98.         "publicIPAllocationMethod": "Dynamic"
  99.       }
  100.     },
  101.     {
  102.       "apiVersion": "2015-06-15",
  103.       "type": "Microsoft.Network/virtualNetworkGateways",
  104.       "name": "primaryGateway",
  105.       "location": "[parameters('Location')]",
  106.       "dependsOn":
  107.       [
  108.         "Microsoft.Network/publicIPAddresses/primaryGatewayPIP",
  109.         "[concat('Microsoft.Network/virtualNetworks/', variables('primaryvnetname'))]"
  110.       ],
  111.       "tags":
  112.       {
  113.         "displayName": "primaryGateway"
  114.       },
  115.       "properties":
  116.       {
  117.         "ipConfigurations":
  118.         [
  119.           {
  120.             "properties":
  121.             {
  122.               "privateIPAllocationMethod": "Dynamic",
  123.               "subnet":
  124.               {
  125.                 "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets',variables('primaryvnetname'),'GatewaySubnet')]"
  126.               },
  127.               "publicIPAddress":
  128.               {
  129.                 "id": "[resourceId('Microsoft.Network/publicIPAddresses','primaryGatewayPIP')]"
  130.               }
  131.             },
  132.             "name": "vnetGatewayConfig"
  133.           }
  134.         ],
  135.         "gatewayType": "Vpn",
  136.         "vpnType": "RouteBased",
  137.         "enableBgp": false
  138.       }
  139.     },
  140.     {
  141.       "name": "[variables('secondaryvnetname')]",
  142.       "type": "Microsoft.Network/virtualNetworks",
  143.       "location": "[resourceGroup().location]",
  144.       "apiVersion": "2015-06-15",
  145.       "dependsOn":
  146.       [
  147.       ],
  148.       "tags":
  149.       {
  150.         "displayName": "secondaryvnet"
  151.       },
  152.       "properties":
  153.       {
  154.         "addressSpace":
  155.         {
  156.           "addressPrefixes":
  157.           [
  158.             "[variables('secondaryvnetPrefix')]"
  159.           ]
  160.         },
  161.         "subnets":
  162.         [
  163.           {
  164.             "name": "[variables('secondaryvnetSubnet1Name')]",
  165.             "properties":
  166.             {
  167.               "addressPrefix": "[variables('secondaryvnetSubnet1Prefix')]"
  168.             }
  169.           },
  170.           {
  171.             "name": "[variables('secondaryvnetSubnet2Name')]",
  172.             "properties":
  173.             {
  174.               "addressPrefix": "[variables('secondaryvnetSubnet2Prefix')]"
  175.             }
  176.           },
  177.           {
  178.             "name": "GatewaySubnet",
  179.             "properties":
  180.             {
  181.               "addressPrefix": "[variables('secondaryvnetGatewaySubnetPrefix')]"
  182.             }
  183.           }
  184.         ]
  185.       }
  186.     },
  187.     {
  188.       "apiVersion": "2015-06-15",
  189.       "type": "Microsoft.Network/publicIPAddresses",
  190.       "name": "secondaryGatewayPIP",
  191.       "location": "[parameters('Location')]",
  192.       "tags":
  193.       {
  194.         "displayName": "Secondary Gateway PIP"
  195.       },
  196.       "properties":
  197.       {
  198.         "publicIPAllocationMethod": "Dynamic"
  199.       }
  200.     },
  201.     {
  202.       "apiVersion": "2015-06-15",
  203.       "type": "Microsoft.Network/virtualNetworkGateways",
  204.       "name": "secondaryGateway",
  205.       "location": "[parameters('Location')]",
  206.       "dependsOn":
  207.       [
  208.         "Microsoft.Network/publicIPAddresses/secondaryGatewayPIP",
  209.         "[concat('Microsoft.Network/virtualNetworks/', variables('secondaryvnetname'))]"
  210.       ],
  211.       "tags":
  212.       {
  213.         "displayName": "secondaryGateway"
  214.       },
  215.       "properties":
  216.       {
  217.         "ipConfigurations":
  218.         [
  219.           {
  220.             "properties":
  221.             {
  222.               "privateIPAllocationMethod": "Dynamic",
  223.               "subnet":
  224.               {
  225.                 "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets',variables('secondaryvnetname'),'GatewaySubnet')]"
  226.               },
  227.               "publicIPAddress":
  228.               {
  229.                 "id": "[resourceId('Microsoft.Network/publicIPAddresses','secondaryGatewayPIP')]"
  230.               }
  231.             },
  232.             "name": "vnetGatewayConfig"
  233.           }
  234.         ],
  235.         "gatewayType": "Vpn",
  236.         "vpnType": "RouteBased",
  237.         "enableBgp": false
  238.       }
  239.     },
  240.     {
  241.       "name": "primary2secondary",
  242.       "type": "Microsoft.Network/connections",
  243.       "apiVersion": "2016-03-30",
  244.       "dependsOn":
  245.       [
  246.         "[concat('Microsoft.Network/virtualNetworks/', variables('primaryvnetname'))]",
  247.         "[concat('Microsoft.Network/virtualNetworks/', variables('secondaryvnetname'))]",
  248.         "Microsoft.Network/virtualNetworkGateways/primaryGateway",
  249.         "Microsoft.Network/virtualNetworkGateways/secondaryGateway"
  250.       ],
  251.       "location": "[parameters('Location')]",
  252.       "tags":
  253.       {
  254.         "displayName": "Primary to Secondary Connection"
  255.       },
  256.       "properties":
  257.       {
  258.         "virtualNetworkGateway1":
  259.         {
  260.           "id": "[resourceId('Microsoft.Network/virtualNetworkGateways', 'primaryGateway')]"
  261.         },
  262.         "virtualNetworkGateway2":
  263.         {
  264.           "id": "[resourceId('Microsoft.Network/virtualNetworkGateways', 'secondaryGateway')]"
  265.         },
  266.         "connectionType": "Vnet2Vnet",
  267.         "sharedKey": "[parameters('VPNSharedKey')]"
  268.       }
  269.     },
  270.     {
  271.       "name": "secondary2primary",
  272.       "type": "Microsoft.Network/connections",
  273.       "apiVersion": "2016-03-30",
  274.       "dependsOn":
  275.       [
  276.         "[concat('Microsoft.Network/virtualNetworks/', variables('primaryvnetname'))]",
  277.         "[concat('Microsoft.Network/virtualNetworks/', variables('secondaryvnetname'))]",
  278.         "Microsoft.Network/virtualNetworkGateways/primaryGateway",
  279.         "Microsoft.Network/virtualNetworkGateways/secondaryGateway"
  280.       ],
  281.       "location": "[parameters('Location')]",
  282.       "tags":
  283.       {
  284.         "displayName": "Secondary to Primary Connection"
  285.       },
  286.       "properties":
  287.       {
  288.         "virtualNetworkGateway1":
  289.         {
  290.           "id": "[resourceId('Microsoft.Network/virtualNetworkGateways', 'secondaryGateway')]"
  291.         },
  292.         "virtualNetworkGateway2":
  293.         {
  294.           "id": "[resourceId('Microsoft.Network/virtualNetworkGateways', 'primaryGateway')]"
  295.         },
  296.         "connectionType": "Vnet2Vnet",
  297.         "sharedKey": "[parameters('VPNSharedKey')]"
  298.       }
  299.     }
  300.  
  301.   ],
  302.   "outputs":
  303.   {
  304.   }
  305. }


One of the things I like about this approach is how everything is parallelized.  The VNets and Public IPs are created in parallel, then both gateways are created in parallel.  This is a huge timesaver because each gateway creation takes around 30 minutes for each.. doing this in parallel reduces the time to just over 30 minutes total (my last deployment was just 33 minutes). 

Summary

It’s a neat template and all, but not something that I would recommend for a production deployment.  Instead, I would likely approach this with two different resource groups, and then use script (CLI or PowerShell) to connect the VNets together as shown in the article Configure a VNet-to-VNet connection by using Azure Resource Manager and PowerShell.  That said, I will probably use it often for quick and dirty tests across VNets since it’s easier to manage all the resources at once.

Acknowledgement

I have to thank Devin Lusby from Avanade who showed me how to create the Microsoft.Network/connections resource. 

For More Information

Implementing a secure hybrid network architecture in Azure

Configure a VNet-to-VNet connection by using Azure Resource Manager and PowerShell