Multiple additions and fixes #5

- Multi stage build
- Download and checks Unbound Root anchors
- Download and build malicious hostnames block list for Unbound
- Healthcheck only based on the current IP being different from the initial IP
- IPv6 related completely removed
- Multiple checks at launch with $?
- Launch openvpn as root (can't change user)
- Unbound configured with DNS SEC for DNS over TLS
This commit is contained in:
Quentin McGaw
2018-10-04 22:24:43 +02:00
parent b8dbf0761f
commit b73ad75cde
3 changed files with 145 additions and 37 deletions

View File

@@ -1,3 +1,60 @@
FROM alpine:3.8 AS rootanchors
RUN apk add -q --progress wget perl-xml-xpath && \
wget -q https://www.internic.net/domain/named.root -O named.root && \
echo "602f28581292bf5e50c8137c955173e6 named.root" > hashes.md5 && \
md5sum -c hashes.md5 && \
wget -q https://data.iana.org/root-anchors/root-anchors.xml -O root-anchors.xml && \
echo "1b2a628d1ff22d4dc7645cfc89f21b6a575526439c6706ecf853e6fff7099dc8 root-anchors.xml" > hashes.sha256 && \
sha256sum -c hashes.sha256 && \
KEYTAGS=$(xpath -q -e '/TrustAnchor/KeyDigest/KeyTag/node()' root-anchors.xml) && \
ALGORITHMS=$(xpath -q -e '/TrustAnchor/KeyDigest/Algorithm/node()' root-anchors.xml) && \
DIGESTTYPES=$(xpath -q -e '/TrustAnchor/KeyDigest/DigestType/node()' root-anchors.xml) && \
DIGESTS=$(xpath -q -e '/TrustAnchor/KeyDigest/Digest/node()' root-anchors.xml) && \
i=1 && \
while [ 1 ]; do \
KEYTAG=$(echo $KEYTAGS | cut -d" " -f$i); \
[ "$KEYTAG" != "" ] || break; \
ALGORITHM=$(echo $ALGORITHMS | cut -d" " -f$i); \
DIGESTTYPE=$(echo $DIGESTTYPES | cut -d" " -f$i); \
DIGEST=$(echo $DIGESTS | cut -d" " -f$i); \
echo ". IN DS $KEYTAG $ALGORITHM $DIGESTTYPE $DIGEST" >> /root.key; \
i=`expr $i + 1`; \
done;
FROM alpine:3.8 AS blocks
RUN apk add -q --progress wget && \
wget -q https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts -O temp && \
sed -i '/\(^[ \|\t]*#\)\|\(^[ ]\+\)\|\(^$\)\|\(^[\n\|\r\|\r\n][ \|\t]*$\)\|\(^127.0.0.1\)\|\(^255.255.255.255\)\|\(^::1\)\|\(^fe80\)\|\(^ff00\)\|\(^ff02\)\|\(^0.0.0.0 0.0.0.0\)/d' temp && \
sed -i 's/\([ \|\t]*#.*$\)\|\(\r\)\|\(0.0.0.0 \)//g' temp && \
cat temp >> allHostnames && \
wget -q https://raw.githubusercontent.com/CHEF-KOCH/NSABlocklist/master/HOSTS -O temp && \
sed -i '/\(^[ \|\t]*#\)\|\(^[ ]\+\)\|\(^$\)\|\(^[\n\|\r\|\r\n][ \|\t]*$\)\|\(^127.0.0.1\)/d' temp && \
sed -i 's/\([ \|\t]*#.*$\)\|\(\r\)\|\(0.0.0.0 \)//g' temp && \
cat temp >> allHostnames && \
wget -q https://raw.githubusercontent.com/k0nsl/unbound-blocklist/master/blocks.conf -O temp && \
sed -i '/\(^[ \|\t]*#\)\|\(^[ ]\+\)\|\(^$\)\|\(^[\n\|\r\|\r\n][ \|\t]*$\)\|\(^local-data\)/d' temp && \
sed -i 's/\([ \|\t]*#.*$\)\|\(\r\)\|\(local-zone: \"\)\|\(\" redirect\)//g' temp && \
cat temp >> allHostnames && \
wget -q https://raw.githubusercontent.com/notracking/hosts-blocklists/master/domains.txt -O temp && \
sed -i '/\(^[ \|\t]*#\)\|\(^[ ]\+\)\|\(^$\)\|\(^[\n\|\r\|\r\n][ \|\t]*$\)\|\(::$\)/d' temp && \
sed -i 's/\([ \|\t]*#.*$\)\|\(\r\)\|\(address=\/\)\|\(\/0.0.0.0$\)//g' temp && \
cat temp >> allHostnames && \
wget -q https://raw.githubusercontent.com/notracking/hosts-blocklists/master/hostnames.txt -O temp && \
sed -i '/\(^[ \|\t]*#\)\|\(^[ ]\+\)\|\(^$\)\|\(^[\n\|\r\|\r\n][ \|\t]*$\)\|\(^::\)/d' temp && \
sed -i 's/\([ \|\t]*#.*$\)\|\(\r\)\|\(^0.0.0.0 \)//g' temp && \
cat temp >> allHostnames && \
sort -o allHostnames allHostnames && \
cat allHostnames | uniq -i -u > uniqueHostnames && \
cat allHostnames | uniq -i -d > duplicateHostnames && \
duplicates=$(($(wc -l < allHostnames)-$(wc -l < uniqueHostnames)-$(wc -l < duplicateHostnames))) && \
echo "Removed $duplicates duplicates in a total of $(wc -l < allHostnames) hostnames" && \
mv uniqueHostnames allHostnames && \
cat duplicateHostnames >> allHostnames && \
sort -o allHostnames allHostnames && \
sed -i '/\(psma01.com.\)\|\(psma02.com.\)\|\(psma03.com.\)\|\(MEZIAMUSSUCEMAQUEUE.SU\)/d' allHostnames && \
while read line; do printf "local-zone: \"$line\" static\n" >> blocks-malicious.conf; done < allHostnames && \
tar -cjf blocks-malicious.conf.bz2 blocks-malicious.conf
FROM alpine:3.8 FROM alpine:3.8
LABEL maintainer="quentin.mcgaw@gmail.com" \ LABEL maintainer="quentin.mcgaw@gmail.com" \
description="VPN client to private internet access servers using OpenVPN, IPtables firewall, DNS over TLS with Unbound and Alpine Linux" \ description="VPN client to private internet access servers using OpenVPN, IPtables firewall, DNS over TLS with Unbound and Alpine Linux" \
@@ -6,14 +63,16 @@ LABEL maintainer="quentin.mcgaw@gmail.com" \
ram="13MB" \ ram="13MB" \
cpu_usage="Low" \ cpu_usage="Low" \
github="https://github.com/qdm12/private-internet-access-docker" github="https://github.com/qdm12/private-internet-access-docker"
HEALTHCHECK --interval=1m --timeout=10s --start-period=10s --retries=1 \ COPY --from=rootanchors /named.root /etc/unbound/root.hints
CMD export OLD_VPN_IP="$NEW_VPN_IP" && \ COPY --from=rootanchors /root.key /etc/unbound/root.key
export NEW_VPN_IP=$(wget -qqO- 'https://duckduckgo.com/?q=what+is+my+ip' | grep -ow 'Your IP address is [0-9.]*[0-9]' | grep -ow '[0-9][0-9.]*') && \ COPY --from=blocks /blocks-malicious.conf.bz2 /etc/unbound/blocks-malicious.conf.bz2
[ "$NEW_VPN_IP" != "$INITIAL_IP" ] && [ "$NEW_VPN_IP" != "$OLD_VPN_IP" ] || exit 1 HEALTHCHECK --interval=5m --timeout=15s --start-period=10s --retries=2 \
CMD if [[ "$(wget -qqO- 'https://duckduckgo.com/?q=what+is+my+ip' | grep -ow 'Your IP address is [0-9.]*[0-9]' | grep -ow '[0-9][0-9.]*')" == "$INITIAL_IP" ]]; then echo "IP address is the same as the non VPN IP address"; exit 1; fi
ENV ENCRYPTION=strong \ ENV ENCRYPTION=strong \
PROTOCOL=tcp \ PROTOCOL=tcp \
REGION=Germany REGION=Germany \
RUN apk add -q --progress --no-cache --update openvpn ca-certificates iptables ip6tables unbound && \ BLOCK_MALICIOUS=off
RUN apk add -q --progress --no-cache --update openvpn ca-certificates iptables unbound && \
apk add -q --progress --no-cache --update --virtual=build-dependencies unzip && \ apk add -q --progress --no-cache --update --virtual=build-dependencies unzip && \
mkdir /openvpn-udp-normal /openvpn-udp-strong /openvpn-tcp-normal /openvpn-tcp-strong && \ mkdir /openvpn-udp-normal /openvpn-udp-strong /openvpn-tcp-normal /openvpn-tcp-strong && \
wget -q https://www.privateinternetaccess.com/openvpn/openvpn.zip \ wget -q https://www.privateinternetaccess.com/openvpn/openvpn.zip \
@@ -26,7 +85,8 @@ RUN apk add -q --progress --no-cache --update openvpn ca-certificates iptables i
unzip -q openvpn-strong-tcp.zip -d /openvpn-tcp-strong && \ unzip -q openvpn-strong-tcp.zip -d /openvpn-tcp-strong && \
apk del -q --progress --purge build-dependencies && \ apk del -q --progress --purge build-dependencies && \
rm -rf /*.zip /var/cache/apk/* /etc/unbound/unbound.conf && \ rm -rf /*.zip /var/cache/apk/* /etc/unbound/unbound.conf && \
addgroup -S nonrootusers && adduser -S nonrootuser -G nonrootusers chown unbound /etc/unbound/root.key
COPY unbound.conf /etc/unbound/unbound.conf COPY unbound.conf /etc/unbound/unbound.conf
COPY entrypoint.sh / COPY entrypoint.sh /
ENTRYPOINT /entrypoint.sh RUN chmod +x /entrypoint.sh
ENTRYPOINT /entrypoint.sh

View File

@@ -19,14 +19,30 @@ do
done done
printf "\nTUN device is opened" printf "\nTUN device is opened"
############################################
# BLOCKING MALICIOUS HOSTS WITH UNBOUND
############################################
touch /etc/unbound/blocks-malicious.conf
printf "\nUnbound malicious hosts blocking is $BLOCK_MALICIOUS"
if [[ "$BLOCK_MALICIOUS" == "on" ]]; then
printf "\nExtracting blocks-malicious.conf.bz2..."
tar -xjf /etc/unbound/blocks-malicious.conf.bz2 -C /etc/unbound/
rm /etc/unbound/blocks-malicious.conf.bz2
printf "DONE"
fi
############################################ ############################################
# SETTING DNS OVER TLS TO 1.1.1.1 / 1.0.0.1 # 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..." printf "\nLaunching Unbound daemon to connect to Cloudflare DNS 1.1.1.1 at its TLS endpoint..."
unbound unbound
status=$?
if [[ "$status" != 0 ]]; then printf "ERROR with status code $status\nSleeping for 10 seconds before exit...\n"; sleep 10; exit $status; fi
printf "DONE" printf "DONE"
printf "\nChanging DNS to localhost..." printf "\nChanging DNS to localhost..."
echo "nameserver 127.0.0.1" > /etc/resolv.conf echo "nameserver 127.0.0.1" > /etc/resolv.conf
status=$?
if [[ "$status" != 0 ]]; then printf "ERROR with status code $status\nSleeping for 10 seconds before exit...\n"; sleep 10; exit $status; fi
echo "options ndots:0" >> /etc/resolv.conf echo "options ndots:0" >> /etc/resolv.conf
printf "DONE" printf "DONE"
@@ -35,6 +51,8 @@ printf "DONE"
############################################ ############################################
printf "\nGetting non VPN public IP address..." printf "\nGetting non VPN public IP address..."
export INITIAL_IP=$(wget -qqO- 'https://duckduckgo.com/?q=what+is+my+ip' | grep -ow 'Your IP address is [0-9.]*[0-9]' | grep -ow '[0-9][0-9.]*') export INITIAL_IP=$(wget -qqO- 'https://duckduckgo.com/?q=what+is+my+ip' | grep -ow 'Your IP address is [0-9.]*[0-9]' | grep -ow '[0-9][0-9.]*')
status=$?
if [[ "$status" != 0 ]]; then printf "ERROR with status code $status\nSleeping for 10 seconds before exit...\n"; sleep 10; exit $status; fi
printf "$INITIAL_IP" printf "$INITIAL_IP"
############################################ ############################################
@@ -43,15 +61,23 @@ printf "$INITIAL_IP"
printf "\nSetting firewall for killswitch purposes..." printf "\nSetting firewall for killswitch purposes..."
printf "\n * Detecting local subnet..." printf "\n * Detecting local subnet..."
SUBNET=$(ip route show default | tail -n 1 | awk '// {print $1}') SUBNET=$(ip route show default | tail -n 1 | awk '// {print $1}')
status=$?
if [[ "$status" != 0 ]]; then printf "ERROR with status code $status\nSleeping for 10 seconds before exit...\n"; sleep 10; exit $status; fi
printf "$SUBNET" printf "$SUBNET"
printf "\n * Reading parameters to be used for region $REGION, protocol $PROTOCOL and encryption $ENCRYPTION..." printf "\n * Reading parameters to be used for region $REGION, protocol $PROTOCOL and encryption $ENCRYPTION..."
CONNECTIONSTRING=$(grep -i "/openvpn-$PROTOCOL-$ENCRYPTION/$REGION.ovpn" -e 'privateinternetaccess.com') CONNECTIONSTRING=$(grep -i "/openvpn-$PROTOCOL-$ENCRYPTION/$REGION.ovpn" -e 'privateinternetaccess.com')
status=$?
if [[ "$status" != 0 ]]; then printf "ERROR with status code $status\nSleeping for 10 seconds before exit...\n"; sleep 10; exit $status; fi
PORT=$(echo $CONNECTIONSTRING | cut -d' ' -f3) PORT=$(echo $CONNECTIONSTRING | cut -d' ' -f3)
if [[ "$PORT" == "" ]]; then printf "Port could not be extracted from configuration file\n"; exit 1; fi
PIADOMAIN=$(echo $CONNECTIONSTRING | cut -d' ' -f2) PIADOMAIN=$(echo $CONNECTIONSTRING | cut -d' ' -f2)
if [[ "$PIADOMAIN" == "" ]]; then printf "Port could not be extracted from configuration file\n"; exit 1; fi
printf "\n * Port: $PORT" printf "\n * Port: $PORT"
printf "\n * Domain: $PIADOMAIN" printf "\n * Domain: $PIADOMAIN"
printf "\n * Detecting IP addresses corresponding to $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\}') 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\}')
status=$?
if [[ "$status" != 0 ]]; then printf "ERROR with status code $status\nSleeping for 10 seconds before exit...\n"; sleep 10; exit $status; fi
VPNIPSLENGTH=0 VPNIPSLENGTH=0
for ip in $VPNIPS for ip in $VPNIPS
do do
@@ -60,47 +86,39 @@ do
done done
printf "\n * Deleting all iptables rules..." printf "\n * Deleting all iptables rules..."
iptables --flush iptables --flush
status=$?
if [[ "$status" != 0 ]]; then printf "ERROR with status code $status\nSleeping for 10 seconds before exit...\n"; sleep 10; exit $status; fi
iptables --delete-chain iptables --delete-chain
status=$?
if [[ "$status" != 0 ]]; then printf "ERROR with status code $status\nSleeping for 10 seconds before exit...\n"; sleep 10; exit $status; fi
iptables -t nat --flush iptables -t nat --flush
status=$?
if [[ "$status" != 0 ]]; then printf "ERROR with status code $status\nSleeping for 10 seconds before exit...\n"; sleep 10; exit $status; fi
iptables -t nat --delete-chain iptables -t nat --delete-chain
ip6tables --flush status=$?
ip6tables --delete-chain if [[ "$status" != 0 ]]; then printf "ERROR with status code $status\nSleeping for 10 seconds before exit...\n"; sleep 10; exit $status; fi
printf "DONE" printf "DONE"
iptables -F OUTPUT iptables -F OUTPUT
iptables -P OUTPUT DROP iptables -P OUTPUT DROP
ip6tables -F OUTPUT 2>/dev/null
ip6tables -P OUTPUT DROP 2>/dev/null
printf "\n * Adding rules to accept local loopback traffic..." printf "\n * Adding rules to accept local loopback traffic..."
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT iptables -A OUTPUT -o 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
printf "DONE" printf "DONE"
printf "\n * Adding rules to accept traffic of subnet $SUBNET..." printf "\n * Adding rules to accept traffic of subnet $SUBNET..."
iptables -A OUTPUT -d $SUBNET -j ACCEPT iptables -A OUTPUT -d $SUBNET -j ACCEPT
ip6tables -A OUTPUT -d $SUBNET -j ACCEPT 2>/dev/null status=$?
if [[ "$status" != 0 ]]; then printf "ERROR with status code $status\nSleeping for 10 seconds before exit...\n"; sleep 10; exit $status; fi
printf "DONE" printf "DONE"
for ip in $VPNIPS for ip in $VPNIPS
do do
printf "\n * Adding rules to accept traffic with $ip on port $PROTOCOL $PORT..." 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 OUTPUT -j ACCEPT -d $ip -o eth0 -p $PROTOCOL -m $PROTOCOL --dport $PORT
ip6tables -A OUTPUT -j ACCEPT -d $ip -o eth0 -p $PROTOCOL -m $PROTOCOL --dport $PORT 2>/dev/null status=$?
if [[ "$status" != 0 ]]; then printf "ERROR with status code $status\nSleeping for 10 seconds before exit...\n"; sleep 10; exit $status; fi
printf "DONE" printf "DONE"
done done
printf "\n * Adding rules to accept traffic going through the tun device..." printf "\n * Adding rules to accept traffic going through the tun device..."
iptables -A OUTPUT -o tun0 -j ACCEPT iptables -A OUTPUT -o tun0 -j ACCEPT
ip6tables -A OUTPUT -o 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 "DONE"
############################################
# USE NON-ROOT USER
############################################
printf "\nSwitching from root to nonrootuser..."
su -l nonrootuser
printf "DONE" printf "DONE"
############################################ ############################################
@@ -112,7 +130,10 @@ PREVIOUSIP=$PIADOMAIN
while [ $failed != 0 ] while [ $failed != 0 ]
do do
VPNIP=$(echo $VPNIPS | cut -d' ' -f$i) VPNIP=$(echo $VPNIPS | cut -d' ' -f$i)
printf "\nChanging server VPN address $PREVIOUSIP to $VPNIP..."
sed -i "s/$PREVIOUSIP/$VPNIP/g" $REGION.ovpn sed -i "s/$PREVIOUSIP/$VPNIP/g" $REGION.ovpn
status=$?
if [[ "$status" != 0 ]]; then printf "ERROR with status code $status\nSleeping for 10 seconds before exit...\n"; sleep 10; exit $status; fi
PREVIOUSIP=$VPNIP PREVIOUSIP=$VPNIP
printf "\nStarting OpenVPN using the following parameters:" printf "\nStarting OpenVPN using the following parameters:"
printf "\n * Region: $REGION" printf "\n * Region: $REGION"
@@ -122,12 +143,12 @@ do
openvpn --config "$REGION.ovpn" --auth-user-pass /auth.conf openvpn --config "$REGION.ovpn" --auth-user-pass /auth.conf
failed=$? failed=$?
if [[ $failed != 0 ]]; then if [[ $failed != 0 ]]; then
echo "==> Openvpn failed with error code: $failed" printf "\n==> Openvpn failed with status code: $failed"
i=$((i+1)) i=$((i+1))
if [[ $i -gt $VPNIPSLENGTH ]]; then if [[ $i -gt $VPNIPSLENGTH ]]; then
i=0 i=0
fi fi
else else
echo "==> Openvpn stopped cleanly" printf "\n==> Openvpn stopped gracefully"
fi fi
done done

View File

@@ -1,18 +1,45 @@
server: server:
# See https://www.nlnetlabs.nl/documentation/unbound/unbound.conf/
# logging
verbosity: 0 verbosity: 0
use-syslog: yes val-log-level: 2
qname-minimisation: yes use-syslog: no
do-tcp: yes
# performance
num-threads: 1
prefetch: yes prefetch: yes
prefetch-key: yes
key-cache-size: 16m
key-cache-slabs: 4
msg-cache-size: 4m
msg-cache-slabs: 4
rrset-cache-size: 4m
rrset-cache-slabs: 4
cache-min-ttl: 3600
cache-max-ttl: 9000
# privacy
rrset-roundrobin: yes rrset-roundrobin: yes
use-caps-for-id: yes hide-identity: yes
hide-version: yes
# security
root-hints: "/etc/unbound/root.hints"
trust-anchor-file: "/etc/unbound/root.key"
harden-below-nxdomain: yes
harden-referral-path: yes
harden-algo-downgrade: yes
# set above to no if there is any problem
# network
do-ip4: yes do-ip4: yes
do-ip6: no do-ip6: no
interface: 127.0.0.1 interface: 127.0.0.1
hide-identity: yes
hide-version: yes # other files
include: "/etc/unbound/blocks-malicious.conf"
forward-zone: forward-zone:
name: "." name: "."
forward-addr: 1.1.1.1@853#cloudflare-dns.com forward-addr: 1.1.1.1@853#cloudflare-dns.com
forward-addr: 1.0.0.1@853#cloudflare-dns.com forward-addr: 1.0.0.1@853#cloudflare-dns.com
forward-ssl-upstream: yes forward-tls-upstream: yes