SSH Tunnels (How to Access AWS RDS Locally Without Exposing it to Internet)

Using SSH tunnels, it is possible to access remote resources that are not exposed to the Internet through the intermediate hosts or expose your local services to the Internet.

Setup

To make SSH commands shorter and easier to use, edit the ~/.ssh/config and add the configuration for the hosts you are going to connect.

The configuration defines default ssh options, so instead of the command like this ssh ec2-user@ec2-55-222-55-55.compute-1.amazonaws.com -i ~/.ssh/my_key.pem, we can just use ssh my-remote-host.

An example config:

Host my-remote-host
HostName ec2-55-222-55-55.compute-1.amazonaws.com
StrictHostKeyChecking no
User ec2-user
IdentityFile ~/.ssh/my_key.pem

Access Remote Hidden Resource

The SSH command to access the remote hidden resource locally through the intermediate accessible host is ssh -L:

local_port=5532

accessible_host=my-remote-host

hidden_host=hidden_host.amazonaws.com
hidden_port=5432

ssh ${accessible_host} -L ${local_port}:${hidden_host}:${hidden_port}

With the command above we connect to the my-remote-host that has access to the hidden_host:hidden_port and make the hidden resource available locally:

localhost:5532 =====> my-remote-host =====> hidden_host.amazonaws.com:5432

Expose Local Resource To the Internet

The SSH command to expose the local resource through the intermediate host is ssh -R:

remote_port=8181
local_port=8888

ssh my-remote-host -R *:${remote_port}:localhost:${local_port}

With the command above we connect to the my-remote-host and instruct it to accept connections to the 8181 port and forward them to the localhost:8888.

The *:8181 that remote host will forward connections to any network interface (by default it will use only 127.0.0.1).

localhost:8888 <===== my-remote-host <===== my-remote-host:8181

You also need to make sure that firewall on my-remote-host allows connections to the 8181 port.

Example: Access RDS Database Through the EC2 Instance

It is good idea to make RDS databases not available from the Internet, so they can only be accessed from the EC2 instances where applications are running.

On the other hand, during the development, it is convenient to have the database accessible from your local machine. It is easy to do it, running the following command:

ssh my-aws-host -L 5532:my-rds-host-name.cdiofumqrcpr.us-east-1.rds.amazonaws.com:5432

Here my-aws-host is the EC2 instance that has DB access and my-rds-host-name.cdiofumqrcpr...:5432 is the RDS host name and port.

After that, you can use the localhost:5532 on your local machine to connect to the remote database, for example with psql:

psql postgresql://my_db_user@localhost:5432/my_db_name

Or dump the database with pg_dump:

pg_dump -Fc -v --dbname=postgresql://my_db_user@localhost:5432/my_db_name -f my_db_name$(date --iso-8601).pq

Note: both commands above don’t specify the database password, to make it work, the password can be specified in the ~/.pgpass file (so it will not be present in the shell history or visible on the screen when the command is executed).

The ~/.pgpass looks like this:

localhost:5432:*:my_db_user_one:XXXYYY
localhost:5432:*:my_db_user_two:XXXYYY

Example: Connect to Redis on AWS

Similarly to the PostgreSQL example above, we can create an ssh tunnel to the EC2 instance that has Redis access and expose Redis locally:

ssh my-ec2-instance -L 6379:id.wssxxx.0001.appp1.cache.amazonaws.com:6379

After that, we can use redis-cli or any other tool on the local machine to connect to redis on 6379 port.

Example: Expose Local Server Through the EC2 Instance

Sometimes it can be necessary to make locally running app available through the Internet. There are several ways to do that:

  • If you have the “real” IP address (you may need to ask your Internet provider to setup a real IP for you), you only need to make sure that your firewall allows connections to the port your app is running on
  • Use service like http://ngrok.com which will do the forwarding from the Internet to your local machine
  • Use SSH tunnel to the EC2 instance (or any other machine that’s accessible from the Internet)

Let’s say there is an app running locally on 8888 port (localhost:8888) and we want to make it available via the EC2 instance ec2-55-222-55-55.compute-1.amazonaws.com:8181.

To use the SSH tunnel, first, make sure there is a GatewayPorts yes option in the sshd config on the server. Ssh to the instance, open the /etc/sshd_config:

sudo vim /etc/sshd_config

Find and change or add GatewayPorts yes option, save the config. Restart sshd

sudo service sshd restart

Second, check and change, if necessary, the security group configuration for the EC2 instance and allow access to the port 8181.

Now, from your local machine run

ssh my-aws-host -R *:8181:localhost:8888

Now you should be able to access your local app via ec2-55-222-55-55.compute-1.amazonaws.com:8181.

Troubleshooting

If the connection fails, check the following:

  • ssh connection and tunnel established successfully - check the ssh output
  • sometimes, the connection is working, but the tunnel isn’t, in the ssh output you can see something like “Port forwarding is disabled to avoid man-in-the-middle attacks.” and the instructions on what to do
  • you don’t have anything running locally on the specified port
    • check with nc -v 127.0.0.1 5433 to see if there is a connection
    • additionally check with netstat -l | grep 5433 and lsof -i :5433

Useful searches for other issues are “ssh tunnel aws rds problem” and “ssh tunnel aws rds connection error”.

References

The Black Magic Of SSH / SSH Can Do That?

How to make requests from an external server to localhost

Connect to MongoDB on aws Server From Another Server

profile for Boris Serebrov on Stack Exchange, a network of free, community-driven Q&A sites