├── .github └── FUNDING.yml ├── README.md └── setup.sh /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: jawj 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IKEv2-setup 2 | 3 | ## Table of contents 4 | 5 | * [What?](#what) 6 | + [VPN server](#vpn-server) 7 | + [VPN clients](#vpn-clients) 8 | + [Caveats](#caveats) 9 | * [How?](#how) 10 | + [Troubleshooting](#troubleshooting) 11 | + [Users](#users) 12 | + [Upgrades](#upgrades) 13 | + [Bonus paranoia](#bonus-paranoia) 14 | * [Why?](#why) 15 | + [Why IKEv2?](#why-ikev2) 16 | + [Why not Algo?](#why-not-algo) 17 | 18 | ## What? 19 | 20 | A Bash script that takes Ubuntu Server LTS versions 18.04 - 24.04 from clean install to fully-configured IKEv2 VPN using strongSwan. Comments and pull requests welcome. 21 | 22 | ### VPN server 23 | 24 | * The VPN server identifies itself with a _Let's Encrypt_ certificate, so there's no need for clients to install private certificates — they can simply authenticate with a username and strong password (EAP-MSCHAPv2). 25 | 26 | * The preferred cipher set is the US [Commercial National Security Algorithm Suite (CNSA)](https://docs.strongswan.org/docs/5.9/config/IKEv2CipherSuites.html#_commercial_national_security_algorithm_suite): `aes256gcm16-prfsha384-ecp384`. However, due to an apparent bug in recent versions of macOS, `aes256gcm16-prfsha256-ecp256` is also accepted. 27 | 28 | * The box is firewalled with `iptables` and configured for unattended security upgrades, and the _Let's Encrypt_ certificate is set up to auto-renew, so it _could_ be safe to forget about it all until your chosen Ubuntu version reaches end-of-life. (Note that `iptables` setup includes [basic rate-limiting](https://debian-administration.org/article/187/Using_iptables_to_rate-limit_incoming_connections), dropping new connections if there have been 60+ connection attempts in the last 5 minutes). 29 | 30 | ### VPN clients 31 | 32 | The VPN is tested working with: 33 | 34 | * **macOS 10.12 – 14, iOS 10 – 17** — Built-in clients. A `.mobileconfig` profile is generated for iOS, to set up secure ciphers and enable *Connect on demand* support. An AppleScript script is generated for Mac, to prompt for VPN credentials and then do the same. 35 | * **Windows 10 Pro, 11 Pro** — Built-in client. PowerShell commands are generated to configure the VPN and secure ciphers. 36 | * **Ubuntu (17.04 and presumably others)** — Using strongSwan. A Bash script is generated to set this up. 37 | * **Android** — Using the official strongSwan app. A `.sswan` file is generated for configuration. 38 | 39 | Configuration files, scripts and instructions are sent by email. They are also dropped in the newly-created non-root user's home directory on the server (this point may be important, because VPS providers sometimes block traffic on port 25 by default and, even if successfully sent, conscientious email hosts will sometimes mark the email as spam). 40 | 41 | ### Caveats 42 | 43 | * There's no IPv6 support — and, in fact, IPv6 networking is disabled — because supporting IPv6 prevents the use of `forceencaps`, and honestly also because I haven't got to grips with the security implications (`ip6tables` rules and so on). 44 | * The script **won't** work as-is on 16.04 LTS or earlier (where the `certbot` package is outdated, found under the name `letsencrypt`, and doesn't renew certificates automatically). 45 | * **Don't use this unmodified on a server you use for anything else**: it does as it sees fit with various wider settings that may conflict with what you're doing. 46 | 47 | 48 | ## How? 49 | 50 | 1. Pick a domain name for the VPN server and **ensure that it already resolves to the correct IP** by creating the appropriate `A` record in the DNS and making sure it has propagated. _Let's Encrypt_ needs this in order to create your server certificate. 51 | 52 | _Don't want to use your own domain name here? You could try using the reverse DNS name provided by your server host, or an automatic IP/DNS alias service such as [sslip.io](https://sslip.io/), [xip.io](http://xip.io), [nip.io](https://nip.io), [s.test.cab](https://s.test.cab), or [xip.lhjmmc.cn](https://xip.lhjmmc.cn/) (earlier versions of this script used an [sslip.io](https://sslip.io/) address by default). However, these options may fall foul of Let's Encrypt's per-domain rate limit of [50 certificates per week](https://letsencrypt.org/docs/rate-limits/). Note that ephemeral AWS domain names like `ec2-34-267-212-76.compute-1.amazonaws.com` [are not accepted by Let's Encrypt](https://community.letsencrypt.org/t/policy-forbids-issuing-for-name-on-amazon-ec2-domain/12692)._ 53 | 54 | 2. Start with a clean Ubuntu Server installation. The cheapest VPSs offered by Linode, OVH, vps.ag, Google, AWS Lightsail, Hetzner, Vultr, Scaleway's ARM64-2GB, and Oracle's VM.Standard.E2.1.Micro (AMD) have all been tested working. 55 | 56 | * On Scaleway, unblock SMTP ports in the admin panel and *hard* reboot the server first, or your configuration email will not be delivered. 57 | * On Vultr, port 25 may also be blocked, but you won't know, and the only way to fix it is to open a support ticket. 58 | * On Oracle you'll need to enable network ingress for TCP on port 80 (for Let's Encrypt) and on any custom SSH port you choose, and for UDP on ports 500 and 4500 (for the VPN) in the interface for the relevant VNIC. Egress on port 25 is always blocked unlesss you file a ticket to open it. 59 | 60 | 3. Optionally, set up [key-based SSH authentication](https://help.ubuntu.com/community/SSH/OpenSSH/Keys) (alternatively, this may have been handled automatically by your server provider, or you may choose to stick with password-based authentication). This may require you to run some or all of the following commands, with appropriate substitutions, on the machine you're going to be logging in from: 61 | 62 | ssh-keygen -t ed25519 -C "me@my-domain.tld" # if you need a new key, ed25519 is the latest and possibly most secure option 63 | ssh-keygen -t rsa -b 4096 -C "me@my-domain.tld" # alternatively, use RSA and go (4,096 bits) large 64 | 65 | ssh root@myvpn.example.net # if your host forces a password change before anything else (e.g. Hetzner), do it now, then exit 66 | ssh-copy-id -i ~/.ssh/id_ed25519.pub root@myvpn.example.net # copy your public key over to the VPN server 67 | ssh root@myvpn.example.net # log back in to the server for the next step ... 68 | 69 | 4. On your new server installation, become `root`, download the script, give it execute permissions, and run it: 70 | 71 | wget https://raw.githubusercontent.com/jawj/IKEv2-setup/master/setup.sh 72 | chmod u+x setup.sh 73 | ./setup.sh 74 | 75 | 5. You'll be prompted to enter all the necessary details after the software updates and installations complete. If you are not using key-based SSH authentication, **you *must* pick a really strong password** for the login user when prompted, or your server *will* be compromised. 76 | 77 | The part of your session where the script asks you questions should look something like this: 78 | 79 | --- Configuration: VPN settings --- 80 | 81 | Network interface: eth0 82 | External IP: 100.100.100.100 83 | 84 | ** Note: hostname must resolve to this machine already, to enable Let's Encrypt certificate setup ** 85 | Hostname for VPN: 86 | VPN username: george 87 | VPN password (no quotes, please): 88 | Confirm VPN password: 89 | 90 | Public DNS servers include: 91 | 92 | 176.103.130.130,176.103.130.131 AdGuard https://adguard.com/en/adguard-dns/overview.html 93 | 176.103.130.132,176.103.130.134 AdGuard Family https://adguard.com/en/adguard-dns/overview.html 94 | 1.1.1.1,1.0.0.1 Cloudflare/APNIC https://1.1.1.1 95 | 84.200.69.80,84.200.70.40 DNS.WATCH https://dns.watch 96 | 8.8.8.8,8.8.4.4 Google https://developers.google.com/speed/public-dns/ 97 | 208.67.222.222,208.67.220.220 OpenDNS https://www.opendns.com 98 | 208.67.222.123,208.67.220.123 OpenDNS FamilyShield https://www.opendns.com 99 | 9.9.9.9,149.112.112.112 Quad9 https://quad9.net 100 | 77.88.8.8,77.88.8.1 Yandex https://dns.yandex.com 101 | 77.88.8.88,77.88.8.2 Yandex Safe https://dns.yandex.com 102 | 77.88.8.7,77.88.8.3 Yandex Family https://dns.yandex.com 103 | 104 | DNS servers for VPN users (default: 1.1.1.1,1.0.0.1): 176.103.130.130,176.103.130.131 105 | 106 | --- Configuration: general server settings --- 107 | 108 | Timezone (default: Europe/London): 109 | Email address for sysadmin (e.g. j.bloggs@example.com): me@my-domain.tld 110 | Desired SSH log-in port (default: 22): 2222 111 | New SSH log-in user name: george 112 | Copy /root/.ssh/authorized_keys to new user and disable SSH password log-in [Y/n]? y 113 | New SSH user's password (e.g. for sudo): 114 | Confirm new SSH user's password: 115 | 116 | 6. Once you're up and running, use these commands for some insight into what's going on: 117 | 118 | sudo ipsec statusall # status, who's connected, etc. 119 | sudo iptables -L -v # how much traffic has been forwarded, dropped, etc.? 120 | sudo tail -f /var/log/syslog # real-time logs of (dis)connections etc. 121 | 122 | ### Troubleshooting 123 | 124 | If you ran this script before 13 September 2021, and used the generated PowerShell commands to set up Windows 10 clients, those clients may be unable to connect owing to a bug in Windows 10. If this is the case, see [issue #126](https://github.com/jawj/IKEv2-setup/issues/126). 125 | 126 | Otherwise, if things don't work out right away ... 127 | 128 | * On the client: make sure you created the connection using the newly emailed `.mobileconfig` file, AppleScript or PowerShell commands. Setting it up manually via the OS GUI will _not_ work, since it will default to insecure ciphers which the server has not been configured to support. Also note that `.mobileconfig` files generated with earlier iterations of this script may no longer be compatible, since the configured ciphers have changed from time to time. 129 | 130 | * On the server: check that network ingress for UDP on ports 500 and 4500 is enabled (on some cloud platforms you'll have to add appropriate firewall rules to your virtual network). Also check that packet forwarding is enabled (on some cloud platforms this is controlled by a configuration setting that's off by default). 131 | 132 | * Check the server logs on strongSwan startup and when you try to connect, and the client logs when you try to connect. 133 | 134 | * __On the server:__ Log in via SSH, then `sudo tail -f /var/log/syslog`. To see startup logs, log in to another session and `sudo ipsec restart` there, then switch back. To see what's logged during a connection attempt, try to connect from a client. 135 | 136 | * __On the client:__ On a Mac, open Console.app in /Applications/Utilities. If connecting from an iPhone, plug the iPhone into the Mac. Pick the relevant device (in the bar down the left), filter the output (in the box at top right) to `nesession`, and try to connect. (On Windows or Linux I don't know where you find the logs — if _you_ know, feel free to write the explanation and send a pull request). 137 | 138 | * The setup script is now more or less idempotent — you should be able to run it repeatedly with no ill effects — so, when you've fixed any issues, simply run it again. 139 | 140 | * If you have a tricky question about strongSwan, it's probably better to [raise it with the strongSwan team](https://strongswan.org/support.html) than file an issue here. 141 | 142 | ### Users 143 | 144 | To add or change VPN users, it's: 145 | 146 | sudo nano /etc/ipsec.secrets 147 | 148 | Edit usernames and passwords as you see fit (but don't touch the first line, which specifies the server certificate). The line format for each user is: 149 | 150 | someusername : EAP "somepassword" 151 | 152 | To exit nano it's `Ctrl + O` then `Ctrl + X`, and to have strongSwan pick up the changes it's: 153 | 154 | sudo ipsec secrets 155 | 156 | ### Upgrades 157 | 158 | If you're on an older version of Ubuntu, it's probably easiest to make a record of any changes to `ipsec.secrets`, blow the whole thing away and reinstall, then reinstate `ipsec.secrets`. 159 | 160 | Note that you may also need to delete and recreate all your client connection settings using the updated PowerShell commands or .mobileconfig file, since there have been a few cipher changes over time. 161 | 162 | ### Bonus paranoia 163 | 164 | Your traffic is not logged on the server, but if you're feeling especially paranoid there are various things you could do to reduce logging further. A simple and somewhat drastic option (once you've got everything working) is: 165 | 166 | sudo rm /var/log/syslog && sudo ln -s /dev/null /var/log/syslog 167 | sudo rm /var/log/auth.log && sudo ln -s /dev/null /var/log/auth.log 168 | 169 | ## Why? 170 | 171 | We use a similar setup as a corporate VPN at [PSYT](http://psyt.co.uk). And I use this to bounce my personal web browsing via Europe, in the hope of giving Theresa May's [Investigatory Powers Bill](https://www.openrightsgroup.org/blog/2015/investigatory-powers-bill-published-and-now-the-fight-is-on) the finger. 172 | 173 | ### Why IKEv2? 174 | 175 | * Fair security 176 | * Built-in clients for latest iOS, Mac and Windows (+ trustworthy free install on Android) 177 | * *Connect on demand* support on iOS and Mac 178 | * Robust to connection switching and interruptions via MOBIKE 179 | 180 | More on IKEv2 at https://www.cl.cam.ac.uk/~mas90/resources/strongswan/ and https://www.bestvpn.com/blog/4147/pptp-vs-l2tp-vs-openvpn-vs-sstp-vs-ikev2/ 181 | 182 | ### Why not Algo? 183 | 184 | Feel free to use [Algo](https://github.com/trailofbits/algo) instead. It has similar aims, and now configures [WireGuard](https://www.wireguard.com/) too. However, it has many more moving parts, and requires several local installation steps before you even start setting up your VPN. This script is intended to be much simpler. 185 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # github.com/jawj/IKEv2-setup 4 | # Copyright (c) 2015 – 2024 George MacKerron 5 | # Released under the MIT licence: http://opensource.org/licenses/mit-license 6 | 7 | echo 8 | echo "=== https://github.com/jawj/IKEv2-setup ===" 9 | echo 10 | 11 | 12 | function exit_badly { 13 | echo "$1" 14 | exit 1 15 | } 16 | 17 | UBUNTUVERSION=$(lsb_release -rs) 18 | [[ "${UBUNTUVERSION}" == "18.04" ]] \ 19 | || [[ "${UBUNTUVERSION}" == "20.04" ]] \ 20 | || [[ "${UBUNTUVERSION}" == "22.04" ]] \ 21 | || [[ "${UBUNTUVERSION}" == "24.04" ]] \ 22 | || exit_badly "This script is for Ubuntu 18.04/20.04/22.04/24.04 only: aborting (if you know what you're doing, try deleting this check)" 23 | 24 | [[ $(id -u) -eq 0 ]] || exit_badly "Please run this script as root (e.g. sudo ./path/to/this/script)" 25 | 26 | 27 | echo "--- Adding repositories and installing utilities ---" 28 | echo 29 | 30 | export DEBIAN_FRONTEND=noninteractive 31 | 32 | # see https://github.com/jawj/IKEv2-setup/issues/66 and https://bugs.launchpad.net/subiquity/+bug/1783129 33 | # note: software-properties-common is required for add-apt-repository 34 | apt-get -o Acquire::ForceIPv4=true update 35 | apt-get -o Acquire::ForceIPv4=true install -y software-properties-common 36 | add-apt-repository -y universe 37 | add-apt-repository -y restricted 38 | add-apt-repository -y multiverse 39 | 40 | apt-get -o Acquire::ForceIPv4=true install -y moreutils dnsutils 41 | 42 | 43 | echo 44 | echo "--- Configuration: VPN settings ---" 45 | echo 46 | 47 | ETH0ORSIMILAR=$(ip route get 1.1.1.1 | grep -oP ' dev \K\S+') 48 | IP=$(dig -4 +short myip.opendns.com @resolver1.opendns.com) 49 | 50 | echo "Network interface: ${ETH0ORSIMILAR}" 51 | echo "External IP: ${IP}" 52 | echo 53 | echo "** Note: this hostname must already resolve to this machine, to enable Let's Encrypt certificate setup **" 54 | read -r -p "Hostname for VPN: " VPNHOST 55 | 56 | VPNHOSTIP=$(dig -4 +short "${VPNHOST}") 57 | [[ -n "$VPNHOSTIP" ]] || exit_badly "Cannot resolve VPN hostname: aborting" 58 | 59 | if [[ "${IP}" != "${VPNHOSTIP}" ]]; then 60 | echo "Warning: ${VPNHOST} resolves to ${VPNHOSTIP}, not ${IP}" 61 | echo "Either you're behind NAT, or something is wrong (e.g. hostname points to wrong IP, CloudFlare proxying shenanigans, ...)" 62 | read -r -p "Press [Return] to continue anyway, or Ctrl-C to abort" 63 | fi 64 | 65 | read -r -p "VPN username: " VPNUSERNAME 66 | while true; do 67 | read -r -s -p "VPN password (no quotes, please): " VPNPASSWORD 68 | echo 69 | read -r -s -p "Confirm VPN password: " VPNPASSWORD2 70 | echo 71 | [[ "${VPNPASSWORD}" = "${VPNPASSWORD2}" ]] && break 72 | echo "Passwords didn't match -- please try again" 73 | done 74 | 75 | echo ' 76 | Public DNS servers include: 77 | 78 | 176.103.130.130,176.103.130.131 AdGuard https://adguard.com/en/adguard-dns/overview.html 79 | 176.103.130.132,176.103.130.134 AdGuard Family https://adguard.com/en/adguard-dns/overview.html 80 | 1.1.1.1,1.0.0.1 Cloudflare/APNIC https://1.1.1.1 81 | 84.200.69.80,84.200.70.40 DNS.WATCH https://dns.watch 82 | 8.8.8.8,8.8.4.4 Google https://developers.google.com/speed/public-dns/ 83 | 208.67.222.222,208.67.220.220 OpenDNS https://www.opendns.com 84 | 208.67.222.123,208.67.220.123 OpenDNS FamilyShield https://www.opendns.com 85 | 9.9.9.9,149.112.112.112 Quad9 https://quad9.net 86 | 77.88.8.8,77.88.8.1 Yandex https://dns.yandex.com 87 | 77.88.8.88,77.88.8.2 Yandex Safe https://dns.yandex.com 88 | 77.88.8.7,77.88.8.3 Yandex Family https://dns.yandex.com 89 | ' 90 | 91 | read -r -p "DNS servers for VPN users (default: 1.1.1.1,1.0.0.1): " VPNDNS 92 | VPNDNS=${VPNDNS:-'1.1.1.1,1.0.0.1'} 93 | 94 | 95 | echo 96 | echo "--- Configuration: general server settings ---" 97 | echo 98 | 99 | read -r -p "Timezone (default: Europe/London): " TZONE 100 | TZONE=${TZONE:-'Europe/London'} 101 | 102 | read -r -p "Email address for sysadmin (e.g. j.bloggs@example.com): " EMAILADDR 103 | 104 | read -r -p "Desired SSH log-in port (default: 22): " SSHPORT 105 | SSHPORT=${SSHPORT:-22} 106 | 107 | read -r -p "New SSH log-in user name: " LOGINUSERNAME 108 | 109 | CERTLOGIN="n" 110 | if [[ -s /root/.ssh/authorized_keys ]]; then 111 | while true; do 112 | read -r -p "Copy /root/.ssh/authorized_keys to new user and disable SSH password log-in [Y/n]? " CERTLOGIN 113 | [[ ${CERTLOGIN,,} =~ ^(y(es)?)?$ ]] && CERTLOGIN=y 114 | [[ ${CERTLOGIN,,} =~ ^no?$ ]] && CERTLOGIN=n 115 | [[ $CERTLOGIN =~ ^(y|n)$ ]] && break 116 | done 117 | fi 118 | 119 | while true; do 120 | [[ ${CERTLOGIN} = "y" ]] && read -r -s -p "New SSH user's password (e.g. for sudo): " LOGINPASSWORD 121 | [[ ${CERTLOGIN} != "y" ]] && read -r -s -p "New SSH user's log-in password (must be REALLY STRONG): " LOGINPASSWORD 122 | echo 123 | read -r -s -p "Confirm new SSH user's password: " LOGINPASSWORD2 124 | echo 125 | [[ "${LOGINPASSWORD}" = "${LOGINPASSWORD2}" ]] && break 126 | echo "Passwords didn't match -- please try again" 127 | done 128 | 129 | VPNIPPOOL="10.101.0.0/16" 130 | 131 | 132 | echo 133 | echo "--- Upgrading and installing packages ---" 134 | echo 135 | 136 | apt-get -o Acquire::ForceIPv4=true --with-new-pkgs upgrade -y 137 | apt autoremove -y 138 | 139 | debconf-set-selections <<< "postfix postfix/mailname string ${VPNHOST}" 140 | debconf-set-selections <<< "postfix postfix/main_mailer_type string 'Internet Site'" 141 | 142 | apt-get -o Acquire::ForceIPv4=true install -y \ 143 | language-pack-en iptables-persistent postfix mutt unattended-upgrades certbot uuid-runtime \ 144 | strongswan libstrongswan-standard-plugins strongswan-libcharon libcharon-extra-plugins 145 | 146 | # in 22.04 libcharon-standard-plugins is replaced with libcharon-extauth-plugins 147 | apt-get -o Acquire::ForceIPv4=true install -y libcharon-standard-plugins \ 148 | || apt-get -o Acquire::ForceIPv4=true install -y libcharon-extauth-plugins 149 | 150 | echo 151 | echo "--- Configuring firewall ---" 152 | echo 153 | 154 | # firewall 155 | # https://www.strongswan.org/docs/LinuxKongress2009-strongswan.pdf 156 | # https://wiki.strongswan.org/projects/strongswan/wiki/ForwardingAndSplitTunneling 157 | # https://www.zeitgeist.se/2013/11/26/mtu-woes-in-ipsec-tunnels-how-to-fix/ 158 | 159 | iptables -P INPUT ACCEPT 160 | iptables -P FORWARD ACCEPT 161 | iptables -P OUTPUT ACCEPT 162 | 163 | iptables -F 164 | iptables -t nat -F 165 | iptables -t mangle -F 166 | 167 | # INPUT 168 | 169 | # accept anything already accepted 170 | iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 171 | 172 | # accept anything on the loopback interface 173 | iptables -A INPUT -i lo -j ACCEPT 174 | 175 | # drop invalid packets 176 | iptables -A INPUT -m state --state INVALID -j DROP 177 | 178 | # rate-limit repeated new requests from same IP to any ports 179 | iptables -I INPUT -i "${ETH0ORSIMILAR}" -m state --state NEW -m recent --set 180 | iptables -I INPUT -i "${ETH0ORSIMILAR}" -m state --state NEW -m recent --update --seconds 300 --hitcount 60 -j DROP 181 | 182 | # accept (non-standard) SSH 183 | iptables -A INPUT -p tcp --dport "${SSHPORT}" -j ACCEPT 184 | 185 | 186 | # VPN 187 | 188 | # accept IPSec/NAT-T for VPN (ESP not needed with forceencaps, as ESP goes inside UDP) 189 | iptables -A INPUT -p udp --dport 500 -j ACCEPT 190 | iptables -A INPUT -p udp --dport 4500 -j ACCEPT 191 | 192 | # forward VPN traffic anywhere 193 | iptables -A FORWARD --match policy --pol ipsec --dir in --proto esp -s "${VPNIPPOOL}" -j ACCEPT 194 | iptables -A FORWARD --match policy --pol ipsec --dir out --proto esp -d "${VPNIPPOOL}" -j ACCEPT 195 | 196 | # reduce MTU/MSS values for dumb VPN clients 197 | iptables -t mangle -A FORWARD --match policy --pol ipsec --dir in -s "${VPNIPPOOL}" -o "${ETH0ORSIMILAR}" -p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 -j TCPMSS --set-mss 1360 198 | 199 | # masquerade VPN traffic over eth0 etc. 200 | iptables -t nat -A POSTROUTING -s "${VPNIPPOOL}" -o "${ETH0ORSIMILAR}" -m policy --pol ipsec --dir out -j ACCEPT # exempt IPsec traffic from masquerading 201 | iptables -t nat -A POSTROUTING -s "${VPNIPPOOL}" -o "${ETH0ORSIMILAR}" -j MASQUERADE 202 | 203 | 204 | # fall through to drop any other input and forward traffic 205 | 206 | iptables -A INPUT -j DROP 207 | iptables -A FORWARD -j DROP 208 | 209 | iptables -L 210 | 211 | netfilter-persistent save 212 | 213 | 214 | echo 215 | echo "--- Configuring RSA certificates ---" 216 | echo 217 | 218 | mkdir -p /etc/letsencrypt 219 | 220 | # note: currently we stick to RSA because iOS/macOS may have trouble with ECDSA 221 | # (see https://github.com/jawj/IKEv2-setup/issues/159) 222 | 223 | echo " 224 | standalone = true 225 | agree-tos = true 226 | non-interactive = true 227 | preferred-challenges = http 228 | rsa-key-size = 4096 229 | email = ${EMAILADDR} 230 | pre-hook = /sbin/iptables -I INPUT -p tcp --dport 80 -j ACCEPT 231 | post-hook = /sbin/iptables -D INPUT -p tcp --dport 80 -j ACCEPT 232 | renew-hook = /usr/sbin/ipsec reload && /usr/sbin/ipsec secrets 233 | " > /etc/letsencrypt/cli.ini 234 | 235 | # certbot on older Ubuntu doesn't recognise the --key-type switch, so try without if it errors with 236 | certbot certonly --key-type rsa -d "${VPNHOST}" || certbot certonly -d "${VPNHOST}" 237 | 238 | 239 | ln -f -s "/etc/letsencrypt/live/${VPNHOST}/cert.pem" /etc/ipsec.d/certs/cert.pem 240 | ln -f -s "/etc/letsencrypt/live/${VPNHOST}/privkey.pem" /etc/ipsec.d/private/privkey.pem 241 | ln -f -s "/etc/letsencrypt/live/${VPNHOST}/chain.pem" /etc/ipsec.d/cacerts/chain.pem 242 | 243 | grep -Fq 'jawj/IKEv2-setup' /etc/apparmor.d/local/usr.lib.ipsec.charon || echo " 244 | # https://github.com/jawj/IKEv2-setup 245 | /etc/letsencrypt/archive/${VPNHOST}/* r, 246 | " >> /etc/apparmor.d/local/usr.lib.ipsec.charon 247 | 248 | aa-status --enabled && invoke-rc.d apparmor reload 249 | 250 | 251 | echo 252 | echo "--- Configuring VPN ---" 253 | echo 254 | 255 | # ip_forward is for VPN 256 | # ip_no_pmtu_disc is for UDP fragmentation 257 | # others are for security 258 | 259 | grep -Fq 'jawj/IKEv2-setup' /etc/sysctl.conf || echo " 260 | # https://github.com/jawj/IKEv2-setup 261 | net.ipv4.ip_forward = 1 262 | net.ipv4.ip_no_pmtu_disc = 1 263 | net.ipv4.conf.all.rp_filter = 1 264 | net.ipv4.conf.all.accept_redirects = 0 265 | net.ipv4.conf.all.send_redirects = 0 266 | net.ipv6.conf.all.disable_ipv6 = 1 267 | net.ipv6.conf.default.disable_ipv6 = 1 268 | net.ipv6.conf.lo.disable_ipv6 = 1 269 | net.ipv6.conf.${ETH0ORSIMILAR}.disable_ipv6 = 1 270 | " >> /etc/sysctl.conf 271 | 272 | sysctl -p 273 | 274 | 275 | echo "config setup 276 | strictcrlpolicy=yes 277 | uniqueids=never 278 | 279 | conn roadwarrior 280 | auto=add 281 | compress=no 282 | type=tunnel 283 | keyexchange=ikev2 284 | fragmentation=yes 285 | forceencaps=yes 286 | 287 | # https://docs.strongswan.org/docs/5.9/config/IKEv2CipherSuites.html#_commercial_national_security_algorithm_suite 288 | # ... but we also allow aes256gcm16-prfsha256-ecp256, because that's sometimes just what macOS proposes 289 | ike=aes256gcm16-prfsha384-ecp384,aes256gcm16-prfsha256-ecp256! 290 | esp=aes256gcm16-ecp384! 291 | 292 | dpdaction=clear 293 | dpddelay=900s 294 | rekey=no 295 | left=%any 296 | leftid=@${VPNHOST} 297 | leftcert=cert.pem 298 | leftsendcert=always 299 | leftsubnet=0.0.0.0/0 300 | right=%any 301 | rightid=%any 302 | rightauth=eap-mschapv2 303 | eap_identity=%any 304 | rightdns=${VPNDNS} 305 | rightsourceip=${VPNIPPOOL} 306 | rightsendcert=never 307 | " > /etc/ipsec.conf 308 | 309 | echo "${VPNHOST} : RSA \"privkey.pem\" 310 | ${VPNUSERNAME} : EAP \"${VPNPASSWORD}\" 311 | " > /etc/ipsec.secrets 312 | 313 | ipsec restart 314 | 315 | 316 | echo 317 | echo "--- User ---" 318 | echo 319 | 320 | # user + SSH 321 | 322 | id -u "${LOGINUSERNAME}" &>/dev/null || adduser --disabled-password --gecos "" "${LOGINUSERNAME}" 323 | echo "${LOGINUSERNAME}:${LOGINPASSWORD}" | chpasswd 324 | adduser "${LOGINUSERNAME}" sudo 325 | 326 | sed -r \ 327 | -e "s/^#?Port 22$/Port ${SSHPORT}/" \ 328 | -e 's/^#?LoginGraceTime (120|2m)$/LoginGraceTime 30/' \ 329 | -e 's/^#?PermitRootLogin yes$/PermitRootLogin no/' \ 330 | -e 's/^#?X11Forwarding yes$/X11Forwarding no/' \ 331 | -e 's/^#?UsePAM yes$/UsePAM no/' \ 332 | -i.original /etc/ssh/sshd_config 333 | 334 | if [[ $CERTLOGIN = "y" ]]; then 335 | mkdir -p "/home/${LOGINUSERNAME}/.ssh" 336 | chown "${LOGINUSERNAME}" "/home/${LOGINUSERNAME}/.ssh" 337 | chmod 700 "/home/${LOGINUSERNAME}/.ssh" 338 | 339 | cp "/root/.ssh/authorized_keys" "/home/${LOGINUSERNAME}/.ssh/authorized_keys" 340 | chown "${LOGINUSERNAME}" "/home/${LOGINUSERNAME}/.ssh/authorized_keys" 341 | chmod 600 "/home/${LOGINUSERNAME}/.ssh/authorized_keys" 342 | 343 | sed -r \ 344 | -e "s/^#?PasswordAuthentication yes$/PasswordAuthentication no/" \ 345 | -i.allows_pwd /etc/ssh/sshd_config 346 | fi 347 | 348 | service ssh restart 349 | 350 | 351 | echo 352 | echo "--- Timezone, mail, unattended upgrades ---" 353 | echo 354 | 355 | timedatectl set-timezone "${TZONE}" 356 | /usr/sbin/update-locale LANG=en_GB.UTF-8 357 | 358 | 359 | sed -r \ 360 | -e "s/^myhostname =.*$/myhostname = ${VPNHOST}/" \ 361 | -e 's/^inet_interfaces =.*$/inet_interfaces = loopback-only/' \ 362 | -i.original /etc/postfix/main.cf 363 | 364 | grep -Fq 'jawj/IKEv2-setup' /etc/aliases || echo " 365 | # https://github.com/jawj/IKEv2-setup 366 | root: ${EMAILADDR} 367 | ${LOGINUSERNAME}: ${EMAILADDR} 368 | " >> /etc/aliases 369 | 370 | newaliases 371 | service postfix restart 372 | 373 | 374 | sed -r \ 375 | -e 's|^//Unattended-Upgrade::MinimalSteps "true";$|Unattended-Upgrade::MinimalSteps "true";|' \ 376 | -e 's|^//Unattended-Upgrade::Mail "root";$|Unattended-Upgrade::Mail "root";|' \ 377 | -e 's|^//Unattended-Upgrade::Automatic-Reboot "false";$|Unattended-Upgrade::Automatic-Reboot "true";|' \ 378 | -e 's|^//Unattended-Upgrade::Remove-Unused-Dependencies "false";|Unattended-Upgrade::Remove-Unused-Dependencies "true";|' \ 379 | -e 's|^//Unattended-Upgrade::Automatic-Reboot-Time "02:00";$|Unattended-Upgrade::Automatic-Reboot-Time "03:00";|' \ 380 | -i /etc/apt/apt.conf.d/50unattended-upgrades 381 | 382 | echo 'APT::Periodic::Update-Package-Lists "1"; 383 | APT::Periodic::Download-Upgradeable-Packages "1"; 384 | APT::Periodic::AutocleanInterval "7"; 385 | APT::Periodic::Unattended-Upgrade "1"; 386 | ' > /etc/apt/apt.conf.d/10periodic 387 | 388 | service unattended-upgrades restart 389 | 390 | echo 391 | echo "--- Creating configuration files ---" 392 | echo 393 | 394 | cd "/home/${LOGINUSERNAME}" 395 | 396 | cat << EOF > vpn-ios.mobileconfig 397 | 398 | 399 | 400 | 401 | PayloadContent 402 | 403 | 404 | IKEv2 405 | 406 | AuthenticationMethod 407 | None 408 | ChildSecurityAssociationParameters 409 | 410 | EncryptionAlgorithm 411 | AES-256-GCM 412 | IntegrityAlgorithm 413 | SHA2-384 414 | DiffieHellmanGroup 415 | 20 416 | LifeTimeInMinutes 417 | 1440 418 | 419 | DeadPeerDetectionRate 420 | Medium 421 | DisableMOBIKE 422 | 0 423 | DisableRedirect 424 | 0 425 | EnableCertificateRevocationCheck 426 | 0 427 | EnablePFS 428 | 429 | ExtendedAuthEnabled 430 | 431 | IKESecurityAssociationParameters 432 | 433 | EncryptionAlgorithm 434 | AES-256-GCM 435 | IntegrityAlgorithm 436 | SHA2-384 437 | DiffieHellmanGroup 438 | 20 439 | LifeTimeInMinutes 440 | 1440 441 | 442 | OnDemandEnabled 443 | 1 444 | OnDemandRules 445 | 446 | 447 | Action 448 | Connect 449 | 450 | 451 | RemoteAddress 452 | ${VPNHOST} 453 | RemoteIdentifier 454 | ${VPNHOST} 455 | UseConfigurationAttributeInternalIPSubnet 456 | 0 457 | 458 | IPv4 459 | 460 | OverridePrimary 461 | 1 462 | 463 | PayloadDescription 464 | Configures VPN settings 465 | PayloadDisplayName 466 | VPN 467 | PayloadIdentifier 468 | com.apple.vpn.managed.$(uuidgen) 469 | PayloadType 470 | com.apple.vpn.managed 471 | PayloadUUID 472 | $(uuidgen) 473 | PayloadVersion 474 | 1 475 | Proxies 476 | 477 | HTTPEnable 478 | 0 479 | HTTPSEnable 480 | 0 481 | 482 | UserDefinedName 483 | ${VPNHOST} 484 | VPNType 485 | IKEv2 486 | 487 | 488 | PayloadDisplayName 489 | IKEv2 VPN configuration (${VPNHOST}) 490 | PayloadIdentifier 491 | com.mackerron.vpn.$(uuidgen) 492 | PayloadRemovalDisallowed 493 | 494 | PayloadType 495 | Configuration 496 | PayloadUUID 497 | $(uuidgen) 498 | PayloadVersion 499 | 1 500 | 501 | 502 | EOF 503 | 504 | cat << EOF > vpn-mac.applescript 505 | set vpnuser to text returned of (display dialog "Please enter your VPN username" default answer "") 506 | set vpnpass to text returned of (display dialog "Please enter your VPN password" default answer "" with hidden answer) 507 | set plist to " 508 | 509 | 510 | 511 | PayloadContent 512 | 513 | 514 | IKEv2 515 | 516 | AuthenticationMethod 517 | None 518 | ChildSecurityAssociationParameters 519 | 520 | EncryptionAlgorithm 521 | AES-256-GCM 522 | IntegrityAlgorithm 523 | SHA2-384 524 | DiffieHellmanGroup 525 | 20 526 | LifeTimeInMinutes 527 | 1440 528 | 529 | DeadPeerDetectionRate 530 | Medium 531 | DisableMOBIKE 532 | 0 533 | DisableRedirect 534 | 0 535 | EnableCertificateRevocationCheck 536 | 0 537 | EnablePFS 538 | 539 | ExtendedAuthEnabled 540 | 541 | AuthName 542 | " & vpnuser & " 543 | AuthPassword 544 | " & vpnpass & " 545 | IKESecurityAssociationParameters 546 | 547 | EncryptionAlgorithm 548 | AES-256-GCM 549 | IntegrityAlgorithm 550 | SHA2-384 551 | DiffieHellmanGroup 552 | 20 553 | LifeTimeInMinutes 554 | 1440 555 | 556 | OnDemandEnabled 557 | 1 558 | OnDemandRules 559 | 560 | 561 | Action 562 | Connect 563 | 564 | 565 | RemoteAddress 566 | ${VPNHOST} 567 | RemoteIdentifier 568 | ${VPNHOST} 569 | UseConfigurationAttributeInternalIPSubnet 570 | 0 571 | 572 | IPv4 573 | 574 | OverridePrimary 575 | 1 576 | 577 | PayloadDescription 578 | Configures VPN settings 579 | PayloadDisplayName 580 | VPN 581 | PayloadIdentifier 582 | com.apple.vpn.managed.$(uuidgen) 583 | PayloadType 584 | com.apple.vpn.managed 585 | PayloadUUID 586 | $(uuidgen) 587 | PayloadVersion 588 | 1 589 | Proxies 590 | 591 | HTTPEnable 592 | 0 593 | HTTPSEnable 594 | 0 595 | 596 | UserDefinedName 597 | ${VPNHOST} 598 | VPNType 599 | IKEv2 600 | 601 | 602 | PayloadDisplayName 603 | IKEv2 VPN configuration (${VPNHOST}) 604 | PayloadIdentifier 605 | com.mackerron.vpn.$(uuidgen) 606 | PayloadRemovalDisallowed 607 | 608 | PayloadType 609 | Configuration 610 | PayloadUUID 611 | $(uuidgen) 612 | PayloadVersion 613 | 1 614 | 615 | " 616 | set tmpdir to do shell script "mktemp -d" 617 | set tmpfile to tmpdir & "/vpn.mobileconfig" 618 | do shell script "touch " & tmpfile 619 | write plist to tmpfile 620 | do shell script "open /System/Library/PreferencePanes/Profiles.prefPane " & tmpfile 621 | delay 5 622 | do shell script "rm " & tmpfile 623 | EOF 624 | 625 | grep -Fq 'jawj/IKEv2-setup' /etc/mime.types || echo " 626 | # https://github.com/jawj/IKEv2-setup 627 | application/vnd.strongswan.profile sswan 628 | " >> /etc/mime.types 629 | 630 | cat << EOF > vpn-android.sswan 631 | { 632 | "uuid": "$(uuidgen)", 633 | "name": "${VPNHOST}", 634 | "type": "ikev2-eap", 635 | "remote": { 636 | "addr": "${VPNHOST}" 637 | } 638 | } 639 | EOF 640 | 641 | cat << EOF > vpn-ubuntu-client.sh 642 | #!/bin/bash -e 643 | if [[ \$(id -u) -ne 0 ]]; then echo "Please run as root (e.g. sudo ./path/to/this/script)"; exit 1; fi 644 | 645 | read -p "VPN username (same as entered on server): " VPNUSERNAME 646 | while true; do 647 | read -s -p "VPN password (same as entered on server): " VPNPASSWORD 648 | echo 649 | read -s -p "Confirm VPN password: " VPNPASSWORD2 650 | echo 651 | [ "\$VPNPASSWORD" = "\$VPNPASSWORD2" ] && break 652 | echo "Passwords didn't match -- please try again" 653 | done 654 | 655 | apt-get install -y strongswan libstrongswan-standard-plugins libcharon-extra-plugins 656 | apt-get install -y libcharon-standard-plugins || true # 17.04+ only 657 | 658 | ln -f -s /etc/ssl/certs/ISRG_Root_X1.pem /etc/ipsec.d/cacerts/ 659 | 660 | grep -Fq 'jawj/IKEv2-setup' /etc/ipsec.conf || echo " 661 | # https://github.com/jawj/IKEv2-setup 662 | conn ikev2vpn 663 | ikelifetime=60m 664 | keylife=20m 665 | rekeymargin=3m 666 | keyingtries=1 667 | keyexchange=ikev2 668 | ike=aes256gcm16-prfsha384-ecp384! 669 | esp=aes256gcm16-ecp384! 670 | leftsourceip=%config 671 | leftauth=eap-mschapv2 672 | eap_identity=\${VPNUSERNAME} 673 | right=${VPNHOST} 674 | rightauth=pubkey 675 | rightid=@${VPNHOST} 676 | rightsubnet=0.0.0.0/0 677 | auto=add # or auto=start to bring up automatically 678 | " >> /etc/ipsec.conf 679 | 680 | grep -Fq 'jawj/IKEv2-setup' /etc/ipsec.secrets || echo " 681 | # https://github.com/jawj/IKEv2-setup 682 | \${VPNUSERNAME} : EAP \"\${VPNPASSWORD}\" 683 | " >> /etc/ipsec.secrets 684 | 685 | ipsec restart 686 | sleep 5 # is there a better way? 687 | 688 | echo "Bringing up VPN ..." 689 | ipsec up ikev2vpn 690 | ipsec statusall 691 | 692 | echo 693 | echo -n "Testing IP address ... " 694 | VPNIP=\$(dig -4 +short ${VPNHOST}) 695 | ACTUALIP=\$(dig -4 +short myip.opendns.com @resolver1.opendns.com) 696 | if [[ "\$VPNIP" == "\$ACTUALIP" ]]; then echo "PASSED (IP: \${VPNIP})"; else echo "FAILED (IP: \${ACTUALIP}, VPN IP: \${VPNIP})"; fi 697 | 698 | echo 699 | echo "To disconnect: ipsec down ikev2vpn" 700 | echo "To reconnect: ipsec up ikev2vpn" 701 | echo "To connect automatically: change auto=add to auto=start in /etc/ipsec.conf" 702 | EOF 703 | 704 | cat << EOF > vpn-instructions.txt 705 | == iOS == 706 | 707 | A configuration profile is attached as vpn-ios.mobileconfig. 708 | 709 | Open this attachment. Then go to Settings > General > VPN & Device Management, and find the profile under 'DOWNLOADED PROFILE'. 710 | 711 | You will be asked for your device PIN or password, and then your VPN username and password. 712 | 713 | These instructions apply to iOS 15. Earlier (and probably later) versions of iOS will also work, but the exact setup steps may differ. 714 | 715 | 716 | == macOS == 717 | 718 | In macOS Monterey, your VPN username and password must be embedded in the profile file. However, your password cannot be included in a profile sent by email for security reasons. 719 | 720 | So: open vpn-mac.applescript and run it from Script Editor. You'll be prompted for your VPN username and password. 721 | 722 | System Preferences will then open. Select the profile listed as 'Downloaded' on the left, and click 'Install...' in the main panel. 723 | 724 | 725 | == Windows == 726 | 727 | You will need Windows 10 Pro or above. Please run the following commands in PowerShell: 728 | 729 | \$Response = Invoke-WebRequest -UseBasicParsing -Uri https://valid-isrgrootx1.letsencrypt.org 730 | # ^ this line fixes a certificate lazy-loading bug: see https://github.com/jawj/IKEv2-setup/issues/126 731 | 732 | Add-VpnConnection -Name "${VPNHOST}" \` 733 | -ServerAddress "${VPNHOST}" \` 734 | -TunnelType IKEv2 \` 735 | -EncryptionLevel Maximum \` 736 | -AuthenticationMethod EAP \` 737 | -RememberCredential 738 | 739 | Set-VpnConnectionIPsecConfiguration -ConnectionName "${VPNHOST}" \` 740 | -AuthenticationTransformConstants GCMAES256 \` 741 | -CipherTransformConstants GCMAES256 \` 742 | -EncryptionMethod GCMAES256 \` 743 | -IntegrityCheckMethod SHA384 \` 744 | -DHGroup ECP384 \` 745 | -PfsGroup ECP384 \` 746 | -Force 747 | 748 | # Run the following command to retain access to the local network (e.g. printers, file servers) while the VPN is connected. 749 | # On a home network, you probably want this. On a public network, you probably don't. 750 | 751 | Set-VpnConnection -Name "${VPNHOST}" -SplitTunneling \$True 752 | 753 | You will need to enter your chosen VPN username and password in order to connect. 754 | 755 | 756 | == Android == 757 | 758 | Download the strongSwan app from the Play Store: https://play.google.com/store/apps/details?id=org.strongswan.android 759 | 760 | Then open the attached .sswan file, or select it after choosing 'Import VPN profile' from the strongSwan app menu. You will need to enter your chosen VPN username and password in order to connect. 761 | 762 | For a persistent connection, go to your device's Settings app and choose Network & Internet > Advanced > VPN > strongSwan VPN Client, tap the gear icon and toggle on 'Always-on VPN' (these options may differ by Android version and provider). 763 | 764 | 765 | == Ubuntu == 766 | 767 | A bash script to set up strongSwan as a VPN client is attached as vpn-ubuntu-client.sh. You will need to chmod +x and then run the script as root. 768 | 769 | EOF 770 | 771 | EMAIL=$USER@$VPNHOST mutt -s "VPN configuration" -a vpn-ios.mobileconfig vpn-mac.applescript vpn-android.sswan vpn-ubuntu-client.sh -- "${EMAILADDR}" < vpn-instructions.txt 772 | 773 | echo 774 | echo "--- How to connect ---" 775 | echo 776 | echo "Connection instructions have been emailed to you, and can also be found in your home directory, /home/${LOGINUSERNAME}" 777 | 778 | # necessary for IKEv2? 779 | # Windows: https://support.microsoft.com/en-us/kb/926179 780 | # HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\PolicyAgent += AssumeUDPEncapsulationContextOnSendRule, DWORD = 2 781 | --------------------------------------------------------------------------------