Do you have services deep in another network that you’d like to access as if you were on that network, but only have access to a different host via SSH and no VPN? Then the answer to your problems may just be SSH tunneling. SSH tunneling requires only a single SSH command and sets up an encrypted tunnel between a port on your local machine and the target SSH server, referred to hereafter as the jumpbox (you may also hear it referred to as a bastion host). The jumpbox can then forward traffic to a service it can see, providing you reasonably secure access from your local machine to the otherwise inaccessible service.

An example tunnel

Here’s an example command that establishes a tunnel from the local port 1234 to port 8080 on internal-server.othernetwork.lan via the jumpbox.somedomain.com jumpbox. We’ll use this example for the remainder of the post.

ssh -L 1234:internal-server.othernetwork.lan:8080 \
     chrisumbel@jumpbox.somedomain.com

With the component parts being:

ssh -L <local port>:<deep service host>:<deep service port> \
     <username>@<jumpbox host>

That might be enough to get some of you going, but for those of you unsure about what hostname/IP and port to plug into the command I’ll provide some clarity by way of a diagram. Every box that’s green is a computer and the orange box is a process running on the workstation the human is sitting at. The green line represents encrypted traffic and could certainly be traversing the internet. The red lines are plaintext (or at least not encrypted by SSH) traffic. Again, we can curl at localhost:1234, the traffic flows through the SSH tunnel to jumpbox.somedomain.com, and then terminates at internal-server.othernetwrok.lan:8080.

Machines:

  • workstation: The machine the human is directly using. It is likely on an entirely different network than the rest of the machines in the diagram.
  • jumpbox.somedomain.com: This machine is the SSH server that’s accessible to workstation. This machine may very well even be accessible over the open internet. For our purposes here this machine will be running no other useful services. It’s job is simply to pass traffic through to our deeply embedded server: internal-server.othernetwork.lan.
  • internal-server.othernetwork.lan: This is the deep service we’re trying to connect to. This machine is likely not accessible over the internet or by workstation directly in any way. In practice this machine might be a database server, internal app server, or something that’s relatively well-protected. For our purposes here, we’ll just run an nginx process on this server on port 8080, but it could be anything.

Example in action

The first thing we’ll do is establish the SSH tunnel from our workstation to the jumpbox with the following command.

ssh -L 1234:internal-server.othernetwork.lan:8080 \
     chrisumbel@jumpbox.somedomain.com

We’re now connected via SSH.

What may not be obvious, however, is that we also established a tunnel from our local port 1234 (any non-privileged port that’s not in use will do) through the jumpbox to internal-server.othernetwork.lan on port 8080. Note the -L parameter, which is what instructed SSH to set the tunnel up. The ugly format of its argument is:

<local port>:<deep service hostname or ip>:<deep service port>

which in our case is:

1234:internal-server.othernetwork.lan:8080

As a test payload we’ll run nginx on internal-server.othernetwork.lan. In this case, nginx is configured to run on port 8080. Note that this traffic could be almost anything TCP-based, like traffic to a database server. There are also advanced techniques for forwarding UDP traffic, but they’re beyond the scope of this article.

Now we’re ready to test our tunnel! We’ll use curl on our workstation, just to keep things simple. Note that on workstation we’re curling to our own localhost! That’s because the tunnel is created with the near end on the local machine. This traffic will then be passed to internal-server.othernetwork.lan per the -L parameter we passed to ssh.

It worked!

Conclusion

Well, there you have it. We’ve tunneled traffic through a jumpbox to a service on a private network that wasn’t exposed to us. In this case, we only piped a single port through, but keep in mind that multiple -L parameters can be specified — creating additional tunnels to additional host/port combinations.

While SSH tunneling doesn’t replace technologies like VPNs, it’s great for a quick-and-dirty alternative that still maintains a reasonable level of security, for many purposes.