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.

Share your thoughts in the comments below

No Comments

Be the first to start the conversation!

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.