Skip to content

Accessing VMs via Azure Bastion Tunnel + RDP

Overview

All eSHARS VMs have had their public IPs removed as part of the Defender remediation (HM-8334). Access is now exclusively through Azure Bastion tunnels, which create an encrypted tunnel from your local machine to the VM via the Azure backbone — no public IP or VPN required.

Prerequisites

  1. Azure CLI installed

    • Windows: winget install Microsoft.AzureCLI
    • macOS: brew install azure-cli
    • Linux: curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
  2. Azure subscription access — in the HISD subscription (9639039c-86c6-4859-b014-d28688863cd5):

    • Portal / read-only access to VMs & Bastion: Reader role on the relevant resources or resource group.
    • Creating Bastion tunnels (required for this guide): Bastion Tunnel User role (or an equivalent custom role that permits Bastion tunnel creation) on the Bastion resource.
  3. RDP client

    • Windows: mstsc.exe (built-in)
    • macOS: Microsoft Remote Desktop from the App Store
    • Linux: xfreerdp (sudo apt install freerdp2-x11)
  4. VM credentials — Windows local admin credentials (stored in Azure Key Vault kv-eshars-mgmt)

Azure Login

Log in to the HISD tenant (MFA required):

Terminal window
az login --tenant b07ee812-5705-40b5-b227-3df824080db8

Set the correct subscription:

Terminal window
az account set --subscription 9639039c-86c6-4859-b014-d28688863cd5

Quick Start — Using the Helper Scripts

Helper scripts are available in the eshars-ops/scripts/bastion/ repo. Prod and nonprod use separate scripts to prevent accidental prod connections.

Nonprod (PowerShell / Bash)

Terminal window
# List all available nonprod VMs and active tunnels
.\bastion-rdp-nonprod.ps1 -List
# Connect to a VM
.\bastion-rdp-nonprod.ps1 vm-db-api
# Kill an existing tunnel
.\bastion-rdp-nonprod.ps1 -Kill vm-db-api
Terminal window
./bastion-rdp-nonprod.sh --list
./bastion-rdp-nonprod.sh vm-db-api
./bastion-rdp-nonprod.sh --kill vm-db-api

Prod (PowerShell / Bash)

Prod scripts prompt for confirmation before connecting.

Terminal window
# List all available prod VMs
.\bastion-rdp-prod.ps1 -List
# Connect (prompts for confirmation)
.\bastion-rdp-prod.ps1 vm-rns-prod
# Skip confirmation
.\bastion-rdp-prod.ps1 vm-rns-prod -Yes
# Map D:\ into the RDP session (for file transfer)
.\bastion-rdp-prod.ps1 vm-rns-prod -MapDrives
# Map both C:\ and D:\
.\bastion-rdp-prod.ps1 vm-rns-prod -MapDrives -MapC
Terminal window
./bastion-rdp-prod.sh --list
./bastion-rdp-prod.sh vm-rns-prod
./bastion-rdp-prod.sh --yes vm-rns-prod
./bastion-rdp-prod.sh --map-drives vm-rns-prod # D:\ only
./bastion-rdp-prod.sh --map-drives --map-c vm-rns-prod # C:\ and D:\

All scripts handle:

  • Subscription validation and switching
  • Tunnel creation in the background (reuses existing tunnels)
  • RDP file generation with clipboard redirection
  • RDP client launch

Note: Drive mapping behavior differs between environments. Nonprod scripts map all local drives automatically. Prod scripts do not map any local drives by default — use -MapDrives to map D:, and add -MapC to also include C:\ (contains system/profile data).

Manual Steps

If you prefer to connect manually or need to access a VM not in the helper scripts:

Step 1 — Get the VM Resource ID

Terminal window
az vm show -g <RESOURCE_GROUP> -n <VM_NAME> --query id -o tsv

Step 2 — Start the Bastion Tunnel

Terminal window
az network bastion tunnel \
--name <BASTION_NAME> \
--resource-group <BASTION_RG> \
--target-resource-id "<FULL_VM_RESOURCE_ID>" \
--resource-port 3389 \
--port <LOCAL_PORT>

Keep this terminal open — the tunnel runs in the foreground.

Step 3 — Connect via RDP

Open a second terminal:

Terminal window
mstsc /v:localhost:<LOCAL_PORT>

To include local drive mapping, create a .rdp file:

full address:s:localhost:<LOCAL_PORT>
prompt for credentials:i:1
screen mode id:i:2
use multimon:i:1
drivestoredirect:s:*
redirectclipboard:i:1

Save it and open with mstsc (Windows), open (macOS), or xfreerdp (Linux).

VM Registry

Nonprod VMs

