├── .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 |
--------------------------------------------------------------------------------