AWS Setup Bastion Host SSH Tunnel

Amol Kokje
Bizlab Technologies
8 min readAug 1, 2018

In real scenarios of cloud deployment, it is recommended to secure the infrastructure with tight network security. To achieve that in AWS, you can use hardened AMIs allowing only controlled access at the OS and application level. At network or VPC level, you should consider a tiered approach wherein only the application layer or web UI console is accessible to the customers. The database access should be limited to the developers and the instance level access only to the Administrators. A public cloud environment is different than a normal On-Prem environment as it has a greater possibility of getting hacked since it does not sit behind an IT-controlled firewall like in most corporate data centers.

The AWS Shared Responsibility model requires you as a user to ensure that your resources and data are secure. Using a bastion host in such cases comes very handy, and is used very frequently. In this post, I will go over some basics and show how you can access your backend infrastructure by SSH tunneling through a bastion host.

What is a Bastion Host?

In very simple terms, a bastion host is the only host or computer which is allowed public network access. The other common name used for a bastion host is ‘Jump Box’. This is usually a powerful server box with high network security as this is the only host which is allowed public access. System admins can use this machine to connect to other instances in the backend infrastructure via secure authentication mechanisms.

What is SSH Tunneling? or What is Port Forwarding vis SSH?

SSH tunneling or port forwarding is a mechanism in SSH for connecting application ports from the client machine to the server machine or vice versa. For most IT applications, it is used for adding encryption to legacy applications, going through firewalls, and for opening backdoors into the internal network from their local network or corporate network machines.

AWS Setup Bastion Host SSH Tunnel

In this section, I will go over the architecture example shown in the picture below. Here, we have one VPC with a public and a private subnet. Say, for example, your windows and linux server instances in the private subnet are running a web server.

  • Assuming that the web servers on these windows and linux machines are running on default ports, we need to enable public access to port 80. All other ports should be blocked. Bastion host needs access to the SSH port 22 on linux server and RDS port 3389 on the windows server so that it can be used for remote access to these machines. For this example, I have created one security group for both types of instances. As an alternative, you can have a security group for windows with only RDP port 3389 access, and another security group for linux with only SSH port 22 access. The other accesses will still be the same for both security groups. (NOTE: Assumption here is that you are using the default Network Access Control List(NACL) for the subnets which allows all the traffic. If it’s not a default NACL, you will have to configure the NACL to allow access too.)
  • Bastion host needs to be accessible from the internet so the system admins can log in. In this example, the access is open to all IP addresses, but in a real scenario, you should restrict the IP addresses who can access to a range of IP addresses which you know are white-listed. (NOTE: To allow access to a set of IPs in a private corporate network, you should setup VPN connection with the corporate data center, and allow access to only the system admin team’s IP address range).
AWS Setup Bastion Host SSH tunnel

Setup SSH Tunnel/Port Forwarding using Putty.exe

The first step is to set up the tunnel, wherein you configure so as to forward all the traffic from a port on your bastion host to the port of interest on the windows and linux server machines. Before you start, ensure you have the key pair downloaded from AWS(.pem file), the converted private key(.ppk file), and Putty.exe. Here, I will use Putty to demonstrate, but you can use any other similar tool. Follow the following steps with images.

Start putty, enter the Bastion Host IP and SSH port 22 for bastion host access.

Select the private key .ppk file, which will be used for authentication.

AWS Setup Bastion Host SSH tunnel Putty

Click on SSH -> X11. Check ‘Enable X11 forwarding’.

AWS Setup Bastion Host SSH tunnel Putty

Click on SSH->Tunnels. For tunnel to linux server, enter ‘Source Port’ i.e. port on the local machine and Destination ‘<Linux Server IP>:22’

AWS Setup Bastion Host SSH tunnel Putty

Similarly, setup tunnel for windows server from local machine port 33389 to windows server port 3389. Click on ‘Add’, and then click on ‘Apply’.

AWS Setup Bastion Host SSH tunnel Putty

This will set up the bastion host SSH session. This will open bastion host remote session, with tunnels setup from local ports to ports on linux and windows servers.

AWS Setup Bastion Host SSH tunnel

Setup SSH Tunnel/Port Forwarding using Command Line

If you have the SSH command line utility installed, setting up SSH tunnel is pretty straightforward using the following commands:

For linux server,

ssh -i GATEWAY_KEY.pem -N -L 33322:USERNAME@LINUX_SERVER_PRIVATE_IP:22

For windows server,

ssh -i GATEWAY_KEY.pem -N -L 33389:USERNAME@WINDOWS_SERVER_PRIVATE_IP:3389

AWS EC2 Linux instance remote access

Now, with the tunneling setup, to access the linux server machine, all you need to do is connect on your local machine port 33322 via SSH with your private key. Connecting to this local port will connect you to port 22 on the linux server through the bastion host. You can follow the directions in the steps below.

