Weigh a Tunnel
Author: Mark Devine, Principal Automation Engineer, Amach Software

Weigh a Tunnel

The value of SSH Tunnels for remote working

When a person works from an office within a company the local network offers accessibility of services that are not available directly to remote workers. The internal network has a local DNS server which enables workers to access services via their internal hostname. Also the internal network of the organisation has internal IP addresses that are not exposed to the outside world where the remote worker is situated.

Events in the year 2020 meant a lot more workers are working remotely. These workers, used to the easy accessibility of local network services, find themselves more constricted in remote working situations. The limitations they experience could lead to less than optimal performance and affect the bottom line of their employers.

A solution to this issue is to bring the office network into the remote worker’s workspace. If the worker is used to accessing a service on HostA on port B then have the worker access the service on HostA and port B as they would if they were directly within the office network. This can be achieved using SSH Tunnels.

What is a SSH Tunnel?

A SSH Tunnel allows you to forward traffic on a port of your local computer to a SSH server, which is forwarded to a destination server. The ssh connection through the firewall is encrypted, protects confidentiality and integrity, and authenticates communicating parties.

No alt text provided for this image

The basic syntax for a local port forward command is straightforward:

ssh -L local_port:destination_server_ip:remote_port ssh_server_hostname

⦁  ssh – Starts the SSH client program on the local machine and establishes a secure connection to the remote SSH server.

⦁  -L local_port:destination_server_ip:remote_port – The local port on the local client is being forwarded to the port of the destination remote server.

⦁  ssh_server_hostname – This element of the syntax represents the hostname or IP address of the remote SSH server.

A practical example of an SSH port forwarding command would take the following form:

ssh –L 5901:10.10.10.10:4492 pnap@ssh.server.com

In the example above, all traffic sent to port 5901 on your local host is being forwarded to port 4492 on the remote server located at 10.10.10.10.

You can also set up multiple port forwarding to multiple machines in the same command:

$ ssh username@jumpoffServerIp -L6010:remoteServer1.company.com:6010 -L5120remoteServer2.company.com:5120 -L5250:remoteServer3.company.com:5250 -o ServerAliveInterval=100 -fN

$ curl http://localhost:5250/ls/api/shards | python -m json.tool

The above curl is the same as running from remoteServer3.company.com itself i.e. port 5250 on remoteServer3.company.com is mapped to port 5250 locally. In the same sense port 6010 on remoteServer1.company.com is mapped to port 6010 locally and port 5120 on remoteServer2.company.com is mapped to port 5120 locally

The -o option adds options to the ssh command. In this case the serverAliveInterval is set to 100 secs. This means after 100 secs the client will send a null packet to the server to keep the connection alive. The f option puts the ssh tunnel into the background and the N option suppresses starting a shell or command. This option is for port forwarding and your user account on the server does not have the ability to run a shell.

Example

Here is a Python coded version of a ssh Tunnel that can be used to access a private server. It is based on access to the private server being only allowed from a jumpoff server. The user also has access to the jumpoff server. It uses sshtunnel and paramiko. Installation of both of these packages can be done with the following:

pip install paramiko
pip install sshtunnel

After you install both packages create a Tunnel class with the following code:

from sshtunnel import SSHTunnelForwarder
import paramiko


class Tunnel:
    def __init__(self, jumpoff, ssh_username, ssh_pkey, remote_bind_address, localPort):
        self.tunnel=SSHTunnelForwarder(jumpoff, ssh_username=self.ssh_username, ssh_pkey=self.ssh_pkey, remote_bind_address=remote_bind_address, local_bind_address=('0.0.0.0', localPort))
        self.tunnel.start()
        self.client = self.openTunnel()

    
   def openTunnel(self):
        client = paramiko.SSHClient()
        client.load_system_host_keys()
        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        print("connecting to tunnel")

        try:
            client.connect('127.0.0.1', self.localPort, self.ssh_username, key_filename=self.ssh_pkey, banner_timeout=30, timeout=60)
        except Exception as e:
            print(f"Exception thrown connecting to tunnel: {e}")
        
        print("connected to tunnel")
        return client

    
   def runCmd(self, cmd):
        stdin, stdout, stderr = self.client.exec_command(cmd)
        return ''.join(stdout.readlines()).strip()

    
   def closeTunnel(self):
        self.tunnel.stop()

