SSH Tunnels

4 min read


Hello Stackers, SSH Tunnels are about connecting hosts over the network, so every lab below expectedly involves multiple “machines”. Every example requires a valid passphrase-less key pair on the host that is then mounted into the containers to simplify access management. If you don’t have one, generating it is as simple as just ssh-keygen on the host.

Important: SSH daemons in the containers here are solely for educational purposes – containers in this post are meant to represent full-blown “machines” with SSH clients and servers on them. Beware that it’s rarely a good idea to have SSH stuff in real-world containers!

Local Port Forwarding

Starting from the one that we use the most. Oftentimes, there might be a service listening on localhost or a private interface of a machine that we can only SSH to via its public IP. And we desperately need to access this port from the outside. A few typical examples:

  • Accessing a database (MySQL, Postgres, Redis, etc) using a fancy UI tool from your laptop.
  • Using your browser to access a web application exposed only to a private network.
  • Accessing a container’s port from your laptop without publishing it on the server’s public interface.

All of the above use cases can be solved with a single ssh command:

ssh -L [local_addr:]local_port:remote_addr:remote_port [user@]sshd_addr

The -L flag indicates we’re starting a local port forwarding. What it actually means is:

  • On your machine, the SSH client will start listening on local_port (likely, on localhost, but it depends – check the GatewayPorts setting).
  • Any traffic to this port will be forwarded to the remote_private_addr:remote_port on the machine you SSH-ed to.

Here is how it looks on a diagram:

Pro Tip: Use ssh -f -N -L to run the port-forwarding session in the background.

Local Port Forwarding with a Bastion Host

It might not be obvious at first, but the ssh -L command allows forwarding a local port to a remote port on any machine, not only on the SSH server itself. Notice how the remote_addr and sshd_addr may or may not have the same value:

ssh -L [local_addr:]local_port:remote_addr:remote_port [user@]sshd_addr

Not sure how legitimate the use of the term bastion host is here, but that’s how we visualize this scenario for myself:

I often use the above trick to call endpoints that are accessible from the bastion host but not from my laptop (e.g., using an EC2 instance with private and public interfaces to connect to an OpenSearch cluster deployed fully within a VPC).

Remote Port Forwarding

Another popular (but rather inverse) scenario is when you want to momentarily expose a local service to the outside world. Of course, for that, you’ll need a public-facing ingress gateway server. But fear not! Any public-facing server with an SSH daemon on it can be used as such a gateway:

ssh -R [remote_addr:]remote_port:local_addr:local_port [user@]gateway_addr

The above command looks no more complicated than its ssh -L counterpart. But there is a pitfall…

By default, the above SSH tunnel will allow using only the gateway’s localhost as the remote address. In other words, your local port will become accessible only from the inside of the gateway server itself, and highly likely it’s not something you actually need. For instance, we typically want to use the gateway’s public address as the remote address to expose my local services to the public Internet. For that, the SSH server needs to be configured with the GatewayPorts yes setting.

Here is what remote port forwarding can be used for:

  • Exposing a dev service from your laptop to the public Internet for a demo.
  • We can think of a few esoteric examples, but we doubt it’s worth sharing them here. Curious to hear what other people may use remote port forwarding for!

Here is how the remote port forwarding looks on a diagram:

Pro Tip: Use ssh -f -N -R to run the port-forwarding session in the background.

Remote Port Forwarding from a Home/Private Network

Much like local port forwarding, remote port forwarding has its own bastion host mode. But this time, the machine with the SSH client (e.g., your dev laptop) plays the role of the bastion. In particular, it allows exposing ports from a home (or a private) network your laptop has the access to to the outside world through the ingress gateway:

ssh -R [remote_addr:]remote_port:local_addr:local_port [user@]gateway_addr

Looks almost identical to the simple remote SSH tunnel, but the local_addr:local_port pair becomes the address of a device in the home network. Here is how it can be depicted on a diagram:

Conclusion

  • The word “local” can mean either the SSH client machine or an upstream host accessible from this machine.
  • The word “remote” can mean either the SSH server machine (sshd) of an upstream host accessible from it.
  • Local port forwarding (ssh -L) implies it’s the ssh client that starts listening on a new port.
  • Remote port forwarding (ssh -R) implies it’s the sshd server that starts listening on an extra port.
  • The mnemonics are “ssh -L local:remote” and “ssh -R remote:local” and it’s always the left-hand side that opens a new port.

Bima Sena

Leave a Reply

Your email address will not be published. Required fields are marked *