AWS Setup Bastion Host SSH Tunnel


In real scenarios of cloud deployment, it is recommended to keep 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 onPrem 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. For being able to build a cost-effective, scalable, and secure infrastructure on the AWS platform, I recommend the  AWS Automating CloudFormation Course. In this blog, 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. 

This post is divided into the following main sections: 

AWS Setup Bastion Host SSH Tunnel
AWS EC2 Linux instance remote access
AWS EC2 Windows instance remote access
AWS EC2 instance remote access using AWS SDK

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 type 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 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 datacenter, and allow access to only the system admin team's IP address range).

AWS Setup Bastion Host SSH Tunnel

Learn More About AWS Bastion Host 

 Setup SSH Tunnel/Port Forwarding using Putty.exe

First step is to setup the tunnel, wherein you setup such that all the traffic from a port on your bastion host is forwarded 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.

AWS Setup Bastion Host SSH Tunnel

Learn More About AWS Bastion Host 

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

AWS Setup Bastion Host SSH Tunnel

Learn More About AWS Bastion Host 

Click on SSH -> X11. Check 'Enable X11 forwarding'.

AWS Setup Bastion Host SSH Tunnel

Learn More About AWS Bastion Host 

 Click on SSH->Tunnels. For tunnel to linux server, enter 'Source Port' i.e. port on local machine and Destination 'LinuxServer:22'

AWS Setup Bastion Host SSH Tunnel

Learn More About AWS Bastion Host 

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

Learn More About AWS Bastion Host 

This will setup 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

Learn More About AWS Bastion Host 

Setup SSH Tunnel/Port Forwarding using 

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

Learn More About AWS Bastion Host 

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

Learn More About AWS Bastion Host 

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

Learn More About AWS Bastion Host 

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

Learn More About AWS Bastion Host 

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

Learn More About AWS Bastion Host 

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

Learn More About AWS Bastion Host 

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

AWS Setup Bastion Host SSH Tunnel

Learn More About AWS Bastion Host 

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.

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)))
 

Learn More About AWS Bastion Host 

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.

An Amazon Web Services (AWS) and DevOps enthusiast, does a great job describing the CloudFormation and Bastion Host. Watch how he makes AWS simple in this video course. If you have any questions, concerns or feedback, please feel free to contact me. 


 on Email on Github on Linkedin
Amol Kokje, has several years of industry experience in hardware and software engineering domains. His current interests are in the areas of automation for virtualization and cloud computing. He currently works as a software developer with Mcafee, working on automation solutions for enterprise on-premise and cloud software deployment and testing.

About

Amol Kokje, has several years of industry experience in hardware and software engineering domains. His current interests are in the areas of automation for virtualization and cloud computing. He currently works as a software developer with Mcafee, working on automation solutions for enterprise on-premise and cloud software deployment and testing.

Leave a comment