Mettons en place notre serveur DNS-over-HTTPS

Les différents aspects de l'exploitation du DNS ont déjà été abordés à plusieurs reprises par l'auteur dans une série articles d'articles publiés sur le blog. Dans ce contexte, l'accent a toujours été mis sur l'amélioration de la sécurité de ce service clé pour l'ensemble d'Internet.

Mettons en place notre serveur DNS-over-HTTPS

Jusqu'à récemment, malgré l'évidence de la vulnérabilité du trafic DNS, qui, pour la plupart, continue d'être transmis en clair, les actes malveillants de la part des fournisseurs, désireux d'accroître leurs revenus grâce à l'insertion de publicités dans le contenu, des agences gouvernementales et de la censure, ainsi que de simples criminels, le processus de renforcement de sa protection, malgré la présence de diverses technologies telles que DNSSEC/DANE, DNScrypt, DNS-over-TLS et DNS-over-HTTPS, stagnait. Et si les solutions serveur, dont certaines existent depuis un certain temps, sont largement connues et accessibles, le support de leur part par les logiciels clients laisse à désirer.

Heureusement, la situation évolue. En particulier, les développeurs du navigateur populaire Firefox a déclaré ont annoncé leurs projets d'intégrer par défaut le support de DNS-over-HTTPS (DoH) dans un avenir proche. Cela devrait aider à protéger le trafic DNS des utilisateurs du WWW contre les menaces mentionnées ci-dessus, mais pourrait potentiellement en susciter de nouvelles.

1. Problèmes DNS-over-HTTPS

À première vue, le déploiement de masse de DNS-over-HTTPS dans les logiciels fonctionnant sur Internet suscite uniquement une réaction positive. Cependant, le diable, comme on dit, se cache dans les détails.

Le premier problème qui limite le champ d'application de DoH est son orientation exclusivement vers le trafic web. En effet, le protocole HTTP et sa version actuelle HTTP/2, sur lequel DoH est basé, constituent la base du WWW. Mais Internet n'est pas seulement le web. Il existe de nombreux services populaires, tels que le courrier électronique, divers messagers, les systèmes de transfert de fichiers, le streaming multimédia, etc., qui n'utilisent pas HTTP. Ainsi, malgré la perception par beaucoup de DoH comme une panacée, il s'avère inapplicable sans efforts supplémentaires (et inutiles), en dehors des technologies liées aux navigateurs. À cet égard, DNS-over-TLS semble être un candidat bien plus digne, encapsulant le trafic DNS standard dans un protocole standard sécurisé TLS.

Le deuxième problème, qui pourrait être bien plus significatif que le premier, est le rejet de la décentralisation inhérente à DNS par conception en faveur de l'utilisation d'un serveur DoH unique spécifié dans les paramètres du navigateur. En particulier, Mozilla propose un service de Cloudflare. D'autres acteurs majeurs d'Internet, notamment Google, ont également lancé des services similaires. Cela signifie que l'implémentation de DNS-over-HTTPS telle qu'elle est proposée actuellement augmente la dépendance des utilisateurs finaux vis-à-vis des plus grands services. Il n'est un secret pour personne que les informations collectées par l'analyse des requêtes DNS peuvent rassembler encore plus de données à leur sujet, tout en améliorant leur précision et leur actualité.

À cet égard, l'auteur a été et reste un partisan de l'adoption massive non pas de DNS-over-HTTPS, mais de DNS-over-TLS associé à DNSSEC/DANE comme un moyen universel, sécurisé et qui ne contribue pas à la centralisation accrue d'Internet pour assurer la sécurité du trafic DNS. Malheureusement, il est peu probable de s'attendre à une adoption rapide du soutien massif aux alternatives à DoH dans les logiciels clients pour des raisons évidentes, et cela demeure pour l'instant l'apanage des passionnés des technologies sécurisées.

Mais puisque nous avons désormais DoH, pourquoi ne pas l'utiliser, en nous éloignant d'une surveillance potentielle de la part des entreprises via leurs serveurs vers notre propre serveur DNS-over-HTTPS ?

2. Protocole DNS-over-HTTPS

