Getting Around Blocked Ports
Regularly, I find myself in a location that blocks ports to the outside world. In many of those moments, I can’t use Remote Desktop (RDP) sessions to connect to Virtual Machines hosted on Azure. The strategy expressed in this post is one of many possible solutions that also applies to Linux and SSH sessions.
The Strategy
- Using a Load Balancer and NAT rules to map port 443 to the RDP (3389) port for a Jumpbox Virtual Machine (VM)
- Using the Jumpbox to RDP into VMs deployed to the Azure Virtual Network.
Finding an OS Image
For this example, we will be working with Windows Server 2016. This short PowerShell script can help you find the Virtual Machine (VM) Image that fits your need (Linux or Windows).
Login-AzureRmAccount $location = 'canadaeast' Get-AzureRmVMImagePublisher -Location $location ` | Where-Object -Property PublisherName -Like Microsoft* $publisherName = 'MicrosoftWindowsServer' Get-AzureRmVMImageOffer -Location $location ` -PublisherName $publisherName $offer = 'WindowsServer' Get-AzureRmVMImageSku -Location $location ` -PublisherName $publisherName ` -Offer $offer ` | Select-Object -Property 'Skus'
ARM Template Example
The following is an Azure Resource Manager (ARM) Template that creates a basic environment that can be hardened once deployed.
{ "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json", "contentVersion": "1.0.0.0", "parameters": { "adminUsername": { "type": "string", "metadata": { "description": "Admin username" } }, "adminPassword": { "type": "securestring", "metadata": { "description": "Admin password" } }, "dnsNameforLBIP": { "type": "string", "metadata": { "description": "Unique DNS name" } }, "vnetName": { "type": "string", "metadata": { "description": "VNET name" } }, "vmNamePrefix": { "type": "string", "metadata": { "description": "VM name prefix" } } }, "variables": { "lbName": "[concat('jmpbxlb',uniqueString(resourceGroup().id))]", "nicNamePrefix": "[concat('jmpbxnic',uniqueString(resourceGroup().id))]", "publicIPAddressName": "[concat('jmpbxpip',uniqueString(resourceGroup().id))]", "imagePublisher": "MicrosoftWindowsServer", "imageOffer": "WindowsServer", "imageSKU": "2016-Datacenter", "vmSize": "Standard_F2", "storageAccountType": "Standard_LRS", "vmStorageAccountContainerName": "vhds", "availabilitySetName": "jmpboxAvSet", "addressPrefix": "10.0.0.0/16", "subnetName": "Subnet-jmpbx", "subnetPrefix": "10.0.0.0/24", "subnetName2": "Subnet-apps", "subnetPrefix2": "10.0.1.0/24", "publicIPAddressType": "Dynamic", "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',parameters('vnetName'))]", "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables ('subnetName'))]", "publicIPAddressID": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]", "lbID": "[resourceId('Microsoft.Network/loadBalancers',variables('lbName'))]", "frontEndIPConfigID": "[concat(variables('lbID'),'/frontendIPConfigurations/loadBalancerFrontend')]", "storageAccountName": "[uniqueString(resourceGroup().id)]" }, "resources": [ { "type": "Microsoft.Storage/storageAccounts", "name": "[variables('storageAccountName')]", "apiVersion": "2015-05-01-preview", "location": "[resourceGroup().location]", "properties": { "accountType": "[variables('storageAccountType')]" } }, { "type": "Microsoft.Compute/availabilitySets", "name": "[variables('availabilitySetName')]", "apiVersion": "2015-06-15", "location": "[resourceGroup().location]", "properties": {} }, { "apiVersion": "2015-06-15", "type": "Microsoft.Network/publicIPAddresses", "name": "[variables('publicIPAddressName')]", "location": "[resourceGroup().location]", "properties": { "publicIPAllocationMethod": "[variables('publicIPAddressType')]", "dnsSettings": { "domainNameLabel": "[parameters('dnsNameforLBIP')]" } } }, { "apiVersion": "2015-06-15", "type": "Microsoft.Network/virtualNetworks", "name": "[parameters('vnetName')]", "location": "[resourceGroup().location]", "properties": { "addressSpace": { "addressPrefixes": [ "[variables('addressPrefix')]" ] }, "subnets": [ { "name": "[variables('subnetName')]", "properties": { "addressPrefix": "[variables('subnetPrefix')]" } }, { "name": "[variables('subnetName2')]", "properties": { "addressPrefix": "[variables('subnetPrefix2')]" } } ] } }, { "apiVersion": "2015-06-15", "type": "Microsoft.Network/networkInterfaces", "name": "[concat(variables('nicNamePrefix'))]", "location": "[resourceGroup().location]", "dependsOn": [ "[concat('Microsoft.Network/virtualNetworks/', parameters('vnetName'))]", "[concat('Microsoft.Network/loadBalancers/', variables('lbName'))]", "[concat('Microsoft.Network/loadBalancers/', variables('lbName'), '/inboundNatRules/', 'RDP-VM')]" ], "properties": { "ipConfigurations": [ { "name": "ipconfig1", "properties": { "privateIPAllocationMethod": "Dynamic", "subnet": { "id": "[variables('subnetRef')]" }, "loadBalancerBackendAddressPools": [ { "id": "[concat(variables('lbID'), '/backendAddressPools/LoadBalancerBackend')]" } ], "loadBalancerInboundNatRules": [ { "id": "[concat(variables('lbID'),'/inboundNatRules/RDP-VM')]" } ] } } ] } }, { "apiVersion": "2015-06-15", "name": "[variables('lbName')]", "type": "Microsoft.Network/loadBalancers", "location": "[resourceGroup().location]", "dependsOn": [ "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]" ], "properties": { "frontendIPConfigurations": [ { "name": "LoadBalancerFrontend", "properties": { "publicIPAddress": { "id": "[variables('publicIPAddressID')]" } } } ], "backendAddressPools": [ { "name": "LoadBalancerBackend" } ] } }, { "apiVersion": "2015-06-15", "type": "Microsoft.Network/loadBalancers/inboundNatRules", "name": "[concat(variables('lbName'), '/', 'RDP-VM')]", "location": "[resourceGroup().location]", "dependsOn": [ "[concat('Microsoft.Network/loadBalancers/', variables('lbName'))]" ], "properties": { "frontendIPConfiguration": { "id": "[variables('frontEndIPConfigID')]" }, "protocol": "tcp", "frontendPort": "443", "backendPort": 3389, "enableFloatingIP": false } }, { "apiVersion": "2015-06-15", "type": "Microsoft.Compute/virtualMachines", "name": "[concat(parameters('vmNamePrefix'))]", "location": "[resourceGroup().location]", "dependsOn": [ "[concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]", "[concat('Microsoft.Network/networkInterfaces/', variables('nicNamePrefix'))]", "[concat('Microsoft.Compute/availabilitySets/', variables('availabilitySetName'))]" ], "properties": { "availabilitySet": { "id": "[resourceId('Microsoft.Compute/availabilitySets',variables('availabilitySetName'))]" }, "hardwareProfile": { "vmSize": "[variables('vmSize')]" }, "osProfile": { "computerName": "[concat(parameters('vmNamePrefix'))]", "adminUsername": "[parameters('adminUsername')]", "adminPassword": "[parameters('adminPassword')]" }, "storageProfile": { "imageReference": { "publisher": "[variables('imagePublisher')]", "offer": "[variables('imageOffer')]", "sku": "[variables('imageSKU')]", "version": "latest" }, "osDisk": { "name": "osdisk", "vhd": { "uri": "[concat('http://',variables('storageAccountName'),'.blob.core.windows.net/vhds/','osdisk', '.vhd')]" }, "caching": "ReadWrite", "createOption": "FromImage" } }, "networkProfile": { "networkInterfaces": [ { "id": "[resourceId('Microsoft.Network/networkInterfaces',concat(variables('nicNamePrefix')))]" } ] }, "diagnosticsProfile": { "bootDiagnostics": { "enabled": "true", "storageUri": "[concat('http://',variables('storageAccountName'),'.blob.core.windows.net')]" } } } } ] }
Deploying The ARM Template
With the ARM Template above pasted in a text file named azuredeploy.json, we can now proceed to deploy our base environment in Azure. This will create a Virtual Network with two subnets. The Jump Box is placed in the first subnet. Workloads are deployed to the app subnet. This makes for easier to implement security.
No Network Security Groups (NSGs) were added to this environment. It's strongly recommended that you take the appropriate steps to secure the Virtual Network.
Login-AzureRmAccount # Check that you are on the right Subscription. Get-AzureRmSubscription # Switch to my MSDN Account Select-AzureRmSubscription -SubscriptionName 'Visual Studio Ultimate with MSDN' # Create a Resource Group New-AzureRmResourceGroup -Name 'msdemo' -Location 'eastus' # Deploy the Template to the Resource Group New-AzureRmResourceGroupDeployment -ResourceGroupName 'msdemo' ` -TemplateFile 'azuredeploy.json' ` -TemplateParameterObject @{ adminUsername = '############' adminPassword = '############' dnsNameforLBIP = 'briseboisjmpbx' vnetName = 'msdemo' vmNamePrefix = 'abjmpbxvm' }` -Verbose
Establishing an RDP Session
Because the Template uses the uniqueString function to minimize name collisions, we need to find the Load Balancer to retrieve the public IP's name.
Get-AzureRmLoadBalancer -ResourceGroupName 'msdemo' Name : jmpbxlbyfno3jyn2c7ys ResourceGroupName : msdemo Location : eastus Id : /subscriptions/4db19144-ce4d-4dfa-b2c9-043b75b92907/resourceGroups/msdemo/providers/Microsoft.Network/loadBalancers/jm pbxlbyfno3jyn2c7ys Etag : W/"22f3cf3c-dbb7-4b72-b791-cbae732c11d7" ResourceGuid : f8b42dc1-c2ae-4ca1-a7be-bd7aec1314f4 ProvisioningState : Succeeded Tags : FrontendIpConfigurations : [ { "Name": "LoadBalancerFrontend", "Etag": "W/\"22f3cf3c-dbb7-4b72-b791-cbae732c11d7\"", "Id": "/subscriptions/4db19144-ce4d-4dfa-b2c9-043b75b92907/resourceGroups/msdemo/providers/Microsoft.Network/loadB alancers/jmpbxlbyfno3jyn2c7ys/frontendIPConfigurations/LoadBalancerFrontend", "PrivateIpAllocationMethod": "Dynamic", "PublicIpAddress": { "Id": "/subscriptions/4db19144-ce4d-4dfa-b2c9-043b75b92907/resourceGroups/msdemo/providers/Microsoft.Network/pub licIPAddresses/jmpbxpipyfno3jyn2c7ys" }, "ProvisioningState": "Succeeded", "InboundNatRules": [ { "Id": "/subscriptions/4db19144-ce4d-4dfa-b2c9-043b75b92907/resourceGroups/msdemo/providers/Microsoft.Network/l oadBalancers/jmpbxlbyfno3jyn2c7ys/inboundNatRules/RDP-VM" } ], "LoadBalancingRules": [], "InboundNatPools": [] } ] BackendAddressPools : [ { "Name": "LoadBalancerBackend", "Etag": "W/\"22f3cf3c-dbb7-4b72-b791-cbae732c11d7\"", "Id": "/subscriptions/4db19144-ce4d-4dfa-b2c9-043b75b92907/resourceGroups/msdemo/providers/Microsoft.Network/loadB alancers/jmpbxlbyfno3jyn2c7ys/backendAddressPools/LoadBalancerBackend", "BackendIpConfigurations": [ { "Id": "/subscriptions/4db19144-ce4d-4dfa-b2c9-043b75b92907/resourceGroups/msdemo/providers/Microsoft.Network/n etworkInterfaces/jmpbxnicyfno3jyn2c7ys/ipConfigurations/ipconfig1", "Primary": false } ], "LoadBalancingRules": [], "ProvisioningState": "Succeeded" } ] LoadBalancingRules : [] Probes : [] InboundNatRules : [ { "Name": "RDP-VM", "Etag": "W/\"22f3cf3c-dbb7-4b72-b791-cbae732c11d7\"", "Id": "/subscriptions/4db19144-ce4d-4dfa-b2c9-043b75b92907/resourceGroups/msdemo/providers/Microsoft.Network/loadB alancers/jmpbxlbyfno3jyn2c7ys/inboundNatRules/RDP-VM", "FrontendPort": 443, "IdleTimeoutInMinutes": 4, "EnableFloatingIP": false, "BackendIPConfiguration": { "Id": "/subscriptions/4db19144-ce4d-4dfa-b2c9-043b75b92907/resourceGroups/msdemo/providers/Microsoft.Network/net workInterfaces/jmpbxnicyfno3jyn2c7ys/ipConfigurations/ipconfig1", "Primary": false }, "FrontendIPConfiguration": { "Id": "/subscriptions/4db19144-ce4d-4dfa-b2c9-043b75b92907/resourceGroups/msdemo/providers/Microsoft.Network/loa dBalancers/jmpbxlbyfno3jyn2c7ys/frontendIPConfigurations/LoadBalancerFrontend" }, "BackendPort": 3389, "Protocol": "Tcp", "ProvisioningState": "Succeeded" } ] InboundNatPools : []
Now we can lookup the Public IP's address
Get-AzureRmPublicIpAddress -ResourceGroupName 'msdemo'` -Name 'jmpbxpipyfno3jyn2c7ys' Name : jmpbxpipyfno3jyn2c7ys ResourceGroupName : msdemo Location : eastus Id : /subscriptions/4db19144-ce4d-4dfa-b2c9-043b75b92907/resourceGroups/msdemo/providers/Microsoft.Network/publicIPAddresses/jmpbxpipyfno3jyn2 c7ys Etag : W/"b9ddce88-e186-4afb-86e5-f555fc80722e" ResourceGuid : 88994de1-3997-4960-83c5-628aa821cbea ProvisioningState : Succeeded Tags : PublicIpAllocationMethod : Dynamic IpAddress : 13.82.228.8 PublicIpAddressVersion : IPv4 IdleTimeoutInMinutes : 4 IpConfiguration : { "Id": "/subscriptions/4db19144-ce4d-4dfa-b2c9-043b75b92907/resourceGroups/msdemo/providers/Microsoft.Network/loadBalancers/jmpbxlbyfno3 jyn2c7ys/frontendIPConfigurations/LoadBalancerFrontend" } DnsSettings : { "DomainNameLabel": "briseboisjmpbx", "Fqdn": "briseboisjmpbx.eastus.cloudapp.azure.com" }
Ok now we're ready to connect using the address that we just pulled from the Public IP. Be sure to include the 443 port in the address used to create the Remote Desktop (RDP) session.
mstsc /v:13.82.228.8:443
Next Steps
Now that we have a way in, we can now deploy Virtual Machines (VMs) to the App subnet. Accessing these VMs is done by opening an RDP session to the Jump Box. Then from within this session, we can use private IPs to connect normally to the VMs deployed to the App subnet.