Tutorial: PiHole mit Wireguard
Wie viele bereits wissen, nutze ich kein Gerät mehr ohne dnscrypt-proxy bzw. DoH (siehe DNS Artikel). Für iOS gibt es hier leider nur eine sehr überschaubare Anzahl an Lösungen. Sofern einem eine reine DoH App ausreicht, wird man fündig. Sobald es allerdings aber auch an das individuelle Filtern von Domains geht, wird es schwierig. Die sehr populäre App AdGuard (Pro) ist bspw. nicht in der Lage größere Blacklists zu verarbeiten. Die mir bisher einzig bekannte App, die hierzu in der Lage war, ist DNSCloak. Mit iOS14 und der neuen eingebetteten VPN Technik ist die App aber leider nahezu unbrauchbar geworden. Der VPN Tunnel bricht immer wieder zusammen und wird neu aufgebaut. Ob und wann die App ein neues Update erfährt, steht in den Sternen. Der letzte Commit auf GitHub ist jedenfalls lange her.
Ich nutze schon seit längerem PiHole. PiHole stand in einschlägigen Security-Ecken zwar schon in der Kritik, da die voreingestellten Standard-Listen z.B. auf Amazon- und Microsoft Servern liegen, glücklicherweise ist man nicht gezwungen diese zu nutzen, sondern kann jede beliebige Quelle verwenden. Ich baue mir meine Listen bspw. über die Sammlung von Firebog.
In letzter Zeit diente mein PiHole zwar nur noch dazu diese Filterlisten zu bauen und zu verteilen, aber die Möglichkeit ihn auch wieder aktiv zu nutzen bestand natürlich. Da mein bisheriger PiHole auf einem alten 2er Raspberry Pi lief und davon auszugehen war, dass die Anforderungen mit diesem Vorhaben etwas steigen könnten, kam ein neuer 4er ins Haus. Nachdem dieser installiert war, hat der alte 2er übrigens komplett seinen Dienst quitiert. Kein Scherz.
PiHole
Über die Installation der Anwendung PiHole selber brauche ich vermutlich nicht viele Worte verlieren. Die Installation ist selbsterklärend bzw. bedarf eines einzelnen Befehls (wobei ich persönlich eigentlich kein Fan davon bin, einen Download eines Bash-Scripts direkt in die Ausführung zu pipen).
curl -sSL https://install.pi-hole.net | bash
Nach der Installation ist der PiHole startklar. Jedem ist selber überlassen wie er diesen nutzen möchte, bzw. wie er sich seine Listen baut. Für den weiteren Verlauf in diesem Artikel werden nur DNS Konfigurationen für dnscrypt-proxy durchgeführt.
Ich möchte dennoch kurz einen Ausschnitt aus meinem Script zeigen, welches mir wöchentlich die gravity.list automatisch neu baut, anstatt dies über die grafische Oberfläche manuell zu bewerkstelligen.
#!/bin/bash
LOGFILE="/var/log/updateGravity.log"
PIHOLE_DIR="/etc/pihole"
LISTSURL="https://v.firebog.net/hosts/lists.php?type=nocross"
NONCRSDLIST="${PIHOLE_DIR}/nonCrossed.list"
ADLIST="${PIHOLE_DIR}/adlists.list"
GRAVITYLIST="${PIHOLE_DIR}/gravity.list"
ADDITIONALLIST="${PIHOLE_DIR}/additional.list"
ADDITIONALDOMAINS="${PIHOLE_DIR}/additionalDomains.list"
FLUSH_SQL="${PIHOLE_DIR}/FLUSH.sql"
IMPORT_SQL="${PIHOLE_DIR}/IMPORT.sql"
EXPORT_SQL="${PIHOLE_DIR}/EXPORT.sql"
GRAVITY_DB="${PIHOLE_DIR}/gravity.db"
CLEAN_ADLISTS_BEFORE_UPDATE=true
if [ -e $LOGFILE ]; then
LOGFILESIZE=$(wc -c <"$LOGFILE")
if [ $LOGFILESIZE -gt 5000000 ]; then
mv $LOGFILE "$LOGFILE.$(date +%Y%m%d)"
gzip "$LOGFILE.$(date +%Y%m%d)"
fi
fi
echo "***********************************" >> $LOGFILE
echo `date` " - Starting..." >> $LOGFILE
if [ -e ${GRAVITYLIST} ]; then
DOMAINSOLD=`wc -l < ${GRAVITYLIST}`
else
DOMAINSOLD=0
fi
# Cleanup
echo `date` " - Cleanup..." >> $LOGFILE
rm -f /etc/pihole/list.*.*
rm -f ${ADLIST}
rm -f ${GRAVITYLIST}
if [ -e ${NONCRSDLIST} ]; then
rm -f ${NONCRSDLIST}
fi
# Download non crossed list from fireBog
echo `date` " - Download list from fireBog..." >> $LOGFILE
curl ${LISTSURL} > ${NONCRSDLIST}
if ! [ -e ${NONCRSDLIST} ]; then
echo `date` " - Error downloading list from fireBog!" >> $LOGFILE
exit 1
fi
# Add additional list
cat ${ADDITIONALLIST} >> ${NONCRSDLIST}
mv ${NONCRSDLIST} ${ADLIST}
chown root:root ${ADLIST}
# Build new gravity.db
echo `date` " - Build new database..." >> $LOGFILE
cat <<EOF > ${FLUSH_SQL}
DELETE FROM adlist;
EOF
cat <<EOF > ${IMPORT_SQL}
CREATE TEMP TABLE i(txt);
.separator ~
.import ${ADLIST} i
INSERT OR IGNORE INTO adlist (address) SELECT txt FROM i;
DROP TABLE i;
EOF
if ${CLEAN_ADLISTS_BEFORE_UPDATE}; then
sqlite3 ${GRAVITY_DB} < ${FLUSH_SQL}
fi
sqlite3 ${GRAVITY_DB} < ${IMPORT_SQL}
# Do the update
echo `date` " - Update gravity..." >> $LOGFILE
/usr/local/bin/pihole -g
if [ $? -ne 0 ]; then
echo `date` " - Error updating gravity.list!" >> $LOGFILE
exit 2
fi
echo `date` " - Export gravity from database to file..." >> $LOGFILE
# Export gravity to file
cat <<EOF > ${EXPORT_SQL}
.output ${GRAVITYLIST}
select domain from gravity;
EOF
sqlite3 ${GRAVITY_DB} < ${EXPORT_SQL}
if ! [ -e ${GRAVITYLIST} ]; then
echo `date` " - Error updating gravity.list!" >> $LOGFILE
exit 3
fi
chown root:root ${GRAVITYLIST}
# Cleaning sql scripts
rm ${ADLIST}
rm ${FLUSH_SQL}
rm ${IMPORT_SQL}
rm ${EXPORT_SQL}
# Add for all cases this main urls to gravity.list
echo `date` " - Adding custom URLs..." >> $LOGFILE
cat $ADDITIONALDOMAINS >> ${GRAVITYLIST}
# Compare lists
DOMAINSNEW=`wc -l < /etc/pihole/gravity.list`
SIZE=$(wc -c <"/etc/pihole/gravity.list")
echo `date` " - Number of Domains before update: ${DOMAINSOLD}" >> $LOGFILE
echo `date` " - Number of Domains after update: ${DOMAINSNEW}" >> $LOGFILE
NUMOFDOMAINS=$(($DOMAINSNEW-$DOMAINSOLD))
if [ $SIZE -lt 10000000 ]; then
echo `date` " - Error! New gravity.list is too small!" >> $LOGFILE
exit 4
fi
# Moving and mailing
...
# End
echo `date` " - SUCCESS." >> $LOGFILE
Das Script ist hier unvollständig und beinhaltet in Wirklichkeit mehrere Schritte. Die essentiellen Teile sind als Anreiz jedoch vorhanden.
dnscrypt-proxy
PiHole macht in Kombination mit DoH noch mehr Sinn. Abgesehen vom Filtern, wird die Kommunikation bzw. die DNS Anfragen verschlüsselt. Der Client für den Pi (unter der Annahme, dass Linux installiert ist) kann man hier herunterladen:
Wichtig ist, das ARM Paket zu verwenden und nicht das ARM64.
Wo man den Client ablegt ist vollkommen egal. Ich installiere externe Software traditionell unter /opt. Die Beispielonfigurationsdatei example-dnscrypt-proxy.toml kann man fast sofort verwenden (vorher umbenennen in dnscrypt-proxy.toml). Folgende Anpassungen sind allerdings notwendig, oder empfehlenswert.
Die Servernamen, welche unter static an späterer Stelle definiert werden. Der Name ist frei wählbar:
server_names = ['doh']
Die Adresse und der Port unter der dnscrypt-proxy laufen soll. Wichtig ist, dass hier ein anderer Port als der Standard DNS Port 53 verwendet wird. Dieser wird nämlich vom System bzw. PiHole genutzt:
listen_addresses = ['127.0.0.1:5053', '[::1]:5053']
Sollte der definierte statische Server nicht verfügbar sein, kann man einen Fallbackresolver nutzen.
fallback_resolvers = ['185.228.168.9:53']
Gleiches gilt für die Netprobe Adresse:
netprobe_address = '185.228.168.9:53'
Ich verwende die dynamische Nutzung von Resolvern nicht, sondern arbeite ausschließlich mit fest definierten Resolvern, weshalb der sources Teil bei mir komplett auskommentiert ist:
[sources]
#An example of a remote source from https://github.com/DNSCrypt/dnscrypt-resolvers
#[sources.'public-resolvers']
#urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v2/public-resolvers.md', 'https://download.dnscrypt.info/resolvers-list/v2/public-resolvers.md']
#cache_file = 'public-resolvers.md'
#minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
#prefix = ''
...
Die Angabe des zu nutzenden DNS (DoH) Resolvers. Der Name muss mit der Angabe des Servernamens übereinstimmen.
Eine Liste mit DNS Servern und Stamps findet man hier.
[static]
[static.'doh']
stamp = 'sdns://AgMAAAAAAAAADDE3Ni45LjkzLjE5OCA-GhoPbFPz6XpJLVcIS1uYBwWe4FerFQWHb9g_2j24OAtkbnNmb3JnZS5kZQovZG5zLXF1ZXJ5'
Damit sich der Pi bzw. dnscrypt-proxy nicht an den DNS Listen des Routers o.Ä. bedient, muss man natürlich die resolv.conf angepassen:
nameserver [IP des Pi]
Testen der Auflösung:
./dnscrypt-proxy -resolve example.com
Wenn alles richtig konfiguriert wurde, meldet sich als Resolver der Server, welcher im static Bereich definiert ist (in meinem Fall dnsforge).
Resolving [bonn.de]
Domain exists: yes, 2 name servers found
Canonical name: bonn.de.
IP addresses: 185.155.109.25
TXT records: MS=ms77386515 cisco-ci-domain-verification=4c907c7b61f70e0a7477ad99ca23452dca8588eda94e911453e34f51e87a4a61 MS=ms60047536 google-site-verification=eUR0YAGZJGUo6wk6uwgwHvMP6c_KUr8QkATWY4ejgUo v=spf1 a:spf.de.umantis.com mx:umantis.com mx:de.umantis.com include:mail.kdvz-frechen.de ip4:91.213.177.151 ip4:91.213.177.152 ip4:91.213.177.153 ip4:91.213.177.154 mx -all
Resolver IP: 176.9.93.198 (dnsforge.de.)
Damit PiHole nun auch dnscrypt-proxy verwendet, muss in den Einstellungen nur der Custom DNS mit dem speziellen Port eingetragen werden:
Somit ist der PiHole inklusive DoH einsatzbereit. Im lokalen Netz kann man jetzt die IP des Pi als DNS Resolver nutzen - entweder individuell auf einzelnen Geräten, oder zentral am Router für alle.
Wireguard
Kommen wir zum eigentlichen Thema dieses Artikels - ein Ersatz für DNSCloak und/oder Adguard etc.
Ziel ist es, dass der PiHole mit DoH nun auch von unterwegs genutzt werden kann. Da es nur sehr schwer ist einen DNS Resolver mit dynamischer öffentlicher IP zu betreiben, habe ich mich für die VPN Lösung entschieden. Zusätzlich bietet diese Lösung den Komfort, dass man grundsätzlich eine sichere Verbindung zu seinem Heimnetzwerk aufgebaut hat.
Hinweis:
Da man sein Netzwerk von extern zugänglich macht, empfehle ich eine netzwerkseitige Vergrößerung bzw. zusätzliche Absicherung. Zwar ist Wireguard sehr sicher und es gibt einige Hindernisse um an andere Geräte im Netz zu gelangen, aber ein Restrisiko bleibt natürlich bestehen. Man sollte sich also ein separates Netzwerk aufbauen, in welchem der Pi steht. Alle anderen Geräte Zuhause (Clients, NAS etc.) empfehle ich in einem eigenen Netzwerk hinter einer Firewall unterzubringen. Somit ist der Rest geschützt, falls der PiHole doch kompromitiert werden sollte.
Die Installation von Wireguard hat einige Stolpersteine. Da WG ein neues zusätzliches Interface benötigt bzw. erstellt, muss das entsprechende Kernel-Modul gebaut und geladen werden. Hierfür sind die aktuellsten Header Dateien notwendig - und vor allem die richtigen, nämlich für den Raspberry Pi.
Bevor man also mit der eigentlichen Installation loslegt, ist es (auch wenn es sich um einen frisch installierten Pi handelt) empfehlenswert das System vorab zu aktualisieren.
apt-get update
apt-get upgrade
Die benötigten Kernel-Headers werden mit
apt-get install raspberrypi-kernel-headers
installiert. WG befindet sich nicht in den vorhandenen Standard-Repos, sondern paradoxerweise im unstable-Bereich. Daher muss man das Repo und den Key manuell hinzufügen:
echo "deb http://deb.debian.org/debian/ unstable main" | tee --append /etc/apt/sources.list.d/unstable.list
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 04EE7237B7D453EC
printf 'Package: *\nPin: release a=unstable\nPin-Priority: 150\n' | tee --append /etc/apt/preferences.d/limit-unstable
Anschließend kann man WG installieren:
apt-get install wireguard
Danach ist ein Neustart erforderlich.
WG benötigt das ipv4 Forwarding, welches in der /etc/sysctl.conf aktiviert wird. Hier muss man nur die folgende Zeile auskommentieren (mit anschließenden Neustart):
net.ipv4.ip_forward = 1
Die Konfgurationsdateien von WG befinden sich unter /etc/wireguard, wo auch die Keys erstellt bzw. abgelegt werden, nachdem mit umask sichergestellt ist, dass diese die richtigen Berechtigungen haben:
umask 077
Serverkeys:
wg genkey | tee server-private.key | wg pubkey > server-public.key
Clientkeys:
wg genkey | tee mobile-private.key | wg pubkey > mobile-public.key
Die Hauptkonfigurationsdatei des Servers wg0.conf (muss erstellt werden):
[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
DNS = [lokale IP des Pi]
PrivateKey = [Inhalt von server-private.key]
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
# mobile
[Peer]
PublicKey = [Inhalt von mobile-public.key]
AllowedIPs = 10.0.0.2/24
Die Interfaceadresse definiert die Adresse des Servers im neu erstellten VPN Netz. Hier darf man nicht die originale lokale IP des Pis verwenden. WG baut nämlich ein eigenständiges VPN Netzwerk auf.
Einzige Ausnahme ist hier die Adresse des DNS Server, was natürlich der Pi selbst mit seiner lokalen Adresse ist. PostUp und PostDown definieren die iptables Einträge welche zur Laufzeit genutzt bzw. wenn WG gestoppt wird, wieder entfernt werden. Im unteren Bereich werden die Clients vordefiniert (hier nur einer). Der Client bekommt hier die IP 10.0.0.2 fest zugewiesen.
Die Clientkonfiguration:
Jeden Client, der sich mit dem VPN Verbinden soll, muss man separat konfigurieren. Hierzu wird eine eigene Konfigurationsdatei erstellt (Name frei wählbar):
[Interface]
PrivateKey = [Inhalt von mobile-private.key]
Address = 10.0.0.2/24
DNS = [lokale IP des Pi]
[Peer]
PublicKey = [Inhalt von server-public.key]
Endpoint = [url xy]:51820
AllowedIPs = 0.0.0.0/0, [lokale Netz]/24
PersistentKeepalive = 25
AllowedIPs definiert hier, dass der Client sich frei im lokalen Netzwerk bewegen darf.
Sofern auch hier alles richtig installiert und konfiguriert wurde, kann man WG mit
wg-quick up wg0
starten. Der Befehl "wg" zeigt den Status (hier bereits mit einem verbundenen Client):
interface: wg0
public key: 123xy
private key: (hidden)
listening port: 51820
peer: 123xy
endpoint: [öffentliche IP]:63096
allowed ips: 10.0.0.0/24
latest handshake: 1 minute, 28 seconds ago
transfer: 14.44 MiB received, 230.35 MiB sent
Verbinden des Clients:
Wireguard Clients gibt es so gut wie für jedes Betriebssystem. Ich nutze derzeit ausschließlich den Server für mein iPhone, weshalb ich hier nur kurz auf die Wireguard App für iOS eingehe.
Damit die Schlüssel usw. nicht umständlich abgetippt oder kopiert werden müssen, bietet es sich an die Client-Konfigurationsdatei mittels QR Code auszulesen. Hierfür muss nur das Paket qrencode installieren:
apt install -y qrencode
Mit
qrencode -t ansiutf8 < [Name der Clientdatei].conf
wird auf der Kommandozeile ein QR Code generiert, welchen man mit der App einlesen kann.
Hinweis:
Natürlich muss im Router, welcher für die Internetverbindung zuständig ist, der konfigurierte Port (hier 51820) in Richtung des Pi freigegeben werden. Damit u.a. auch das Aufrufen normaler Webseiten funktioniert, muss man TCP und UDP nutzen.
Zudem muss man in PiHole über die grafische Oberfläche konfigurieren, dass auf allen Interfaces gelauscht wird. Dies ist nötig, da Wireguard mit dem Interface wg0 speziell ist. Je nach Netzwerkkonfiguration ist zudem das Conditional forwarding notwendig.
Um den Status des PiHole auch unterwegs bequem einzusehen, ohne über die Webseite gehen zu müssen, empfehle ich (für iOS) die App "Pi-Hole Remote":
Fazit:
Ich war erstaunt wie einfach sich diese Lösung umsetzen ließ. Nach nur wenigen Stunden (vom Zusammenbauen des Pi bis zur Verwendung über das Handy) war alles voll funktionsfähig. Insbesondere die Performance hat mich staunen lassen. Letzteres darf man mit Sicherheit der Software Wireguard zu verdanken haben, welche den Ruf hat besonders schmal und schnell zu sein. Ein Langzeittest wird natürlich zeigen, wie gut sich die Lösung im Alltag verhält. Aber schon jetzt ist diese deutlich stabiler als die App DNSCloak (was ich wirklich sehr schade finde).
Diskussionen, Anmerkungen usw. zu diesem Artikel findet ihr auf Mastodon.