VM NameLocal PortResource GroupBastion
vm-sql16-np33891rg-eshars-nonprodbastion-eshars-nonprod
vm-sql16-1-np33892rg-eshars-nonprod-centralusbastion-eshars-nonprod
vm-ops-nonprod33893rg-eshars-nonprodbastion-eshars-nonprod
vm-successed-nonprod33894rg-eshars-nonprodbastion-eshars-nonprod
vm-db-api33895rg-eshars-nonprodbastion-eshars-nonprod
vm-cleanup-jobs33896rg-eshars-nonprodbastion-eshars-nonprod
vm-jobscheduler-np-00133897rg-eshars-nonprodbastion-eshars-nonprod
vm-jobscheduler33898rg-eshars-nonprodbastion-eshars-nonprod

Prod VMs

VM NameLocal PortResource GroupBastion
vm-rns-prod33900rg-eshars-prodbastion-eshars-prod
vm-jobscheduler-prodrg-eshars-prodbastion-eshars-prod
vm-tmhp-intrg-eshars-prodbastion-eshars-prod
vm-tmhp-prodrg-eshars-prodbastion-eshars-prod
vm-ssrs16-prodrg-eshars-prodbastion-eshars-prod
vm-ops-prodrg-eshars-prodbastion-eshars-prod

VMs with a for local port are not yet in the helper script. Use the manual steps above, or add them to bastion-rdp-prod.ps1 / .sh.

Bastion Instances

BastionResource GroupSKUVNetSubnetTunneling
bastion-eshars-nonprodrg-eshars-nonprodStandardvnet-eshars-nonprod (10.1.0.0/16)AzureBastionSubnet (10.1.1.0/26)Yes
bastion-eshars-prodrg-eshars-prodDevelopervnet-eshars (10.0.0.0/16)AzureBastionSubnet (10.0.1.0/26)Yes (native client supported)

Enabling File Sharing in RDP

To access your local drives from within the remote VM:

Using the Helper Scripts

The scripts automatically configure drive mapping. Your local drives will appear under “This PC” in File Explorer on the remote VM as redirected drives.

Using mstsc Manually (Windows)

  1. Open mstsc
  2. Click Show Options
  3. Go to Local Resources tab
  4. Under “Local devices and resources”, click More…
  5. Check the drives you want to share
  6. Click OK, then Connect

Using an .rdp File

Add this line to your .rdp file:

  • Map all drives: drivestoredirect:s:*
  • Map specific drives: drivestoredirect:s:C:\;D:\

Using Microsoft Remote Desktop (macOS)

  1. Open Microsoft Remote Desktop
  2. Edit the connection (or create a new one to localhost:<PORT>)
  3. Go to the Folders tab
  4. Enable Redirect folders
  5. Click + to add folders you want to share

Using xfreerdp (Linux)

Terminal window
xfreerdp /v:localhost:<PORT> /drive:home,$HOME /clipboard /dynamic-resolution

NSG Requirements

Every VM must have an NSG rule allowing Bastion traffic. Without this rule, the tunnel will connect but RDP will hang.

Name: AllowBastionRDP
Priority: 200
Access: Allow
Protocol: TCP
Direction: Inbound
Source: <bastion-subnet-cidr>, 168.63.129.16
Dest Ports: 3389, 22

az CLI:

Terminal window
az network nsg rule create \
--nsg-name <NSG_NAME> -g <RG> \
-n AllowBastionRDP --priority 200 \
--access Allow --protocol Tcp --direction Inbound \
--source-address-prefixes <BASTION_SUBNET> 168.63.129.16 \
--destination-port-ranges 3389 22 \
--description "Allow RDP/SSH from Azure Bastion subnet"

Bastion subnet CIDRs:

  • Prod: 10.0.1.0/26
  • Nonprod: 10.1.1.0/26

Troubleshooting

”Tunnel is ready” but RDP won’t connect

  • Verify the VM’s NSG has the AllowBastionRDP rule (see above)
  • Ensure the VM is running: az vm show -g <RG> -n <VM> --query powerState -o tsv
  • Check that port 3389 (RDP) is enabled inside the VM’s Windows Firewall

az login errors with AADSTS50076 (MFA required)

Log in with the specific tenant:

Terminal window
az login --tenant b07ee812-5705-40b5-b227-3df824080db8

“unrecognized arguments: —target-vm-name”

The tunnel command requires --target-resource-id (the full ARM resource ID), not --target-vm-name.

Port already in use

Another tunnel or process is using that local port. Either:

  • Use the --kill / -Kill flag in the helper script to stop the old tunnel
  • Choose a different local port
  • Find and kill the process: lsof -i :<PORT> (macOS/Linux) or netstat -ano | findstr <PORT> (Windows)

macOS: “line 18: vm: unbound variable”

macOS ships with bash 3.x which doesn’t support associative arrays. Use the bastion-rdp.sh script (v2+) which is compatible with bash 3.x, or install modern bash: brew install bash.

Access denied / no subscription access

You need the Bastion Tunnel User custom role or equivalent permissions. Ask a subscription admin to add you to the Bastion Tunnel Users security group.