Using reverse SSH tunnels to expose a service that lives behind a firewall
Consider the following scenario:
On my local desktop I am using docker to do some testing with a Mongo database. My router doesn't expose the Mongo port. A friend at work needs to access my awesome and suuuuper important data in Mongo but he's also behind a firewall.
In other words:
- I cannot directly connect to my friend's work computer
- My friend cannot directly connect to my work computer
The solution is to use an intermediate host (a cheap VPS :)) so that both can connect there and exchange data.
Here's what the diagram of this looks like:
Reverse SSH tunnel that forwards +----------------------------+ SSH tunnel that forwards 127.0.0.1:36531
127.0.0.1:36531 to the internet | | to the local 127.0.0.1:27017.
accessible server +---------------> | internet accessible server | <-----------------+This is initiated by my
at 127.0.0.1:36531| | with SSH listening on | |friend at work.
This is initiated | | non-standard port 22222 | |
on my desktop. | | | |
| | No other ports are open. | |
| +----------------------------+ |
| |
| |
| |
| |
| |
+ +
+---------------------+ +-----------------------+
| | | |
| My desktop - it's | | Friend at work who is |
| not accessible from | | behind a firewall and |
| the internet and | | wants access to my |
| Mongo is listening | | Mongo installation |
| on 127.0.0.1 on a | | |
| non-standard port | | |
| 36531 | | |
+---------------------+ +-----------------------+
On the internet-accessible server:
(It is assumed you have already created an instance/VPS/droplet (whatever your provider calls it)) :
1. SSH into the instance
2. Install net-tools so you can get netstat
yum install net-tools -y
3. Edit /etc/ssh/sshd_config, find the line with:
#Port 22
and change it to:
Port 22222
4. Restart the SSH daemon:
systemctl restart sshd
5. Confirm that SSH is listening on port 22222 using netstat -ntlp | grep sshd:
netstat -ntlp | grep sshd
tcp 0 0 0.0.0.0:22222 0.0.0.0:* LISTEN 980/sshd
tcp6 0 0 :::22222 :::* LISTEN 980/sshd
6. Add a user:
useradd lampros
7. Set some password:
This will prompt you for your password twice:
passwd lampros
8. Now I'll add a user for my friend at work - this user won't have shell access and no home directory:
useradd --no-create-home --no-user-group --shell /sbin/nologin friend-at-work
9. Set a password for him:
This will prompt you for your password twice:
passwd friend-at-work
On my desktop:
1. Use docker to start Mongo 3.6 and set it to listen to 127.0.0.1:36531:
docker run -dt --name mongo -p 127.0.0.1:36531:27017 mongo:3.6
2. Confirm that Mongo is listening on 127.0.0.1:36531 by using netstat -ntlp | grep 36531:
lampros@portable-goat:~$ netstat -ntlp | grep 36531
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 127.0.0.1:36531 0.0.0.0:* LISTEN -
3. Sanity check - try to access Mongo's port using curl - you should get a response:
curl 127.0.0.1:36531
It looks like you are trying to access MongoDB over HTTP on the native driver port.
4. Forward 127.0.0.1:36531 to the internet-accessible server over port 22222 to 127.0.0.1:36531 - you'll be prompted for the password you set earlier
ssh -p22222 -nN -R 36531:127.0.0.1:36531 lampros@104.245.32.196
5. Back on the internet-accessible server - confirm that the port is forwarded:
netstat -ntlp | grep 36531
tcp 0 0 127.0.0.1:36531 0.0.0.0:* LISTEN 1012/sshd: lampros
tcp6 0 0 ::1:36531 :::* LISTEN 1012/sshd: lampros
6. And once again check with curl:
curl 127.0.0.1:36531
It looks like you are trying to access MongoDB over HTTP on the native driver port.
Now my friend at work needs to do the following:
1. Forward port 36531 from the internet-accessible server to 127.0.0.1:27017:
ssh -4 -p22222 -N -L 27017:127.0.0.1:36531 friend-at-work@104.245.zzz.yyy
2. Test that it works:
curl 127.0.0.1:27017
It looks like you are trying to access MongoDB over HTTP on the native driver port.
Yay!