In this post I show you how to effortless Just-In-Time connect to an Azure VM.
If you expose a VM directly to the internet (not recommended!) literally thousands of automated attacks will happen shortly after. This applies to all infrastructure, of course. If you use a weak combination of username and password, the VM will be taken over in minutes. Therefore a ‘Just in time’ (JIT) concept was introduced to Azure, that basically does the following:
- Lock down the VM: no connections via RDP (port 3389) or SSH (port 22)
- If a user wants to connect: grant the user’s IP access to the VM
- After a specified time: remove the rule and lock down the VM again
The JIT concept that is part of the Azure Security Center is really sophisticated: You can control how long the connection can be open, from what range, to what port for all your VMs - and get a full connection audit! It’s awesome. A nice article from my friend [Tom](https://blog.azureandbeyond.com/ will give you more background.
For my use case of securing two or three VMs it was a little bit too much tho - and I wanted to connect to the VMs directly from my desktop and not through the portal. In other words: I created a script :)
Assumptions
My script has the following assumptions:
- The VM has a public IP and is running
- The NSG has no general allow rule for RDP or SSH
- The connecting user has write permissions to the NSG and at least read permissions to the target VM
- For Linux: the admin user that was used to provision the VM is used for the connection
The Script
The following script is free for use and modification - but comes without any warranty of any kind. It works for me, maybe needs some changes to work for you:
<#
.SYNOPSIS
.
.DESCRIPTION
A simple script to connect to Azure VMs (linux or windows).
Firstly a NSG rule is created to allow the connection from your pc to the VM
Then the conenction is established with either MSTSC or ssh
After the connection is closed, the script removes the NSG rule
Assumptions:
The VM has a public IP and is running
The NSG has no general allow rule for RDP or SSH
For Linux: the admin user that was used to provision the VM is used for the connection
.PARAMETER VMName
Name of the VM you want to connect to
.PARAMETER ResourceGroupName
Name of the ResourceGroup of the VM
.PARAMETER Protocol
Protocol: RDP for Windows, SSH for Linux VMs
.PARAMETER WaitTime
The time the script waits until a session is established.
This is because it takes a while until the NSG kicks in.
.EXAMPLE
jit-connect -protocol <RDP | SSH> -VMName <VMName> -ResourceGroup <ResourceGroupName>
.NOTES
Author: Max Melcher
Date: November 21, 2018
Website: https://melcher.it/post/2018-11-20-AzureSecureRDPLogin/
#>
Param(
[Parameter(Position = 0,
Mandatory = $True,
ValueFromPipeline = $True)]
[string]$VMName,
[Parameter(Position = 1,
Mandatory = $True,
ValueFromPipeline = $True)]
[string]$ResourceGroupName,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[ValidateSet('RDP','SSH')]
[string]$Protocol,
[Parameter(Position = 2,
Mandatory = $False,
ValueFromPipeline = $True)]
[int]$WaitTime = 45
)
write-host "Establishing connection to VM $VMName"
#check the connection - if not available, sign in
$ctx = Get-AzureRmContext
if (!$ctx.Account)
{
Connect-AzureRmAccount -WarningAction SilentlyContinue
}
$status = Get-AzureRmVm -ResourceGroupName $ResourceGroupName -Name $VMName `
-ErrorAction SilentlyContinue -Status -WarningAction SilentlyContinue
$vm = Get-AzureRmVm -ResourceGroupName $ResourceGroupName -Name $VMName `
-ErrorAction SilentlyContinue -WarningAction SilentlyContinue
if (!$status)
{
Write-Host "VM $VMName not found in ResourceGroup $ResourceGroupName"
exit
}
foreach($vmStatus in $status.Statuses ){
if($vmStatus.Code -eq "PowerState/stopped" -or $vmStatus.Code -eq "PowerState/deallocated")
{
write-host "$VMName is not running!"
exit
}
}
if ($Protocol -eq "RDP")
{
$port = 3389
}
else {
#ssh
$port = 22
}
#get the VM's public ip
$nic = Get-AzureRmNetworkInterface -ResourceGroupName $ResourceGroupName `
-Name $(Split-Path -Leaf $vm.NetworkProfile.NetworkInterfaces[0].Id)
$config = $nic | Get-AzureRmNetworkInterfaceIpConfig
$ipname = $config.PublicIpAddress.Id.Substring($config.PublicIpAddress.Id.LastIndexOf("/")+1)
$vmIP = (Get-AzureRmPublicIpAddress -Name $ipname -ResourceGroupName $ResourceGroupName).IpAddress
write-host "VM Public IP is: $vmIP"
$hostname = $env:computername #your hostname
$ip = Invoke-RestMethod http://ipinfo.io/json | Select -exp ip #your external IP
write-host "Your Public IP is: $ip"
#set the allow rule to the NSG
$nsg = Get-AzureRmNetworkSecurityGroup -Name $nic.NetworkSecurityGroup.Id.Substring($nic.NetworkSecurityGroup.Id.LastIndexOf("/")+1) `
-ResourceGroupName $ResourceGroupName
foreach ($rule in $nsg.SecurityRules)
{
if ($rule.Name -notlike "JIT-*" -and $rule.Access -eq "Allow" -and $rule.DestinationPortRange -eq $port)
{
Write-Host -ForegroundColor Red "The general rule '$($rule.Name)' for port $port is already present. Remove it or set the access to 'deny'"
exit
}
}
#set the allow rule to the NSG
$rule = Get-AzureRmNetworkSecurityRuleConfig -Name "JIT-$hostname" -NetworkSecurityGroup $nsg -ErrorAction SilentlyContinue
if ($rule)
{
#do nothing because its there
write-host "NSG rule is already present"
}
else {
write-host "Creating $Protocol NSG rule for JIT access."
Add-AzureRmNetworkSecurityRuleConfig -NetworkSecurityGroup $nsg -Name "JIT-$hostname" -Description "Allow $Protocol for $hostname" `
-Access "Allow" -Protocol "Tcp" -Direction "Inbound" -Priority "109" -SourceAddressPrefix $ip `
-SourcePortRange * -DestinationAddressPrefix * -DestinationPortRange $port | out-null
$nsg | Set-AzureRmNetworkSecurityGroup | out-null
#wait 10 seconds for the NSG rule
write-host -ForegroundColor Yellow "waiting $WaitTime seconds for the NSG rule"
Start-Sleep -Seconds $WaitTime
}
#launch session and wait till exit
write-host "`n`n`n`n`n"
write-host -ForegroundColor Yellow "#################################"
write-host -ForegroundColor Yellow "#################################"
write-host "Do not close this window until you have closed the $Protocol connection!"
write-host -ForegroundColor Yellow "#################################"
write-host -ForegroundColor Yellow "#################################"
if ($Protocol -eq "RDP")
{
mstsc /v:$vmIP | out-null
}
else
{
$user = $vm.OSProfile.AdminUsername
ssh "$user@$vmIP"
}
#remove the nsg Rule
write-host "`n`n`n`n`n"
write-host "removing the NSG rule"
Remove-AzureRmNetworkSecurityRuleConfig -NetworkSecurityGroup $nsg -Name "JIT-$hostname" | out-null
$nsg | Set-AzureRmNetworkSecurityGroup | out-null
write-host -ForegroundColor Green "done."
or in action:
If you create a shortcut to the powershell file with the required parameters, its a secure one-click connect to an Azure VM.
Helpful?
It might take a little longer to connect - but - it is secure.
Does it help you?
Share this post
Twitter
Facebook
LinkedIn
Email