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!