Setting up sshttp

When I was travelling Europe I found some surprisingly restricted wi-fi hotspots in hotels. This was annoying because I use SSH to upload photos back home from my phone, but having not setup any tunneling helpers I just had to wait till I found a better one.

There are a number of solutions to SSH tunneling, but the main thing I wanted to do was implement something which would let me run several fallbacks at once. Enter sshttp.

sshttp is related to sslh, in the sense that they are both SSH connection multiplexers. The idea is that you point a web-browser at port 80, you get a web-page. You point your SSH client, and you get an SSH connection. Naive firewalls let the SSH traffic through without complaint.

The benefit of sshttp over sslh is that it uses Linux's IP_TRANSPARENT flag, which means that your SSH and HTTP logs all show proper source IPs, which is great for auditing and security.

This is a blog about how I set it up for my specific server case, the instructions I used as a guide were adapted from here.

Components

My home server hosts a number of daemons, but namely a large number of nginx name-based virtual hosts for things on my network. I specifically don't want nginx trying to serve most of these pages to the web.

The idea is that sshttp is my first firewall punching fallback, and then I can install some sneakier options on the web-side of sshttp (topic for a future blog). I also wanted sshttp to be nicely integrated with upstart in case I wanted to add more daemons/redirects in the future.

Installing sshttp

There's no deb package available, so installation is from github and then I copy it manually to /usr/local/sbin:

$ git clone https://github.com/stealth/sshttp
$ cd sshttp
$ make
$ sudo cp sshttpd /usr/local/sbin

Upstart Script

I settled on the following upstart script for sshttp (adapted from my favorite nodeJS launching script):

# sshttpd launcher
# note: this at minimum needs an iptables configuration which allows the
# outside ports you're requesting through.

description "sshttpd server upstart script"
author "will rouesnel"

start on (local-filesystems and net-device-up)
stop on shutdown

instance "sshttpd - $NAME"
expect daemon

#respawn
#respawn limit 5 60

pre-start script
    # Check script exists
    if [ ! -e /etc/sshttp.d/$NAME.conf ]; then
        return 1
    fi
    . /etc/sshttp.d/$NAME.conf

    # Clear up any old rules this instance may have left around from an
    # unclean shutdown
    iptables -t mangle -D OUTPUT -p tcp --sport ${SSH_PORT} -j sshttpd-$NAME || true
    iptables -t mangle -D OUTPUT -p tcp --sport ${HTTP_PORT} -j sshttpd-$NAME || true
    iptables -t mangle -D PREROUTING -p tcp --sport ${SSH_PORT} -m socket -j sshttpd-$NAME || true
    iptables -t mangle -D PREROUTING -p tcp --sport ${HTTP_PORT} -m socket -j sshttpd-$NAME || true

    iptables -t mangle -F sshttpd-$NAME || true
    iptables -X sshttpd-$NAME || true

    # Add routing rules
    if ! ip rule show | grep -q "lookup ${TABLE}"; then
        ip rule add fwmark ${MARK} lookup ${TABLE}
    fi

    if ! ip route show table ${TABLE} | grep -q "default"; then
        ip route add local 0.0.0.0/0 dev lo table ${TABLE}
    fi

    # Add iptables mangle rule chain for this instance
    iptables -t mangle -N sshttpd-$NAME || true
    iptables -t mangle -A sshttpd-$NAME -j MARK --set-mark ${MARK}
    iptables -t mangle -A sshttpd-$NAME -j ACCEPT

    # Add the output and prerouting rules
    iptables -t mangle -A OUTPUT -p tcp --sport ${SSH_PORT} -j sshttpd-$NAME
    iptables -t mangle -A OUTPUT -p tcp --sport ${HTTP_PORT} -j sshttpd-$NAME
    iptables -t mangle -A PREROUTING -p tcp --sport ${SSH_PORT} -m socket -j sshttpd-$NAME
    iptables -t mangle -A PREROUTING -p tcp --sport ${HTTP_PORT} -m socket -j sshttpd-$NAME
end script