Open putty.exe, set IP to localhost or 127.0.0.1, and Port 33322.

AWS Setup Bastion Host SSH tunnel Putty

Select the private key .ppk file in SSH–>Auth. When you click the button Open, it will connect you to port 22 on linux server.

AWS Setup Bastion Host SSH tunnel Putty

AWS EC2 Windows instance remote access

Similarly, to access the windows server you need to connect directly to port 33389 on the local machine. This will connect to the RDP port 3389 on the windows server by tunneling through the bastion host. You can follow the steps below.

On the AWS EC2 console, select the windows server instance and click the button ‘Connect’ on the top. This will open a screen as below.

AWS Setup Bastion Host SSH tunnel

When you click on the button ‘Get Password’, it will take you to the screen shown below where you need to choose the .pem key file which you had downloaded when you generated your key pairs. After that, hit the button ‘Decrypt Password’.

AWS Setup Bastion Host SSH tunnel

This will take you to the view below. You can save the username and password in the screen below for use.

AWS Setup Bastion Host SSH tunnel

Then, open Remote Desktop Connection application and enter the Computer Name as localhost, and port as the one you have mapped for tunneling. Click ‘Connect’.

AWS Setup Bastion Host SSH tunnel

Enter the username and password shown on the screen before. This will connect you to the windows server machine.

AWS Setup Bastion Host SSH tunnel

AWS EC2 instance remote access using AWS SDK

Remoting on machines in AWS works a little different. In a usual case, you would use a remoting library and connect via SSH to a remote machine, and execute commands. But in the case of AWS EC2 instances, the AWS SDK provides the ability to do so in their API. AWS provides the Simple Systems Manager (SSM) using which you can run commands on the EC2 instances. Refer to the simple python example below. For detailed example and implementation, you can refer to the Remoting example on my GitHub. In the example, just replace the ACCESS_KEY_ID, SECRET_ACCESS_KEY, and REGION to the relevant values from your environment. I have highlighted the part where the command is executed.

import time
import boto3
def execute_ec2_command(ssm_client, type, instance_id, command, timeout=10):
# Execute command
cmd = [command]
if type == 'linux':
command_meta = ssm_client.send_command ( \
DocumentName = 'AWS-RunShellScript', \
Parameters = {'commands': cmd}, \
InstanceIds = [instance_id])

else:
command_meta = ssm_client.send_command( \
DocumentName = 'AWS-RunPowershellScript', \
Parameters = {'commands': cmd}, \
InstanceIds = [instance_id])


# Wait for timeout or until command returns successfully
time_initial = time.time()
now = time.time()
while now - time_initial < timeout:
command_status = ssm_client.list_commands( \
CommandId = command_meta['Command']['CommandId'])['Commands'][0]['Status']

if command_status == 'Success':
command_result = \
ssm_client.get_command_invocation( \
InstanceId=instance_id, \
CommandId=command_meta['Command'] \
['CommandId'])['StandardOutputContent']
print('Command Output={0}'.format(command_result))
return command_result
else:
time.sleep(0.1)
now = time.time()
if now - time_initial > timeout:
command_status = \
ssm_client.list_commands( \
CommandId = command_meta['Command']['CommandId'])['Commands'][0]['Status']
print('Command Status={0}'.format(command_status))
if command_status != 'Success':
raise RuntimeError('function timed out')

if __name__=='__main__':
ec2_ssm_client = boto3.client('ssm', aws_access_key_id='ACCESS_KEY_ID',
secret_access_key='SECRET_ACCESS_KEY',
region_name='REGION')

# for windows ec2 instance with given instance id
win_ec2_instance_id = 'i-03#####'
cmd = 'ipconfig'
print('Command={0}, Result={1}'.format( \
cmd, \
execute_ec2_command(ssm_client=ec2_ssm_client, \
type='windows', \
instance_id=win_ec2_instance_id, \
command=cmd)))

# for linux ec2 instance with given instance id
linux_ec2_instance_id = 'i-04#####'
cmd = 'ls /tmp/'
print('Command={0}, Result={1}'.format( \
cmd, \
execute_ec2_command(ssm_client=ec2_ssm_client, \
type='linux', \
instance_id=win_ec2_instance_id, \
command=cmd)))

In this example, the method execute_ec2_command() executes the command and waits till it successfully returns or a timeout expires. If you want to execute asynchronously, you may remove the wait.

I hope this will be enough to get you started with remoting on your secure instances launched in the protected cloud network. If you have any questions or feedback, please feel free to contact me.

Join our community Slack and read our weekly Faun topics ⬇

If this post was helpful, please click the clap 👏 button below a few times to show your support for the author! ⬇

--

--

Amol Kokje
Bizlab Technologies

Look forward to waking up every day to an interesting challenge!