Mémo pour installer un serveur DNS classic (port 53), DOH (port 443) et DOT (port 853).
Pré-requis
Un serveur web
Dans mon cas, une instance sur Oracle Cloud en « Always Free » :
- VM.Standard.E2.1.Micronamed.conf.local
- Ram : 1Go
- 2 vCPU
- Ubuntu 20.04 LTS / Debian 11
Ne pas oublier la sécurité (Fail2ban, PSAD, Logwatch, hardening, etc.) et d’ouvrir les ports 53, 443 et 853 (en plus du 22).
Pour Let’s Encrypt, il faut aussi ouvrir le port 80.
Et bien sûr un nom de domaine qui pointe sur le serveur.
Pour le moment, ce mémo ne permet pas d’utiliser IPv6 (à venir).
Désactivation du service systemd-resolved
1 2 3 |
$ systemctl stop systemd-resolved.service $ systemctl disable systemd-resolved.service $ systemctl mask systemd-resolved.service |
1 |
$ rm /etc/resolv.conf |
1 |
/etc/resolv.conf -> ../run/resolvconf/resolv.conf |
1 2 |
nameserver 1.1.1.1 nameserver 1.0.0.1 |
Au cas où ce dernier aurait un soucis de configuration, son accès deviendrait compliqué.
Bind9
Version utilisée au moment de ce mémo :
1 2 |
$ named -v BIND 9.16.1-Ubuntu (Stable Release) <id:d497c32> |
Installation de bind9 :
1 2 3 |
$ apt update $ apt install bind9 $ systemctl stop bind9 |
Fichier /etc/bind/named.conf.local.
Cette configuration permet de consulter les requêtes dans le fichier de log /var/log/named/query.log.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// // Do any local configuration here // // Consider adding the 1918 zones here, if they are not used in your // organization //include "/etc/bind/zones.rfc1918"; logging { channel query.log { file "/var/log/bind/query.log"; severity debug 3; }; category queries { query.log; }; }; |
Fichier /etc/apparmor.d/local/usr.sbin.named
1 2 |
/var/log/bind/** rw, /var/log/bind/ rw, |
Prise en compte de la configuration dans AppArmor :
1 |
$ apparmor_parser -r /etc/apparmor.d/usr.sbin.named |
Créer le répertoire /var/log/bind avec un propriétaire et un groupe à bind :
1 2 |
$ mkdir /var/log/bind $ chown bind: /var/log/bind |
Fichier /etc/bind/named.conf.options
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
acl queriesfrom { 127.0.0.1; }; options { directory "/var/cache/bind"; // Only for DNS forward //forward only; //forwarders { // 1.1.1.1; // 1.0.0.1; //}; listen-on-v6 { none; }; listen-on port 5300 { 127.0.0.1; }; allow-query { queriesfrom; } ; allow-transfer { none; } ; allow-recursion { queriesfrom; } ; recursion yes; auth-nxdomain no; dnssec-validation auto; version "not available"; // Disable log querylog no; }; |
Ce fichier permet :
- [Option] D’utiliser Cloudflare comme serveur DNS. A désactiver si on ne souhaite pas utiliser les DNS root.
- De ne pas répondre aux demandes IPv6.
- D’écouter uniquement sur le port 5300 pour l’ip 127.0.0.1.
- D’autoriser l’interrogation du serveur que pour 127.0.0.1 (queriesfrom).
- Autorise la récursivité.
- Active la validation dnsssec si disponible.
- Ne renvoie pas la version de Bind pour des raisons de sécurité.
La partie commentée « querylog no; » permet de ne pas activer la log au besoin.
Pour configurer Bind9 uniquement pour IPV4, éditer le fichier /etc/default/named.
A la ligne :
1 |
OPTIONS="-u bind" |
Modifier en :
1 |
OPTIONS="-u bind -4" |
Ne pas démarrer Bind9 de suite.
dnsdist
Version utilisée au moment de ce mémo :
1 2 3 |
$ dnsdist --version dnsdist 1.5.2 (Lua 5.1.4 [LuaJIT 2.1.0-beta3]) Enabled features: cdb dns-over-tls(gnutls openssl) dns-over-https(DOH) dnscrypt ebpf fstrm ipcipher libsodium lmdb protobuf re2 recvmmsg/sendmmsg snmp systemd |
A adapter en fonction de la disponibilité : https://repo.powerdns.com/
Prendre « dnsdist – version X.X.X (stable) ».
Choisir la version la plus récente en fonction de sa distribution.
Installation de dnsdist 1.5 pour Ubuntu 20.04 LTS Focal :
1 |
$ echo "deb [arch=amd64] http://repo.powerdns.com/ubuntu focal-dnsdist-15 main" | sudo tee /etc/apt/sources.list.d/dnsdist.list |
On va figer la source du paquet en provenance uniquement de dépôt officiel.
Créer le fichier /etc/apt/preferences.d/dnsdist et ajouter ce contenu :
1 2 3 |
Package: dnsdist* Pin: origin repo.powerdns.com Pin-Priority: 600 |
Importer la clé publique du dépôt de PowerDNS :
1 |
$ curl https://repo.powerdns.com/FD380FBB-pub.asc | apt-key add - |
Installer dnsdist :
1 2 3 |
$ apt update $ apt install dnsdist $ systemctl stop dnsdist |
Fichier de configuration /etc/dnsdist/dnsdist.conf.
Pour la partie certificat, laisser tel quel. On voit après.
Basé sur https://dnsprivacy.org/running_a_dns_privacy_server/using_dnsdist/ et mes recherches ici et là…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
addACL('0.0.0.0/0') -- DNS addLocal('0.0.0.0:53',{doTCP=true, reusePort=true, tcpFastOpenSize=0}) -- DOT addTLSLocal("0.0.0.0:853", "/etc/dnsdist/dot-server.crt", "/etc/dnsdist/dot-server.key", { doTCP=true, reusePort=true, tcpFastOpenSize=0 }) -- DOH -- Without nginx -- addDOHLocal("127.0.0.1:5353", "/etc/dnsdist/dot-server.crt", "/etc/dnsdist/dot-server.key", "/dns-query", { doTCP=true, reusePort=true, tcpFastOpenSize=64 }) -- With nginx addDOHLocal("127.0.0.1:5353", nil, nil, "/dns-query", { reusePort=true }) -- set X(int) number of queries to be allowed per second from a IP addAction(MaxQPSIPRule(100), DropAction()) -- drop ANY queries sent over udp --addAction(AndRule({QTypeRule(DNSQType.ANY), TCPRule(false)}), DropAction()) -- set X number of entries to be in dnsdist cache by default -- memory will be preallocated based on the X number pc = newPacketCache(10000, {maxTTL=86400}) getPool(""):setCache(pc) -- server policy to choose the downstream servers for recursion setServerPolicy(leastOutstanding) -- Bind9 dns server newServer({address="127.0.0.1:5300", name="local-bind9"}) setMaxUDPOutstanding(65535) --setMaxTCPClientThreads(10) -- set X(int) to handle number of maximum tcp clients setMaxTCPConnectionDuration(30) -- set X(int) for tcp connection duaration from a connected client. X is number of seconds. setMaxTCPConnectionsPerClient(100) -- set X(int) for number of tcp connections from a single client. Useful for rate limiting the concurrent connections. setMaxTCPQueriesPerConnection(100) -- set X(int) , similiar to addAction(MaxQPSIPRule(X), DropAction()) |
Nginx
Dans le cas où un serveur Nginx est installé, exemple de configuration Nginx à adapter suivant les besoins :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
proxy_cache_path /var/run/doh_cache levels=1:2 keys_zone=doh_cache:10m; server { listen 443 ssl http2; server_name dot.monserveurdns.tld; error_log /var/log/nginx/dot.monserveurdns.tld-error.log warn; access_log /var/log/nginx/dot.monserveurdns.tld-access.log; # nginx version server_tokens off; root /var/www/html; index index.html index.htm index.nginx-debian.html; location /.well-known/acme-challenge/ { allow all; root /var/www/certbot; } ssl_certificate /etc/letsencrypt/live/dot.monserveurdns.tld/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/dot.monserveurdns.tld/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot # DoH may use GET or POST requests, Cache both proxy_cache_methods GET POST; # Return 404 to all responses, except for those using our published DoH URI location / { try_files $uri $uri/ =404; } location /dns-query { # Proxy HTTP/1.1, clear the connection header to enable Keep-Alive proxy_http_version 1.1; proxy_set_header Connection ""; # Enable Cache, and set the cache_key to include the request_body proxy_cache doh_cache; proxy_cache_key $scheme$proxy_host$uri$is_args$args$request_body; # proxy pass to dnsdist proxy_pass http://127.0.0.1:5353; } } server { if ($host = dot.monserveurdns.tld) { return 301 https://$host$request_uri; } # managed by Certbot listen 80; server_name dot.monserveurdns.tld; return 404; } |
Let’s Encrypt
Version utilisée au moment de ce mémo :
1 2 |
$ certbot --version certbot 1.21.0 |
Installation :
1 2 |
$ snap install --classic certbot $ ln -s /snap/bin/certbot /usr/bin/certbot |
Pour le moment, la chaine « ISRG Root X1 » est demandée pour les smartphones Android (pas testé avec iPhone).
Création du certificat pour un serveur sans Nginx (cas d’un serveur autonome) :
1 |
$ certbot certonly --standalone --preferred-challenges http --agree-tos --email mon@email.tld -d url.monserveurdns.tld --preferred-chain "ISRG Root X1" |
Avec un Nginx installé :
1 |
$ certbot --preferred-chain "ISRG Root X1" |
Les certificats seront donc dans le répertoire : /etc/letsencrypt/live/
Dans la configuration de dnsdist, il faut qu’ils soient dans /etc/dnsdist/.
Script hook pour copier les certificats et relancer le service dnsdist à la mise à jour de Let’s Encrypt.
Fichier : /etc/letsencrypt/renewal-hooks/deploy/deploy-dnsdist.sh
1 2 3 4 5 6 7 8 9 10 11 12 |
#!/usr/bin/env bash DOMAIN="url.monserveurdns.tld" DNSDIST_GROUP="$(grep dnsdist /etc/group | cut -d ':' -f 1)" cp /etc/letsencrypt/live/${DOMAIN}/fullchain.pem /etc/dnsdist/dot-server.crt cp /etc/letsencrypt/live/${DOMAIN}/privkey.pem /etc/dnsdist/dot-server.key chown root:${DNSDIST_GROUP} /etc/dnsdist/dot-server.crt /etc/dnsdist/dot-server.key chmod 640 /etc/dnsdist/dot-server.key systemctl restart dnsdist |
Remplacer url.monserveurdns.tld par le domaine.
Exécution des services
Tester la configuration de dnsdist :
1 |
$ dnsdist --check-config |
Résultat :
1 2 |
No certificate provided for DoH endpoint 127.0.0.1:5353, running in DNS over HTTP mode instead of DNS over HTTPS Configuration '/etc/dnsdist/dnsdist.conf' OK! |
Tester la configuration de Bind9 :
1 |
$ named-checkconf /etc/bind/named.conf |
Démarrer bind9 puis dnsdist. Attendre 10 secondes entre chaque.
1 2 |
$ systemctl start bind9 $ systemctl start dnsdist |
Vérifier le status des services :
1 2 |
$ systemctl status bind9 $ systemctl status dnsdist |
S’il y a une erreur dans le fichier /var/log/syslog, vérifier la configuration.
Quand tout va bien pour dnsdist :
1 |
dnsdist[11547]: Marking downstream local-bind9 (127.0.0.1:5300) as 'up' |
Tester le serveur DNS
J’utilise Doggo.
L’installation se fait rapidement.
Test du DNS sur le port 53 :
1 |
$ doggo domain.tld @tcp://url.monserveurdns.tld |
Test du DNS DOH sur le port 443 :
1 |
$ doggo domain.tld @https://url.monserveurdns.tld/dns-query |
Test du DNS DOT sur le port 853 :
1 |
$ doggo domain.tld @tls://url.monserveurdns.tld |
Test du certificat pour DOT sur le port 853 :
1 |
$ openssl s_client -connect url.monserveurdns.tld:853 |
Un autre outil pour tester : kdig
1 |
$ apt install knot-dnsutils |
Exemple pour tester DOT :
1 |
kdig -d @url.monserveurdns.tld +tls-ca domain.tld |
Configuration dans Android
Configuration globale du smartphone Android.
Dans les paramètres > Réseau et internet > DNS privé, saisir le nom de domaine utilisé.
Configuration pour Chrome, Vivaldi, etc. (parceque la configuration précédente ne suffit pas pour ces navigateurs à base de Chromium/Blink) :
Rechercher « Confidentialité et sécurité » > « Utiliser un DNS sécurisé ».
Saisir l’url : « https://dot.monserveurdns.tld/dns-query »
Et voilà !
AdBlock
Pour rajouter en bonus un bloqueur de pub.
Voir https://github.com/Trellmor/bind-adblock
Je conseille de faire le git clone dans le répertoire /opt.
1 2 3 4 |
$ cd /opt $ git clone https://github.com/Trellmor/bind-adblock.git $ cd bind-adblock $ pip install -r requirements.txt |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
acl queriesfrom { 127.0.0.1; }; options { directory "/var/cache/bind"; forwarders { 1.1.1.1; 1.0.0.1; }; listen-on-v6 { none; }; listen-on port 5300 { 127.0.0.1; }; allow-query { queriesfrom; } ; allow-transfer { none; } ; allow-recursion { queriesfrom; } ; forward only; recursion yes; auth-nxdomain no; dnssec-validation yes; version "not available"; response-policy { zone "rpz.dot.monserveurdns.tld"; }; // Disable log querylog no; }; |
1 2 3 4 5 6 7 |
// AdBlock zone "rpz.dot.monserveurdns.tld" { type master; file "/etc/bind/db.rpz.dot.monserveurdns.tld"; masterfile-format text; allow-query { none; }; }; |
Exemple pour ne pas bloquer le domaine t.co (les liens dans Twitter) :
1 |
domain_whitelist: [t.co] |
1 |
$ /usr/bin/python3 /opt/bind-adblock/update-zonefile.py --no-bind /etc/bind/db.rpz.dot.monserveurdns.tld rpz.dot.monserveurdns.tld |
1 |
$ /usr/sbin/rndc reload |
1 2 |
0 */12 * * * /usr/bin/python3 /opt/bind-adblock/update-zonefile.py --no-bind /etc/bind/db.rpz.dot.monserveurdns.tld rpz.dot.monserveurdns.tld > /tmp/bind-update.log 2>&1 2 */12 * * * /usr/sbin/rndc reload > /dev/null 2>&1 |