This post details how to set up a bastion host, or jump server, for Windows in AWS EC2. As any Windows Server admin knows, the remote desktop protocol (RDP) is an essential tool in maintaining Windows. RDP runs on TCP port 3389 and because this port is so well-known, it is a favorite port for attacks and is one of the first ports a security-conscious admin wants to close.
Update 2017-07-24: See this post for a DevOps approach to configuring a Windows jump server.
AWS’s recommendation to address this exposure (as well as exposure on other ports) is to put Windows Servers inside a private subnet (which isolates them from the Internet) and set up a Windows Server Remote Desktop Gateway (RDG) bastion host (aka, jumper server) in a public subnet. A public VPC subnet differs from a private subnet in two crucial ways: a public subnet has both a route to a VPC internet gateway (IGW) and instances which have been assigned an elastic IP (EIP). See this documentation for an illustration of what AWS recommends in this regard.
But this design for Windows jump hosts introduces a security issue: if you want to access the RD gateway server itself via the public subnet you must open port 3389 in the security group and/or the network ACL.
This is clunky if you ask me: you can close port 3389 to the public subnet only if you never need to access the server on which the Remote Desktop Gateway is running. Sure, you could open it temporarily just to apply maintenance, configure the gateway and/or restart it. But be honest — you’ll forget to close 3389 and then you have a port open that you are trying to keep closed.
To get around this limitation, we can use the fact that a single network interface in Windows Server EC2 instance can respond on both an internal, non-routable subnet IP addresses as well as a public, routable EIP address. (Actually, all access to the EC2 instance is on the private IP address because it’s “behind” an invisible VPC router.)
Simply put, your RDG jump host connects to itself. Incoming requests go to the external EIP address on port 443 (which, of course, you have to open in the security group for the public subnet). The RDG then looks for an internal address, resolved by Route 53, that’s listening on port 3389. Thanks to the fact that it’s a different resolved name, it finds…itself!
Here’s how to set this up.
First, set up an internal CNAME reference for the RDG and a an external A record for the same instance using the EIP for the instance.
Here you see that I use the subdomain “internal” as a convention to refer to an instance by its VPC private address.
Next, set up the RDP connection. Here, I am using Devolutions’ Remote Desktop Manager which is an excellent RDP client.
Finally, in the RDG settings, use the external name (that is, the A record) name of the RD gateway server.
To prove this all works, remove port 3389 from your VPC security group and connect. You can verify that you connected from an external address to the internal address of the RDG in the Windows event log.
Please let me know how this worked for you in the comments section of this post.