Si l'on se penche sur la norme RFC8484 qui décrit le protocole DNS-over-HTTPS, on peut observer qu'il s'agit en réalité d'une API web permettant d'encapsuler le paquet DNS standard dans le protocole HTTP/2. Cela se fait grâce à des en-têtes HTTP spécifiques, ainsi qu'à la conversion du format binaire des données DNS transférées (voir RFC1035 et les documents suivants) en un format permettant de les transmettre et de les recevoir, ainsi que de travailler avec les métadonnées nécessaires.

Le standard ne supporte que HTTP/2 et les connexions sécurisées TLS.

L'envoi d'une requête DNS peut se faire par les méthodes standards GET et POST. Dans le premier cas, la requête est transformée en une chaîne encodée base64URL, et dans le second — via le corps de la requête POST sous forme binaire. Dans ce cas, une type MIME spécifique est utilisé pour les requêtes et les réponses DNS. application/dns-message.

root@eprove:~ # curl -H 'accept: application/dns-message' 'https://my.domaint/dns-query?dns=q80BAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE' -v
*   Trying 2001:100:200:300::400:443...
* TCP_NODELAY set
* Connected to eprove.net (2001:100:200:300::400) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /usr/local/share/certs/ca-root-nss.crt
  CApath: none
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=my.domain
*  start date: Jul 22 00:07:13 2019 GMT
*  expire date: Oct 20 00:07:13 2019 GMT
*  subjectAltName: host "my.domain" matched cert's "my.domain"
*  issuer: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X3
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x801441000)
> GET /dns-query?dns=q80BAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE HTTP/2
> Host: eprove.net
> User-Agent: curl/7.65.3
> accept: application/dns-message
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* Connection state changed (MAX_CONCURRENT_STREAMS == 100)!
< HTTP/2 200
< server: h2o/2.3.0-beta2
< content-type: application/dns-message
< cache-control: max-age=86274
< date: Thu, 12 Sep 2019 13:07:25 GMT
< strict-transport-security: max-age=15768000; includeSubDomains; preload
< content-length: 45
<
Warning: Binary output can mess up your terminal. Use "--output -" to tell
Warning: curl to output it to your terminal anyway, or consider "--output
Warning: " to save to a file.
* Failed writing body (0 != 45)
* stopped the pause stream!
* Connection #0 to host eprove.net left intact