The tunnel can simply be used by opening a python shell and instantiating the class. The runCmd method can be used to verify the connection like so:

tun = Tunnel(jumpoffUrl, jumpoffUser, jumpoffKeyFile, (cassandraHost, int(cassandraPort)), int(cassandraPort))
tun.runCmd(“hostname”)

where 

• the jumpoffUrl can be the fully qualified hostname or ip address of the jumpoff server

• the jumpoffUser is the username that has access to the jumpoff server

• the jumpoffKeyFile is the ssh private key file name that has its public key in the authorized_keys file in the jumpoff server

• the cassandraHost is the remote private server that we want to get to. For this example it’s a host for a Cassandra DB

• the cassandraPort is the port to communicate with Cassandra on. In this case the Cassandra port on the private server is mapped to the same port on the local server

the runCmd should return the hostname of the remote server that hosts the Cassandra DB. You can use it to run shell commands on the remote server

For this example a Cassandra DB is hosted on the private server. Here is a Cassandra class that will access a Cassandra database hosted locally with a query method to query it. It uses cql which can be installed using:

pip install cql

Here is the code for the class:

class Cassandra:
    def __init__(self, port):
        self.conn=cql.connect('localhost', port,  'SAL', cql_version='3.2.1', native=True)
        self.cursor = self.conn.cursor()

    def query(self, cmd):
        self.cursor.execute(cmd)
        return self.cursor.fetchall()

Place the following method in a class that you want to have access to the remote Cassandra DB. When you call the method a connection is made to the remote Cassandra DB via the Tunnel class and you can use the query method from the Cassandra class (above) to query the DB like it was hosted locally.

def cassandra(self):
    try:
        self.cassandraTunnel=Tunnel(self.config['jumpoffUrl'], self.config['jumpoffUser'], self.config['jumpoffKeyFile'], (self.config['cassandraHost'], int(self.config['cassandraPort'])), int(self.config['cassandraPort']))

    except paramiko.ssh_exception.SSHException:
        pass

    self.conn=Cassandra(int(self.config['cassandraPort']))

This is just one example of how you could use ssh tunnels to make your remote working environment more like the office. 

Conclusion

When you are working remotely and you need to access services as if you were in the office a solution is to use ssh tunneling. It's easy to implement and can be coded and incorporated into test and development frameworks. The connection is secure. A test engineer, for example, could have his test framework on his local machine and access the corporate staging environment to test software through a secure ssh tunnel. 

This is part of the amach software approach to setting up functional and end-to-end test frameworks that integrate with your CI/CD. For more information contact Amach

Links:

https://www.ssh.com/ssh/tunneling/#benefits-of-ssh-tunneling-for-enterprises

https://sshtunnel.readthedocs.io/en/latest/

http://www.paramiko.org/


To view or add a comment, sign in

More articles by Mark Devine

  • All together now - follow my queue!

    Parallel testing when unique users are required for each running test I have often come across situations where I had a…

  • Make your Tests Stub-borne

    Using Stubs to enable testing when all components are not available In practically every company I have tested systems…

  • When SCP makes you feel RSYNC

    Transferring large files over scp with rsync On Mac or Linux copying large files over to a remote server via SCP can be…

  • Picture the Problem!

    Screenshot the UI when tests fail After a test suite has been created and many test cases are in place, sometimes…

  • App Testing with Limited Hardware

    Getting the Most Testing with Available Hardware From our time working with various clients, Amach has come across a…

    2 Comments
  • Leveraging containers for automated testing

    Based on our experience working with Amach clients, one area that invariably throws up issues is setting up test…

    6 Comments
  • Test Assured!

    REST is a very popular software architectural style that defines a set of constraints to be used for creating Web…

  • Test Data Generation Using Model Classes

    When testing modern REST based microservices and systems REST-assured is a great tool for testing the RESTful endpoints…

Others also viewed

Explore content categories