User Tools

Site Tools


vpn

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

vpn [2020/08/13 13:23] (current)
robm created
Line 1: Line 1:
 +====== VPN over SSH ======
 +
 +  * **Server**: GNU/Linux (Ubuntu 12.04) -- ''​172.16.0.1''​
 +  * **Client**: MacOS/BSD (El Capitan, v10.11.5) -- ''​172.16.0.2''​
 +
 +Create and configure a tunnelled connection between client and server, via ''​tun0''​ interfaces:
 +
 +  - Go install http://​tuntaposx.sourceforge.net/​download.xhtml on the Mac, it is needed by ''​ssh''​
 +  - SSH into the server and edit ''/​etc/​ssh/​sshd_config''​ to include <​code>​
 +PermitRootLogin yes
 +PermitTunnel yes
 +</​code>​
 +  - Restart SSHd on the server (''​service ssh reload''​ does not appear to be sufficient) <​code>​sudo service ssh stop; sudo service ssh start</​code>​
 +  - Log out of server (need to reconnect to make use of config changes)
 +  - **As root** on the client machine, SSH into the root account on the server with tun devices enabled via ''​-w''​ option<​code>​ssh root@server.hostname.com -w 0:​0</​code>​ (''​0:​0''​ specifies that both local and remote ends will create ''​tun0''​ interfaces)
 +  - Within the resulting root shell on the **server**, configure the new ''​tun0''​ network interface: <​code>​
 +ifconfig tun0 inet 172.16.0.1 dstaddr 172.16.0.2
 +ifconfig tun0
 +ping 172.16.0.1 ​ # Check tun0 has an IP address
 +ping 172.16.0.2 ​ # Should fail, as we've not yet configured the client'​s tun0
 +</​code>​
 +   - In a root shell on the **client**, configure the new ''​tun0''​ network interface: <​code>​
 +ifconfig tun0 inet 172.16.0.2 172.16.0.1
 +ifconfig tun0
 +ping 172.16.0.2 ​ # Check tun0 has an IP address
 +ping 172.16.0.1 ​ # Check we can communicate with remote end (server) via tun0
 +</​code>​
 +   - Back in the root shell on the **server**, repeat ''​ping 172.16.0.2''​ and this time it should respond
 +   - The tunnel is now configured. It will remain so until the SSH session is closed.
 +
 +Configure IPv4 (ICMP+TCP+UDP) forwarding and Network Address Translation (NAT):
 +
 +   - In the root shell on the **server** (only needs to be done one per boot): <​code>​
 +# Prepare networking stack for use by forced commands in
 +# /​root/​.ssh/​authorized_keys that creates a point-to-point network (via tun0)
 +# between 172.16.0.1 (this host) and 172.16.0.2 (remote end).
 +
 +# We then want to enabling forwarding of IPv4 traffic, i.e. we want to act as a
 +# router. We enable this in the kernel, and then ensure traffic originating
 +# from the remote side of the point-to-point link is accepted, and any
 +# responses are likewise accepted
 +echo 1 > /​proc/​sys/​net/​ipv4/​ip_forward
 +/​sbin/​iptables -F
 +/​sbin/​iptables -A FORWARD ! --source 172.16.0.2 --destination 172.16.0.2 \
 +        -m state --state RELATED,​ESTABLISHED -j ACCEPT
 +/​sbin/​iptables -A FORWARD --source 172.16.0.2 ! --destination 172.16.0.2 -j ACCEPT
 +
 +# Any traffic originating from the remote side should go through Network
 +# Address Translation (NAT), so responses from (e.g.) DNS servers are sent to
 +# this host, so *we* can forward it to the remote end. This is the MASQUERADE
 +# rule.
 +/​sbin/​iptables -t nat -F
 +/​sbin/​iptables -t nat -A POSTROUTING ! --destination 172.16.0.2 ​ -j MASQUERADE
 +
 +# Monitor packets
 +watch -n0.5 -d ifconfig tun0
 +</​code>​
 +   - As root on the **client**: <​code>​
 +route add 10.0.0.0/8 -interface tun0
 +</​code>​ (you can undo this by repeating the command with ''​delete''​ in place of ''​add''​)
 +   - IPv4 forwarding via ''​tun0''​ is in effect.
 +
 +Note: The changes made to the server persist after the SSH session has ended.
 +
 +To get name resolution working, you need to configure the client to use a DNS server at the remote end, e.g.
 +
 +   - Discover the DNS nameservers used by the **server**: <​code>​cat /​etc/​resolv.conf</​code>​
 +   - Add these to the **client** system: <​code>​networksetup -setdnsservers Wi-Fi 10.1.2.24 10.1.2.23 192.168.1.253 8.8.8.8 8.8.4.4</​code>​ This step must be manually undone (e.g. after closing the SSH session) by running <​code>​ networksetup -setdnsservers Wi-Fi Empty</​code>​
 +
 +You may also want to add your remote system'​s DNS search domains, e.g.: <​code>​networksetup -setsearchdomains Wi-Fi site.example.com example.com local</​code>​ Again, this must be manually undone after you close the VPN connection: <​code>​sudo networksetup -setsearchdomains Wi-Fi Empty</​code>​
 +
 +Most useful guides:
 +
 +   * http://​ubuntuforums.org/​showthread.php?​t=926435&​page=3&​p=7886699#​post7886699
 +   * http://​kovyrin.net/​2006/​03/​17/​how-to-create-ip-ip-tunnel-between-freebsd-and-linux/​
 +   * https://​help.ubuntu.com/​community/​SSH_VPN
 +   * https://​blog.brixandersen.dk/​2006/​10/​23/​vpn-using-openssh-and-tun4-under-freebsd/​
 +   * http://​osxdaily.com/​2015/​06/​02/​change-dns-command-line-mac-os-x/​
 +   * NAT: http://​www.revsys.com/​writings/​quicktips/​nat.html
 +   * SSH, including forced-commands via ''​.ssh/​authorized_key'':​ https://​www.digitalocean.com/​community/​tutorials/​ssh-essentials-working-with-ssh-servers-clients-and-keys
 +
 +More thorough networking (Ethernet layer, instead of link layer): http://​sgros.blogspot.co.uk/​2011/​11/​ssh-vpns-bridged-connection-to-lan.html
 +
 +===== Automating via SSH configuration files =====
 +
 +<note important>​All commands here are run as **root** on the client system</​note>​
 +
 +  - **As root** on your client system, generate a new SSH keypair to use for VPN. <​code>​ssh-keygen -f ~/​.ssh/​id_rsa_vpn -N ''</​code>​
 +  - Install new public key into remote system, and prefix with a ForeCommand which is run whenever this key is used to authenticate:<​code>​( \
 +  printf '​tunnel="​0",​command="​ifconfig tun0 inet 172.16.0.1 dstaddr 172.16.0.2"​ ' ; \
 +  cat ~/​.ssh/​id_rsa_test.pub \
 +) | ssh root@www.robmeerman.co.uk tee -a .ssh/​authorized_keys</​code>​
 +  - Configure client via ''​~/​.ssh/​config''​. Add the following to the end of ''​.ssh/​config''​ (create it if it does not exist) and replace ''​$SERVER''​ with your server'​s hostname: <​code>​Host vpn
 +  Hostname $SERVER
 +  User root
 +  # Remote'​s .ssh/​authorised_keys entry for this identity is prefixed with:
 +  # tunnel="​0",​command="​ifconfig tun0 inet 172.16.0.1 dstaddr 172.16.0.2"​ ssh-rsa
 +  IdentityFile ~root/​.ssh/​id_rsa_vpn
 +  Tunnel yes
 +  TunnelDevice 0:0
 +  PermitLocalCommand yes
 +  LocalCommand ~root/​.ssh/​vpn.sh %h %T
 +  # Disable connection sharing, otherwise closing VPN may not actually reset
 +  # network settings because vpn.sh (cf. LocalCommand) continues to wait
 +  # for the `ssh` process to exit (which it may not if another session is
 +  # active)
 +  ControlPath none
 +  # Disable use of ssh-agent, as it seems to prevent our preferred identity
 +  # (cf. IdentityFile) being applied, which in turn means we don't trigger the
 +  # ForceCommand of the remote'​s authorized_keys file
 +  IdentityAgent none</​code>​
 +  - Create a new script on your client machine at ''​~root/​.ssh/​vpn.sh''​ which configures your Mac to route traffic headed to your server via the current gateway, and then change the default gateway (that applies to all __other__ traffic) to go via the new SSH ''​tun''​ device at 172.16.0.1, then wait for the ''​ssh''​ process to exit before returning settings to normal: <​code>#​!/​bin/​bash
 +# .ssh/​config:​ LocalCommand vpn.sh %h %T
 +REMOTE_HOST=$1
 +TUNNEL_DEVICE=$2
 +
 +ifconfig $TUNNEL_DEVICE inet 172.16.0.2 172.16.0.1
 +ROUTE=$(route get $REMOTE_HOST)
 +GATEWAY=$(sed -ne 's/^ *gateway: //p' <<<"​$ROUTE"​)
 +INTERFACE=$(sed -ne 's/^ *interface: //p' <<<"​$ROUTE"​)
 +route add $REMOTE_HOST $GATEWAY
 +route add 10/8 $GATEWAY
 +route change default 172.16.0.1
 +WAIT_PID=$PPID
 +(
 +while kill -0 $WAIT_PID >/​dev/​null 2>&​1;​ do sleep 0.5; done
 +# The route gets deleted when the SSH tunnel closes gracefully and tun0 disappears
 +route change default $GATEWAY
 +route add default $GATEWAY
 +route delete 10/8 $GATEWAY
 +route delete $REMOTE_HOST $GATEWAY
 +) &</​code>​ This script is unlikely to work on other OS
 +  - Make the new script executable: <​code>​chmod a+x ~root/​.ssh/​vpn.sh</​code>​
 +  - Test it by running <​code>​ssh vpn</​code>​
 +
 +Sample session showing the output from the commands above:
 +
 +<​code>​
 +# ssh-keygen -f ~/​.ssh/​id_rsa_vpn -N ''​
 +Generating public/​private rsa key pair.
 +Your identification has been saved in /​var/​root/​.ssh/​id_rsa_vpn.
 +Your public key has been saved in /​var/​root/​.ssh/​id_rsa_vpn.pub.
 +The key fingerprint is:
 +SHA256:​4c8jh23lnMr7ZEmiDCCenKEEo6ROBDIku3XCmKLqqcw root@roberts-mbp
 +The key's randomart image is:
 ++---[RSA 2048]----+
 +|X+               |
 +|OB               |
 +|Bo* o   ​. ​       |
 +|** B . . .       |
 +|+.=   . S . o    |
 +|.      o * * o   |
 +|.       = B B    |
 +|+ .      = =     |
 +|oE        +o.    |
 ++----[SHA256]-----+
 +
 +# ( \
 +#   ​printf '​tunnel="​0",​command="​ifconfig tun0 inet 172.16.0.1 dstaddr 172.16.0.2"​ ' ; \
 +#   cat ~/​.ssh/​id_rsa_test.pub \
 +# ) | ssh root@www.robmeerman.co.uk tee -a .ssh/​authorized_keys
 +tunnel="​0",​command="​ifconfig tun0 inet 172.16.0.1 dstaddr 172.16.0.2"​ ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+pPee+HqiExk28lwKGcjoAMnkWRVKoQsn8b+90ST3HteZq1oCKtig49YOtlXDZGma0vR/​y9Xbelk26xJfZO32BR3GCPou6XYSU67qwC8wK256H0LfTUlquUufklmKd3BaKamAtXU0JwhVxQCFH0hToG6dgc0FLelqs1r8u6cPni1wTxaId6epHrYCBrKvP+fwYz0S0K3e2opcqZUTwMyPYwu280UxQr2HYvzykdoJeiJtsKgneFRxhX7gnlKCYoia0fToKHel24GfUFfqipFrJbsm8LDYuVh5KVgx1J1Hx19Fu0LM3IIqoXQESob91TjTx1bq41iIMZ0n0td5gDVj root@roberts-mbp
 +
 +# ssh vpn
 +add host www.robmeerman.co.uk:​ gateway 10.1.36.1
 +add net 10: gateway 10.1.36.1
 +change net default: gateway 172.16.0.1
 +
 +# Nothing further appears to happen. VPN is up and running! Try `traceroute
 +# google.com` in another terminal to verify that the traffic is going via your
 +# server and not its default route.
 +
 +# When all done, press ^C to kill the VPN and restore default settings. Your
 +# prompt will return first, and *then* the clean-up code will execute and
 +# print:
 +^C
 +route: writing to routing socket: not in table
 +change net default: gateway 10.1.36.1: not in table
 +add net default: gateway 10.1.36.1
 +delete net 10: gateway 10.1.36.1
 +delete host www.robmeerman.co.uk:​ gateway 10.1.36.1
 +</​code>​
 +
 +===== systemd service =====
 +
 +Configure a remote host, which lives behind a firewall, to maintain an SSH connection to my home network that provides a reverse tunnel back into `sshd` on the remote host.
 +
 +''/​etc/​systemd/​system/​my-vpn.service''​
 +
 +<​code>​
 +[Unit]
 +Description=SSH-based VPN
 +After=network.target
 +
 +[Service]
 +Restart=always
 +# Disable rate-limiting,​ which may result in "​giving up"
 +StartLimitInterval=0
 +
 +ExecStartPre=/​bin/​sh -c "echo 1 > /​proc/​sys/​net/​ipv4/​ip_forward"​
 +ExecStartPre=/​sbin/​iptables -A FORWARD ! --source 172.16.0.2 --destination 172.16.0.2 \
 +        -m state --state RELATED,​ESTABLISHED -j ACCEPT
 +ExecStartPre=/​sbin/​iptables -A FORWARD --source 172.16.0.2 ! --destination 172.16.0.2 -j ACCEPT
 +ExecStartPre=/​sbin/​iptables -t nat -A POSTROUTING ! --destination 172.16.0.2 ​ -j MASQUERADE
 +
 +ExecStart=/​usr/​bin/​ssh user@example.com -R10022:​localhost:​22 -N -oServerAliveInterval=15 -oExitOnForwardFailure=yes
 +
 +[Install]
 +WantedBy=multi-user.target
 +</​code>​
  
vpn.txt · Last modified: 2020/08/13 13:23 by robm