Veuillez également noter l'en-tête cache-control: dans la réponse du serveur web. Dans le paramètre max-age se trouve la valeur TTL pour l'enregistrement DNS renvoyé (ou la valeur minimale si un ensemble d'enregistrements est renvoyé).

En résumé, le fonctionnement du serveur DoH se compose de plusieurs étapes.

  • Recevoir la requête HTTP. Si c'est un GET, alors décoder le paquet de l'encodage base64URL.
  • Envoyer ce paquet au serveur DNS.
  • Recevoir la réponse du serveur DNS.
  • Trouver la valeur minimale de TTL parmi les enregistrements reçus.
  • Retourner la réponse au client via HTTP.

3. Votre propre serveur DNS-over-HTTPS

Le moyen le plus simple, rapide et efficace de mettre en place votre propre serveur DNS-over-HTTPS est d'utiliser un serveur web HTTP/2 H2O, dont l'auteur a déjà brièvement parlé (voir «Serveur web haute performance H2O«).

Ce choix est renforcé par le fait que tout le code de votre propre serveur DoH peut être entièrement réalisé à l'aide de l'interpréteur intégré H2O mruby. En plus des bibliothèques standard, une bibliothèque (mrbgem) Socket est nécessaire pour l'échange de données avec le serveur DNS, qui, heureusement, est déjà incluse dans la version de développement actuelle d'H2O 2.3.0-beta2. présente dans les ports FreeBSD. Cependant, il n'est pas difficile de l'ajouter dans n'importe quelle version antérieure en clonant le dépôt. de la bibliothèque Socket dans le répertoire /deps avant la compilation.

root@beta:~ # uname -v
FreeBSD 12.0-RELEASE-p10 GENERIC
root@beta:~ # cd /usr/ports/www/h2o
root@beta:/usr/ports/www/h2o # make extract
===>  Licence MIT BSD2CLAUSE acceptée par l'utilisateur
===>   h2o-2.2.6 dépend du fichier : /usr/local/sbin/pkg - trouvé
===> Récupération de tous les fichiers nécessaires à h2o-2.2.6 pour la construction
===>  Extraction de h2o-2.2.6.
=> Vérification de la somme de contrôle SHA256 OK pour h2o-h2o-v2.2.6_GH0.tar.gz.
===>   h2o-2.2.6 dépend du fichier : /usr/local/bin/ruby26 - trouvé
root@beta:/usr/ports/www/h2o # cd work/h2o-2.2.6/deps/
root@beta:/usr/ports/www/h2o/work/h2o-2.2.6/deps # git clone https://github.com/iij/mruby-socket.git
Clonage dans « mruby-socket »…
remote: Énumération des objets : 385, fait.
remote: Total 385 (delta 0), réutilisé 0 (delta 0), pack-réutilisé 385
Récupération des objets : 100 % (385/385), 98.02 KiB | 647.00 KiB/s, prêt.
Détermination des changements : 100 % (208/208), prêt.
root@beta:/usr/ports/www/h2o/work/h2o-2.2.6/deps # ll
total 181
drwxr-xr-x   9 root  wheel  18 12 août 16:09 brotli/
drwxr-xr-x   2 root  wheel   4 12 août 16:09 cloexec/
drwxr-xr-x   2 root  wheel   5 12 août 16:09 golombset/
drwxr-xr-x   4 root  wheel  35 12 août 16:09 klib/
drwxr-xr-x   2 root  wheel   5 12 août 16:09 libgkc/
drwxr-xr-x   4 root  wheel  26 12 août 16:09 libyrmcds/
drwxr-xr-x  13 root  wheel  32 12 août 16:09 mruby/
drwxr-xr-x   5 root  wheel  11 12 août 16:09 mruby-digest/
drwxr-xr-x   5 root  wheel  10 12 août 16:09 mruby-dir/
drwxr-xr-x   5 root  wheel  10 12 août 16:09 mruby-env/
drwxr-xr-x   4 root  wheel   9 12 août 16:09 mruby-errno/
drwxr-xr-x   5 root  wheel  14 12 août 16:09 mruby-file-stat/
drwxr-xr-x   5 root  wheel  10 12 août 16:09 mruby-iijson/
drwxr-xr-x   5 root  wheel  11 12 août 16:09 mruby-input-stream/
drwxr-xr-x   6 root  wheel  11 12 août 16:09 mruby-io/
drwxr-xr-x   5 root  wheel  10 12 août 16:09 mruby-onig-regexp/
drwxr-xr-x   4 root  wheel  10 12 août 16:09 mruby-pack/
drwxr-xr-x   5 root  wheel  10 12 août 16:09 mruby-require/
drwxr-xr-x   6 root  wheel  10 12 sept. 16:10 mruby-socket/
drwxr-xr-x   2 root  wheel   9 12 août 16:09 neverbleed/
drwxr-xr-x   2 root  wheel  13 12 août 16:09 picohttpparser/
drwxr-xr-x   2 root  wheel   4 12 août 16:09 picotest/
drwxr-xr-x   9 root  wheel  16 12 août 16:09 picotls/
drwxr-xr-x   4 root  wheel   8 12 août 16:09 ssl-conservatory/
drwxr-xr-x   8 root  wheel  18 12 août 16:09 yaml/
drwxr-xr-x   2 root  wheel   8 12 août 16:09 yoml/
root@beta:/usr/ports/www/h2o/work/h2o-2.2.6/deps # cd ../../../
root@beta:/usr/ports/www/h2o # make install clean
...

La configuration du serveur Web est globalement standard.

root@beta: /usr/ports/www/h2o # cd /usr/local/etc/h2o/
root@beta: /usr/local/etc/h2o # cat h2o.conf
# cet exemple de configuration vous donne une idée de la façon dont h2o peut être utilisé
# et une configuration haute sécurité pour TLS et les en-têtes HTTP
# voir https://h2o.examp1e.net/ pour la documentation détaillée
# et h2o --help pour les options et paramètres de ligne de commande

# v.20180207 (c)2018 par Max Kostikov http://kostikov.co e-mail: max@kostikov.co

user: www
pid-file: /var/run/h2o.pid
access-log:
    path: /var/log/h2o/h2o-access.log
    format: "%h %v %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-agent}i\""
error-log: /var/log/h2o/h2o-error.log

expires: off
compress: on
file.dirlisting: off
file.send-compressed: on

file.index: [ 'index.html', 'index.php' ]

listen:
    port: 80
listen:
    port: 443
    ssl:
        cipher-suite: ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS
        cipher-preference: server
        dh-file: /etc/ssl/dhparams.pem
        certificate-file: /usr/local/etc/letsencrypt/live/eprove.net/fullchain.pem
        key-file: /usr/local/etc/letsencrypt/live/my.domain/privkey.pem

hosts:
    "*.my.domain":
        paths: &go_tls
            "/":
                redirect:
                    status: 301
                    url: https://my.domain/
    "my.domain:80":
        paths: *go_tls
    "my.domain:443":
        header.add: "Strict-Transport-Security: max-age=15768000; includeSubDomains; preload"
        paths:
            "/dns-query":
               mruby.handler-file: /usr/local/etc/h2o/h2odoh.rb

L'exception est le gestionnaire d'URL /dns-query qui est en fait notre serveur DNS-over-HTTPS, écrit en mruby et appelé via l'option de gestionnaire mruby.handler-file.

root@beta:/usr/local/etc/h2o # cat h2odoh.rb
# H2O HTTP/2 serveur web en tant que service DNS-over-HTTP
# v.20190908 (c)2018-2019 Max Kostikov https://kostikov.co e-mail : max@kostikov.co

proc {|env|
    if env['HTTP_ACCEPT'] == "application/dns-message"
        case env['REQUEST_METHOD']
            when "GET"
                req = env['QUERY_STRING'].gsub(/^dns=/,'')
                # décodage base64URL
                req = req.tr("-_", "+/")
                if !req.end_with?("=") && req.length % 4 != 0
                    req = req.ljust((req.length + 3) & ~3, "=")
                end
                req = req.unpack1("m")
            when "POST"
                req = env['rack.input'].read
            else
                req = ""
        end
        if req.empty?
            [400, { 'content-type' => 'text/plain' }, [ "Demande incorrecte" ]]
        else
            # --- interroger le serveur DNS
            sock = UDPSocket.new
            sock.connect("localhost", 53)
            sock.send(req, 0)
            str = sock.recv(4096)
            sock.close
            # --- trouver le TTL le plus bas dans la réponse
            nans = str[6, 2].unpack1('n') # nombre de réponses
            if nans > 0 # pas d'échec DNS
                shift = 12
                ttl = 0
                while nans > 0
                    # traiter la compression du nom de domaine
                    if str[shift].unpack1("C")  curttl
                        ttl = curttl
                    end
                    nans -= 1
                 end
                 cc = 'max-age=' + ttl.to_s
            else
                 cc = 'no-cache'
            end
            [200, { 'content-type' => 'application/dns-message', 'content-length' => str.size, 'cache-control' => cc }, [ str ] ]
        end
    else
        [415, { 'content-type' => 'text/plain' }, [ "Type de média non pris en charge" ]]
    end
}

Veuillez noter que le traitement des paquets DNS est assuré par le serveur de cache local, dans ce cas Unbound fourni avec FreeBSD. D'un point de vue sécurité, c'est la solution optimale. Cependant, rien n'empêche de le remplacer localhost par l'adresse d'un autre DNS que vous envisagez d'utiliser.

root@beta:/usr/local/etc/h2o # version local-unbound
Usage :  local-unbound [options]
        démarrer le démon unbound résolveur DNS.
-h      cette aide
-c fichier config à lire au lieu de /var/unbound/unbound.conf
        le format de fichier est décrit dans unbound.conf(5).
-d      ne pas se détacher en arrière-plan.
-p      ne pas créer de fichier pid.
-v      verbeux (plusieurs fois pour augmenter la verbosité)
Version 1.8.1
librairies liées : mini-event interne (il utilise select), OpenSSL 1.1.1a-freebsd 20 Nov 2018
modules liés : dns64 respip validateur itérateur
Sous licence BSD, voir LICENSE dans le paquet source pour plus de détails.
Signaler les bugs à unbound-bugs@nlnetlabs.nl
root@eprove:/usr/local/etc/h2o # sockstat -46 | grep unbound
unbound  local-unbo 69749 3  udp6   ::1:53                *:*
unbound  local-unbo 69749 4  tcp6   ::1:53                *:*
unbound  local-unbo 69749 5  udp4   127.0.0.1:53          *:*
unbound  local-unbo 69749 6  tcp4   127.0.0.1:53          *:*

Il ne reste plus qu'à redémarrer H2O et voir ce que cela a donné.

root@beta:/usr/local/etc/h2o # service h2o restart
Arrêt de h2o.
Attente des PIDS : 69871.
Démarrage de h2o.
start_server (pid:70532) démarrage maintenant...

4. Test

Nous allons donc vérifier les résultats en envoyant de nouveau une requête d'essai et en observant le trafic réseau à l'aide de l'outil tcpdump.

root@beta/usr/local/etc/h2o # curl -H 'accept: application/dns-message' 'https://my.domain/dns-query?dns=q80BAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE'
Avertissement : La sortie binaire peut désorganiser votre terminal. Utilisez "--output -" pour demander
Avertissement : curl de l'afficher sur votre terminal de toute façon, ou envisagez "--output
Avertissement : " pour enregistrer dans un fichier.
...
root@beta:~ # tcpdump -n -i lo0 udp port 53 -xx -XX -vv
tcpdump: écoute sur lo0, type de lien NULL (boucle BSD), taille de capture 262144 octets
16:32:40.420831 IP (tos 0x0, ttl 64, id 37575, offset 0, flags [none], proto UDP (17), longueur 57, mauvaise somme de contrôle 0 (->e9ea)!)
    127.0.0.1.21070 > 127.0.0.1.53 : [mauvaise somme de contrôle udp 0xfe38 -> 0x33e3!] 43981+ A? example.com. (29)
        0x0000:  0200 0000 4500 0039 92c7 0000 4011 0000  ....E..9....@...
        0x0010:  7f00 0001 7f00 0001 524e 0035 0025 fe38  ........RN.5.%.8
        0x0020:  abcd 0100 0001 0000 0000 0000 0765 7861  .............exa
        0x0030:  6d70 6c65 0363 6f6d 0000 0100 01         mple.com.....
16:32:40.796507 IP (tos 0x0, ttl 64, id 37590, offset 0, flags [none], proto UDP (17), longueur 73, mauvaise somme de contrôle 0 (->e9cb)!)
    127.0.0.1.53 > 127.0.0.1.21070 : [mauvaise somme de contrôle udp 0xfe48 -> 0x43fa!] 43981 q: A? example.com. 1/0/0 example.com. A 93.184.216.34 (45)
        0x0000:  0200 0000 4500 0049 92d6 0000 4011 0000  ....E..I....@...
        0x0010:  7f00 0001 7f00 0001 0035 524e 0035 fe48  .........5RN.5.H
        0x0020:  abcd 8180 0001 0001 0000 0000 0765 7861  .............exa
        0x0030:  6d70 6c65 0363 6f6d 0000 0100 01c0 0c00  mple.com........
        0x0040:  0100 0100 0151 8000 045d b8d8 22         .....Q...].."
