C²S Consulting logo
C²S Consulting | Primers | SSH (Reverse tunnelling)

Using SSH to access a Server behind a NAT

Diarmuid O'Briain, diarmuid@obriain.com
22/02/2022, version 2.0

Last updated: 22-02-2022 09:57


  • Cloud Server
    • Hostname: Public - middle.com
    • IP address: Public - 174.111.111.111
    • Username: charles
    • Password: babbage
  • Computer behind NAT
    • Hostname: Private - target.net
    • IP address: Private - 192.168.10.10
    • Username: ada
    • Password: lovelace
  • Workstation on the Internet
    • Hostname: Public/Private - laptop.eu
    • IP address: Public/Private - 174.222.222.222
    • Username: luigi
    • Password: menabrea

    SSH behind NAT

    Sometimes you have a computer (target.net) behind a Network Address Translation (NAT) and you require access it from a workstation with Internet access.

    Well from the computer (target.net) it is possible to SSH to a server (middle.com) on the Internet as the SSH connection traverses from the private network to the public one without issue. However it is not possible to perform a straight SSH connection from the a workstation (other) to the computer (target.net) as the latter has a private address. Another application of this is connecting to Virtual Machines (VM) that are behind a NAT. It can be problematic using the Copy/Paste functionality of the Hypervisor to GNU/Linux hosts who do not have X.org installed. Using reverse SSH tunnelling is a means to aviod the problem.

    SSH From computer (target.net) to server (middle.com)

    ada@target.net~$ ssh charles@middle.com
    charles@middle.com's password: babbage
    
    The programs included with the Debian GNU/Linux system are free software;
    the exact distribution terms for each program are described in the
    individual files in /usr/share/doc/*/copyright.
    
    Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
    permitted by applicable law.
    Last login: Tue Feb  22 15:52:53 2022 from 196.32.243.24
    charles@middle.com~$ 
    
    

    SSH from server (middle.com) to computer (target.net)

    Well obviously from the server (middle.com) or the workstation (laptop.eu) is is not possible to simply SSH to 192.168.10.10 as that sits behind a NAT. What can be done however is to establish an SSH from the computer (target.net) to the server (middle.com) and use a little known feature of SSH to reverse SSH (-R) back to the computer (target.net) from the server (middle.com). This command binds a remote TCP port to a local TCP port over a secure channel. On the remote server (middle.com), host port 22 traffic is forwarded to port 9999 on the the localhost (computer (target.net)). Note however while 9999 is used here, it can be any unused port number.

    ada@target.net~$ ssh -f -N -T -R 9999:localhost:22 charles@middle.com
    charles@middle.com's password: babbage
    
    Last login: Tue Feb 22 15:52:53 2022 from 196.32.243.24
    charles@middle.com~$ 
    
    

    Note: the flags in the command mean:

    -f    tells ssh to background itself after it authenticates, so you a shell is not opened from the server.
    -N    indicates that an SSH connection is required, but remote commands will not be ran. 
    -T    disables pseudo-tty allocation, which is appropriate because an interactive shell is not being ran.
    

    Now from the server (middle.com) it is possible via reverse SSH tunneling to connect to the computer (target.net) through this binding.

    charles@middle.com~$  ssh ada@localhost -p 9999
    ada@localhost's password: lovelace
    
    
    Last login: Tue Feb  22 16:17:48 2022
    ada@target.net~$
    
    

    Review the TCP connections

    Taking a look at the TCP connections between computer (target.net) and server (middle.com). Before the initial -R connection from computer (target.net) to server (middle.com).

    ada@target.net~$ netstat -at | grep -E '(9999|ssh)'
    tcp        0      0 *:ssh                   *:*                     LISTEN     
    tcp6       0      0 [::]:ssh                [::]:*                  LISTEN 
    
    charles@middle.com~$ netstat -at | grep -E '(9999|ssh)'
    tcp        0      0 *:ssh                   *:*                     LISTEN 
    tcp6       0      0 [::]:ssh                [::]:*                  LISTEN     
    
    

    Now after the bind connection is made from computer (target.net) to server (middle.com). An SSH connection can be seen established and there is a localhost:9999 TCP socket in Listen mode.

    ada@target.net~$ netstat -at | grep -E '(9999|ssh)'
    tcp        0      0 localhost:9999          *:*                     LISTEN     
    tcp        0      0 *:ssh                   *:*                     LISTEN     
    tcp        0      0 174.111.111.111:35140     174.111.111.111:ssh   ESTABLISHED
    tcp        0      0 174.111.111.111:ssh       174.111.111.111:35140 ESTABLISHED
    tcp6       0      0 ip6-localhost:9999      [::]:*                  LISTEN     
    tcp6       0      0 [::]:ssh                [::]:*                  LISTEN   
    
    

    Now after the SSH from the server (middle.com) to the computer (target.net), a second SSH connection can be seen established via localhost:9999.

    ada@target.net~$ netstat -at | grep -E '(9999|ssh)'
    tcp        0      0 localhost:9999          *:*                     LISTEN     
    tcp        0      0 *:ssh                   *:*                     LISTEN     
    tcp        0      0 localhost:41728         localhost:9999          ESTABLISHED
    tcp        0      0 192.168.10.3:35140      192.168.10.3:ssh        ESTABLISHED
    tcp        0      0 localhost:9999          localhost:41728         ESTABLISHED
    tcp        0      0 192.168.10.3:ssh        192.168.10.3:35140      ESTABLISHED
    tcp6       0      0 ip6-localhost:9999      [::]:*                  LISTEN     
    tcp6       0      0 [::]:ssh                [::]:*                  LISTEN    
    
    charles@middle.com~$ netstat -at | grep -E '(9999|ssh)'
    tcp        0      0 *:ssh                   *:*                     LISTEN     
    tcp        0    116 10.0.2.15:55008         192.168.10.3:ssh        ESTABLISHED
    tcp6       0      0 [::]:ssh                [::]:*                  LISTEN     
    tcp6       0      0 localhost:42366         localhost:ssh           ESTABLISHED
    tcp6       0      0 localhost:ssh           localhost:42366         ESTABLISHED
    
    

    SSH From workstation (laptop.eu) to computer (target.net) via server (middle.com)

    SSH to the server (middle.com) first. Once connected there continue with an SSH connection to the computer (target.net) via the tunnel.

    luigi@laptop.eu ~ $ ssh charles@middle.com
    charles@middle.com's password: babbage
    
    The programs included with the Debian GNU/Linux system are free software;
    the exact distribution terms for each program are described in the
    individual files in /usr/share/doc/*/copyright.
    
    Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
    permitted by applicable law.
    Last login: Tue Feb  22 15:52:53 2022 from 196.32.243.24
    charles@middle.com~$  ssh ada@localhost -p 9999
    ada@localhost's password: lovelace
    
    
    Last login: Tue Feb  22 16:17:48 2022
    ada@target.net~$
    
    

    Automating the tunnel setup

    Generate SSH keys.

    ada@target.net~$ ssh-keygen
    Generating public/private rsa key pair.
    Enter file in which to save the key (/home/ada/.ssh/id_rsa): 
    Created directory '/home/ada/.ssh'.
    Enter passphrase (empty for no passphrase): <leave blank>
    Enter same passphrase again: <leave blank> 
    Your identification has been saved in /home/ada/.ssh/id_rsa.
    Your public key has been saved in /home/ada/.ssh/id_rsa.pub.
    The key fingerprint is:
    SHA256:324ebiYmO4+a5Z9drmXp0ioI6tpQiE6x8vZ31D/2pyU ada@target.net
    The key's randomart image is:
    +---[RSA 2048]----+
    |                 |
    |                 |
    | .               |
    | .o.             |
    |oo. .   S.       |
    |+. .  . ....   . |
    | .+  . o....o.E .|
    | . +. .+=.+=OB.o.|
    |  .o+.oo+B+OB*=o |
    +----[SHA256]-----+
    

    Copy the SSH key to the server (middle.com) computer

    ada@target.net~$ ssh-copy-id charles@middle.com
    /usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/ada/.ssh/id_rsa.pub"
    /usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
    /usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
    charles@middle.com's password: <babbage>
    bash: warning: setlocale: LC_ALL: cannot change locale (en_IE.UTF-8)
    
    Number of key(s) added: 1
    
    Now try logging into the machine, with:   "ssh 'charles@middle.com'"
    and check to make sure that only the key(s) you wanted were added.
    

    Create the following directory if it does not exist already.

    ada@target.net~$ mkdir -p /home/${USER}/.config/systemd/user
    
    • -p, --parents: Make parent directories as needed.

    Create an Systemd service for the connection. Note this is NOT as sudo but as a regular user.

    ada@target.net~$ cat <<EOF > /home/${USER}/.config/systemd/user/sshtun.service
    [Unit]
    Description=Setup a secure tunnel to middle.com
    After=network.target
    
    [Service]
    ExecStart=/usr/bin/ssh -N -T \
    -o ServerAliveInterval=60 \
    -o ExitOnForwardFailure=yes \
    -R 9999:localhost:22 \
    -l charles \
    middle.com
    
    # Restart Regularly to avoid StartLimitInterval failure
    RestartSec=5
    Restart=always
    
    # Stop and reload commands
    ExecStop=/usr/bin/kill $MAINPID
    ExecReload=/usr/bin/kill -HUP $MAINPID
    
    [Install]
    WantedBy=default.target
    EOF
    

    Enable and start the service.

    ada@target.net~$ systemctl --user daemon-reload
    ada@target.net~$ systemctl --user enable sshtun.service
    ada@target.net~$ systemctl --user start sshtun.service
    ada@target.net~$ systemctl --user status sshtun.service
    

    loginctl may be used to introspect and control the state of the systemd and enable-linger enables a specified user such that a user manager is spawned for that user at boot time and kept around after logouts. This permits services owned by users who are not logged in to run long-running services.

    ada@target.net~$ sudo loginctl enable-linger ${USER}
    

    Check the service is running

    ada@target.net~$ systemctl --user status sshtun.service
    ● sshtun.service - Setup a secure tunnel to middle.comn
     Loaded: loaded (/home/pi/.config/systemd/user/sshtun.service; enabled; vendor preset: enabled)
     Active: active (running) since Tue 2022-02-22 15:03:26 GMT; 50min ago
     Main PID: 837 (ssh)
     CGroup: /user.slice/user-1000.slice/user@1000.service/sshtun.service
             └─837 /usr/bin/ssh -N -T -o ServerAliveInterval=60 -o ExitOnForwardFailure=yes -R 999:localhost:22 -l charles middle.com
    
    Feb 22 15:03:26 raspberrypi systemd[458]: Started Setup a secure tunnel to middle.com.
    

    Test the full connection from the workstation (laptop.eu)

    luigi@laptop.eu ~ $ ssh charles@middle.com
    charles@middle.com's password: babbage
    
    The programs included with the Debian GNU/Linux system are free software;
    the exact distribution terms for each program are described in the
    individual files in /usr/share/doc/*/copyright.
    
    Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
    permitted by applicable law.
    Last login: Tue Feb  22 15:52:53 2022 from 196.32.243.24
    charles@middle.com~$  ssh ada@localhost -p 9999
    ada@localhost's password: lovelace
    
    Last login: Tue Feb  22 16:17:48 2022
    ada@target.net~$
    
    
  • Copyright © 2024 C²S Consulting