28 December 2010

Local Port Forwarding In SSH

Anyone familiar with UNIX administration is almost assuredly familiar
with SSH.  As a de facto standard of remote access, it can be used as
a replacement to telnet, ftp, rcp, and rlogin.  Additionally, it can
be used to provide a secure channel for other needs such encrypting
arbitrary IP services between two hosts or tunnelling IP services
through an intermediary host.  The following details how to use local
port forwarding to access web services on a remote network that are
otherwise unavailable from the outside world.  Our setup details:
        PROMPT:         troy@server [0]
        LOCALBOX:       homebox (home workstation)
        INTERMEDIARY:   jumphost (internet accessible)
        REMOTE HOSTS:   mgmtserv (10.18.3.138, corporate accessible only)
                        monserv  (10.5.76.28, corporate accessible only)
        NOTES:          OpenSSH is used in the examples on both the
                        localbox and intermediary, though SUNWssh would
                        also work (presumably SSH from SSH.com would
                        work as well, but I haven't tested it)
So I'm working from home and I need to access a management web host
and a monitoring web host at work.  The problem is that both of these
hosts serve up pages only accessible to internal networks at work.
Thankfully, I can use SSH to tunnel connections I make to local ports on
my workstation (homebox) to the ports that the remote internal servers
are listening on (see note 1).  Knowing in advance that I need to connect
to two remote hosts, I can use:
        troy@homebox [0] ssh -l troy jumphost -L8080:10.18.3.138:80 -L8081:10.5.76.28:80
        troy@jumphost's password:
        Last login: Tue Dec 28 01:04:01 2010 from homebox
        troy@jumphost [0]
At this point, I can simply open a web browser to access either of the
internal web sites by connecting to ports bound to localhost on homebox:
        http://localhost:8080
                |-> mgmtserv
        http://localhost:8081
                |-> monserv
Let's say instead that I tunnelled connectivity to 'mgmtserv' through
'jumphost' and after the session was open, I remembered that I needed
to connect to 'monserv' as well.  Instead of opening a new SSH session
through the intermediary host (jumphost), I can add a new port forwarded
session to the existing active SSH session (note 2):
        troy@homebox [0] ssh -l troy jumphost -L8080:mgmtserv:80
        troy@jumphost's password:
        Last login: Thu Dec  9 13:22:57 2010 from homebox
        troy@jumphost [0] ~C
        ssh> -L8081:monserv:80
        Forwarding port.

        troy@jumphost [0]
After logging in to 'jumphost' above, the first command shows '~C', which
opens a command line session to be interpreted by SSH and not the shell
(note 3).  Following the 'ssh>' prompt, we can add both local and remote
port forwardings.  For our needs, we are adding a locally forwarded port.
After this is done, if you would like to see a listing of your forwarded
connections, use '~#':
        troy@jumphost [0] ~#
        The following connections are open:
          #4 client-session (t4 r0 i0/0 o0/0 fd 8/9 cfd -1)
The above shows the current ssh, though before connecting to any locally
forwarded ports on 'homebox'.  The example below shows the same, though
also the connection to 'mgmtserv' after connecting to localhost:8080 on
'homebox':
        troy@jumphost [0] ~#
        The following connections are open:
          #4 client-session (t4 r0 i0/0 o0/0 fd 8/9 cfd -1)
          #7 direct-tcpip: listening port 8080 for 10.18.3.138 port 80, connect from 127.0.0.1 port 52500 (t4 r3 i0/0 o3/0 fd 13/13 cfd -1)
While I suggested earlier using a web browser to connect to either of
the remote hosts, SSH can tunnel arbitrary services, not just HTTP.
This means that you are not required to use a web browser to access
those services.  In the following, I simply use telnet to connect to
to the locally forwarded ports on 'homebox' which connect to the
remote services (note 4):
    mgmtserv:
        troy@homebox [0] telnet 0 8080
        Trying 0.0.0.0...
        Connected to 0.
        Escape character is '^]'.
        GET / HTTP/1.1
        HOST: mgmtserv

        HTTP/1.1 200 OK
        Date: Tue, 28 Dec 2010 07:34:51 GMT
        Server: Apache/2.2.6 (FreeBSD) mod_ssl/2.2.6 OpenSSL/0.9.7e-p1 DAV/2 PHP/5.2.4 with Suhosin-Patch
        X-Powered-By: PHP/5.2.4
        Set-Cookie: PHPSESSID=c2b347fbc78ea76b8d989231fc8ba49c; path=/
        Expires: Thu, 19 Nov 1981 08:52:00 GMT
        Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
        Pragma: no-cache
        Content-Length: 3090
        Content-Type: text/html
        <snip...>

    monserv:
        troy@homebox [0] telnet 0 8081
        Trying 0.0.0.0...
        Connected to 0.
        Escape character is '^]'.
        GET / HTTP/1.1
        HOST: monserv

        HTTP/1.1 200 OK
        Date: Tue, 28 Dec 2010 07:35:49 GMT
        Server: Apache/2.2.3 (Red Hat)
        X-Powered-By: PHP/5.1.6
        Expires: Thu, 19 Nov 1981 08:52:00 GMT
        Last-Modified: Tue, 28 Dec 2010 07:35:54 GMT
        Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
        Pragma: no-cache
        P3P: CP="CAO PSA OUR"
        Set-Cookie: Cacti=m24387bvvna78238g4l5ar8md2; path=/
        Content-Length: 1189
        Connection: close
        Content-Type: text/html; charset=UTF-8
        <snip...>
In the above, I've asked the remote web servers for the root page and
told it which host I want the page for (in case the server uses name
based hosting):
        GET / HTTP/1.1
        HOST: mgmtserv
Other commands to the web server could have been used but this will
suffice for example purposes.  After I've finished working, closed out
the connections to the local ports on 'homebox' and ended the ssh session
through 'jumphost', again using telnet, I get back the following showing
the local ports are now closed:
        troy@homebox [0] telnet 0 8080
        Trying 0.0.0.0... 
        telnet: connect to address 0.0.0.0: Connection refused
        troy@homebox [1] telnet 0 8081
        Trying 0.0.0.0...
        telnet: connect to address 0.0.0.0: Connection refused
        troy@homebox [1]

NOTES

Note 1: To use port forwarding via SSH, 'AllowTcpForwarding' on the
    intermediary host must be enabled (set to "yes"; see sshd_config).
    Also, make sure to use local ports that either aren't blocked by a
    local firewall or modify your local firewall to allow connectivity
    to the local ports.  The local ports will listen on localhost
    (127.0.0.1), which is also where you will be connecting from.

Note 2: In the second port forwarding example, host names are used
    instead of IP addresses.  This is perfectly acceptable as long as
    the intermediary host can resolve those host names to an IP address.
    If it cannot, IP addresses must be used.

Note 3: You will likely not see '~C' appear on screen since it is caught
    by SSH as an escape sequence to allow you to add the additional port
    forwardings.  It is displayed here simply for reference.  The example
    showing open sessions with '~#' will behave in the same manner.
    Also, SSH escape sequences must always be followed by a newline to
    be interpreted by SSH.

Note 4: Somewhat unrelated, using telnet as listed can be a useful means
    of troubleshooting HTTP services.  You could use telnet to
    troubleshoot other services in this manner as well.