Szyfrowany DNS z dnscrypt-proxy i dnsmasq na Debian linux
Spis treści
Ostatnio na forum dug.net.pl jeden z użytkowników miał dość spory problem z ogarnięciem
zadania polegającego na zaszyfrowaniu zapytań DNS z wykorzystaniem dnscrypt-proxy i
dnsmasq. Ładnych parę lat temu opisywałem jak skonfigurować te dwa narzędzia na Debianie
(i też na OpenWRT), choć od tamtego czasu w świecie linux'owym trochę rzeczy się pozmieniało. Dla
przykładu, dnscrypt-proxy przeszedł gruntowną przebudowę, no i też systemd jest w powszechniejszym
użyciu niż to miało miejsce w tamtych czasach, przez co w sporej części przypadków usługi takie jak
systemd-networkd.service
czy systemd-resolved.service
są już włączone domyślnie. Zatem sporo
informacji zawartych w tych napisanych przeze mnie artykułach już niekoniecznie może znaleźć
obecnie zastosowanie. Dlatego też pomyślałem, że nadszedł już czas, by ździebko zaktualizować tamte
wpisy. Ostatecznie stanęło jednak na tym, by w oparciu o te artykuły napisać kompletnie nowy tekst
na temat szyfrowania zapytań DNS na linux przy wykorzystaniu oprogramowania dnscrypt-proxy
oraz
dnsmasq
i zawrzeć w nim te wszystkie ciekawsze informacje, które udało mi się pozyskać przez te
ostatnie lata w kwestii poprawy bezpieczeństwa i prywatności przy przeglądaniu stron WWW.
Rozwiązywanie nazw DNS na linux
Jak zostało wspomniane we wstępnie, mamy zamiar zaszyfrować wszystkie zapytania DNS, które lokalne
aplikacje naszego linux'a będą generować podczas swojej codziennej pracy. Gdy chcemy odwiedzić
jakiś adres WWW (przykładowo w przeglądarce internetowej wpiszemy kernel.org
), to linux musi
pierw ustalić adres IP powiązany z domeną tego serwisu. Taki zabieg musi mieć miejsce z faktu, że
człowiek bardzo słabo operuje na liczbach, w przeciwieństwie do komputerów, i o wiele łatwiej jest
mu zapamiętać nazwę kernel.org
niż adres IP w postaci 198.145.29.83
(o adresach IPv6 lepiej nie
wspominać). Dlatego potrzebna jest jakaś warstwa pośrednicząca, która te czytelne dla człowieka
nazwy zamieni na adresy IP i tym właśnie zajmuje się system DNS.
Aplikacje na linux korzystają przy rozwiązywaniu nazw domenowych na adresy IP z Name Service
Switch (NSS), który to jest częścią biblioteki GNU C (glibc). NSS daje możliwość dostarczania
systemowych baz danych jako osobne usługi, których kolejność przeszukiwania może zostać
skonfigurowana przez administratora systemu w pliku /etc/nsswitch.conf
. W tym przypadku
najbardziej interesuje nas baza hosts
, która na moim Debianie wygląda mniej więcej tak:
...
hosts: files dns myhostname
...
W skład bazy hosts
wchodzi plik /etc/hosts
(usługa files
), resolver glibc, który czyta plik
/etc/resolv.conf
(usługa dns
) oraz usługa myhostname
. Zatem by rozwiązać nazwę domeny na
adres IP, system operacyjny przeszukuje pierw plik /etc/hosts
ale tutaj zwykle odpowiedzi nie
znajdzie (chyba, że wycinamy adserwery i pozbywamy się w ten sposób reklam przy pomocy tego pliku).
Następnie do gry wchodzi resolver DNS, który przesyła zapytania DNS pod określony w pliku
/etc/resolv.conf
adres IP. Zwykle server DNS zwraca odpowiedź w postaci adresu IP, pod którym
dana domena występuje. Jeśli zaś chodzi o myhostname
, to ta usługa umożliwia dynamiczne
rozwiązywanie nazwy lokalnego hosta i na wielu systemach obecność i korzystanie z pliku /etc/hosts
jest jedynie opcjonalne. Chodzi generalnie o to, że szereg usług systemowych do prawidłowego
działania wymaga, by tę nazwę hosta można było łatwo rozwiązać na adres IP, a czasami zapis do
pliku /etc/hosts
niekoniecznie jest możliwy (katalog /etc/
w trybie tylko do odczytu) lub też
ciągłe przepisywanie tego pliku mija się z celem.
Większość aplikacji korzysta z NSS ale część z nich może bezpośrednio odczytywać pliki
/etc/resolv.conf
i/lub /etc/hosts
. Możliwe jest także pominięcie całej ten infrastruktury
systemowej przy rozwiązywaniu nazw, przez co aplikacje w swoim zakresie będą realizować funkcję
resolver'a, choć to komplikuje proces tworzenia aplikacji i raczej tego rozwiązania się nie stosuje.
Wady linux'owego resolver'a
Ten resolver glibc ma szereg wad. Pierwszą z nich jest brak obsługi cache zapytań DNS, przez co każde zapytanie (nawet o tę samą domenę chwilę później) trzeba przesyłać do upstream'owego serwera DNS, co generuje opóźnienia i sprawia, że połączenie internetowe (zwłaszcza http/https) nam trochę/bardzo spowalnia. Drugim problemem zaś jest brak możliwości szyfrowania zapytań DNS, przez co cały ruch DNS zawierający wrażliwe pod kątem prywatności informacje przesyłany jest czystym tekstem. Zatem każdy podmiot na drodze tych pakietów jest w stanie podejrzeć, jak i zmienić, nasze zapytania DNS, co otwiera drogę dla szeroko rozumianej cenury treści w internecie, no i oczywiście powoduje całą masę problemów natury bezpieczeństwa. Do tych dwóch kluczowych problemów trzeba jeszcze dodać fakt, że ten linux'owy resolver nie jest w stanie przesyłać zapytań DNS na inny port niż domyślny, tj. 53 (przynajmniej w Debianie). Jeśli chcemy wyeliminować którąkolwiek z tych wyżej wymienionych wad, to musimy ratować się dodatkowym oprogramowaniem w postaci dnscrypt-proxy (szyfrowanie zapytań DNS oraz cache DNS) lub/i dnsmasq (cache DNS oraz rozdzielanie zapytań DNS na różne adresy/porty).
Usługi systemd-networkd.service i systemd-resolved.service
W ostatnich latach, szereg większych dystrybucji linux'a (w tym Debian i Ubuntu) przesiadło się z wykorzystywanego dotychczas systemu inicjacji procesów SysVinit na systemd. Systemd jest dość rozbudowanym mechanizmem mającym za zadanie ogarniać procesy w systemie i gdyby skupiał się on jedynie na tym zadaniu, to nie byłoby z nim problemu. Niemniej jednak, w gestii systemd leży także m.in. obsługa sieci, w tym konfiguracja interfejsów sieciowych oraz rozwiązywanie nazw domen na adresy IP.
W przypadku mojego Debiana, systemd po tylu latach wciąż nie potrafi ogarnąć konfiguracji mojej
sieci ze względu na fakt wykorzystywania bonding'u (połączenie interfejsów eth0
oraz
wlan0
). No może i sam bonding jako tako można skonfigurować ale operowanie na nim przy użyciu
systemd jest ździebko upierdliwe oraz generuje bardzo dziwne błędy, których nikt nie ma
pojęcia jak poprawić. Dlatego też ten cały moduł odpowiedzialny za konfigurację sieci w systemd, tj.
usługę systemd-networkd.service, mam wyłączony:
# systemctl disable systemd-networkd.service
Removed /etc/systemd/system/network-online.target.wants/systemd-networkd-wait-online.service.
Removed /etc/systemd/system/multi-user.target.wants/systemd-networkd.service.
Removed /etc/systemd/system/dbus-org.freedesktop.network1.service.
Removed /etc/systemd/system/sockets.target.wants/systemd-networkd.socket.
Usługa systemd-networkd.service
w zasadzie konfiguruje jedynie interfejsy sieciowe. Wyłączenie
jej sprawia, że sami musimy zatroszczyć się o skonfigurowanie tych interfejsów. W moim Debianie
proces konfiguracji interfejsów sieciowych odbywa się za sprawą pakietu ifupdown
, tj. przez plik
/etc/network/interfaces
oraz usługę networking.service
.
Oczywiście jeśli nie wykorzystujemy zaawansowanych mechanizmów sieciowych, np. wspomniany bonding,
to jest spore prawdopodobieństwo, że systemd będzie potrafił skonfigurować interfejsy sieciowe i
nie ma potrzeby wyłączać usługi systemd-networkd.service
. Chciałem tutaj jedynie zaznaczyć, że w
tym artykule użytku z tej usługi robić nie będziemy i jeśli pojawią się jakieś problemy z nią
związane, to trzeba będzie samemu poszukać rozwiązania.
W skład modułu sieciowego systemd wchodzi także usługa systemd-resolved.service, która ma za
zadanie zapewnić rozwiązywanie nazw DNS dla lokalnych aplikacji. Jeśli zamierzamy korzystać jedynie
z dnscrypt-proxy, to ta usługa systemd-resolved.service
będzie w stanie poprawnie z nim pracować,
bo resolver dostarczany z systemd działa na innym adresie, tj. 127.0.0.53
, w stosunku do
127.0.2.1
, na którym działa standardowo dnscrypt-proxy. Niemnie jednak, jeśli chcemy używać
dnsmasq, to trzeba wyłączyć systemd-resolved.service
, bo te dwie usługi realizują podobne
zadania, tj. przesyłają otrzymane zapytania DNS do upstream'owego serwera DNS oraz każda z tych
usług ma zaimplementowany cache DNS. Jeśli chcemy uniknąć ewentualnych problemów, to lepiej nie
mieszać/wykorzystywać wielu rozwiązań mających realizować to samo zadanie w tym samym czasie.
Jako, że w tym artykule będziemy korzystać z dnscrypt-proxy i dnsmasq, to wyłączamy usługę
systemd-resolved.service
:
# systemctl disable systemd-resolved.service
Removed /etc/systemd/system/multi-user.target.wants/systemd-resolved.service.
Removed /etc/systemd/system/dbus-org.freedesktop.resolve1.service.
Generalnie chodzi o to, by przed przystąpieniem do pracy żadna usługa w systemie nie zajmowała
portu 53, co możemy sprawdzić przy pomocy ss
czy netstat
:
# ss -lpn 'sport = :53'
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
# netstat -napletu | grep :53
Potrzebne oprogramowanie
Jeśli nie korzystamy z systemd i chcemy jedynie zaszyfrować zapytania DNS, to w zasadzie wystarczy
nam pakiet dnscrypt-proxy
. Jeśli chcemy korzystać z bardziej zaawansowanych konfiguracji DNS,
np. rozdzielić ruch do różnych serwerów DNS i/lub część zapytań DNS słać w formie niezaszyfrowanej
(np. zapytania o lokalne domeny przesyłane do naszego domowego routera WiFi), to potrzebny nam
będzie także pakiet dnsmasq
. Jeśli zaś zamierzamy korzystać z systemd i mamy włączoną usługę
systemd-resolved.service
), to możemy odpuścić sobie instalowanie dnsmasq. Tak czy inaczej, oba
te pakiety są dostępne w głównym repozytorium Debiana i bez większego problemu możemy je sobie
wgrać przy pomocy tego poniższego polecenia:
# aptitude install dnsmasq dnscrypt-proxy
Konfiguracja resolver'a glibc
Przede wszystkim nasz linux'owy resolver musi słać zapytania DNS na właściwy adres. Trzeba zatem
będzie dostosować plik /etc/resolv.conf
. Standardowo do tego pliku trafiają wpisy typu
nameserver 8.8.8.8
(DNS Google) czy nameserver 1.1.1.1
(DNS CloudFlare). W naszej konfiguracji
musimy tutaj podać adres, na którym będzie nasłuchiwał dnsmasq i w tym przypadku jest to
127.0.0.1
. Oczywiście ten adres można sobie dostosować. Grunt, by określić ten sam adres później
w konfiguracji dnsmasq. Zatem dodajemy do /etc/resolv.conf
poniższy wpis:
nameserver 127.0.0.1
Dodatkowo, z racji wykorzystywania lokalnego serwera DNS musimy także ustawić bit AD (Authenticated Data), który oznaczy nasz lokalny serwer DNS jako zaufany:
options trust-ad
Tylko te dwa powyższe wpisy powinny się znaleźć w pliku /etc/resolv.conf
.
Warto tutaj jeszcze dodać, że jeśli wykorzystywaliśmy usługę systemd-resolved.service
, to plik
/etc/resolv.conf
w takiej konfiguracji jest zwykle linkiem do pliku
/run/systemd/resolve/stub-resolv.conf
. Po wyłączeniu tej usługi, ten link nie jest w żaden
sposób ruszany, tj. rozwiązywanie nazw DNS nam przestanie działać. Dlatego też trzeba ten link
usunąć i w jego miejsce stworzyć zwykły plik oraz uzupełnić go zawartością podaną wyżej:
# rm /etc/resolv.conf
# touch /etc/resolv.conf
Konfiguracja dnsmasq
W pliku /etc/resolv.conf
przekierowujemy zapytania DNS do lokalnej instancji dnsmasq, która w
tym przypadku nasłuchuje zapytań na adresie 127.0.0.1
i porcie 53
. Dlatego też potrzebna nam
jest odpowiednia usługa dla systemd oraz plik konfiguracyjny dla dnsmasq. W Debianie, z pakietem
dnsmasq jest dostarczana usługa /lib/systemd/system/dnsmasq.service
ale nie ma ona wymaganych
zależności i dlatego będziemy korzystać z własnej usługi dla systemd. Poniższą zawartość zapisujemy
w pliku /etc/systemd/system/dnsmasq.service
:
[Unit]
Description=dnsmasq - A lightweight DHCP and caching DNS server
Requires=network.target dnscrypt-proxy.service
Wants=nss-lookup.target
Before=nss-lookup.target
After=network-online.target dnscrypt-proxy.service
[Service]
Type=forking
PIDFile=/run/dnsmasq/dnsmasq.pid
# Test the config file and refuse starting if it is not valid.
ExecStartPre=/usr/sbin/dnsmasq --test
# We run dnsmasq via the /etc/init.d/dnsmasq script which acts as a
# wrapper picking up extra configuration files and then execs dnsmasq
# itself, when called with the "systemd-exec" function.
ExecStart=/etc/init.d/dnsmasq systemd-exec
# The systemd-*-resolvconf functions configure (and deconfigure)
# resolvconf to work with the dnsmasq DNS server. They're called like
# this to get correct error handling (ie don't start-resolvconf if the
# dnsmasq daemon fails to start).
ExecStartPost=/etc/init.d/dnsmasq systemd-start-resolvconf
ExecStop=/etc/init.d/dnsmasq systemd-stop-resolvconf
ExecReload=/bin/kill -HUP $MAINPID
[Install]
WantedBy=multi-user.target
W zasadzie to zostały zmienione tutaj dwie rzeczy. Pierwszą z nich jest dodanie usługi
dnscrypt-proxy.service
w Requires=
i After=
. Chodzi o to, by dnsmasq został uruchomiony po
dnscrypt-proxy, a nie odwrotnie, co może czasami powodować problemy z rozwiązywaniem nazw DNS.
Dodatkowo, te zależności sprawiają, że ilekroć będziemy restartować (czy też zatrzymywać) usługę
dnscrypt-proxy, to również i usługa dnsmasq będzie restartowana/zatrzymywana, jako że dnsmasq w
takiej konfiguracji nie może poprawnie działać bez dnscrypt-proxy i by oszczędzić sobie nerw przy
ewentualnym debugowaniu, dobrze jest te zależności uzupełnić.
Poniżej jest przykład logu w sytuacji, gdy postanowimy zatrzymać dnscrypt-proxy:
systemd[1]: Stopping dnsmasq.service...
dnsmasq[609718]: exiting on receipt of SIGTERM
systemd[1]: dnsmasq.service: Succeeded.
systemd[1]: Stopped dnsmasq.service.
dnscrypt-proxy[609712]: [2020-09-17 16:43:03] [NOTICE] Stopped.
systemd[1]: Stopping dnscrypt-proxy.service...
systemd[1]: dnscrypt-proxy.service: Succeeded.
systemd[1]: Stopped dnscrypt-proxy.service.
A tutaj z kolei jest proces restartu dnscrypt-proxy:
systemd[1]: Stopping dnsmasq.service...
dnsmasq[614691]: exiting on receipt of SIGTERM
systemd[1]: dnsmasq.service: Succeeded.
systemd[1]: Stopped dnsmasq.service.
dnscrypt-proxy[614682]: [2020-09-17 16:45:44] [NOTICE] Stopped.
systemd[1]: Stopping dnscrypt-proxy.service...
systemd[1]: dnscrypt-proxy.service: Succeeded.
systemd[1]: Stopped dnscrypt-proxy.service.
systemd[1]: Started dnscrypt-proxy.service.
systemd[1]: Starting dnsmasq.service...
dnsmasq[614741]: dnsmasq: syntax check OK.
dnsmasq[614751]: started, version 2.82 cachesize 4096
dnsmasq[614751]: compile time options: IPv6 GNU-getopt DBus no-UBus i18n IDN2 DHCP DHCPv6 no-Lua TFTP conntrack ipset auth DNSSEC loop-detect inotify dumpfile
dnsmasq[614751]: DBus support enabled: connected to system bus
dnsmasq-dhcp[614751]: DHCP, IP range 192.168.122.2 -- 192.168.122.254, lease time 12h
dnsmasq-dhcp[614751]: DHCP, sockets bound exclusively to interface virbr0
dnsmasq[614751]: using only locally-known addresses for domain lh
dnsmasq[614751]: using nameserver 192.168.1.1#53 for domain mhouse
dnsmasq[614751]: using nameserver 127.0.2.1#53
dnsmasq[614751]: read /etc/hosts - 14 addresses
systemd[1]: Started dnsmasq.service.
dnscrypt-proxy[614740]: [2020-09-17 16:45:45] [NOTICE] dnscrypt-proxy 2.0.44
dnscrypt-proxy[614740]: [2020-09-17 16:45:45] [NOTICE] Network connectivity detected
dnscrypt-proxy[614740]: [2020-09-17 16:45:45] [NOTICE] Dropping privileges
dnscrypt-proxy[614740]: [2020-09-17 16:45:45] [NOTICE] Network connectivity detected
dnscrypt-proxy[614740]: [2020-09-17 16:45:45] [NOTICE] Now listening to 127.0.2.1:53 [UDP]
dnscrypt-proxy[614740]: [2020-09-17 16:45:45] [NOTICE] Now listening to 127.0.2.1:53 [TCP]
dnscrypt-proxy[614740]: [2020-09-17 16:45:45] [NOTICE] Now listening to https://127.0.2.1:3000/dns-query [DoH]
dnscrypt-proxy[614740]: [2020-09-17 16:45:45] [NOTICE] Source [public-resolvers] loaded
dnscrypt-proxy[614740]: [2020-09-17 16:45:45] [NOTICE] Source [relays] loaded
dnscrypt-proxy[614740]: [2020-09-17 16:45:45] [NOTICE] Loading the set of whitelisting rules from [/etc/dnscrypt-proxy/whitelist.txt]
dnscrypt-proxy[614740]: [2020-09-17 16:45:45] [NOTICE] Firefox workaround initialized
dnscrypt-proxy[614740]: [2020-09-17 16:45:45] [NOTICE] Loading the set of blocking rules from [/etc/dnscrypt-proxy/blacklist.txt]
dnscrypt-proxy[614740]: [2020-09-17 16:45:45] [NOTICE] Loading the set of cloaking rules from [/etc/dnscrypt-proxy/cloaking-rules.txt]
dnscrypt-proxy[614740]: [2020-09-17 16:45:45] [NOTICE] Loading the set of forwarding rules from [/etc/dnscrypt-proxy/forwarding-rules.txt]
dnscrypt-proxy[614740]: [2020-09-17 16:45:45] [INFO] [cloudflare] TLS version: 304 - Protocol: h2 - Cipher suite: 4865
dnscrypt-proxy[614740]: [2020-09-17 16:45:45] [NOTICE] [cloudflare] OK (DoH) - rtt: 60ms
dnscrypt-proxy[614740]: [2020-09-17 16:45:45] [NOTICE] Server with the lowest initial latency: cloudflare (rtt: 60ms)
dnscrypt-proxy[614740]: [2020-09-17 16:45:45] [NOTICE] dnscrypt-proxy is ready - live servers: 1
Jak widać, w obu przypadkach te dopisane zależności troszczą się o odpowiednią kolejność zatrzymywania i uruchamiania tych dwóch usług. Oczywiście, możemy także obejść się bez tych dodatkowych zależności.
Drugą zmianą, którą wprowadza nasza usługa, jest przepisanie w After=
targetu network.target
na
network-online.target
. Chodzi generalnie o to, że dnscrypt-proxy potrzebuje aktywnego połączenia
sieciowego, by móc pobrać aktualną listę serwerów DNS. Zgodnie z tym co możemy wyczytać tutaj,
target network.target
nie daje gwarancji, że ta sieć będzie działać, w przeciwieństwie do
network-online.target
i dlatego by uniknąć błędów w logu związanych z dnscrypt-proxy dobrze jest
przepisać również i te zależności.
Plik /etc/dnsmasq.conf
Po skonfigurowaniu zależności w usłudze systemd dla dnsmasq, możemy przejść do napisania konfiguracji dla tego demona. Oczywiście dnsmasq jest rozbudowanym kawałkiem oprogramowania i liczba jego zastosowań jest dość spora. Dlatego też poniżej znajdzie się opis konfiguracji, której używam na co dzień w moim Debianie. Nie wszystkie opcje są potrzebne, by szyfrowanie zapytań DNS wdrożyć, dlatego też trzeba uważnie czytać opis poszczególnych opcji i ocenić czy będą one użyteczne w naszym przypadku czy też nie.
Poniższą zwrotkę zapisujemy w pliku /etc/dnsmasq.conf
:
enable-dbus
port=53
domain-needed
bogus-priv
stop-dns-rebind
rebind-localhost-ok
rebind-domain-ok=free.aero2.net.pl
no-resolv
no-poll
server=127.0.2.1#53
server=/mhouse/192.168.1.1#53
local=/lh/
address=/bdi.free.aero2.net.pl/10.2.37.78
user=dnsmasq
group=nogroup
interface=lo
no-dhcp-interface=lo
bind-interfaces
expand-hosts
domain=lh
dns-forward-max=256
Poniżej jest wyjaśnienie użytych opcji:
enable-dbus
włącza opcjonalne wsparcie dla D-Bus. Dnsmasq jest w stanie bez większego problemu działać i bez D-Bus.port
odpowiada za port, na którym dnsmasq będzie nasłuchiwał. Domyślnie jest to53
i jeśli nam on nie odpowiada, to możemy tutaj ustawić inny port, choć linux'owy resolver wspiera póki co tylko ten port.user
orazgroup
mają na celu zrzucenie uprawnień, tak by proces dnsmasq nie działał z uprawnieniami root.domain-needed
ma za zadanie odfiltrować zapytania, na które nie potrafią odpowiedzieć publiczne serwery DNS, tj. nie forwarduje on nazw bez kropek (bez części domeny).bogus-priv
ma za zadanie uniemożliwić przekazywanie do upstream'owych serwerów DNS zapytań dla prywatnych zakresów IP (np. 192.168.0.0/16) przy odwrotnej translacji adresów DNS (Reverse DNS).local
sprawia, że wszystkie zapytania we wskazanej domenie będą rozwiązywane lokalnie, tj. w oparciu o plik/etc/hosts
lub lease wydawane za sprawą protokołu DHCP.domain
jest bardzo podobny dolocal
i ustawia domenę dla serwera DHCP, którego my nie będziemy wykorzystywać. W efekcie wszystkie hosty, które otrzymują konfigurację od serwera DHCP (np. maszyny wirtualne), będą skonfigurowane na tę domenę. Dodatkowo, nazwa określona tutaj jest wykorzystywana w opcjiexpand-hosts
.expand-hosts
ma za zadanie dodać część domenową określoną wdomain
przy korzystaniu z prostych nazw. Dla przykładu, gdyby odpytaćmorfikownia
, to dnsmasq automatycznie doda do tej nazwy.lh
i w ten sposób powstaniemorfikownia.lh
i to ta nazwa zostanie rozwiązana na adres IP.no-resolv
sprawia, że nie będzie czytany plik/etc/resolv.conf
w poszukiwaniu upstream'owych serwerów DNS.no-poll
ma za zadanie powstrzymać dnsmasq przed monitorowaniem zmian w pliku/etc/resolv.conf
i innych podobnych plikach, które biorą udział w rozwiązywaniu nazw DNS.server
określa upstream'owe serwery DNS, do których dnsmasq będzie przesyłał zapytania o domeny. Wpisów może być kilka, a to do którego serwera poleci zapytanie DNS jest precyzowane po domenie. Przykładowo, wpisserver=/mhouse/192.168.1.1#53
mówi, że zapytanie o domenęmhouse
(i jej wszystkie subdomeny, np.morfikownia.mhouse
) zostanie skierowane do serwera192.168.1.1
na port53
bezpośrednio z pominięciem szyfrowania via dnscrypt-proxy. Natomiast wpisserver=127.0.2.1#53
określa, że wszystkie pozostałe zapytania DNS mają być kierowane na adres127.0.2.1
port53
, na którym to będzie nasłuchiwał dnscrypt-proxy i tylko te zapytania będą podlegać szyfrowaniu.interface
określa interfejs, na którym będzie nasłuchiwał serwer DNS. Można określić kilka interfejsów podając parokrotnie nazwę tego parametru. Można także skorzystać zlisten-address
i zamiast interfejsu postać adres (lub adresy) IP. Jako, że usługa DNS zalicza się do tych wrażliwych, to nie powinniśmy jej wystawiać na interfejsach publicznych i powinniśmy się ograniczyć jedynie do interfejsu pętli zwrotnej (lo
).no-dhcp-interface
ma za zadanie nie uruchamiać serwera DHCP na interfejsie pętli zwrotnej. Chodzi o to, że dnsmasq poza usługami DNS jest w stanie także robić za serwer DHCP. Nam ta funkcjonalność jest zupełnie zbędna, dlatego wyłączamy serwer DHCP dla pętli zwrotnej.bind-interfaces
ma za zadanie przypisać proces dnsmasq do danego interfejsu, przez co gdy taki interfejs zniknie i pojawi się ponownie, np. podczas rekonfiguracji połączenia sieciowego, to dnsmasq będzie mógł działać jak gdyby nigdy nic. Ten parametr pozwala nam też uruchomić inne oprogramowanie, które ma się zajmować rozwiązywaniem nazw DNS, np. dnscrypt-proxy. Bezbind-interfaces
, dnscrypt-proxy nie mógłby się uruchomić na interfejsie pętli zwrotnej i zwróciłby błąd[FATAL] listen udp 127.0.2.1:53: bind: address already in use
.dns-forward-max
odpowiada za ilość jednoczesnych zapytań, które mogą być obsługiwane przez serwer DNS.stop-dns-rebind
zapobiega przekierowaniu w oparciu o DNS, tj. wpisujemy jedną domenę, a jest nam zwracana inna, tzw. DNS rebinding.rebind-localhost-ok
zezwala na DNS rebinding ale tylko dla interfejsu pętli zwrotnej.rebind-domain-ok
zezwala na DNS rebinding dla określonych domen. W tym przypadku chodzi o konfigurację dla Aero2.address
ma za zadanie przypisać określonej domenie konkretny adres IP. Podobnie jak w parametrze wyżej, ta opcja dotyczy jedynie Aero2.
Konfiguracja dnscrypt-proxy
Mamy zatem skonfigurowany resolver linux'owy wskazujący na adres IP, na którym nasłuchuje dnsmasq.
W konfiguracji dnsmasq mamy zaś określony upstream'owy serwer DNS jako 127.0.2.1#53
. Trzeba
teraz tak skonfigurować dnscrypt-proxy, by na tym adresie i porcie nasłuchiwał zapytań DNS. Możemy
to zrobić na dwa sposoby, tj. wykorzystując mechanizm gniazd systemd lub też go kompletnie
pomijając.
Konfiguracja dnscrypt-proxy z gniazdami systemd
Standardowa konfiguracja dnscrypt-proxy w Debianie zakłada wykorzystanie mechanizmu gniazd systemd. W skrócie, ten mechanizm ma za zadanie oszczędzanie zasobów systemowych przez powstrzymywanie uruchamiania usług do momentu, aż będą one potrzebne. W tym przypadku, proces dnscrypt-proxy nie zostanie uruchomiony chyba, że jakiś proces będzie chciał wysłać zapytanie DNS. To zapytanie zostanie przechwycone, a w międzyczasie systemd uruchomi dnscrypt-proxy. Jak tylko ten proces zostanie odpalony, to zapytanie DNS zostanie mu przekazane i już wszystko dalej odbywać się będzie po staremu. By skorzystać z mechanizmu gniazd, potrzebne nam są dwa pliki, które standardowo są zawarte w paczce dnscrypt-proxy.
Plik /lib/systemd/system/dnscrypt-proxy.service
:
[Unit]
Description=DNSCrypt client proxy
Documentation=https://github.com/DNSCrypt/dnscrypt-proxy/wiki
Requires=dnscrypt-proxy.socket
After=network.target
Before=nss-lookup.target
Wants=nss-lookup.target
[Install]
Also=dnscrypt-proxy.socket
WantedBy=multi-user.target
[Service]
NonBlocking=true
ExecStart=/usr/sbin/dnscrypt-proxy -config /etc/dnscrypt-proxy/dnscrypt-proxy.toml
ProtectHome=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectControlGroups=true
MemoryDenyWriteExecute=true
User=_dnscrypt-proxy
CacheDirectory=dnscrypt-proxy
LogsDirectory=dnscrypt-proxy
RuntimeDirectory=dnscrypt-proxy
Plik /lib/systemd/system/dnscrypt-proxy.socket
:
[Unit]
Description=dnscrypt-proxy listening socket
Documentation=https://github.com/DNSCrypt/dnscrypt-proxy/wiki
Before=nss-lookup.target
Wants=nss-lookup.target
Wants=dnscrypt-proxy-resolvconf.service
[Socket]
ListenStream=127.0.2.1:53
ListenDatagram=127.0.2.1:53
NoDelay=true
DeferAcceptSec=1
[Install]
WantedBy=sockets.target
W pliku usługi dnscrypt-proxy.service
mamy zależność Requires=dnscrypt-proxy.socket
. W
konfiguracji gniazda mamy zaś ListenStream=
(protokół TCP) oraz ListenDatagram=
(protokół UDP),
w których mamy określony port i adres 127.0.2.1:53
. Zatem systemd podczas startu systemu
stworzy gniazdo dla pakietów TCP i UDP na tym konkretnym adresie i porcie. Ustawienie opcji
NoDelay
ma za zadanie wyłączyć algorytm Nagle'a, którego celem jest połączenie wielu mniejszych
segmentów TCP w jeden większy i przesłanie większego pakietu przez sieć. Bez tej opcji, system
byłby w stanie zapakować wiele zapytań DNS w jeden pakiet i posłać go przez sieć. Takie podejście
oszczędza łącze internetowe ale generuje przy tym opóźnienia, których powinniśmy unikać w protokole
DNS. Dlatego algorytm Nagle'a dla gniazd dnscrypt-proxy lepiej jest wyłączyć, by te zapytania DNS
były wysyłane w świat jak tylko napłyną.
Jeśli chcemy wykorzystywać gniazda systemd, to upewnijmy się, że w pliku konfiguracyjnym
/etc/dnscrypt-proxy/dnscrypt-proxy.toml
nie mamy ustawionego adresu, na którym dnscrypt-proxy ma
nasłuchiwać, tj. parametr listen_addresses
ma zostać pusty:
listen_addresses = []
Korzystanie z gniazd systemd ma tę zaletę, że to systemd będzie otwierał gniazda, a nie sam proces
aplikacji. Gdy pojawią się pierwsze pakiety adresowane na konkretny IP i port, to systemd uruchomi
usługę powiązaną z plikiem .socket
i przekaże jej wszystkie otworzone gniazda. W ten sposób
odpada nam przyznawanie uprawnień, np. CAP_NET_BIND_SERVICE
, by proces mógł nasłuchiwać na
porcie o numerze niższym niż 1024, czy też CAP_SETGID
i CAP_SETUID
, które mają na celu pomóc
procesowi uruchomionemu jako root zrzucić uprawnienia, tak by działał on jako user _dnscrypt-proxy
z grupą nogroup
. Przy korzystaniu z gniazd, systemd uruchamia dnscrypt-proxy jako określony
user/grupa i odpada nam ta cała zabawa ze zrzucaniem uprawnień i nie trzeba się martwić o to czy
wykorzystywane przez tę usługę porty są z zakresu uprzywilejowanego (<1024), do którego ma dostęp
jedynie root.
Konfiguracja dnscrypt-proxy bez gniazd systemd
Alternatywnym podejściem jest rezygnacja z gniazd systemd. Niemniej jednak, to zadanie wymaga od
nas przepisania domyślnej usługi systemd oraz wyłączenia usługi gniazd dla dnscrypt-proxy. Tworzymy
zatem plik /etc/systemd/system/dnscrypt-proxy.service
i dodajemy do niego poniższą treść:
[Unit]
Description=DNSCrypt client proxy
Documentation=https://github.com/DNSCrypt/dnscrypt-proxy/wiki
#Requires=dnscrypt-proxy.socket
After=network-online.target
Before=nss-lookup.target
Wants=nss-lookup.target
[Service]
NonBlocking=true
ExecStart=/usr/sbin/dnscrypt-proxy -config /etc/dnscrypt-proxy/dnscrypt-proxy.toml
AmbientCapabilities=CAP_NET_BIND_SERVICE,CAP_SETGID,CAP_SETUID
NoNewPrivileges=true
ProtectHome=true
ProtectSystem=strict
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectControlGroups=true
MemoryDenyWriteExecute=true
SystemCallArchitectures=native
PrivateTmp=true
PrivateDevices=true
RestrictRealtime=true
RemoveIPC=true
KeyringMode=private
#User=_dnscrypt-proxy
CacheDirectory=dnscrypt-proxy
LogsDirectory=dnscrypt-proxy
RuntimeDirectory=dnscrypt-proxy
ReadWritePaths=/var/log/dnscrypt-proxy /var/cache/dnscrypt-proxy /etc/dnscrypt-proxy
ReadOnlyPaths=/usr/bin/dnscrypt-proxy
LogsDirectoryMode=0644
[Install]
#Also=dnscrypt-proxy.socket
WantedBy=multi-user.target
Kluczowe w tej usłudze jest wykomentowanie linijek Requires=dnscrypt-proxy.socket
w sekcji
[Unit]
, User=_dnscrypt-proxy
w sekcji [Service]
oraz Also=dnscrypt-proxy.socket
w sekcji
[Install]
. Tak przygotowaną usługę zapisujemy. Wyłączamy przy tym też usługę gniazd i
przeładowujemy konfigurację systemd:
# systemctl disable dnscrypt-proxy.socket
# systemctl daemon-reload
Plik /etc/dnscrypt-proxy/dnscrypt-proxy.toml
W zależności od wybranego sposobu uruchamiania trzeba będzie inaczej skonfigurować dnscrypt-proxy.
W zasadzie domyślny plik /etc/dnscrypt-proxy/dnscrypt-proxy.toml
, który jest dostarczany z
pakietem Debiana, zawiera wszystkie niezbędne wpisy, które mają za zadanie sprawić, by
dnscrypt-proxy działał poprawnie w konfiguracji z gniazdami systemd i nie ma potrzeby ruszać tego
pliku. W przypadku, gdy nie korzystamy z gniazd systemd albo w ogóle nie używamy tego init'a, to
trzeba będzie nieco inaczej skonfigurować tego demona szyfrującego zapytania DNS.
W katalogu /usr/share/doc/dnscrypt-proxy/examples/
znajduje się dość rozbudowany plik
example-dnscrypt-proxy.toml
, którego nazwę trzeba zmienić na dnscrypt-proxy.toml
i skopiować
do katalogu /etc/dnscrypt-proxy/
. Poniżej zaś znajduje się krótki opis opcji, na które
powinniśmy rzucić okiem w celu ich dostosowania.
Przede wszystkim musimy zadbać, by w tym pliku znalazł się adres IP oraz port, na którym
dnscrypt-proxy będzie nasłuchiwał zapytań od dnsmasq, tj. 127.0.2.1
i port 53
(ewentualnie też
[::1]
jeśli nasz ISP zapewnia połączenie z siecią za sprawą protokołu IPv6):
#listen_addresses = ['127.0.0.1:53', '[::1]:53']
listen_addresses = ['127.0.2.1:53']
Zapytania DNS muszą być przesyłane do jakiegoś upstream'owego serwera DNS. W tym przypadku wykorzystywany będzie CloudFlare ale oczywiście możemy określić taki serwer DNS, który nam pasuje i niekoniecznie musi to być CloudFlare. Lista dostępnych serwerów znajduje się tutaj. Istnieje także możliwość zdefiniowania wielu upstream'owych serwerów DNS, np. w celu automatycznego doboru tego najszybszego z nich lub też przesyłania części zapytań DNS do jednego serwera, a części do innego:
# server_names = ['scaleway-fr', 'google', 'yandex', 'cloudflare']
server_names = ['cloudflare']
Bez gniazd systemd, usługa dnscrypt-proxy jest uruchamiana jako użytkownik root. Nie powinna ona
jednak działać z prawami administratora systemu, dlatego też twórcy tej aplikacji zaprojektowali ją
w taki sposób, by w pewnym momencie po uruchomieniu procesu można było zrzucić uprawnienia, tak by
ten proces był słabiej uprzywilejowany. Do zrzucenia uprawnień potrzebujemy jednak określić
użytkownika. Przy instalacji pakietu dnscrypt-proxy w Debianie, tworzony jest użytkownik
_dnscrypt-proxy
i to z niego możemy skorzystać:
user_name = '_dnscrypt-proxy'
Możemy także wymusić, by zapytania DNS szły protokołem TCP ale lepiej tego nie robić. Jeśli nie korzystamy z Tor'a do przesyłania zapytań DNS, to w zasadzie nie ma potrzeby wymuszać, by komunikacja z upstream'owymi serwerami DNS odbywała się z wykorzystaniem protokołu TCP. Dnscrypt-proxy zawsze szyfruje zapytania DNS, nawet w przypadku, gdy wykorzystywany jest protokół UDP. Wymuszenie protokołu TCP nie poprawia w żaden sposób bezpieczeństwa procesu rozwiązywania nazw domen na adresy IP i może powodować jedynie dodatkowe opóźnienia spowalniając tym samym cały ten proces:
force_tcp = false
Jeśli w dnsmasq określiliśmy maksymalną ilość jednoczesnych zapytań DNS, to możemy zsynchronizować
tę poniższą wartość z tym, co określiliśmy w konfiguracji dnsmasq w parametrze dns-forward-max
:
max_clients = 256
W przypadku, gdy nasz operator ISP nie przydzielił nam żadnego adresu IPv6, to dnscrypt-proxy na powiązane z tym protokołem zapytania DNS może zwracać pustą odpowiedź. Takie zachowanie przyśpieszy proces rozwiązywania nazw na hostach niemających skonfigurowanego połączenia IPv6, choć niekiedy może ono powodować problemy:
block_ipv6 = true
Warto też zadbać o to, by zapytania DNS bez części domenowej (np. morfikownia
vs.
morfikownia.mhouse
) nie były przesyłane do upstream'owych serwerów DNS. Podobnie sprawa ma się z
zapytaniami DNS ze stref lokalnych. Przesyłanie tego typu zapytań DNS do zewnętrznych
podmiotów sprawia, że cierpi na tym nasza prywatność i lepiej tego nie robić:
block_unqualified = true
block_undelegated = true
Standardowo, dnscrypt-proxy używa tego samego certyfikatu (klucza publicznego) przy rozwiązywaniu nazw domenowych na adresy IP. W pewnych sytuacjach, np. w mobilnym świecie laptopów/smartfonów i ich przemieszczaniu się między sieciami, ten certyfikat może pomóc zidentyfikować konkretne urządzenie. By temu zaradzić, został stworzony mechanizm kluczy efemerycznych (Ephemeral Keys), które są generowane dla każdego nowego zapytania DNS. Może i takie zachowanie poprawia naszą prywatność ale trzeba się liczyć z mocniejszym wykorzystaniem procesora. Nie zaleca się włączać tej opcji w przypadku, gdy nasza maszyna generuje tych zapytań bardzo dużo w krótkim czasie. Gdy mamy przydzielony statyczny adres IP, to również możemy sobie darować ustawianie tej opcji:
dnscrypt_ephemeral_keys = true
Poniższy parametr wyłącza śledzenie sesji TLS (TLS session tickets), co poprawia prywatność ale kosztem większych opóźnień przy rozwiązywaniu domen, bo trzeba na nowo negocjować informacje sesji TLS między klientem i serwerem za każdym razem, gdy klient próbuje nawiązać szyfrowane połączenie.
tls_disable_session_tickets = true
Możemy też określić szyfr wykorzystywany przy szyfrowaniu zapytań. Domyślnie jest
wykorzystywana wartość 4865
, która w zapisie HEX ma postać 0x1301
i odpowiada za szyfr
TLS_AES_128_GCM_SHA256
. Możemy także tutaj ustawić 4866
( 0x1302
) lub 4867
( 0x1303
)
odpowiednio dla TLS_AES_256_GCM_SHA384
oraz TLS_CHACHA20_POLY1305_SHA256
. Te trzy wyżej
wymienione szyfry dotyczą jedynie protokołu TLS 1.3 (nie zaleca się używanie szyfrów protokołu TLS
1.2 i niższych). Dodatkowo, nie ma raczej potrzeby korzystać z TLS_AES_256_GCM_SHA384
, bo jest
on obecnie tak samo bezpieczny jak TLS_AES_128_GCM_SHA256
ale , gdy chodzi o wydajność, to jest
on niestety sporo wolniejszy.
tls_cipher_suite = [4865]
Poniższe opcje mają za zadanie opóźnić start dnscrypt-proxy w przypadku, gdy sieć nie jest jeszcze dostępna, np. podczas fazy rozruchu systemu. Niemniej jednak, mając odpowiednio skonfigurowane usługi systemd, to próbkowanie sieci jest zbędne i można je z powodzeniem wyłączyć:
netprobe_timeout = 60
netprobe_address = '9.9.9.9:53'
Ruch przez fallback resolver jest puszczony nieszyfrowany i ma w zasadzie na celu jedynie umożliwienie pobrania listy serwerów DNS. Adres określony w poniższym parametrze nie będzie nigdy wykorzystywany do rozwiązywania domen, o które proszą aplikacje użytkownika:
fallback_resolvers = ['9.9.9.9:53']
Poniższy parametr ma na celu upewnienie się, że w przypadku, gdy zajdzie potrzeba skorzystania z fallback resolver'a, to będzie to ten określony powyżej, a nie ten ustawiony w konfiguracji systemu:
ignore_system_dns = true
Powinniśmy mieć także skonfigurowane jakieś źródła resolver'ów. Te dwa poniższe są domyślne:
[sources]
[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'
refresh_delay = 72
prefix = ''
[sources.'relays']
urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v2/relays.md', 'https://download.dnscrypt.info/resolvers-list/v2/relays.md']
cache_file = 'relays.md'
minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
refresh_delay = 72
prefix = ''
Poniższa zwrotka odpowiada za skonfigurowanie lokalnego serwera DoH, który można wykorzystać w celu wdrożenia szyfrowanego SNI (ESNI) w Firefox:
[local_doh]
listen_addresses = ['127.0.0.1:3000']
path = "/dns-query"
cert_file = "localhost.pem"
cert_key_file = "localhost.pem"
Cache DNS w dnsmasq i dnscrypt-proxy
Nowy dnscrypt-proxy dość znacznie różni się od swojego poprzednika, tj. posiada wsparcie dla
protokołów DNSCrypt v2, DoH i DoT. Posiada on także obsługę cache DNS, przez co w
zasadzie korzystanie z dnsmasq traci powoli na znaczeniu. Jeśli korzystaliśmy do tej pory w
dnsmasq by mieć ten cache zapytań DNS, to z powodzeniem możemy się tego oprogramowania pozbyć.
Niemniej jednak, dnsmasq czasem przydaje się do rozdzielania ruchu DNS do różnych serwerów DNS i
jeśli potrzebujemy takiej funkcjonalności, to naturalnie dnsmasq będzie musiał działać równolegle z
dnscrypt-proxy. Pojawia się zatem problem związany z cache DNS. Musimy się zdecydować, które z tych
dwóch narzędzi będzie ten cache obsługiwać. Lepszym rozwiązaniem wydaje się być obsługa cache DNS w
dnsmasq, bo przez niego będą przechodzić wszystkie zapytania DNS, w tym też te, które będą
szyfrowane przy pomocy dnscrypt-proxy. Dodatkowo, dnsmasq występuje wcześniej w tym całym łańcuchu
rozwiązywania domen na adresy IP, przez co jego cache będzie szybszy. Dlatego też w konfiguracji
dnscrypt-proxy w pliku /etc/dnscrypt-proxy/dnscrypt-proxy.toml
wyłączmy cache:
cache = false
Natomiast w konfiguracji dnsmasq w pliku /etc/dnsmasq.conf
, cache konfigurujemy w poniższy
sposób:
cache-size=4096
min-cache-ttl=600
max-cache-ttl=3600
Rozmiar cache będzie miał 4096
wpisów. Każdy z wpisów zajmuje trochę pamięci operacyjnej i na
maszynach 32-bitowych, taki wpis w cache ma 82 lub 74 bajty w zależności od protokołu, tj. IPv6 lub
IPv4. W przypadku maszyn 64-bitowych, rozmiar wpisów wynosi odpowiednio 94 oraz 86 bajtów. Zatem
4096 wpisów zajmie około 350 KiB pamięci -- na desktopach taka wartość nie ma praktycznie większego
znaczenia. Niemniej jednak, im większy cache, tym wolniej trwa jego przeszukiwanie. Trzeba zatem
uważać by nie zdegradować sobie wydajności za sprawą przechowywania starych nieużywanych lub też
rzadko używanych wpisów.
Opcje min-cache-ttl
oraz max-cache-ttl
odpowiadają za widełki czasowe (określone w sekundach)
ważności rekordu DNS w cache. Odpytując upstream'owy serwer DNS o jakaś domenę, tworzony jest
rekord DNS w cache z określonym TTL (zwykle ten czas podawany jest w odpowiedzi zwrotnej razem z
adresem IP). Niektóre TTL mogą być bardzo krótkie (rzędu kilku-kilkudziesięciu sekund), a inne zbyt
długie (parę godzin czy nawet dni). Do momentu upłynięcia tego czasu określonego w TTL, odpowiedź
na każde zapytanie o tę określoną domenę będzie brana z cache. Jeśli czasy TTL są zbyt długie, to
rekord w cache może nie odpowiadać stanu faktycznemu (zmieni się adres IP dla danej domeny) i
przydałoby się unikać takiej sytuacji. Dlatego te czasy TTL powinniśmy sobie dostosować, tak by
system z jednej strony nie odpytywał upstream'owego serwera DNS zbyt często (bonus dla prywatności),
a z drugiej strony by te wpisy w cache były aktualne. Dlatego też można ustawić minimalny TTL na
10-30 minut, zaś maksymalny na 1-4 godziny.
W przypadku nieudanego rozwiązania nazwy na adres IP, taka odpowiedź również może być buforowana.
Zajmuje ona jednak cenne miejsce w cache. Jeśli zatem dodatkowo określimy no-negcache
, to możemy
wyłączyć dodawanie takich wpisów do cache, przez co więcej prawidłowych wpisów będzie mogło się w
tym cache znaleźć bez potrzeby zwiększania jego rozmiaru i spowalniania całego mechanizmu:
no-negcache
Odpytując teraz przykładową domenę przy pomocy dig
możemy zweryfikować jaki TTL zostanie
ustawiony dla wpisu w cache:
# dig mozilla.org
...
;; ANSWER SECTION:
mozilla.org. 40 IN A 63.245.208.195
;; Query time: 205 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Sun Sep 13 13:51:13 CEST 2020
;; MSG SIZE rcvd: 56
Po czasie 205 ms możemy stwierdzić, że to zapytanie było rozwiązane bezpośrednio. W sekcji
odpowiedzi widnieje liczba 40
-- to jest właśnie TTL dla tego rekordu DNS zwracany z
upstream'owego serwera DNS. Po odpytaniu domeny, rekord DNS powędrował do cache dnsmasq. Jeśli
jeszcze raz odpytamy o tę domenę, to otrzymamy poniższy wynik:
# dig mozilla.org
...
;; ANSWER SECTION:
mozilla.org. 596 IN A 63.245.208.195
;; Query time: 1 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Sun Sep 13 13:51:17 CEST 2020
;; MSG SIZE rcvd: 56
Czas zapytania już nie wynosi 205 ms ale 1ms, więc odpowiedź pochodzi z cache. Jak możemy również
zauważyć, czas TTL wynosi teraz 596s. Zatem ten TTL został przez dnsmasq przepisany do wartości
określonej w min-cache-ttl
(600). Każdy wpis mający TTL niższy niż 600 sekund będzie miał
ustawiony tę wartość przy dodawaniu rekordu do cache DNS. Natomiast każda odpowiedź, która ma TTL
wyższy niż zostało to określone w max-cache-ttl
, również zostanie przepisana, z tym, że do
wartości 3600. Jeśli natomiast trafi się wpis mający TTL z tego przedziału 600-3600, np. 1200, to
taka wartość TTL zostanie zapisana w cache DNS.
Statystyki cache w dnsmasq
Statystyki całego cache możemy wyciągnąć z logu systemowego, tylko pierw trzeba wysłać odpowiedni
sygnał do demona dnsmasq ( USR1
):
# kill -s USR1 $(pidof dnsmasq)
Po wydaniu tego powyższego polecenia, w logu systemowym powinniśmy zarejestrować poniższy komunikat:
dnsmasq[614751]: cache size 4096, 0/3599 cache insertions re-used unexpired cache entries.
dnsmasq[614751]: queries forwarded 13596, queries answered locally 10157
dnsmasq[614751]: queries for authoritative zones 0
dnsmasq[614751]: pool memory in use 48, max 48, allocated 2400
dnsmasq[614751]: server 192.168.1.1#53: queries sent 0, retried or failed 0
dnsmasq[614751]: server 127.0.2.1#53: queries sent 13596, retried or failed 28
Jak czytać te powyższe statystyki? W pierwszej linijce mamy rozmiar cache 4096
. Dalej mamy
0/3599
i zgodnie z tym co wyczytałem na liście mailingowej dnsmasq, to 0
oznacza brak
usuniętych wpisów z cache zanim utraciły one ważność, natomiast 3599
oznacza ilość wpisów w cache
ogółem, zatem w cache jest jeszcze trochę wolnego miejsca. Te dwie wartości mówią nam czy
ustawienia rozmiaru cache (czy też czasu ważności wpisów) należy dostosować. W przypadku, gdy
pierwsza wartość zacznie się zwiększać, trzeba pomyśleć nad zmianą parametrów cache. Dalej w logu
nie ma już nic niezwykłego. W linijkach z server
są zapytania, które zostały przesłane do
skonfigurowanych serwerów DNS. W tym przypadku 192.168.1.1
to adres serwera DNS domowego routera
WiFi, a na 127.0.2.1
nasłuchuje lokalnie dnscrypt-proxy. Praktycznie wszystkie zapytania lecą
właśnie do niego. W sumie system wysłał 23753
zapytań o domeny, z czego 13596
poleciało do
dnscrypt-proxy (cache miss) i dalej po zaszyfrowaniu do upstream'owego serwera DNS, zaś 10157
zapytań zostało wzięte z cache dnsmasq (cache hit, ~42%) i nie było potrzeby odpytywać zdalnego
serwera DNS. Kilka zapytań się nie powiodło ale ten fakt nie jest niczym niezwykłym. Grunt by tych
błędów nie było dużo w stosunku do ilości wykonywanych zapytań DNS. Jeśli zaś chodzi o cache hit,
to jest on trochę mały i by go poprawić można ździebko podnieść minimalny jak i maksymalny TTL.
Statystyki cache możemy także uzyskać via dig
:
# dig +short chaos txt cachesize.bind insertions.bind evictions.bind misses.bind hits.bind auth.bind servers.bind
"4096"
"296"
"0"
"1021"
"646"
"0"
"192.168.1.1#53 0 0" "127.0.2.1#53 1021 0"
Test szyfrowanych zapytań DNS
To w zasadzie cała konfiguracja dnscrypt-proxy i dnsmasq, która trzeba było poczynić, by te
zapytania DNS mogły w końcu wędrować po kablach w formie zaszyfrowanej. Przydałoby się jednak
przetestować czy aby na pewno tak się dzieje. Szereg provider'ów DNS zapewnia stosowne usługi by
zweryfikować szyfrowany DNS, np. CloudFlare ma tę stronę. My jednak nie będziemy się opierać
na zewnętrznych stronach WWW i sprawdzimy ręcznie czy te zapytania DNS są faktycznie szyfrowane, a
posłuży nam do tego celu wireshark
.
Musimy odpalić dwie instancję tego sniffer'a pakietów. Jedna z nich będzie monitorować interfejs
lo
, a druga interfejs, którym pakiety wychodzą w świat z naszej maszyny, w tym przypadku jest to
bond0
. Mając odpalonego wireshark'a, zapuszczamy proste polecenie ping
na jakąś domenę, której
standardowo nie odwiedzamy. Chodzi o to by uniknąć ewentualnego trafienia w cache DNS, bo w takim
przypadku zapytanie DNS naturalnie nie zostanie rozwiązane, tylko odpowiedź zostanie wzięta z tego
cache.
Na poniższym obrazku mamy widoczny podgląd portu 53 protokołu TCP/UDP interfejsu lo
:
Jak widzimy, na interfejsie lokalnym ruch DNS odbywa się w postaci odszyfrowanej. Pierwsze dwa
pakiety (te ze źródłowym i docelowym adresem 127.0.0.1
) to zapytania linux'owego resolver'a do
dnsmasq o adresy IPv4 (rekord A
) i IPv6 (rekord AAAA
). Następnie dnsmasq przekazuje te
zapytania na adres 127.0.2.1
do dnscrypt-proxy (kolejne dwa pakiety). Dalej mamy odpowiedzi na
wysłane zapytania (kolejne cztery pakiety). Odpowiedzi przychodzą w odwrotnej kolejności, tj. pierw
dnscrypt-proxy przesyła je do dnsmasq, a na końcu dnsmasq do linux'owego resolver'a. Zatem dnsmasq
i dnscrypt-proxy rozmawiają ze sobą i przekazują między sobą zarówno zapytania jak i odpowiedzi. No
ale co się dzieje z tymi zapytaniami DNS, gdy docierają do interfejsu bond0
? Podejrzyjmy zatem
port 53 na interfejsie bond0
:
Brak jakiegokolwiek ruchu, zatem na interfejsie wychodzącym z naszej maszyny nie widać żadnych zapytań DNS.
To co się właściwie dzieje z tymi zapytaniami DNS? Są one przesyłane do serwera DNS CloudFlare na
port 443 (jako że w opisanej wyżej konfiguracji korzystamy z DoH). Zobaczmy zatem co się dzieje na
interfejsie bond0
na porcie 443:
Jak widzimy, nasza maszyna rozmawia z serwerem 1.0.0.1
przy wykorzystaniu protokołu TLS 1.3.
Wiemy zatem, że nasze zapytania DNS są przesyłane przez sieć w formie zaszyfrowanej i nikt
nieuprawniony nie jest w stanie ich podejrzeć.
Jeśli się przyjrzymy uważnie, to zapytania AAAA
(o adres IPv6) są blokowane, a właściwie
odpowiada na nie dnscrypt-proxy komunikatami CPU: AAAA queries have been locally blocked by dnscrypt-proxy
oraz OS: Set block_ipv6 to false to disable this feature
:
Czyli wszystko działa prawidłowo.
Szyfrowany DNS sam w sobie nie wystarczy
Szyfrowany DNS co do zasady uniemożliwia podejrzenie pakietów i podsłuchanie ruchu DNS na linii klient-serwer. Niemniej jednak, trzeba także zatroszczyć się o zaszyfrowanie pozostałego ruchu przesyłanego do i odbieranego z serwera. Zwykle w przypadku stron WWW wykorzystywany jest protokół SSL/TLS. Niemniej jednak, by serwer był w stanie serwować wiele stron WWW z wykorzystaniem tego bezpiecznego protokółu i szyfrować ruch niezależnie od skonfigurowanych na nim domen, to musi robić użytek z rozszerzenia protokołu TLS zwanego SNI (Server Name Indication). Problem z tym całym SNI jest taki, że nazwa odwiedzanej przez nas domeny jest przesyłana do serwera otwartym tekstem, przez co nasz ISP (czy też inne podmioty) są w stanie bez najmniejszego problemu ustalić strony, które odwiedzamy i ocenzurować je nawet jeśli szyfrujemy ruch DNS. Póki co, przed tym wyciekiem tak kluczowej informacji jak nazwa odwiedzanej domeny nie ma ochrony. Niemniej jednak Mozilla oraz CloudFlare działają na rzecz zmiany tego stanu rzeczy przez wdrażanie ESNI (Encrypted SNI). Ten proces zajmie jeszcze pewnie lata zanim wyciek informacji za sprawą SNI zostanie załatany ale już w tej chwili można ręcznie w przeglądarce Firefox włączyć szyfrowany SNI. Trzeba jednak mieć na uwadze fakt, że to czy nazwa domeny zostanie zaszyfrowana zależy głównie od samego serwera WWW, na którym hostowana jest odwiedzana przez nas strona. Jeśli serwer nie wspiera ESNI, to w dalszym ciągu nazwa domeny zostanie przesłana przez sieć otwartym tekstem i szyfrowany DNS w zasadzie na nic nam się zda, przynajmniej w kwestii ochrony naszej prywatności.
Czy chattr +i na /etc/resolv.conf to dobry pomysł
Wielu użytkowników linux'a w obawie przed przepisaniem przez system pliku /etc/resolv.conf
ustawia temu plikowi bit (atrybut) odporności, tj. Immutable Bit, przy pomocy chattr
. To
rozwiązanie ma na celu uniemożliwienie wszystkim użytkownikom w systemie zmiany pliku
/etc/resolv.conf
, przez co nawet administrator root nie będzie miał prawa tknąć tego pliku.
Z bitu odporności powinno się korzystać jedynie w przypadku, gdy wiemy, że żadne części systemu nie
będą próbować przepisać zawartości tego pliku w sposób nieuprawniony. Niemniej jednak, cała masa
użytkowników wykorzystuje bit odporności, by uniemożliwić systemowi jego prawidłową pracę, w
efekcie czego pewne usługi mogą generować błędy, przykładowo w katalogu /etc/
może zacząć
pojawiać się cała masa plików pokroju resolv.conf.dhclient-new.xxxx
. Te pliki są tworzone przez
dhclient
(standardowy klient DHCP na Debianie), który w lease DHCP może otrzymać od routera
domowego (czy ISP) adres serwera DNS. Później ten adres jest umieszczany w pliku /etc/resolv.conf
ale, że system nie może zapisać tego pliku (za sprawą ustawienia mu bitu odporności), to generowany
jest błąd i te tymczasowe pliki nie są usuwane, bo proces nie został dokończony.
Zatem mamy sytuację, gdzie w uprawniony sposób klient DHCP chce przepisać plik /etc/resolv.conf
i
zamiast w tym miejscu ustawiać bit odporności, powinniśmy tak skonfigurować klienta DHCP, by nie
ruszał pliku /etc/resolv.conf
. W przypadku dhclient
możemy napisać prosty skrypt o nazwie
no-resolv
, który trzeba umieścić w katalogu /etc/dhcp/dhclient-enter-hooks.d/
. Poniżej jest
treść tego skryptu:
#/bin/sh
# see /sbin/dhclient-script
RUN="yes"
if [ "$RUN" = "yes" ]; then
if [ "${interface}" = "wwan0" ] ||
[ "${interface}" = "bond0" ] ||
[ "${interface}" = "usb0" ] ||
[ "${interface}" = "eth0" ] ||
[ "${interface}" = "wlan0" ]; then
case $reason in
BOUND|RENEW|REBIND|REBOOT)
make_resolv_conf() {
return 0
}
;;
esac
fi
fi
W skrócie, dhclient
standardowo wywołuje funkcję make_resolv_conf() {}
, która ma za zadanie
przepisać plik /etc/resolv.conf
podczas pobierania lease DHCP. Gdy dhclient
będzie pobierał
lease DHCP dla jednego z uwzględnionych wyżej interfejsów, to zamiast korzystać ze standardowej
funkcji make_resolv_conf() {}
będzie korzystał z tej naszej funkcji, która w zasadzie nie robi
nic. W ten sposób plik /etc/resolv.conf
nie zostanie ruszony przez dhclient
, co otwiera nam
drogę do skorzystania z bitu odporności.
Oczywiście w systemie może być więcej usług, które operują na tym pliku /etc/resolv.conf
i trzeba
je wszystkie wyłapać tak, by te usługi odpowiednio skonfigurować. W tym przypadku tylko dhclient
próbował przepisywać ten plik. Po zastosowaniu tego powyższego skryptu, żadna inna aplikacja w
sposób uprawniony nie powinna pliku /etc/resolv.conf
ruszać i by mieć pewność, że nic przez
przypadek (bez naszej świadomej i dobrowolnej zgody) nie ruszy tego pliku, możemy mu nałożyć bit
odporności. By ustawić bit odporności, w terminalu trzeba wpisać to poniższe polecenie:
# chattr +i /etc/resolv.conf
Przy pomocy lsattr
możemy dodatkowo zweryfikować czy bit został poprawnie nałożony:
# lsattr /etc/resolv.conf
----i---------e----- /etc/resolv.conf
Tam gdzie są -
, to dany atrybut nie jest ustawiony. W tym powyższym przypadku są ustawione dwa
atrybuty, tj. i
oraz e
. Atrybut odporności i
sami ustawiliśmy przed chwilą, zaś e
wskazuje,
że plik wykorzystuje zakresy (extents w EXT4) do mapowania bloków na dysku. Więcej informacji
o atrybutach można znaleźć w man chattr.
Czy szyfrować zapytania o domeny serwerów czasu
Niekiedy na necie można się spotkać z dodawaniem do konfiguracji dnsmasq osobnego wpisu dla domen
serwerów czasu w postaci server=/pool.ntp.org/1.1.1.1
. W takim przypadku, wszystkie zapytania o
tę domenę (i jej wszystkie subdomeny) będą realizowane z pominięciem szyfrowania. Pojawia się tutaj
jednak dylemat czy te zapytania powinny być przesyłane przez sieć nieszyfrowane czy wręcz odwrotnie.
Z jednej strony nie po to chcemy zaszyfrować wszystkie zapytania DNS, by część z nich puszczać w
formie niezabezpieczonej. Z drugiej jednak strony, szyfrowanie zapytań DNS dodaje zwykle niedające
się do końca przewidzieć opóźnienia, bo system musi przecież poświęcić trochę dodatkowego czasu,
by dane trafiające do pakietów sieciowych zaszyfrować. Takie dodatkowe opóźnienia są bardzo
niepożądane z perspektywy synchronizacji czasu, a im bardziej procesor naszego komputera jest
obciążony, tym proces szyfrowania danych zajmuje więcej czasu. Niemniej jednak, rozwiązywanie nazw
domen na adresy IP dokonywane jest przed rozpoczęciem procesu synchronizacji czasu i nie wpływa na
niego w żaden sposób. Dlatego też unikajmy dodawania podobnych wpisów w konfiguracji dnsmasq.
Podsumowanie
Sposobów na zabezpieczenie usługi DNS rozwiązującej nazwy domen na adresy IP jest co najmniej kilka. W tym artykule został przedstawiony mechanizm, który szyfruje zapytania DNS przy wykorzystaniu dnscrypt-proxy i dnsmasq, w efekcie czego zapytania DNS przestają być czytelne dla całej masy wścibskich podmiotów zagrażających naszej prywatności i bezpieczeństwu w sieci. Niemniej jednak, szyfrowanie ruchu DNS samo w sobie nie jest rozwiązaniem, które może nas uchronić przez masową inwigilacją. Wymagane zatem jest stosowanie dodatkowych zabezpieczeń, np. przeglądanie stron WWW z wykorzystaniem bezpiecznego protokołu HTTPS. Trzeba sobie jednak zdawać sprawę, że obecnie nie ma sposobu by ukryć nazwę domeny odwiedzanego przez nas serwisu z winy SNI, przez co ta nazwa jest przesyłana przez sieć w postaci niezabezpieczonej. Jeśli wdrażamy szyfrowany DNS głównie po to, by uniemożliwić osobom trzecim ustalenie jakie strony WWW odwiedzamy, to lepszym rozwiązaniem będzie postawienie i skonfigurowanie serwera VPN. W takim przypadku jedyną informacją, którą lokalny ISP będzie w stanie pozyskać, będzie adres VPN. Wszystkie pozostałe dane (czy to DNS, czy też nazwa domeny w SNI) będą przez tę jednostkę przechodzić w formie zaszyfrowanej. Niemniej jednak, takie rozwiązanie jest nas w stanie ochronić jedynie przed lokalną cenzurą, bo w dalszym ciągu ten SNI z VPN do serwera WWW będzie przesyłany w formie niezaszyfrowanej. Tak czy inaczej, ESNI (szyfrowany SNI) być może uda się wdrożyć w niedalekiej przyszłości i wtedy szyfrowanie ruchu DNS (by poprawić naszą prywatność) będzie miało nieco więcej sensu.