# the daemon
script
    . /etc/sshttp.d/$NAME.conf

    /usr/local/sbin/sshttpd -n 1 -S ${SSH_PORT} -H ${HTTP_PORT} -L${LISTEN_PORT} -U nobody -R /var/empty >> ${LOG_PATH} 2>&1
end script

post-stop script
    . /etc/sshttp.d/$NAME.conf

    # Try and leave a clean environment
    iptables -t mangle -D OUTPUT -p tcp --sport ${SSH_PORT} -j sshttpd-$NAME || true
    iptables -t mangle -D OUTPUT -p tcp --sport ${HTTP_PORT} -j sshttpd-$NAME || true
    iptables -t mangle -D PREROUTING -p tcp --sport ${SSH_PORT} -m socket -j sshttpd-$NAME || true
    iptables -t mangle -D PREROUTING -p tcp --sport ${HTTP_PORT} -m socket -j sshttpd-$NAME || true

    iptables -t mangle -F sshttpd-$NAME || true
    iptables -X sshttpd-$NAME || true

    # Remove routing rules
    if ip rule show | grep -q "lookup ${TABLE}"; then
        ip rule del fwmark ${MARK} lookup ${TABLE}
    fi
    if ip route show table ${TABLE} | grep -q "default"; then
        ip route del local 0.0.0.0/0 dev lo table ${TABLE}
    fi

    # Let sysadmin know we went down for some reason.
    cat ${LOG_PATH} | mail -s "sshttpd - $NAME process killed." root
end script

This script nicely sets up and tears down the bits of iptables mangling and routing infrastructure needed for sshttp, and neatly creates chains for different sshttp instances based on configuration files. It'll only launch a single instance, so launching them all on boot is handled by this upstart script.

To use this script, you need an /etc/sshttp.d directory:

$ sudo mkdir /etc/sshttp.d

and a configuration file like the following, with a *.conf extension:

$ cat /etc/sshttp.d/http.conf
SSH_PORT=22022
HTTP_PORT=20080
LISTEN_PORT=20081
MARK=1
TABLE=22080
LOG_PATH=/var/log/sshttp.log

LISTEN_PORT is your sshttp port. It's 20081 because we're going to use iptables to forward port 80 to 20081 (to accomodate nginx - more on this later). SSH_PORT is an extra SSH port for openssh - so we have both 22 and 22022 open as SSH ports since 22022 can't be publically accessible (and we'd like 22 to be publically accessible).

HTTP_PORT is the port your web-server is listening on, for the same reasons as the SSH_PORT. MARK is the connection marker the daemon looks for - it has to be unique for each one. TABLE is the route table used for look ups - I think. This value can be anything - I think.

LOG_PATH I currently set to the same value for each host for simplicity - sshttp doesn't really log anything too useful (and one of it's features is that you don't need it's logs anyway).

IP Tables configuration

In addition to the sshttpd upstart scripts, some general iptables configuration was needed for my specific server.

To make configuring nGinx simpler for my local network, IP Tables is set to redirect all traffic coming into the server on the ppp0 interface (my DSL line) on port 80 and 443, to the listen ports specified for sshttpd for each interface (so port 80 goes to port 20081 in the above).

This means I can happily keep setting up private internal servers on port 80 on my server withou futzing around with bind IPs, and instead can put anything I want to serve externally onto port 20080 as per the configuration file above.

I like Firewall Builder at the moment for my configuration (although I'm thinking a shell script might be better practice in future).

The relevant IP tables lines if you were doing it manually would be something like

iptables -t nat -A PREROUTING -i ppp0 -p tcp -m tcp -d <your external ip> --dport 80 -j REDIRECT --to-ports 20081

Configuring iptables like this is covered fantastically elsewhere so I won't go into it here.

But with this redirect, externally port 80 now goes to sshttp, which then redirects it to either SSH or to the specific external application I want to serve over HTTP on port 20080.

Conclusion

At the moment with this setup I just have nginx serving 404s back on my sshttp server ports. But the real benefit is, I can turn those into secure proxy's to use with something like corkscrew or proxytunnel.

Or I can go further - use httptunnel to tunnel TCP over GET and POST connections (my actual intent) on the public facing HTTP ports. Or do both - each method has it's trade-offs, so we can just step down till we find one which works!