diff --git a/Dockerfile b/Dockerfile index 3dd988e3..e07e2aef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ FROM alpine:3.8 LABEL maintainer="quentin.mcgaw@gmail.com" \ - description="VPN client to private internet access servers using OpenVPN, Alpine and IPtables firewall" \ - download="5.7MB" \ - size="8.94MB" \ - ram="11MB" \ + description="VPN client to private internet access servers using OpenVPN, IPtables firewall, DNS over TLS with Unbound and Alpine Linux" \ + download="???MB" \ + size="15.7MB" \ + ram="13MB" \ cpu_usage="Low" \ github="https://github.com/qdm12/private-internet-access-docker" HEALTHCHECK --interval=1m --timeout=10s --start-period=10s --retries=1 \ diff --git a/README.md b/README.md index 9641d809..7602d519 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Private Internet Access Client (OpenVPN on Alpine Linux) +# Private Internet Access Client (OpenVPN+Iptables+DNS over TLS on Alpine Linux) -Docker VPN client to private internet access servers using [OpenVPN](https://openvpn.net/) and Iptables on Alpine Linux. +Docker VPN client to private internet access servers using [OpenVPN](https://openvpn.net/), Iptables and Unbound (Cloudflare DNS over TLS) on Alpine Linux. Optionally set the protocol (TCP, UDP) and the level of encryption using Docker environment variables. @@ -24,28 +24,28 @@ A killswitch is implemented with the *iptables* firewall, only allowing traffic | Download size | Image size | RAM usage | CPU usage | | --- | --- | --- | --- | -| 5MB | 8.94MB | 11MB | Low | +| ???MB | 15.7MB | 14MB | Low | -It is based on: +## Features -- [Alpine 3.8](https://alpinelinux.org) -- [OpenVPN 2.4.6-r3](https://pkgs.alpinelinux.org/package/v3.8/main/x86_64/openvpn) -- [IPtables 1.6.2-r0](https://pkgs.alpinelinux.org/package/v3.8/main/x86_64/iptables) -- CA-Certificates for the healthcheck (through HTTPS) +- Uses [OpenVPN 2.4.6-r3](https://pkgs.alpinelinux.org/package/v3.8/main/x86_64/openvpn) to connect to PIA servers +- The firewall [IPtables 1.6.2-r0](https://pkgs.alpinelinux.org/package/v3.8/main/x86_64/iptables) enforces the container to communicate only through the VPN or with other containers in its virtual network +- Your DNS queries are encrypted using [Unbound 1.7.3-r0](https://pkgs.alpinelinux.org/package/v3.8/main/x86_64/unbound) configure with Cloudflare's 1.1.1.1 DNS over TLS +- Malicious domain names resolution is blocked with [Unbound 1.7.3-r0](https://pkgs.alpinelinux.org/package/v3.8/main/x86_64/unbound) +- Lightweight, based on [Alpine 3.8](https://alpinelinux.org) +- Restarts OpenVPN on failure using another IP address corresponding to the PIA server domain name (usually 10 IPs per subdomain name) +- Regular Docker healthchecks using wget on duckduckgo.com +- Connect other containers to it -It requires: +## Requirements - A Private Internet Access **username** and **password** - [Sign up](https://www.privateinternetaccess.com/pages/buy-vpn/) - [Docker](https://docs.docker.com/install/) installed on the host - -The PIA *.ovpn* configuration files are downloaded from [the PIA website](https://www.privateinternetaccess.com/openvpn/openvpn.zip) when the Docker image is built. You can build the image yourself if you are paranoid. - -You might also want to use [my Cloudflare DNS over TLS Docker container](https://hub.docker.com/r/qmcgaw/cloudflare-dns-server/) to connect to any PIA server so that: - -- Man-in-the-middle (ISP, hacker, government) can't block you from resolving the PIA server domain name - *For example, `austria.privateinternetaccess.com` maps to `185.216.34.229`* -- Man-in-the-middle (ISP, hacker, government) can't see to which server you connect nor when. - *As the domain name are sent to 1.1.1.1 over TLS, there is no way to examine what domains you are asking to be resolved* +- If you use an advanced firewall: + - Allow outgoing TCP port 501 for TCP strong encryption + - Allow outgoing TCP port 502 for TCP normal encryption + - Allow outgoing UDP port 1197 for UDP strong encryption + - Allow outgoing UDP port 1198 for UDP normal encryption ## Setup @@ -217,14 +217,13 @@ For more containers, add more `--link pia:xxx` and modify *nginx.conf* according ## EXTRA: For the paranoids -- You might want to build the image yourself +- You might want to build the Docker image yourself - The download and unziping is done at build for the ones not able to download the zip files with their ISPs. - Checksums for PIA openvpn zip files are not used as these files change often - You should use strong encryption for the environment variable `ENCRYPTION` +- Let me know if you have any extra idea :) ! ### TODOs -- More iptables restrictions -- Rework readme with unbound required for VPN - Block malicious websites with Unbound - Add checks when launching PIA $? \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh index f32d4d18..1bc1471c 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -2,16 +2,7 @@ printf "=== PIA CONTAINER ===" -############################################ -# SETTING DNS OVER TLS TO 1.1.1.1 / 1.0.0.1 -############################################ -printf "\nChanging DNS to localhost..." -echo "nameserver 127.0.0.1" > /etc/resolv.conf -echo "options ndots:0" >> /etc/resolv.conf -printf "DONE" -printf "\nLaunching Unbound daemon to connect to Cloudflare DNS 1.1.1.1 at its TLS endpoint..." -unbound -printf "DONE" +cd /openvpn-$PROTOCOL-$ENCRYPTION ############################################ # ORIGINAL IP FOR HEALTHCHECK @@ -35,13 +26,17 @@ printf "\n * Port: $PORT" printf "\n * Domain: $PIADOMAIN" printf "\n * Detecting IP addresses corresponding to $PIADOMAIN..." VPNIPS=$(nslookup $PIADOMAIN localhost | tail -n +5 | grep -o '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}') +VPNIPSLENGTH=0 for ip in $VPNIPS do printf "\n $ip" + VPNIPSLENGTH=$((VPNIPSLENGTH+1)) done printf "\n * Deleting all iptables rules..." iptables --flush iptables --delete-chain +iptables -t nat --flush +iptables -t nat --delete-chain ip6tables --flush ip6tables --delete-chain printf "DONE" @@ -52,10 +47,8 @@ ip6tables -P OUTPUT DROP 2>/dev/null printf "\n * Adding rules to accept local loopback traffic..." iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -A OUTPUT -o lo -j ACCEPT -iptables -A INPUT -i lo -j ACCEPT ip6tables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null ip6tables -A OUTPUT -o lo -j ACCEPT 2>/dev/null -ip6tables -A INPUT -i lo -j ACCEPT 2>/dev/null printf "DONE" printf "\n * Adding rules to accept traffic of subnet $SUBNET..." iptables -A OUTPUT -d $SUBNET -j ACCEPT @@ -65,41 +58,61 @@ for ip in $VPNIPS do printf "\n * Adding rules to accept traffic with $ip on port $PROTOCOL $PORT..." iptables -A OUTPUT -j ACCEPT -d $ip -o eth0 -p $PROTOCOL -m $PROTOCOL --dport $PORT - iptables -A INPUT -j ACCEPT -s $ip -i eth0 -p $PROTOCOL -m $PROTOCOL --sport $PORT ip6tables -A OUTPUT -j ACCEPT -d $ip -o eth0 -p $PROTOCOL -m $PROTOCOL --dport $PORT 2>/dev/null - ip6tables -A INPUT -j ACCEPT -s $ip -i eth0 -p $PROTOCOL -m $PROTOCOL --sport $PORT 2>/dev/null printf "DONE" done printf "\n * Adding rules to accept traffic going through the tun device..." iptables -A OUTPUT -o tun0 -j ACCEPT -iptables -A INPUT -i tun0 -j ACCEPT ip6tables -A OUTPUT -o tun0 -j ACCEPT 2>/dev/null -ip6tables -A INPUT -i tun0 -j ACCEPT 2>/dev/null printf "DONE" -printf "\n * Allowing outgoing DNS queries on port 53 UDP..." -iptables -A OUTPUT -p udp -m udp --dport 53 -j ACCEPT -ip6tables -A OUTPUT -p udp -m udp --dport 53 -j ACCEPT 2>/dev/null +#printf "\n * Allowing outgoing DNS queries on port 53 UDP..." +#iptables -A OUTPUT -p udp -m udp --dport 53 -j ACCEPT +#ip6tables -A OUTPUT -p udp -m udp --dport 53 -j ACCEPT 2>/dev/null +#printf "DONE" + +############################################ +# SETTING DNS OVER TLS TO 1.1.1.1 / 1.0.0.1 +############################################ +printf "\nLaunching Unbound daemon to connect to Cloudflare DNS 1.1.1.1 at its TLS endpoint..." +unbound +printf "DONE" +printf "\nChanging DNS to localhost..." +echo "nameserver 127.0.0.1" > /etc/resolv.conf +echo "options ndots:0" >> /etc/resolv.conf printf "DONE" ############################################ -# SUMMARY +# USE NON-ROOT USER ############################################ -printf "\nStarting OpenVPN using the following parameters:" -printf "\n * Domain: $PIADOMAIN" -printf "\n * Port: $PORT" -printf "\n * Protocol: $PROTOCOL" -printf "\n * Encryption: $ENCRYPTION" - -############################################ -# OPENVPN LAUNCH -############################################ -cd /openvpn-$PROTOCOL-$ENCRYPTION printf "\nSwitching from root to nonrootuser..." su -l nonrootuser -printf "DONE\n" -openvpn --config "$REGION.ovpn" --auth-user-pass /auth.conf +printf "DONE" ############################################ -# CLEANUP +# OPENVPN LAUNCH (retry with next VPN IP if fail) ############################################ -printf "\nExiting...\n\n" \ No newline at end of file +failed=1 +i=1 +PREVIOUSIP=$PIADOMAIN +while [ $failed != 0 ] +do + VPNIP=$(echo $VPNIPS | cut -d' ' -f$i) + sed -i "s/$PREVIOUSIP/$VPNIP/g" $REGION.ovpn + PREVIOUSIP=$VPNIP + printf "\nStarting OpenVPN using the following parameters:" + printf "\n * Region: $REGION" + printf "\n * Encryption: $ENCRYPTION" + printf "\n * Address: $PROTOCOL://$VPNIP:$PORT" + printf "\n\n" + openvpn --config "$REGION.ovpn" --auth-user-pass /auth.conf + failed=$? + if [[ $failed != 0 ]]; then + echo "==> Openvpn failed with error code: $failed" + i=$((i+1)) + if [[ $i -gt $VPNIPSLENGTH ]]; then + i=0 + fi + else + echo "==> Openvpn stopped cleanly" + fi +done