^C
2 paquets capturés
23 paquets reçus par le filtre
0 paquets supprimés par le noyau

Il est visible dans la sortie comment la requête de résolution d'adresse example.com a été reçue et traitée avec succès par le serveur DNS.

Il ne reste plus qu'à activer notre serveur dans le navigateur Firefox. Pour cela, il faudra modifier quelques paramètres dans la page de configuration about:config.

Mettons en place notre serveur DNS-over-HTTPS

Tout d'abord, voici l'adresse de notre API par laquelle le navigateur fera des requêtes d'informations DNS dans network.trr.uri. Il est également conseillé d'indiquer l'adresse IP du domaine de cette URL pour une résolution sécurisée vers l'IP par le navigateur lui-même sans avoir à consulter le DNS dans network.trr.bootstrapAddress. Enfin, le paramètre proprement dit network.trr.mode qui active l'utilisation de DoH. Mettre la valeur sur « 3 » obligera le navigateur à utiliser exclusivement DNS-over-HTTPS pour la résolution des noms, tandis qu'une valeur plus fiable et sécurisée de « 2 » privilégiera DoH en laissant l'accès standard au DNS comme option de secours.

5. PROFIT!

L'article a été utile ? Alors n'hésitez pas à soutenir financièrement via le formulaire de don (ci-dessous).

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster