SSH Tunneling Primer

SSH Tunneling, is the ability to use ssh to create a bi-directional encrypted network connection between machines over which data can be exchanged, typically TCP/IP. This allows us to easily & securely make services available between machines with minimal effort while at the same time leveraging ssh for user authentication and encryption with little to no overhead. The icing on the cake is that you get encryption out of the box without any additional work on your application service side.

NOTE: I will not be deep diving and explaining each and every ssh flag used below, the idea is to showcase the power of ssh tunneling. For a deeper understanding of the ssh commands & flags used, kindly refer to the ssh manual.

Use Cases

Local Port Forwarding:

$ ssh -nNT -L 8000:localhost:3306 user@server.com

The above command sets up an ssh tunnel between your machine and the server, and forwards all traffic from localhost:3306 (in the context of server, localhost is the server itself. 3306 is the default port of MySQL) to localhost:8000 (on your machine).

So now you could connect to MySQL running on your server via localhost on port 8000 on your machine. This is useful when you want to expose services running on your server which isn’t accessible to the outside world directly (for good reason).

Note that this also allows you to be able to forward multiple MySQL servers from multiple servers to your local machine over different ports and access data safely.

Remote Port Forwarding:

$ ssh -nNT -R 4000:localhost:3000 user@server.com

The above command sets up an ssh tunnel between your machine and the server, and forwards all traffic from localhost:3000 (on your machine) to localhost:4000 (in the context of the server). So now you can connect to the locally running service on port 3000 on the server on port 4000.

This trick is very useful because it allows you to be able to expose a locally running service through your server to others on the internet without having to deploy it / setup on the server.

SOCKS Proxy:

$ ssh -D 5000 -nNT user@server.com

The above command sets up a SOCKS proxy server supporting SOCKS4 and SOCKS5 protocols leveraging dynamic application-level port forwarding through the ssh tunnel. You could now configure your network proxy (within the browser or OS-wide as supported by the OS you’re using) to localhost:5000 as a SOCKS proxy and then when you browse, all the traffic is proxied through the ssh tunnel using your server.

This has a lot of benefits :

* It protects others (ISP/Govt./Hackers/etc) from eve’s dropping since all the traffic is encrypted. Even if you’re accessing websites/services which do not support encryption (https) the traffic is encrypted.

* Since the entire web traffic goes through the SOCKS proxy, you will be able to access web sites that your ISP/firewall may have blocked.

* It also helps protect your privacy since, for the web services you access, they will see the requests as coming from the server as opposed to coming from your machine. So that protects your identity, your location information (IP based location tracking), etc.

Advanced Use Cases

The above-mentioned use cases are the most commonly used, however, they can be modified slightly and used in interesting ways to be able to establish the ssh tunnel not only between your local machine and your server, but also additional machines, either internal to your network or internal to your servers network. Let us see how.

$ ssh -nNT -R 0.0.0.0:4000:192.168.1.101:631 user@server.com

Notice the subtle difference in the above command in contrast with the one I showed previously (for Remote Port Forwarding).

Instead of using the default bind address, I explicitly use 0.0.0.0. This implies that the service available on the server on port 4000, will be accessible internally to the server network across all networks interfaces, including bridge networks & virtual networks such as used by container environments such as docker.

Instead of using localhost as the bind address for the local service, I have explicitly used the 192.168.1.101 IP Address, which can be the IP Address of an internal machine (other than the machine you’re using this command on), such as a network printer. This allows me to be able to expose and use my internal network printer directly from the server, without any additional changes from within my internal network either on the router or the network printer.

NOTE: From the ssh manual regarding bind address usage :

By default, the local port is bound in accordance with the GatewayPorts setting. However, an explicit bind_address may be used to bind the connection to a specific address. The bind_address of ``localhost’’ indicates that the listening port be bound for local use only, while an empty address or `*’ indicates that the port should be available from all interfaces.

This technique can also be used while doing a local port forwarding or for setting up the socks proxy server in a similar manner.