Remote Access to Windows EC2 instances, the easy (and secure) way
Getting a Remote Desktop to a Windows instance in AWS EC2 is a long process. Here we demonstrate how we can reduce the long series of manual steps down to a single PowerShell command.
Some time ago I wrote “Remote Access to EC2 instances, the easy (and secure) way”, which explored some features of AWS EC2 and Systems Manager for logging into EC2 instances. In the end I had worked out a way to give an instance ID to a special SSH command, and in that single command I’d get a terminal session to any EC2 instance, no matter how deeply it was locked down inside private VPCs. That only worked for Linux instances, so my next challenge was to see if we can do the same with EC2 Windows instances - can a single command start up a Remote Desktop session to a Windows instance in EC2, even if it’s buried inside a private VPC subnet?
This turned out to be quite a challenge…
Busy? Need the answer right now? Then yes, it can be done. Head on over to https://github.com/cloudsoft/EC2Access, follow the installation instructions, and then connect to your Windows instance with a PowerShell command like this:
Start-EC2RemoteDesktopViaSessionManager -InstanceId i-12345678abcd `
-Region eu-west-1 `
But if you want to read about the journey to get here, and more details on how to use these new commands, read on.
Accessing the Windows desktop using Remote Desktop
The normal-for-AWS way for you to access a Windows desktop goes like this:
- Get the Windows Administrator password from the AWS Console. To do this you’ll need to provide your private key file.
- Make sure that TCP port 3389 is open to your own public IP address in the instance’s security group.
- Run the Remote Desktop client, and fill in the remote host address, username (Administrator) and password.
This is quite a few manual steps and, like with Linux SSH, involves opening ports up which you wouldn't normally want the Internet to be able to see. Let's see what's in our toolkit to automate and secure this.
First of all, head over to https://github.com/cloudsoft/EC2Access and follow the instructions to install our PowerShell module developed for this purpose. Once installed, you’ll have access to 3 new PowerShell commands:
We’ll explore these commands in the rest of this post.
Getting the Administrator password
The process here is quite interesting. EC2 stores SSH public keys and when you start an instance, you choose which SSH public key you want to associate with the instance. On Linux instances, the key data is simply dropped into the instance's SSH configuration. On Windows instances, something different happens.
When the instance boots, a special EC2 "first boot" task is run which generates a random password for the Administrator user. It then fetches the SSH public key for the instance - but instead of using it for an SSH purpose, it simply treats it as a generic RSA public key, and uses it to encrypt a copy of the Administrator password. This encrypted blob of data is then sent back to the EC2 control plane.
When you ask the AWS Console to give you the Administrator password, you also supply a copy of your SSH private key. Again this is treated as a simple RSA private key, and EC2 uses it to decrypt the encrypted blob of data, and provides you with the cleartext password.
RSA is a standard and well-known cryptosystem, with multiple implementations, so it's entirely possible to repeat these steps in another system that we can automate. We can do this with PowerShell, and write some script that will query the AWS EC2 API to get the encrypted data, and then use our own SSH private key and an off-the-shelf implementation of RSA to decrypt the data ourselves. Brilliant!
This is implemented in the Get-EC2Password command. Give it the details of an instance and a private key file, and it will return a PowerShell SecureString object containing the instance’s Administrator password.
Like the rest of these new commands, the PrivateKeyFile argument is optional. Omit it, and it will default to the OpenSSH default location for the private key, which is “.ssh\id_rsa” in your home directory. Also optional is a Region argument - omitting it will use the default region configured in your environment variables or AWS configuration files.
Starting Remote Desktop
Having got the credentials we need to log in, we need to pass those into the Remote Desktop client somehow. The Remote Desktop client is actually mstsc.exe, and if we run mstsc.exe /? to see if it accepts any relevant parameters for credentials, we’re disappointed - there’s many parameters but nothing relevant to our needs.
The solution turns out to be something called cmdkey.exe, a tool for managing saved credentials. We can use this to store the instance’s credentials, and as long as we format the credential in exactly the right way, mstsc.exe will already know the credentials needed to connect to the instance. This is key for our requirement to get the remote desktop started without it needing to ask any questions.
A correctly-formed invocation of this command will look something like this:
C:\Windows\system32\cmdkey.exe /generic:TERMSRV/hostname /user:username /pass:password
In this case, the hostname is the public DNS name (or IP address, if you prefer) of the EC2 instance. (At this point we’re only thinking about publicly-addressable instances - the Systems Manager magic for private subnets comes a bit later.) The username will always be Administrator, and the password we got in the previous step.
Now we can start the remote desktop session:
C:\Windows\system32\mstsc.exe /v hostname
As long as we use the same hostname as our cmdkey command (we can’t use the DNS name in one and the IP address in the other), Remote Desktop will start and straight away log in to your EC2 instance without any further questions.
This is implemented in the Start-DirectEC2RemoteDesktop command. Give it an instance ID, and optional region name and private key file path, and it will start a Remote Desktop session - you don’t need to do anything except click a button to accept the certificate validation check.
Integrating with Session Manager
In my previous post on this subject, we saw how AWS Systems Manager Session Manager gave us a way to access instances that had no public IP address, or even were buried deep inside a private subnet. As long as the instance can reach a Systems Manager API endpoint, and the instance has the Systems Manager agents installed, it can tunnel connections through the Systems Manager API instead of needing the public Internet.
For Windows instances, we can set up a port forwarding session. This opens up a TCP port listening for connections on our local workstation. When a connection is made to this port, Systems Manager proxies the data to a port on the EC2 instance. We can use this to tunnel the instance’s Remote Desktop port - TCP port 3389 - onto our workstation, and then direct the Remote Desktop client to connect to the local port. This needs a helper app, the “session manager plugin”, which is available from AWS and easily installed on Windows.
There’s just one flaw. You can see it here in the documentation for Start-SSMSession PowerShell command.
Did you spot it?
Yes, in quite small letters at the end of a paragraph is the message “Start-SSMSession is not currently supported by AWS Tools for PowerShell on Windows local machines.”
This is because AWS Tools for PowerShell is not capable of invoking the session-manager-plugin helper app. The Python-based awscli tool special-cases this API operation to not only invoke the API, but also start session-manager-plugin with appropriate parameters. AWS Tools for PowerShell invokes the API, but it does not start session-manager-plugin.
Therefore, we need to do this ourself, but session-manager-plugin is (a) closed-source, (b) undocumented, and (c) supplied in binary executable format which can’t be easily examined. There wasn’t any way I could examine the file itself to determine how to use it. In the end I resorted to running the equivalent awscli command in Linux under strace to see how it invoked session-manager-plugin. It takes 6 command line arguments, two of which are JSON documents containing many more parameters. I’ll spare you the details, but you can see for yourself in the PowerShell source code.
Having got over this hurdle, we’re ready to go. We’ve got credentials, we’ve got a port forward set up, all we need to do is invoke cmdkey and mstsc again, but this time with an address of localhost and specifying our local port number for the port forward.
Now we’ve achieved our goal: with a single command, a Remote Desktop session will start, securely tunnelled to an instance even in a private subnet, and without having to manually supply credentials:
If you haven’t already, go to https://github.com/cloudsoft/EC2Access, follow the installation instructions, and then easily connect to your Windows instance with a simple PowerShell command.