easy tunneling with sshuttle
sshuttle is a genius program that allows you to tunnel all of your traffic through SSH. as a result, it can act as a VPN for any machine you can SSH to. you don't even need to be an admin of the distant server to achieve this. how amazing is that?
here's what we want to achieve through SSH tunneling:
- easily redirect all traffic through the SSH tunnel, DNS and all UDP traffic included
- automate even more the process with some scripting
here's what we want to avoid by using sshuttle:
- wasting hours configuring things on the server acting as a proxy
- creating a new connection through
nmcli
rather than being tunneled automatically - messing things up by misconfiguring something, openvpn being a good example of this.
we'll still have to make a reasonably configured server for our proxy, but this will take way less time than setting up a VPN with any other tool. let's get started, with a digitalocean VPS.
setting up a digitalocean VPS with sane defaults
first of all, create your droplet on digitalocean. even if this would work for many linux flavor, this tutorial will use ubuntu. pick the latest LTS version and choose to create a $5 droplet, which will be more than enough for a proxy.
choose to paste your SSH public key to login in the settings. a one time password is hardly secure nor a good idea.
once your droplet is up and online, connect with SSH:
ssh root@my-droplet-ip
first thing you should do is updating this freshly created server:
apt-get update
then, we should work on disabling root access. let's create a new user on this server:
adduser your-user
give it a strong password during the creation process. next, add this user to the sudo group:
usermod -aG sudo your-user
and while we're at it, enable ufw (uncomplicated firewall). we're gonna use it later on:
ufw enable
we wanna connect through SSH with the user we just created. just as the same way we provided our public key during the droplet creation, we now have to repeat this process for the new user. first, switch from root to the user:
su - your-user
and then, create a .ssh folder with the appropriate permissions:
mkdir ~/.ssh
chmod 700 ~/.ssh
on your client (not the server!), run the following command to get the output of your public key:
cat ~/.ssh/id_rsa.pub
then paste it in the following file on the server:
vim ~/.ssh/authorized_keys
save the file and exit, and change again the permissions to restrict access to the file:
chmod 600 ~/.ssh/authorized_keys
now return to the root user:
exit
the last step with SSH is to edit sshd_config in order to restrict the access via the created user and the corresponding public key:
vim /etc/ssh/sshd_config
look up and change appropriately the following lines in this file. make sure they are not commented as well (not beginning with #):
PasswordAuthentication no
PubkeyAuthentication yes
ChallengeResponseAuthentication no
PermitRootLogin no
we're almost done, but keep in mind that we have to allow incoming SSH connections with ufw:
ufw limit ssh/tcp
The limit setting here will ban any IP attempting and failing to connect repeatedly. combined with our changes on sshd_config, this is more than enough to protect this opened port.
last step for good measure is upgrading the system and rebooting the server:
apt-get dist-upgrade && shutdown -r now
after reboot, make sure that you cannot SSH to the server with root, and connect with the created user instead:
ssh your-user@my-droplet-ip
we're 100% done with our server, unless you're interested in the misc part at the end of this article. let's move on to the client side.
setting up sshuttle
sshuttle is already available on most package managers. on archlinux, you can easily get it with yay
:
yay sshuttle
keep in mind that sshuttle doesn't tunnel UDP traffic (except DNS) by default. a bit of extra work is needed on that part with tproxy, as described in the documentation: https://sshuttle.readthedocs.io/en/stable/tproxy.html
basically it boils down to the following steps:
- run the following command as root after booting up:
ip route add local default dev lo table 100
ip rule add fwmark 1 lookup 100
ip -6 route add local default dev lo table 100
ip -6 rule add fwmark 1 lookup 100
- run sshuttle as root with the tproxy method:
sudo SSH_AUTH_SOCK="$SSH_AUTH_SOCK" sshuttle --method=tproxy \
--disable-ipv6 \
--dns \
--exclude your-server-ip \
-r your-user@your-server-ip 0/0
that one is quite a mouthful so let's break it down:
SSH_AUTH_SOCK="$SSH_AUTH_SOCK"
ensures that you can connect normally with SSH despise running sshuttle as root--method=tproxy
is here to, well, activate the tproxy method for UDP traffic--disable-ipv6
is self-explanatory. sadly, i found that my IPv6 address would leak otherwise, and i'm not sure what is causing this at the moment.--dns
is to forward all DNS requests through SSH--exclude your-server-ip
is required when forwarding all your traffic with tproxy, to prevent sshuttle from intercepting SSH packets-r your-user@your-server-ip 0/0
finally, where to forward your entire traffic (represented here as 0/0)
since IPv6 can leak even with the --disable-ipv6
command, let's disable it while using sshuttle. this can be achieved temporarily by running the following commands as root:
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=1
this is all nice and well but i don't see myself running all of this at each boot, so let's wrap everything we mentioned in a script:
#!/usr/bin/env bash
if [ "$EUID" -ne 0 ]; then
echo "This script must be run as root. Exiting."
exit
fi
ip route add local default dev lo table 100
ip rule add fwmark 1 lookup 100
ip -6 route add local default dev lo table 100
ip -6 rule add fwmark 1 lookup 100
sysctl -w net.ipv6.conf.all.disable_ipv6=1
sysctl -w net.ipv6.conf.default.disable_ipv6=1
sysctl -w net.ipv6.conf.lo.disable_ipv6=1
sshuttle --method=tproxy \
--disable-ipv6 \
--dns \
-e "sudo -u your-user ssh" \
--daemon \
--pidfile=/home/your-user/sshuttle.pid \
--exclude your-server-ip \
-r your-user@your-server-ip 0/0
three options have been added here:
--daemon
to run sshuttle in background--pidfile
to put a file containing shuttle's PID in a defined path. change the latter according to your needs.
the -e
flag will allow you to run the command as your main user and prompt for your SSH passphrase. this will use the right SSH pubkey to connect, even if ssh-agent isn't loaded yet and sshuttle is ran as root.
stopping the VPN is easy: just kill
the PID given in sshuttle.pid
. this can be automated with the following alias, ran as root:
alias vpndown="kill '$(cat /home/your-user/sshuttle.pid)'"
you should now be all set, just by adding the previous script in your $path
.
misc: accessing the VPN when the SSH port is blocked
if you want to connect on wifi hotspots, you might want to change the SSH port to 443 on your server as it is nearly guaranteed to be always opened, no matter how strict the wifi's firewall is.
we have to change SSH's default port on our server:
vim /etc/ssh/sshd_config
find and change the following line accordingly. uncomment it if necessary:
Port 443
one thing left now is to change ufw rules on your server to open that port:
sudo ufw limit in 443/tcp
don't forget to remove port 22 on ufw:
sudo ufw status numbered
delete related rules with their IDs:
sudo ufw delete <rule-id-here>
now restart the sshd service:
sudo service sshd restart
now, the only thing left is to specify the 443 port in the sshuttle script:
-r your-user@your-server-ip:443 0/0
the VPN should now be accessible from almost everywhere.