Blog de Shaft - Articles complets
https://www.shaftinc.fr/
https://www.shaftinc.fr/favicon.png
2022-09-06T21:50:05+02:00
Shaft
john+blog@shaftinc.fr
<p>© Shaft, 2019. Sauf mention contraire, les contenus sont sous licence <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.fr">CC BY-NC-SA</a>.</p>
Premier anniversaire de dns.shaftinc.fr
2022-08-23T20:05:00+02:00
2023-09-07T20:21:34+02:00
https://www.shaftinc.fr/dns-shaftinc-1an.html
<h2>🥳 et 🎂</h2>
<p>Il y a tout juste un an, le 23 août 2021, j’ouvrais au public un service de résolveur DNS-sur-TLS (DoT) et DNS-sur-HTTPS (DoH), le bien nommé <code>dns.shaftinc.fr</code>, après des mois de <s>procrastin</s> bêta-tests intensifs. L'occasion de faire un petit bilan.</p>
<h3>Quelques statisques</h3>
<p>En premier lieu, l'usage. Le service est relativement peu utilisé, avec en moyenne 0,378 requêtes/s reçus en moyenne sur l'année, ce qui permet d'estimer sommairement à environ 12 millions le nombre de requêtes. Le pic est à 17,01 req/s. Côté protocole, DoH a d'abord été proéminent puis supplanté par DoT.</p>
<img src="images/dnsshaftinc-1an-repart-proto.png" alt="">
<p>(Le trou dans les statistiques en août 2022 vient d'un problème sur le réseau de la machine de supervision.)</p>
<p>Avec cette utilisation peu intensive, on se retrouve logiquement avec un cache peu alimenté et donc une majorité de requêtes nécessitant d'aller chercher les réponses dans le Grand Internet. Détail intéressant : plus DoH est utilisé, plus le cache est solicité. On peut en déduire que les personnes utilisant DoT ont des résolveurs récursifs de leurs côtés afin de profiter également d'un cache.</p>
<img src="images/dnsshaftinc-1an-repart-cache.png" alt="">
<p>Le cache configuré pour <code>dnsdist</code> (pour rappel, le logiciel qui répond à vos requêtes) peut contenir 100 000 entrées, il y a de la marge.</p>
<img src="images/dnsshaftinc-1an-cache-entries.png" alt="">
<h3>Pannes & évolutions</h3>
<p>Durant l'année écoulée, quelques pannes à déplorer. La première dans les jours qui ont suivi l'ouverture du service et qui était due à une erreur de configuration réseau de ma part. Cela a entrainé une coupure de service d'une douzaine de minutes. La seconde panne, le 23 juin dernier, fut causée par une maintenance de l'hébergeur induite par <a href="https://status.scaleway.com/incidents/fpsczq5hc459">un routeur défectueux en amont du serveur</a>. La perte de connectivité fut d'environ 45 minutes.</p>
<p>Pour le reste, des interruptions programmées du service ont eût lieu, sans impact (l'avantage d'être un couche-tard), toute l'année afin de mettre à jour la machine ou de faire évoluer la configuration du service.</p>
<p>En parlant de configuration du service, elle a évoluée au gré des nouvelles versions des logiciels utilisés. 2 évolutions importantes :</p>
<ul>
<li>En septembre 2021, la version 1.6.0 de <code>dnsdist</code> apportait le traitement non-séquentielle des requêtes (OOOR dans le jargon).</li>
<li>Fin janvier 2022, la version 1.7.0 de <code>dnsdist</code> apportait le remplissage EDNS(0) afin d'améliorer la protection de la vie privée.</li>
</ul>
<h3>Le turfu</h3>
<p>Et pour l'année à venir ?</p>
<p>A priori peu de nouveautés à prévoir pour les utilisateur·trice·s du service. Les grandes évolutions devraient être l'arrivée, un jour, de DNS-sur-QUIC (DoQ) et DNS-sur-HTTP/3 dans les logiciels, mais tout porte à croire que ce ça ne sera pas disponible d'ici août 2023. <code>dnsdist</code> sera peut-être compatible DoQ et DoH/3 (les travaux sont en cours), mais pas sûr que cela soit facilement utilisable dans Debian. Par facilement, j'entends par là directement exploitable éventuellement en installant le paquet qui va bien et avec 2 lignes dans un fichier de configuration, sans avoir à récupérer et compiler <a href="https://github.com/quictls/openssl/">une version forké d'OpenSSL</a> ou une autre crémerie cryptographique comme le BoringSSL de Google).</p>
<p>En revanche, côté opérateur réseau les choses changent : <code>dns.shaftinc.fr</code> n'est accessible qu'en IPv6. Sur le réseau fixe, <a href="https://www.arcep.fr/fileadmin/reprise/observatoire/ipv6/Arcep_Barometre_2021_de_la_transition_vers_IPv6.pdf">le déploiement continue son bonhomme de chemin</a> (sauf chez SFR). Sur le réseau mobile, les FAI étaient frileux jusqu'à récemment. C'était sans compter sur l'ARCEP qui a fait d'IPv6 <a href="https://www.arcep.fr/uploads/tx_gsavis/19-1386.pdf">une obligation pour pouvoir prétendre à des fréquences 5G</a>. Par conséquent, il devient possible d'avoir presque en permanence de l'IPv6 sur son ordiphone, même en dehors de Maçon Télécom (c'est mon cas chez Free Mobile depuis quelques mois par exemple). Il reste une bonne marge de progression, mais il devient donc plus aisé d'utiliser <code>dns.shaftinc.fr</code> depuis son mouchard portatif.</p>
<p>En attendant, <a href="dns-shaftinc.html">n'hésitez pas à utiliser le service</a> (il se tourne les pouces, aidez le à bosser 😬) et à suivre les annonces (soit via <a href="announce-dns.atom">un flux de syndication dédié</a>, soit via <a href="https://mamot.fr/@dns_shaftinc">le Fédivers</a>) !</p>
DNS-over-TLS avec Pi-hole®
2022-01-29T23:55:00+01:00
2022-01-30T22:10:10+01:00
https://www.shaftinc.fr/pihole-dot.html
<h2>Trou Crypte</h2>
<p><a href="https://pi-hole.net/">Pi-hole® est un système de blocage de publicités</a> reposant sur le DNS relativement populaire chez les <i>geeks</i>. De par sa conception, reposant en grande partie sur <a href="https://thekelleys.org.uk/dnsmasq/doc.html">le logiciel dnsmasq</a>, il n'est malheureusement pas capable d'utiliser DNS-over-TLS (DoT) afin de chiffrer le trafic vers le résolveur qu'il utilise. Il y a <a href="https://docs.pi-hole.net/guides/dns/cloudflared/">une méthode pour utiliser DNS-over-HTTPS</a> (DoH), reposant sur le proxy <code>cloudflared</code> de CloudFlare mais son installation est longue, nécessitant d'installer des logiciels depuis GitHub et de faire plein de trucs chiant d'admin-sys. Voyons voir une méthode plus simple, basée sur des logiciels répandus, au choix Unbound ou Stubby (d'autres sont possibles comme Knot Resolver), plus simples à installer et à configurer. Pour le reste de l'article, je considère que Pi-hole® est déjà configuré sur votre machine. Je ne couvrirai donc pas son installation.</p>
<h3>Choix du logiciel</h3>
<p>On s'aventure ici dans le royaume des goûts et des couleurs. Unbound ou Stubby ont chacuns leurs avantages et désavantages. Si vous n'avez pas accès à une version récente d'Unbound (1.13.0 minimum), choisissez Stubby. Les performances d'Unbound sont catastrophiques en TLS (et TCP) avant cette version (en gros Unbound fermait la connexion via un RST après chaque requête). Chez Debian, Bullseye propose la version 1.13.1.</p>
<h3>Unbound</h3>
<p>Commençons par regarder comment configurer Unbound. Comme d'habitude sous Debian & dérivé, on crée un fichier dans <code>/etc/unbound/unbound.conf.d/</code>. Cela donne :</p>
<pre><code> server:
interface: 127.0.0.1@5335
do-not-query-localhost: no
tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt
forward-zone:
name: "."
forward-addr: 2001:bc8:2c86:853::853@853#dns.shaftinc.fr
forward-tls-upstream: yes</code></pre>
<p>Le port est bien sûr pris au hasard. Le format de l'adresse renseignée dans <code>forward-addr</code> est un peu bizarre et mérite d'être explicité. La syntaxe est <code>ip-du-résolveur-DoT@port-tls#nom-de-domaine</code>. Le nom de domaine renseigné étant celui qui doit être présent dans le certificat fourni par le serveur à la connexion. Unbound l'utilise pour authentifier le serveur (méthode <q>ADN + IP</q> <a href="https://www.bortzmeyer.org/8310.html">dans le RFC 8310</a>).</p>
<h3>Stubby</h3>
<p>Je reprends ici <a href="https://www.shaftinc.fr/dns-shaftinc.html#dotlinux">le guide de configuration de <code>dns.shaftinc.fr</code></a>. Il faut éditer <code>/etc/stubby/stubby.yml</code>. Dès l'installation, quelques serveurs DoT sont déjà configurés dans la section <code>DEFAULT UPSTREAMS</code>. Commenter l'ensemble des entrées (à moins que vous ne souhaitez utiliser un des résolveurs proposés) et mettre les informations relatives au résolveur DoT de votre choix pour une authentication stricte. Attention ceci dit, Stubby utilise <a href="https://fr.wikipedia.org/wiki/YAML">la syntaxe YAML</a>, il faut donc respecter l'indentation particulière (2 espaces ici) :</p>
<pre><code> upstream_recursive_servers:
- address_data: 2001:bc8:2c86:853::853
tls_auth_name: "dns.shaftinc.fr"
tls_pubkey_pinset:
- digest: "sha256"
value: ilee9nHBVT0DVWER1VDA+0NCaYd25zVvP0C1Jb4gCIc=</code></pre>
<p>Ne reste plus qu'à lui donner le port d'écoute d'où viendront les requêtes:</p>
<pre><code> listen_addresses:
- 127.0.0.1@5335</code></pre>
<p>(La ligne <code>- 0::1</code> n'est pas vraiment nécessaire et peut dégager)</p>
<h3>Pi-hole®</h3>
<p>Une fois Unbound ou Stubby configuré et le service du logiciel relancé, on teste que tout fonctionne bien :</p>
<pre>
$ dig @127.0.0.1 -p 5335 framapiaf.org
; <<>> DiG 9.17.21-1-Debian <<>> @127.0.0.1 -p 5335 framapiaf.org
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 34069
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;framapiaf.org. IN A
;; ANSWER SECTION:
framapiaf.org. 10800 IN A 88.99.213.22
;; Query time: 123 msec
;; SERVER: 127.0.0.1#5335(127.0.0.1) (UDP)
;; WHEN: Sat Jan 29 23:09:48 CET 2022
;; MSG SIZE rcvd: 58</pre>
<p>Parfait, on a donc un service local transmettant ses requêtes à un résolveur DoT. Ne reste plus qu'à configurer Pi-hole® pour lui dire de l'utiliser.</p>
<p>Depuis l'interface Web de ce dernier, se rendre dans l'onglet <q>DNS</q> des paramètres. On doit y trouver la configuration renseignée à l'installation :</p>
<img src="images/pihole-dot-1.png" alt="Capture d'écran de Pi-hole montrant les paramètres DNS. Un seul serveur personnalisé est renseigné, il a pour adresse 192.168.0.4">
<p>Décocher les serveurs pré-configurés (Google, OpenDNS, Quad9...) si certains le sont et rentrer <code>127.0.0.1#5335</code> comme seul et unique serveur personnalisé :</p>
<img src="images/pihole-dot-2.png" alt="La partie de l'interface permettant de renseiger un serveur personnalisé. L'adresse 127.0.0.1#5335 est renseignée">
<p>Au passage, la validation DNSSEC n'est (toujours) pas activée par défaut, donc penser à vérifier que la case correspondante est cochée dans les paramètres avancés se trouvant en dessous. Sauvegarder et se rendre dans le tableau de bord. On doit voir le nouveau serveur configuré apparaître dans les <q>Upstream servers</q> (et à terme supplanter le ou les anciens serveurs configurés) :</p>
<img src="images/pihole-dot-3.png" alt="Détail du tableau de bord. On voit notamment un camenbert montrant que le serveur 127.0.0.1#5335 répond désormais aux requêtes">
<p>Et voilà un Pi-hole® qui chiffre désormais son trafic sortant.</p>
Documentation technique du service dns.shaftinc.fr
2021-08-23T09:00:00+02:00
2023-09-07T20:21:24+02:00
https://www.shaftinc.fr/dns-shaftinc-serveur.html
<h2>Everything-over-TLS</h2>
<p><b>Mise à jour du 06/07/2023 :</b> Suppression d'un paramètre inutile dans la configuration DoH (<code>provider</code>).</p>
<p><b>Mise à jour du 30/01/2022 :</b> Prise en compte de la version 1.7.0 de dnsdist (modification de la configuration de <code>webserver</code> et ajout de l'addendum sur le remplissage EDNS).</p>
<p><b>Mise à jour du 12/09/2021 :</b> Prise en compte de la version 1.6.0 de dnsdist (ajout de l'option <code>maxInFlight</code>).</p>
<p>Après des mois de <s>procrastination</s> bêta-tests privés, je lance enfin mon résolveur DNS sur TLS (DoT) et DNS sur HTTPS (DoH) public : <code>dns.shaftinc.fr</code>. Pour la politique suivie par ce résolveur et la configuration côté client, <a href="dns-shaftinc.html">voir la page dédiée à ces questions</a>. Ce billet traite de la configuration du serveur, au cas où des gens veulent également se lancer dans l'aventure. Attention, billet long et technique.</p>
<p><code>dns.shaftinc.fr</code> utilise 2 logiciels : <a href="https://nlnetlabs.nl/projects/unbound/about/">Unbound</a> et <a href="https://dnsdist.org/">dnsdist</a>. dnsdist est un répartiteur de charge pour serveurs DNS avec la particularité de gérer DoH et DoT. Le but est donc de l'installer sur la même machine qu'Unbound et de le mettre devant : Unbound n'écoutera que localement et dnsdist, lui, sera ouvert au public. Il est bien évidemment possible d'utiliser un autre résolveur (Knot Resolver, PowerDNS Recursor...) ou bien d'utiliser un résolveur distant (sous votre contrôle de préférence). Le principe général étant posé, voyons comment configurer le tout. Commme d'habitude sur ce blog, les exemples sont valables pour Debian (Bullseye minimum, la version de dnsdist dans Buster est trop vieille) et ses dérivés.</p>
<h3>Unbound</h3>
<p>La configuration d'Unbound est dérivée de celle que j'utilise pour mon réseau personnel, en augmentant un peu la taille des caches. Elle n'entre finalement pas tellement dans le cadre de ce billet, il n'y a pas vraiment de paramètres spécifiques à passer pour le brancher à dnsdist. Pour les curieux·ses <a href="misc/unbound-shaft.conf">la configuration est consultable par ici</a>. À noter que la taille des caches est sans doute un peu grande, elle diminuera peut-être avec plus de recul.</p>
<h3>dnsdist</h3>
<p>La base de la configuration est tirée de <a href="https://www.bortzmeyer.org/doh-mon-resolveur.html">celle documentée par Stéphane Bortzmeyer</a>, complétée par la lecture de la (très complète) <a href="https://dnsdist.org/index_TOC.html">documentation du logiciel</a>. Le fichier qui nous intéresse est <code>/etc/dnsdist/dnsdist.conf</code>. Sous Debian, il doit comporter le paramètre <code>setSecurityPollSuffix("")</code> : il s'agit de la désactivation <a href="https://dnsdist.org/reference/config.html#security-polling">d'un contrôle de sécurité de la version</a> ajoutée par les personnes en charge de la maintenance du paquet chez Debian. Si ce paramètre revient à sa valeur par défaut (contôle actif), dnsdist plante au démarrage.</p>
<p>Commençons par créer le serveur DoT :</p>
<pre><code>-- DoT
addTLSLocal(
"[2001:bc8:2c86:853::853]:853",
"/etc/dnsdist/certificat.pem",
"/etc/dnsdist/cléprivée.key",
{
provider="openssl",
minTLSVersion="tls1.2",
ciphers="ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384",
ciphersTLS13="TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384",
ocspResponses={"/etc/dnsdist/dnsshaftinc.oscp"},
tcpFastOpenQueueSize=256,
maxInFlight=300
}
)
</code></pre>
<p>Les premiers paramètres sont explicites : on indique sur quelle adresse et port écouter puis on indique le chemin vers le certificat X.509 et la clé privée. Attention toutefois, sous Debian, dnsdist est très restreint en capacité afin de protéger le système (jetez un œil au fichier de service systemd), clé et certificat doivent donc être dans un répertoire accessible par le logiciel (<code>/etc/dnsdist</code>) et doivent être lisible pour l'utilisateur <code>_dnsdist</code>.</p>
<p>On passe ensuite les options que l'on souhaite. Les 4 premières sont explicites (à noter que dnsdist est agnostique et peut utiliser <a href="https://fr.wikipedia.org/wiki/GnuTLS">GnuTLS</a> comme fournisseur — au prix de quelques modifications de paramétrage), mais les 2 suivantes sont plus obscures et nécessitent quelques explications. Elles sont par ailleurs totalement optionnelles (et je ne suis pas sûr que tout les clients soient en mesures d'utiliser ces techniques). Enfin, la dernière option (<code>maxInFlight</code>), ajouté dans dnsdist 1.6.0, est requise pour faire proprement du DNS au-dessus de TCP (ce qui est le cas de DoT, étant donné que TLS se fait sur TCP).</p>
<p><code>ocspResponses</code> permet d'utiliser l'<i>OCSP Stapling</i> ou agafrage OCSP. Acronyme de <i>Online Certificate Status Protocol</i> et défini <a href="https://www.bortzmeyer.org/6960.html">dans le RFC 6960</a>, OCSP est une technique utilisée lors de l'établissement d'une connexion TLS et permettant à un client de vérifier le statut d'un certificat X.509 auprès de l'autorité de certification l'ayant signé. Cette dernière répond si le certificat en question est révoqué ou non. Le problème de ce protocole est qu'il ralenti l'établissement de la connexion TLS (il faut demander des choses à l'autorité de certification) et surtout pose un souci de vie privée (l'autorité connaît de fait les domaines que vous visitez). Pour palier à ces deux problèmes, on permet au serveur d'envoyer directement la réponse OCSP au client lors de la poignée de main TLS via l'agrafage (<i>stapling</i> donc) de cette dernière. Pour ce faire le serveur demande cette réponse à l'autorité de certification, cette dernière nous en donne une valable en général quelques jours et signée cryptographiquement afin d'éviter les problèmes, et il ne reste plus qu'à la fournir au client. Les serveurs Web les plus courants (Nginx & Apache) ont <a href="https://www.octopuce.fr/accelerer-votre-ssl-tls-avec-ocsp-stapling/">des mécanismes pour faire tout cela automatiquement</a>, ce n'est pas le cas de dnsdist, ce qui demande quelques manipulations.</p>
<p>La technique suivante est reprise de <a href="https://dnsdist.org/advanced/ocsp-stapling.html">la documentation de dnsdist</a>. Dans un premier temps, il faut récupérer l'URL du serveur OCSP de l'autorité de certification. Ce dernier est présent dans le certificat et on le récupère avec OpenSSL :<p>
<pre>
# openssl x509 -noout -ocsp_uri -in /etc/dnsdist/certificat.pem
</pre>
<p>Avec une autorité comme Let's Encrypt, la réponse devrait-être quelque chose comme <code>http://r3.o.lencr.org</code>. Ne reste plus qu'à interroger ladite autorité et écrire sa réponse dans un fichier, toujours avec OpenSSL :</p>
<pre>
# openssl ocsp -no_nonce -issuer /chemin/vers/certificat/autorité/certification -cert /etc/dnsdist/certificat.pem -text -url url/trouvé/ci/dessus -respout /etc/dnsdist/dnsshaftinc.oscp
</pre>
<p>Si vous utilisez <a href="https://packages.debian.org/bullseye/certbot">le client ACME certbot</a>, le certificat de l'autorité s'obtient en passant le paramètre <code>--chain-path /chemin/vers/...</code> lors de la création ou le renouvellement du certificat.</p>
<p>L'autorité doit répondre un gros pâté de ce type :</p>
<pre>
OCSP Request Data:
Version: 1 (0x0)
Requestor List:
...
OCSP Response Data:
OCSP Response Status: <b>successful (0x0)</b>
Response Type: Basic OCSP Response
Version: 1 (0x0)
Responder Id: C = US, O = Let's Encrypt, CN = R3
Produced At: Aug 10 22:45:00 2021 GMT
Responses:
Certificate ID:
Hash Algorithm: sha1
Issuer Name Hash: 48DA...
Issuer Key Hash: 142E...
Serial Number: 0415...
Cert Status: good
<b>This Update: Aug 10 22:00:00 2021 GMT
Next Update: Aug 17 22:00:00 2021 GMT</b>
Signature Algorithm: sha256WithRSAEncryption
8f:0a:...
Response verify OK
...
</pre>
<p>Dans la réponse nous avons bien le statut <code>0x0</code> indiquant un succès et l'on remarque que la réponse est valable une semaine. De ce que j'ai pu constater avec Let's Encrypt, elle est en réalité renouvellée tous les 3 jours. Dans tous les cas, il faut mettre à jour notre fichier automatiquement, sous peine de se retrouver avec une réponse périmée. Le plus simple est de faire un script et de le déclencher quotidiennement via la <code>crontab</code>. Cela donne :</p>
<pre><code>#!/bin/bash
ocspURL=$(openssl x509 -noout -ocsp_uri -in /etc/dnsdist/certificat.pem)
openssl ocsp -no_nonce -issuer /chemin/vers/certificat/autorité/certification -cert /etc/dnsdist/certificat.pem -text -url $ocspURL -respout /etc/dnsdist/dnsshaftinc.oscp
chown _dnsdist: /etc/dnsdist/dnsshaftinc.oscp
# On recharge le tout dans dnsdist
dnsdist -e "reloadAllCertificates()"</code></pre>
<p>Pour vérifier que l'ensemble fonctionne bien une fois en place, on peut utiliser OpenSSL :</p>
<pre>
$ openssl s_client -connect dns.shaftinc.fr:853 -status | grep OCSP
...
OCSP response:
OCSP Response Data:
OCSP Response Status: <b>successful (0x0)</b>
Response Type: Basic OCSP Response
</pre>
<p>Encore une fois, nous avons le statut <code>0x0</code>, tout fonctionne.</p>
<p>Attention à bien regénérer ce fichier à chaque changement de certificat, et à bien lancer la commande</p>
<pre>
dnsdist -e "reloadAllCertificates()"</pre>
<p>à chaque modification du certificat, de la clé privée ou du fichier OCSP.</p>
<p>Le deuxième option obscure était <code>tcpFastOpenQueueSize</code>. TCP Fast Open (ou TFO, <a href="https://www.bortzmeyer.org/7413.html">décrit dans le RFC 7413</a>) est une méthode permettant d'accélerer l'établissement d'une connexion TCP. Normalement, une connexion TCP s'établie via la fameuse triple poignée de main :</p>
<ul>
<li>Le client demande à se connecter (SYN)</li>
<li>Le serveur répond qu'il veut se connecter et acquiesce (SYN + ACK)</li>
<li>Le client acquiesce (ACK) et la connexion est établie</li>
</ul>
<p>C'est relativement lent, surtout qu'ensuite dans le cadre de DoH et DoT il faut établir la connexion TLS. Pour accélérer l'ensemble, TFO propose au client d'envoyer des données dès le premier paquet SYN. Pour faire simple, la première connexion à un serveur gérant TFO se fait toujours de la manière usuelle, mais si le client a indiqué connaître TFO (via une option dans le premier paquet SYN), alors le serveur fourni au passage un petit cookie qu'il a généré. Pour les connexions suivantes, le client n'a qu'à fournir ce cookie dans son premier paquet SYN et y mettre directement des données, dans le cas qui nous intéresse un <code>ClientHello</code> de TLS.<p>
<p>TFO est entièrement implémenté dans le noyau Linux depuis la version 3.16 et il est actif en tant que client depuis la même période a peu près (un peu avant, le noyau 3.16 apporte juste TFO pour IPv6). Il se configure via le paramètre <code>net.ipv4.tcp_fastopen</code>. Ce dernier accepte les valeurs suivantes :</p>
<ul>
<li>0 : désactivé</li>
<li>1 : mode client (valeur par défaut)</li>
<li>2 : mode serveur</li>
<li>3 : mode client & serveur</li>
</ul>
<p>Pour <code>dns.shaftinc.fr</code>, je suis parti sur le mode client & serveur. Pour l'activer, on passe la commande :</p>
<pre>
$ sudo sysctl -w net.ipv4.tcp_fastopen=3
</pre>
<p>On vérifie au besoin que c'est pris en compte :</p>
<pre>
$ sudo sysctl -a --pattern "fastopen"
net.ipv4.tcp_fastopen = 3
...
</pre>
<p>Le problème de la commande <code>sysctl</code> est que le changement de paramètre ne survivra pas au redémarrage de la machine. Pour palier à ça, on va créer un fichier <code>/etc/sysctl.d/10-tcp-fastopen.conf</code> (le numéro peut être différent) et y mettre tout simplement :</p>
<pre><code>net.ipv4.tcp_fastopen=3</code></pre>
<p>Pour revenir à notre paramètre <code>tcpFastOpenQueueSize</code>, il sert juste à dire à dnsdist de conserver les données pour 256 clients. J'avoue avoir choisi la valeur un peu au hasard, n'ayant pas de recul. Elle pourra peut-être évoluer.</p>
<p>Enfin le paramètre <code>maxInFlight</code>, ajout récent, permet à dnsdist de traiter les requêtes venant d'une même connexion TCP <a href="https://dnsdist.org/advanced/out-of-order.html">dans un ordre non-séquentiel</a>. C'est à dire que si un client envoie 2 requêtes R1 et R2 et que la réponse pour R2 arrive avant celle de R1, alors dnsdist est en mesure de l'envoyer en premier au client. C'est une <a href="https://www.rfc-editor.org/rfc/rfc7766.txt">recommandation du RFC 7766 (section 6.2.1.1)</a>. La valeur associée est le nombre de requêtes pouvant être traitées de la sorte simulanément sur une même connexion. La valeur de 300 est lié au paramètre <code>setMaxTCPQueriesPerConnection</code> (cf. infra).</p>
<p>Le serveur DoT est configuré, passons à DNS over HTTPS. Les paramètrages sont identiques, à quelques excpetions liées à HTTP :</p>
<pre><code>-- DoH
addDOHLocal(
"[2001:bc8:2c86:853::853]:443",
"/etc/dnsdist/certificat.pem",
"/etc/dnsdist/cléprivée.key",
{"/", "/about", "/politique", "/config"},
{
minTLSVersion="tls1.2",
ciphers="ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384",
ciphersTLS13="TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384",
ocspResponses={"/etc/dnsdist/dnsshaftinc.oscp"},
customResponseHeaders={
["Expect-CT"]="max-age=604800, enforce",
["Strict-Transport-Security"]="max-age=31536000",
["link"]="<https://www.shaftinc.fr/dns-shaftinc.html> rel=\"service-meta\"; type=\"text/html\""
},
tcpFastOpenQueueSize=256
}
)
-- Liens externes
helppages = {
newDOHResponseMapEntry("^/about$", 308, "https://www.shaftinc.fr/dns-shaftinc.html"),
newDOHResponseMapEntry("^/politique$", 308, "https://www.shaftinc.fr/dns-shaftinc.html#politique"),
newDOHResponseMapEntry("^/config$", 308, "https://www.shaftinc.fr/dns-shaftinc.html#configuration")
}
dohFE = getDOHFrontend(0)
dohFE:setResponsesMap(helppages)</code></pre>
<p>Le paramétrage est donc sensiblement similaire. Notons que l'on peut utiliser une clé privée et donc un certificat différents pour le serveur DoT. Les différences viennent de la ligne :
<pre><code>{"/", "/about", "/politique", "/config"},</code></pre>
<p>Le premier chemin est celui que les clients utiliseront pour interroger le serveur (si on avait mis par exemple "<code>/query</code>", l'URL du serveur aurait été <code>https://dns.shaftinc.fr/query</code>). Les 3 autres sont des pages d'aides, défini dans la section en dessous comme étant des redirections. À noter également l'ajout de quelques entêtes HTTP, notamment afin d'améliorer la sécurité (<code>Expect-CT</code> et <code>Strict-Transport-Security</code>).</p>
<p>On notera que l'option <code>maxInFlight</code> n'est pas présente pour DoH car le comportement permit par ce paramètre est actif par défaut avec DoH.</p>
<p>On ajoute ensuite les autorisations et limitations, reprises de la configuration de <a href="https://www.bortzmeyer.org/doh-mon-resolveur.html">Stéphane Bortzmeyer</a> (hormis pour la limitation de requêtes par seconde, montée à 200 :</p>
<pre><code>-- ACL
addACL("[::]/0")
-- Limitation à 200 QpS
addAction(MaxQPSIPRule(200), DropAction())
-- Limitations de connexions
setMaxUDPOutstanding(65535) -- Nombre maximum de requêtes en attente pour un résolveur
setMaxTCPClientThreads(30) -- Nombre maximum de fils d'exécution TCP (chacun pouvant traiter plusieurs clients)
setMaxTCPConnectionDuration(1800) -- Après trente minutes, on raccroche
setMaxTCPQueriesPerConnection(300) -- Après trois cents requêtes, on raccroche
setMaxTCPConnectionsPerClient(10) -- Dix connexions par client (élévé mais il faut penser à des choses comme le CG-NAT)
-- Cache
pc = newPacketCache(100000)
getPool(""):setCache(pc)</code></pre>
<p>Pour le cache, <a href="https://dnsdist.org/guides/cache.html">la documentation de dnsdist</a> précise que pour une machine dotée de 8 Gio de RAM et en considérant la taille moyenne d'une réponse à 512 octets, un cache de 1 000 000 d'entrées est une estimation raisonnable. La machine hébergeant <code>dns.shaftinc.fr</code> n'a que 4 Gio de RAM, fait tourner d'autres services consommateurs de mémoire (dont Unbound)... donc dans un premier temps, 100 000 entrées maximum dans le cache semble plus que correct.</p>
<p>On configure ensuite le serveur Web interne et la console. Ils serviront à sortir des statistiques ou bien passer des commandes à dnsdist via la console (pour recharger le certificat X.509 par exemple). Attention, la configuration du serveur Web change avec dnsdist 1.7.0 :</p>
<pre><code>-- Webserver (dndsit 1.7.0+)
webserver("127.0.0.1:8083")
setWebserverConfig({password=hashPassword("super phrase de passe compliquée"), apiKey=hashPassword("clé pour l'API compliquée aussi")})
-- Console
controlSocket('[::1]:5199')
setKey("phrase de passe super balaise")</code></pre>
<p>Attention à bien suivre <a href="https://dnsdist.org/guides/console.html">la procédure pour générer la clé de la console</a>.</p>
<p>Pour les versions de dnsdist inférieures à la 1.7.0, le serveur Web se configure ainsi :</p>
<pre><code>webserver("127.0.0.1:8083", "super phrase de passe compliquée", "clé pour l'API compliquée aussi")</code></pre>
<p>Tout est presque en place, ne manque plus qu'à donner à dnsdist un ou plusieurs résolveurs avec qui discuter. Pour <code>dns.shaftinc.fr</code>, dnsdist n'est connecté qu'avec le Unbound local, mais le logiciel étant un répartiteur de charge, il est possible d'en ajouter plusieurs et appliquer une politique de répartition. En n'utilisant qu'un serveur local la configuration est au final simple :</p>
<pre><code>newServer({address="[::1]:53", useClientSubnet=false, maxInFlight=1000, name="Unbound"})</code></pre>
<p>On ajoute également l'option <code>maxInFlight</code>, pour que les requêtes entre dnsdist et Unbound soient également traitées dans un ordre non-séquentiel, quand la communication se fait via TCP, <a href="https://dnsdist.org/advanced/internal-design.html">ce qui est normalement le cas avec les requêtes venant de l'extérieur voyageant par TCP</a>, sachant qu'Unbound gère par défaut le traitement non-séquentiel depuis sa version 1.9.0.</p>
<h3>Supervision</h3>
<p>Pour superviser dnsdist, l'utilitaire <code>getdns_server_mon</code> présent dans le paquet <code>getdns-utils</code> permet de faire des tests pour DoT proposant une sortie conforme à ce qu'attendent Nagios & dérivés. Attention le logiciel à une syntaxe lourde. Pour tester le RTT par exemple :</p>
<pre>$ getdns_server_mon -M -K 'pin-sha256="ilee9nHBVT0DVWER1VDA+0NCaYd25zVvP0C1Jb4gCIc="' -S -T @2001:bc8:2c86:853::853#853~dns.shaftinc.fr rtt 2500,3000 . SOA
DNS SERVER OK - RTT lookup succeeded in 23ms
</pre>
<p>Détaillons un peu les paramètres de cette commande :</p>
<ul>
<li><code>-M</code> formatte la sortie afin qu'elle soit adaptée aux logiciels de supervision</li>
<li><code>-K</code> ou <code>--spki-pin</code> permet d'indiquer un épinglage SPKI, qui suit</li>
<li><code>-S</code> active le profil strict de DoT c'est à dire avec chiffrement et authentification du serveur DoT, d'où la présence de la SPKI dans la commande précédente et le nom de domaine du résolveur (ADN pour reprendre le vocable du <a href="https://www.rfc-editor.org/info/rfc8310">RFC 8310</a>)</li>
<li><code>-T</code> indique que le transport se fera avec TLS</li>
<li>le blob commençant par un arobase est l'<i>upstream</i>, la syntaxe est <code>@<ip>#<port_tls>~<ADN></code>. On peut complexifier cette syntaxe mais ce n'est pas utile ici</li>
<li>Dernier paramètre, le test à effectuer : ici la mesure du RTT d'une requête, avec les durées limites en millisecondes (avertissement et critque) et l'enregistrement à demander (ici le SOA de la racine)</li>
</ul>
<p>La commande pourrait être allégée, par exemple en n'utilisant un profil opportuniste et donc en authentifiant pas la connexion, mais cela perd en intéret. L'aide de <code>getdns_server_mon</code> détaille l'ensemble des tests possibles. Ceux qui me semblent utiles sont :</p>
<ul>
<li><code>lookup</code> : effectue la résolution d'un nom. Le paramètre <code>-E</code> qui permet de faire échouer un test en cas d'échec de la résolution (NXDOMAIN, REFUSED...) est particulièrement indiqué</li>
<li><code>rtt</code> : mesure du RTT, comme vu ci-dessus</li>
<li><code>tls-auth</code> : vérifie l'authentication TLS. N'est pas vraiment utile si le paramètre <code>-S</code> (profil d'authentification strict) est utilisé pour les autres tests</li>
</ul>
<p>Et de manière plus occasionnelle, les tests suivants, qui permettent notamment de vérifier que les résolveurs avec lesquelles dnsdist ont bien les fonctionnalités attendues :</p>
<ul>
<li><code>qname-min</code> : test que la minimisation de la question posée (RFC 7816) fonctionne bien</li>
<li><code>dnssec-validate</code> : vérifie que la validation DNSSEC fonctionne</li>
</ul>
<p>Voici une commande pour Icinga 2 utilisant <code>getdns_server_mon</code>. Elle comporte toutes les options à l'exception de <code>-D</code> (mode debug) et <code>-V</code> (affiche la version) :</p>
<pre><code>object CheckCommand "dot" {
import "plugin-check-command"
command = [ "/usr/bin/getdns_server_mon", "-M", "-v" ]
arguments += {
"-E" = {
description = "Fail on DNS error (NXDOMAIN, SERVFAIL)"
set_if = "$getdns_fail_dns_error$"
}
"-K" = {
description = "SPKI pin for TLS connections (can repeat)"
repeat_key = true
value = "$getdns_spki$"
}
"-S" = {
description = "Use strict profile (require authentication)"
set_if = "$getdns_strict_auth$"
}
"-T" = {
description = "Use TLS transport"
set_if = "$getdns_tls$"
}
"-t" = {
description = "Use TCP transport"
set_if = "$getdns_tcp$"
}
"-u" = {
description = "Use UDP transport"
set_if = "$getdns_udp$"
}
test = {
description = "Test to apply"
order = 98
required = true
skip_key = true
value = "$getdns_test$"
}
test_params = {
description = "Optionnal params for tests"
order = 99
skip_key = true
value = "$getdns_test_param$"
}
upstream = {
description = "@<ip>[%<scope_id>][@<port>][#<tls_port>][~<tls name>][^<tsig spec>]"
order = 97
required = true
skip_key = true
value = "$getdns_upstream$"
}
}
}</code></pre>
<p>Et on peut ensuite créer des services, voici celui effectuant le test <code>lookup</code> :</p>
<pre><code>object Service "DoT - Lookup" {
host_name = "dns.shaftinc.fr"
check_command = "dot"
check_timeout = 16s
vars.getdns_fail_dns_error = true
vars.getdns_spki = "pin-sha256=\"ilee9nHBVT0DVWER1VDA+0NCaYd25zVvP0C1Jb4gCIc=\""
vars.getdns_strict_auth = true
vars.getdns_test = "lookup"
vars.getdns_test_param = [ "d.nic.fr", "AAAA" ]
vars.getdns_tls = true
vars.getdns_upstream = "@2001:bc8:2c86:853::853#853~dns.shaftinc.fr"
}</code></pre>
<p>À noter que l'ensemble a été généré avec <a href="https://github.com/Icinga/icingaweb2-module-director/">le module Director d'Icinga 2</a> (et que je suis plutôt débutant pour tout ce qui touche à Icinga 🙂).</p>
<p>Pour la surveillance de DoH, j'utilise le plugin <code>check_doh</code> présenté dans le <a href="https://www.bortzmeyer.org/doh-mon-resolveur.html">billet de Stéphane Bortzmeyer</a>. Il faut aussi penser à surveiller la date d'expiration du certificat. Le plugin <code>check_ssl_cert</code> inclut dans le paquet <code>monitoring-plugins-contrib</code> fait parfaitement l'affaire.</p>
<p>Pour terminer cette section sur la surveillance/supervision, dnsdist est capable de <a href="https://dnsdist.org/guides/carbon.html">se brancher à Carbon</a> (non testé) ou <a href="https://dnsdist.org/advanced/snmp.html">parler SNMP</a> (non testé également). Par ailleurs, le webserver interne est capable de cracher de la statistique au format JSON en interrogeant notamment <code>/jsonstat?command=stats</code> et <code>/api/v1/servers/localhost</code> :</p>
<pre>
$ curl -s --header "X-API-Key: 8DjQ..." http://127.0.0.1:8083/jsonstat?command=stats | jq
{
"acl-drops": 0,
"cache-hits": 1076,
"cache-misses": 447,
"cpu-iowait": 130965,
"cpu-steal": 0,
...
}
$ curl -s --header "X-API-Key: 8DjQ..." http://127.0.0.1:8083/api/v1/servers/localhost | jq
"cache-hit-response-rules": [],
"daemon_type": "dnsdist",
"dohFrontends": [
{
"address": "[2001:bc8:2c86:853::853]:443",
"bad-requests": 2,
"error-responses": 0,
"get-queries": 327,
...
}
</pre>
<p>Il est par exemple possible de récupérer ce qui nous intéresse et le donner à manger à Munin notamment. Voici par exemple un graphe Munin montrant le nombre d'entrées dans le cache :</p>
<img src="images/dnsdist_munin_cache.png" alt="">
<p>Le script Munin que j'ai fabriqué est trop spécifique à mon installation (et un peu trop sale aussi) pour être partagé, mais si vous arrivez à manipuler du JSON avec <code>jq</code> ou les outils de votre langage préféré, cela ne devrait pas poser trop de soucis à faire. Encore une fois <a href="https://dnsdist.org/statistics.html">la documentation de dnsdist</a> est très bien faite.</p>
<h3>Addendum : Utilisation du remplissage EDNS</h3>
<p>Par défaut, la configuration décrite ci-dessus n'est pas la plus optimale en terme de protection de la vie privée. Il manque en effet un élément : le remplissage EDNS ou <i>EDNS(0) Padding</i> en anglais. Technique <a href="https://www.bortzmeyer.org/7830.html">normalisée dans le RFC 7830</a> (et complétée dans le RFC 8467).</p>
<p>TLS chiffre un canal de communication mais n'est pas pensé pour dissimuler la taille des paquets. Or cette information peut permettre à un attaquant de deviner une requête, même si elle est chifrée. S'il soupçonne que vous faites des requêtes pour le domaine <code>opposant-politique.example</code>, il n'a qu'à faire des requêtes lui-même et noter la taille de la réponse chiffrée. S'il voit des paquets de même taille dans les échanges entre votre client DNS et le résolveur, alors il saura que vous avez très probablement fait une requête pour ce domaine.</p>
<p>Pour parer à cela, le RFC 7830 introduit dans le DNS une technique classique en cryptographie : <a href="https://fr.wikipedia.org/wiki/Remplissage_(cryptographie)">le remplissage</a>. En l'occurence, via <a href="https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-11">une option EDNS</a>, client et résolveur vont remplir leurs requêtes de 0 afin d'en changer la taille. Le RFC 8467 documente plusieurs méthodes de remplissage (remplir de façon à avoir des paquets de taille fixe, remplir afin d'avoir des paquets de taille aléatoire...) et recommande le remplissage en blocs de taille fixe : le client remplit la question jusqu'à atteindre un multiple de 128 octets, le résolveur remplit la réponse jusqu'à atteindre un multiple de 468 octets.</p>
<p>Par défaut, il est fort probable qu'un client ajoute l'option de remplissage dans sa requête (Stubby et Firefox le font par défaut par exemple), mais dnsdist n'en fait rien : le remplissage est pensé pour TLS et surtout doit être ajouté à la réponse lors de la conception du paquet, c'est à dire que c'est le résolveur auquel dnsdist transmet la requête qui doit l'ajouter. Or, dnsdist communique en UDP ou en TCP avec le résolveur configuré via la fonction <code>newServer()</code>. On le voit si l'on ajoute explicitement du remplissage dans une question :</p>
<pre>
$ dig +tls +padding=128 @dns.shaftinc.fr www.shaftinc.fr AAAA
...
;; SERVER: 2001:bc8:2c86:853::853#853(dns.shaftinc.fr) (TLS)
;; WHEN: Sun Jan 30 16:36:42 CET 2022
;; MSG SIZE rcvd: 72
$ dig +https=/ +padding=128 @dns.shaftinc.fr framapiaf.org NS
...
;; SERVER: 2001:bc8:2c86:853::853#443(dns.shaftinc.fr) (HTTPS)
;; WHEN: Sun Jan 30 16:37:25 CET 2022
;; MSG SIZE rcvd: 118</pre>
<p>On constate bien que les deux réponses n'ont pas la même taille, quand bien même l'option de remplissage est ajouté à la requête (option <code>+padding</code> de dig).</p>
<p>En l'état permettre à dnsdist d'ajouter lui-même le remplissage est <a href="https://github.com/PowerDNS/pdns/issues/10018">un problème complexe et ouvert</a>. En revanche, dnsdist 1.7.0 ajoute la possibilité de communiquer avec le <i>backend</i> en utilisant DoT ou DoH, ce qui va permettre de contourner le problème : on configure dnsdist pour se servir de DoT pour discuter avec le Unbound configuré localement. EDNS(0) Padding a été ajouté à Unbound dans la version 1.13.1 et est configuré par défaut pour s'en servir (paramètre <code>pad-responses</code> à <i>yes</i> pour le cas qui nous intéresse).</p>
<p>Il va falloir en premier lieu modifier la configuration d'Unbound afin qu'il écoute localement sur le port 853. On commence par générer un certificat auto-signé :</p>
<pre>
# openssl ecparam -genkey -name secp256r1 | openssl ec -out /etc/unbound/unbound_server.key
read EC key
using curve name prime256v1 instead of secp256r1
writing EC key
# openssl req -new -x509 -days 3650 -key /etc/unbound/unbound_server.key -out /etc/unbound/unbound_server.pem
...
# chmod 400 /etc/unbound/unbound_server.key</pre>
<p>Puis on ajoute cela à la configuration d'Unbound :</p>
<pre><code>interface: ::1@853
tls-service-key: /etc/unbound/unbound_server.key
tls-service-pem: /etc/unbound/unbound_server.pem</code></pre>
<p>On a au final une configuration <a href="misc/unbound-shaft.conf">ressemblant à ceci</a>.</p>
<p>Ne reste plus qu'à modifier la fonction <code>newServer()</code> de la configuration de dnsdist :</p>
<pre><code>newServer({address="[::1]:853", tls="openssl", validateCertificates=false, useClientSubnet=false, maxInFlight=1000, name="Unbound"})</code></pre>
<p>Le certificat étant auto-signé, on désactive logiquement sa validation. Une fois l'ensemble redémarré, on peut refaire les tests précédents :</p>
<pre>
$ dig +tls +padding=128 @dns.shaftinc.fr www.shaftinc.fr AAAA
...
PAD: (402 bytes)
...
;; SERVER: 2001:bc8:2c86:853::853#853(dns.shaftinc.fr) (TLS)
;; WHEN: Sun Jan 30 17:40:16 CET 2022
;; MSG SIZE rcvd: 468
$ dig +https=/ +padding=128 @dns.shaftinc.fr framapiaf.org NS
...
PAD: (386 bytes)
...
;; SERVER: 2001:bc8:2c86:853::853#443(dns.shaftinc.fr) (HTTPS)
;; WHEN: Sun Jan 30 16:40:31 CET 2022
;; MSG SIZE rcvd: 468</pre>
<p>On voit que 402 et 386 octets ont été ajoutés aux réponses afin d'obtenir une taille de 468 octets, tout fonctionne correctement.</p>
Une zone locale home.arpa signée avec DNSSEC
2020-11-14T16:00:00+01:00
2022-08-11T15:50:01+02:00
https://www.shaftinc.fr/home-arpa-dnssec.html
<h2>Pour s'auto-authentifier</h2>
<p>Ce billet fait suite à une <a href="https://mamot.fr/@e1672/105205389855263238">question posée sur le fédivers</a> : comment profiter d'une zone <code>home.arpa.</code> à soi et <a href="https://www.bortzmeyer.org/4255.html">d'enregistrements SSHFP</a> pour faciliter la gestion de SSH sur le réseau local. Question non triviale car SSHFP nécessite DNSSEC. Voyons donc comment signer une zone locale. La technique va impliquer 2 outils supplémentaires en plus d'Unbound : NSD pour servir la zone locale et ldns pour gérer DNSSEC (génération de clés, signature de la zone). Pour ce billet, je ne rentrerai pas dans les détails de DNSSEC (ZSK, KSK, DS...), une connaissance préalable de la chose est donc préférable.</p>
<h3>Fichier de zone</h3>
<p>Donc, ce n'est plus Unbound qui va servir la zone locale, mais NSD, un vrai serveur faisant autorité. La première étape va donc être de <q>nettoyer</q> la configuration Unbound pour y enlever les paramètres <code>local-zone</code>, <code>local-data</code>,... (si présents) et installer NSD. On édite un fichier de configuration dans <code>/etc/nsd/nsd.conf.d/</code></p>
<pre><code> server:
interface: 127.0.0.1:53530 # On n'écoute que localement et surtout pas sur le port 53
zone:
name: "home.arpa"
zonefile: "/etc/nsd/nsd.conf.d/home.arpa.zone.signed"</code></pre>
<p>Le fichier de zone n'existe pas encore, fabriquons le. Avant d'avoir un fichier de zone signé, il faut un fichier... sans signatures. Il devrait ressembler à quelque chose comme cela :</p>
<pre><code> $TTL 86400
$ORIGIN home.arpa.
@ 14400 IN SOA ns.home.arpa. hostmaster.home.arpa. (
1 ; Serial
4H ; refresh after 4 hour
1H ; retry after 1 hour
3W ; expire after 3 weeks
1H) ; minimun TTL of 1 hour
;IPv4
router IN A 192.168.0.1
cthulhu IN A 192.168.0.2
eth.azathoth IN A 192.168.0.3
wifi.azathoth IN A 192.168.0.4
;IPv6
router IN AAAA 2001:db8:1a1a:ca11::1
cthulhu IN AAAA 2001:db8:1a1a:ca11:0f:c700:100:1926
;Divers
azathoth IN TXT "Une adresse par interface"</code></pre>
<p>Quelques remarques :</p>
<ul>
<li>Les commentaires sont signalés par un point-virgule</li>
<li>Si on n'indique pas de TTL, NSD utilisera celui indiqué par défaut via <code>$TTL</code></li>
<li>Vous pouvez mettre un peu n'importe quoi dans le <code>SOA</code> pour le <code>MNAME</code> (ici <code>ns.home.arpa.</code>) et le <code>RNAME</code> (ici <code>hostmaster.home.arpa.</code>)</li>
<li>Bien penser au point final pour <code>home.arpa.</code>. Si l'on indique seulement <code>home.arpa</code>, NSD y ajoutera <code>$ORIGIN</code> (on aura donc <code>home.arpa.home.arpa.</code>)</li>
<li>Le numéro de série n'a pas trop d'importance (il n'y aura pas de serveurs secondaires demandant de la donnée fraîche). On peut quand même s'amuser à l'incrémenter à chaque modification de la zone, pour prendre l'habitude de le faire (ou écrire un script qui le fait)</li>
<li>Pour l'instant, NSD ne fonctionne pas, on le relancera plus tard</li>
</ul>
<h3>DNSSEC</h3>
<p>C'est donc ldns qui va gérer la partie DNSSEC. C'est une boîte à outils très utile, notamment pour une gestion artisanale de ses zones signées (pour industrialiser, on utiliserait OpenDNSSEC). Sous Debian, il s'agit du paquet ldnsutils. On l'installe :</p>
<pre>
# apt-get install ldns-utils</pre>
<p>Les 2 outils qui nous intéressent sont <code>ldns-keygen</code> et <code>ldns-signzone</code>. Les noms ont l'avantage d'être explicites.</p>
<h4>Génération des clés</h4>
<p>Il faut générer une ZSK et une KSK. Commençons par la ZSK. Avant cela, il faut choisir un algorithme. ldns est dépendant d'OpenSSL de ce point de vue là. Pour connaître la liste supportée par <code>ldns</code>, on entre :</p>
<pre>
# ldns-keygen -a list
Possible algorithms:
RSAMD5
RSASHA1
RSASHA1-NSEC3-SHA1
RSASHA256
RSASHA512
ECDSAP256SHA256
ECDSAP384SHA384
ED25519
ED448
DSA
DSA-NSEC3-SHA1
hmac-md5.sig-alg.reg.int
hmac-sha1
hmac-sha256
hmac-sha224
hmac-sha384
hmac-sha512</pre>
<p>Pour ce billet, on prendra un algorithme répandu : ECDSAP256SHA256. Une précision importante : ldns n'a pas d'option pour indiquer le répertoire où écrire les clés générées, il le fait automatiquement dans le répertoire courant. Pensez à bien se positionner avant la génération. Cette étape génera jusqu'à 3 fichiers pour chaque clé :</p>
<ul>
<li>K<domaine>+<alg>+<id>.key : la clé publique au format d'un enregistrement DNS (<code>example.com. IN DNSKEY...</code>)</li>
<li>K<domaine>+<alg>+<id>.private : la clé privée. On a beau être sur une zone locale, pensez à protéger ce fichier tout de même</li>
<li>K<domaine>+<alg>+<id>.ds : l'enregistrement DS que l'on met normalement dans la zone parente, généré uniquement pour la KSK</li>
</ul>
<p>Générons donc notre ZSK</p>
<pre>
# ldns-keygen -a ECDSAP256SHA256 home.arpa
Khome.arpa.+013+53551</pre>
<p>Puis la KSK. Même commande, on ajoute uniquement le paramètre <code>-k</code></p>
<pre>
# ldns-keygen -k -a ECDSAP256SHA256 home.arpa
Khome.arpa.+013+18062</pre>
<p>On vérifie que l'on a tout :</p>
<pre>
# ls Khome.arpa.+013+*
Khome.arpa.+013+18062.ds Khome.arpa.+013+18062.key Khome.arpa.+013+18062.private Khome.arpa.+013+53551.key Khome.arpa.+013+53551.private</pre>
<p>Nous avons donc une clé avec l'id 18062</p>
<pre>
cat Khome.arpa.+013+18062.key
home.arpa. IN DNSKEY 257 3 13 bOcYev/grRO3AOc/mtc+VJNIsfpzTP2da1LhUreOrMUViE1CGY0wye1WXsAAqHIWBD0z5EtZCKV6qS0Y8OOg2g== ;{id = 18062 (ksk), size = 256b}</pre>
<p>On a le drapeau 257, c'est bien une KSK. Vérifions la clé avec l'id 53551</p>
<pre>
# cat Khome.arpa.+013+53551.key
home.arpa. IN DNSKEY 256 3 13 l+GT51VQ/PgJuJUXr3j6RkXqyDSCj1djhK+HOyoviKQnqmRi5Qs/6m71jsQ4lxj9caVXC0KlJB1OMove0keDkw== ;{id = 53551 (zsk), size = 256b}</pre>
<p>On a le drapeau 256, c'est bien une ZSK. Les indications en commentaires sont correctes, ldns a bien fait son travail.</p>
<h4>Signature de la zone</h4>
<p>Pour la signature, nous allons simplifier la procédure au maximum en ne s'occupant pas de NSEC (et NSEC3), ldns générant par défaut des enregistrement NSEC. Pour la durée de vie des signatures, 2 possibiltés :</p>
<ul>
<li>Mettre une valeur <q>standard</q>, comme on en trouve dans la nature, dans les 30-35 jours mais qui va nécessiter de re-signer la zone régulièrement</li>
<li>Mettre une grande valeur, mettons 10 ans, afin d'être tranquille. Nous allons opter pour cette option. À voir si cela ne gène pas dans le temps (ça ne devrait pas, mais sait-on jamais)</li>
</ul>
<p>On signe donc notre zone :</p>
<pre>
# ldns-signzone -e $(($(date +%s) + 315365000)) -o home.arpa -f /etc/nsd/nsd.conf.d/home.arpa.zone.signed /etc/nsd/nsd.conf.d/home.arpa.zone Khome.arpa.+013+53551 Khome.arpa.+013+18062</pre>
<p>S'il n'y a pas de messages d'erreurs, vérifions le fichier généré :</p>
<pre>
# cat /etc/nsd/nsd.conf.d/home.arpa.zone.signed
home.arpa. 14400 IN SOA ns.home.arpa. hostmaster.home.arpa. 1 14400 3600 1814400 3600
home.arpa. 14400 IN RRSIG SOA 13 2 14400 20301112033940 20201114021620 53551 home.arpa. u7KPfzsPJXeal/24cdLbkCpCQzcb25Sn+Dch6yjIaKYv5M9/8b6pHR/XWVcvTUNVKL/P01FT1LtcrSEyTABKkw==
home.arpa. 14400 IN DNSKEY 256 3 13 l+GT51VQ/PgJuJUXr3j6RkXqyDSCj1djhK+HOyoviKQnqmRi5Qs/6m71jsQ4lxj9caVXC0KlJB1OMove0keDkw== ;{id = 53551 (zsk), size = 256b}
home.arpa. 14400 IN DNSKEY 257 3 13 bOcYev/grRO3AOc/mtc+VJNIsfpzTP2da1LhUreOrMUViE1CGY0wye1WXsAAqHIWBD0z5EtZCKV6qS0Y8OOg2g== ;{id = 18062 (ksk), size = 256b}
home.arpa. 14400 IN RRSIG DNSKEY 13 2 14400 20301112033940 20201114021620 18062 home.arpa. 85j6NrPruCrPcytBe4ckfse9EnCfUfYeMjHQ69+chYtvk6J0wlmAF1SraSS/wgCWx2FciFhCMdzCYAjUr7kfjQ==
home.arpa. 3600 IN NSEC azathoth.home.arpa. SOA RRSIG NSEC DNSKEY
...</pre>
<p>Tout semble bon. On peut relancer NSD.</p>
<pre>
# systemctl restart nsd</pre>
<p>Si dans le futur vous modifiez la zone :</p>
<ul>
<li>Pensez à la resigner !</li>
<li>Pas besoin de relancer NSD, on peut recharger la zone via la commande :</li>
</ul>
<pre>
# nsd-control reload home.arpa</pre>
<h4>Configuration d'Unbound</h4>
<p>Le plus dur est fait, reste à configurer le résolveur. Il y a 2 éléments à passer à Unbound :</p>
<ul>
<li>La configuration de la zone, où on lui dit à qui envoyer les requêtes. Cela se fera via une <code>stub-zone</code></li>
<li>Lui donner de quoi valider les signature. On utilisera <code>trust-anchor</code></li>
</ul>
<p>Pour ce dernier paramètre, on récupère l'enregistrement DS précédemment généré</p>
<pre>
# cat Khome.arpa.+013+18062.ds
home.arpa. IN DS 18062 13 2 4eda0e1d8f64c986f3f1e5bb1f2ba834af9847955b0b1a8d5fc29c1e0cb9791a</pre>
<p>On le copie, et on configure Unbound</p>
<pre><code> server:
...
trust-anchor: "home.arpa. IN DS 18062 13 2 4eda0e1d8f64c986f3f1e5bb1f2ba834af9847955b0b1a8d5fc29c1e0cb9791a"
stub-zone:
name: "home.arpa."
stub-addr: 127.0.0.1@53530</code></pre>
<p>On le redémarre et on teste</p>
<pre>
# dig cthulhu.home.arpa
; <<>> DiG 9.16.6-Debian <<>> cthulhu.home.arpa
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 6043
;; flags: qr rd ra <b>ad</b>; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;test.home.arpa. IN A
;; ANSWER SECTION:
cthulhu.home.arpa. 86400 IN A 192.168.0.2
;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: sam. nov. 14 03:31:05 CET 2020
;; MSG SIZE rcvd: 59</pre>
<p>On a bien le bit AD (Authentificated Data), indiquant que la réponse est validée. Tout fonctionne bien donc. Une dernière chose : sur les systèmes récents, disposant de la <code>libc6</code> 2.31 (et supérieure), <a href="https://sourceware.org/legacy-ml/libc-announce/2020/msg00001.html">le système ne transmet plus automatiquement le bit AD aux applications qui en ont besoin</a> (c'est a priori le cas de SSH lorsqu'on utilise SSHFP — à tester ceci dit, n'utilisant pas SSHFP — ou <code>gnutls-cli</code> lorsque l'on essaye d'utiliser DANE). Pour que les applications disposent de ce bit, il faut ajouter l'option suivante à votre <code>resolv.conf</code> :</p>
<pre><code> options trust-ad</code></pre>
<p>Ajoutons que ce nouveau comportement par défaut de <code>libc6</code> est pensé pour les environnement où le résolveur n'est pas forcément fiable. S'il tourne localement, ce n'est normalement pas le cas (à moins de ne pas se faire confiance 🙂).</p>
Gérer un domaine local avec Unbound
2020-02-29T21:30:00+01:00
2022-01-30T22:10:02+01:00
https://www.shaftinc.fr/unbound-domaine-local.html
<h2>home.arpa sweet home.arpa</h2>
<p>Rapide billet pour documenter une technique prolongeant le principe déjà étudié pour donner dans <a href="blocage-pubs-unbound.html">le blocage de parasites</a>. Il va s'agir ici de se créer un domaine local, pour le réseau privé, ce qui est bien plus pratique que de retenir des IPs ou maintenir un fichier <code>hosts</code> sur les machines où l'on y a accès.</p>
<p>Le principe est de profiter du <a href="https://www.bortzmeyer.org/8375.html">RFC 8375</a>, qui réserve le domaine <code>home.arpa.</code> pour les protocoles de <a href="https://www.bortzmeyer.org/7788.html">la famille Homenet</a> (une série de protocoles pour les bidules connectés dont je n'avais pas entendu parlé avant) et donc d'attribuer un domaine à chaque machine du réseau local. On pourrait a priori également utiliser le domaine <code>local.</code>, réservé pour le <a href="https://fr.wikipedia.org/wiki/Zeroconf#Multicast_DNS_(mDNS)">DNS multicast (mDNS)</a> (qui est assez répandu et dont les implémentations les plus connues sont sans doutes Bonjour d'Apple et Avahi sous Linux — vous savez le truc bien souvent installé par défaut et qui hurle des insanités sur le port 5353 🙂), mais comme pour le coup il a un vrai usage, laissons le tranquille.</p>
<p>Le programme est assez simple : on liste les appareils du réseau, on note leurs adresses IPv4 et IPv6 (si cette dernière est fixe) et on passe le tout à Unbound.</p>
<p>On crée donc un fichier de configuration dans <code>/etc/unbound/unbound.conf.d/</code> (sous Debian et dérivés. Pour les systèmes type Arch, on peut mettre cela dans le fichier de configuration principal ou bien dans un fichier à part que l'on inclut dans la conf principale via <code>include</code>) et on y ajoute :</p>
<pre><code> server:
local-zone: "home.arpa." transparent
# IPv4
local-data: "router.home.arpa. 86400 IN A 192.168.0.1"
local-data: "cthulhu.home.arpa. 86400 IN A 192.168.0.2"
local-data: "eth.azathoth.home.arpa. 86400 IN A 192.168.0.3"
local-data: "wifi.azathoth.home.arpa. 86400 IN A 192.168.0.4"
...
# IPv6
local-data: "router.home.arpa. 86400 IN AAAA 2001:db8:1a1a:ca11::1"
local-data: "cthulhu.home.arpa. 86400 IN AAAA 2001:db8:1a1a:ca11:0f:c700:100:1926"
...
# On ajoute les enregistrements PTR
# PTR IPv4
local-data-ptr: "192.168.0.1 86400 router.home.arpa."
...
# PTR IPv6
local-data-ptr: "2001:db8:1a1a:ca11::1 86400 router.home.arpa."
...
# Divers
local-data: 'azathoth.home.arpa. 86400 IN TXT "Une adresse par interface"'</code></pre>
<p>Quelques commentaires :</p>
<ul>
<li>Le type <code>transparent</code> de <code>local-zone</code> a le fonctionnement suivant : si le domaine est listé dans les <code>local-data</code>, Unbound répond avec ces données ou <code>NODATA</code> si l'enregistrement demandé ne s'y trouve pas. Le mécanisme de résolution normal est utilisé si le domaine ne possède aucune donnée locale. Je l'utilise car, hébergeant une <a href="https://atlas.ripe.net/">sonde RIPE Atlas</a>, mon résolveur peut-être utilisé pour des tests et notamment des requêtes demanant le <code>SOA</code> de <code>home.arpa.</code> (qui est un enregisrement qui existe dans les véritables NS du domaine). Donc, pour essayer d'éviter de révéler que ce domaine est utilisé localement, <code>transparent</code> me semble une bonne solution.</li>
<li>Sans cette contrainte, le type <code>static</code> (le résolveur intercepte toutes requêtes pour tout l'arbre <code>home.arpa.</code>, sert les données qu'il possède, réponds <code>NODATA</code> ou <code>NXDOMAIN</code> sinon) est parfaitement adapté.</li>
<li>On peut ajouter tout type d'enregistrements — non liés à DNSSEC évidemment. On peut donc mettre un enregistrement <code>TXT</code> pour chaque machine qui fournirait une petite description de celle-ci. Dans l'exemple ci-dessus, j'ajoute un enregistrement pour <code>azathoth.home.arpa.</code> principalement car sinon, le domaine étant vide, seuls les 2 sous-domaines existent et les requêtes pour ce parent suivraient le mécanisme de résolution récursif.</li>
<li>Attention par ailleurs à la syntaxe pour ajouter un enregistrement <code>TXT</code> : utilisation de guillemets simples pour l'enregisrement complet et double pour le texte à passer.</li>
</ul>
<p>Une fois tout configuré et Unbound relancé, on peut faire quelques tests :</p>
<pre>
$ dig cthulhu.home.arpa
; <<>> DiG 9.11.14-3-Raspbian <<>> cthulhu.home.arpa
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 59243
;; flags: qr <b>aa</b> rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;cthulhu.home.arpa. IN A
;; ANSWER SECTION:
cthulhu.home.arpa. 86400 IN A 192.168.0.2
;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: sam. févr. 29 02:42:28 CET 2020
;; MSG SIZE rcvd: 57</pre>
<p>Et le <code>PTR</code> :</p>
<pre>
$ dig -x 192.168.0.2
; <<>> DiG 9.11.14-3-Raspbian <<>> -x 192.168.0.2
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 16057
;; flags: qr <b>aa</b> rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;2.0.168.192.in-addr.arpa. IN PTR
;; ANSWER SECTION:
2.0.168.192.in-addr.arpa. 86400 IN PTR cthulhu.home.arpa.
;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)</pre>
<p>On a bien, dans les 2 cas, l'indication <b>AA</b> (<i>Authoritative Answer</i>) dans les réponses. Le résolveur fait donc autorité et nous a bien servi les enregistrements configurés. Continuons :</p>
<pre>
$ dig AAAA azathoth.home.arpa
...
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 33254
;; flags: qr <b>aa</b> rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;azathoth.home.arpa. IN AAAA
;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)</pre>
<p>Le bit <b>AA</b> et une réponse vide (statut <code>NOERROR</code> et <code>ANSWER</code> de 0), ce qui est bien ce qui est attendu. Dernier test :</p>
<pre>
$ dig SOA home.arpa
...
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 15498
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0,ADDITIONAL: 1
...
;; ANSWER SECTION:
home.arpa. 300 IN SOA prisoneriana.org. hostmaster.root-servers.org. 2002040800 1800 900604800 604800
;; Query time: 32 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)</pre>
<p>Cette fois-ci, pas de bit <b>AA</b> dans la réponse, Unbound est donc allé chercher la réponse via le mécanisme récursif habituel.</p>
<p>On peut enfin configurer son routeur, si ce dernier le permet, pour utiliser ce domaine (notamment pour la recherche) et dire aux appareils du réseau de l'utiliser. De quoi avoir cette entrée dans <code>/etc/resolv.conf</code> :</p>
<pre><code> domain home.arpa
search home.arpa</code></pre>
<p>Si votre routeur ne le permet pas ou est incomplet (comme le mien qui ne permet pas de dire sur quel domaine faire la recherche), il est possible de forcer ces paramètres via le client DHCP de chaque machine.</p>
<p>Et voilà, on peut faire <code>ping</code> et <code>traceroute</code> sur le réseau local en s'aidant du DNS 😁.</p>
Escalade dans la traque en ligne, le cas Eulerian
2019-11-11T02:15:00+01:00
2022-01-30T22:10:03+01:00
https://www.shaftinc.fr/escalade-traque-eulerian.html
<h2>This time it's war</h2>
<p>Récemment, <a href="https://web.archive.org/web/20191010130901/https://www.liberation.fr/france/2019/10/10/donnees-personnelles-liberation-va-retirer-les-trackers-publicitaires-pour-ses-abonnes_1756535">le quotidien Libération a annoncé en fanfare</a> que ce serait le premier média à supprimer les traqueurs publicitaires de son site web pour ses abonné·e·s (ce que je ne suis pas), oubliant que d'autres le font déjà, comme <a href="https://www.nextinpact.com">Next INpact</a> ou <a href="https://www.canardpc.com">Canard PC</a>, voire pire que certains médias dangereusement radicalisés comme <a href="https://reflets.info/">Reflets</a> ne déposent pas de mouchards que vous possédiez un abonnement ou pas. Mais surtout, cette annonce est totalement fausse.</p>
<p><a href="https://reflets.info/articles/quand-libe-cache-les-trackers-publicitaires-sous-le-cyber-tapis">Ce que ce bidonnage a laissé apparaître</a>, c'est que Libération passe par les services d'Eulerian, une sympathique société proposant <q>une solution de mesure publicitaire</q>, dont le <s>PDG</s> <em>bullshiteur</em> en chef <a href="https://www.eulerian.com/societe/">déclare sans ambages</a> :</p>
<blockquote cite="https://www.eulerian.com/societe/">La collecte des données marketing est de plus en plus difficile à réaliser en raison du déploiement des adblockers, d’ITP ou encore du traitement dégradé des cookies tiers. Les choix technologiques d’Eulerian permettent à nos clients de disposer de données exhaustives et de qualité pour mener à bien leurs projets d’attribution marketing et de segmentation de données dans le respect total des législations en vigueur.</blockquote>
<p>Et de fait, Libération a mis les traqueurs sous le cyber-tapis pour reprendre l'expression de Reflets. Ils (et d'autres, comme la SNCF – <a href="https://www.eulerian.com/vie-privee/">voir la liste des domaines passant par les services d'Eulerian</a> ou bien <a href="https://web.archive.org/web/20191110231716/www.eulerian.com/vie-privee">une archive en date du 11 novembre 2019</a>) passent par le DNS afin de masquer leurs cochonneries. Prenons l'exemple de la SNCF. L'importun script est chargé depuis le domaine <code>v.oui.sncf</code>, comme nous l'apprend le code source de la page web en <a href="https://oui.sncf/">https://oui.sncf/</a> :</p>
<pre><code> <!--begineulerian-->
script type="text/javascript"
(fuction(){var d=document,l=d.location;if(!l.protocol.indexOf('http')){var o=d.createElement('script'),a=d.getElementsByTagName('script')[0],cn=parseInt((new Date()).getTime()/3600000);o.type='text/javascript';o.async='async';o.defer='defer';
o.sc='//<b>v.oui.sncf</b>/content/vsc-fr/8lL.QlYVeQ7BL6AqQORYg_FeHeIQMaObMRxsXxGG0g--/'+cn+'.js';
a.prentNode.insertBefore(o,a);}})();
/script
<!--endeulerian--></code></pre>
<p>Si l'on regarde ce que donne une requête DNS pour ce domaine, on obtient :</p>
<pre>
$ dig v.oui.sncf
; <<>> DiG 9.11.5-P4-5.1+b1-Debian <<>> v.oui.sncf
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 28329
;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;v.oui.sncf. IN A
;; ANSWER SECTION:
v.oui.sncf. 3600 IN CNAME voyages-sncf.eulerian.net.
voyages-sncf.eulerian.net. 7200 IN CNAME vsc.eulerian.net.
vsc.eulerian.net. 7200 IN A 109.232.194.20
vsc.eulerian.net. 7200 IN A 109.232.194.10
;; Query time: 521 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: dim. nov. 10 16:55:46 CET 2019
;; MSG SIZE rcvd: 128</pre>
<p>La fourberie est de passer par un système de nom canonique (<code>CNAME</code>) : <code>v.oui.sncf</code> est l'alias de <code>voyages-sncf.eulerian.net</code>, lui-même alias de <code>vsc.eulerian.net</code>. Le but est de neutraliser les bloqueurs publicitaires habituels. Typiquement uBlock ou uMatrix ne bloquent pas par défaut les sous-domaines du domaine du site visité ou bien <a href="https://framagit.org/Shaft/unbound-adblock/">le système de blocage via Unbound</a> que j'utilise est également impuissant face à ces <code>CNAME</code>.</p>
<p>Comment faire pour bloquer ces mouchards ? Une première solution est de passer par un pare-feu. La deuxième est de muscler la configuration d'Unbound.</p>
<h3>Pare-feu</h3>
<p>Par chance, Eulerian ne possède que peu d'adresses IP : <a href="https://stat.ripe.net/widget/routing-status#w.resource=AS50234">2048 adresses IPv4 en tout et pour tout</a> contenues dans le préfixe: 109.232.192.0/21 soit les adresses de 109.232.192.0 à 109.232.199.255.
<p>Il est donc possible de facilement bloquer tout ça via le pare-feu de votre système ou mieux de votre routeur en y indiquant soit le préfixe, soit la plage d'adresses. Étant donné le nombre de pare-feu existants, je vous laisse consulter le manuel du votre. À noter que le désavantage de cette méthode est de laisser fuiter des requêtes DNS et de nécessiter de surveiller les IPs annoncées par Eulerian, si jamais cela devait changer. La technique est également plus laborieuse à mettre en place pour les sociétés annonçant plus de préfixes (typiquement Critéo annonce 10 préfixes IPv4 et 5 IPv6 au moment de l'écriture de ce billet).</p>
<h3>Unbound</h3>
<p><a href="https://www.shaftinc.fr/blocage-pubs-unbound.html">Le blocage habituel par Unbound</a> n'est pas fonctionnel, il va donc falloir passer par une autre méthode. La documentation suggère de passer par des <code>stub-zone</code>, c'est à dire une zone pour laquelle l'on va indiquer le serveur faisant autorité à utiliser. Le problème étant que nous n'avons pas un tel serveur et qu'Unbound n'interroge pas par défaut le <code>localhost</code> (s'il le faisait, il ne serait plus un résolveur récursif, ce que nous souhaitons éviter). Heureusement d'autres options sont disponibles, en particulier <code>auth-zone</code>, qui permet de fournir <a href="https://en.wikipedia.org/wiki/Zone_file">un fichier de zone</a>, exactement comme si nous étions les gestionnaires des domaines d'Eulerian. Du coup, l'idée est de configurer Unbound pour se servir des données d'un fichier de zone en lieu et place du mécanisme de résolution habituel.</p>
<p>Eulerian possède au moins 3 domaines : <code>eulerian.com</code>, <code>eulerian.fr</code> et <code>eulerian.net</code>. Les mouchards semblent tous servis via <code>eulerian.net</code>, nous allons donc nous concentrer sur ce domaine. Nous bloquerons par précaution les autres via une habile manœuvre de copier/coller. À noter que le site web <q>corporate</q> de la société ne sera par conséquent plus disponible sur les machines utilisant le résolveur.</p>
<h4>Création du fichier de zone</h4>
<p>Créer, dans le répertoire de votre choix, un fichier <code>eulerian.net.zone</code>. Le remplir avec : </p>
<pre><code> $TTL 10800
eulerian.net. IN SOA localhost. nobody.invalid. (
1
3600
1200
604800
10800
)</code></pre>
<p>Dans le détail, on indique la durée de vie par défaut des enregistrements (<code>$TTL 10800</code>) puis le début de l'autorité pour la zone (avec le nom du serveur faisant autorité (<code>localhost.</code>), le mail de l'admin (<code>nobody@invalid.</code> – le @ est un caractère particulier dans le DNS, on le remplace donc par un point), puis plein de nombres (le 1er est le numéro de série de la zone, le reste, <a href="https://fr.wikipedia.org/wiki/SOA_Resource_Record">je vous laisse le découvrir</a> 🙂) et c'est tout, le reste du domaine sera vide. On peut s'amuser à ajouter des enregistrements, par un exemple un <code>TXT</code> qui explique que le domaine est bloqué, en ajoutant à la suite du fichier de zone :</p>
<pre><code> *.eulerian.net. IN TXT "Domaine bloqué"</code></pre>
<p>Ne reste plus qu'à créer 2 copies de ce fichier nommées <code>eulerian.com.zone</code> et <code>eulerian.fr.zone</code>. et d'y changer le domaine.</p>
<h4>Configuration d'Unbound</h4>
<p>À la suite de votre fichier <code>unbound.conf</code> ou dans un fichier <code>.conf</code> spécifique, typiquement dans <code>/etc/unbound/unbound.conf.d/</code> sous Debian et dérivés, ajouter :</p>
<pre><code> auth-zone:
name: "eulerian.net."
zonefile: "/chemin/vers/eulerian.net.zone"
auth-zone:
name: "eulerian.fr."
zonefile: "/chemin/vers/eulerian.fr.zone"
auth-zone:
name: "eulerian.com."
zonefile: "/chemin/vers/eulerian.com.zone"</code></pre>
<p>On définit donc 3 zones sur lesquelles nous décrétons avoir l'autorité et pour chacune d'entre elles, on dit à Unbound d'utiliser les fichiers correspondants. On sauvegarde et on relance Unbound et normalement, le blocage est effectif :</p>
<pre>
$ dig v.oui.sncf
; <<>> DiG 9.11.5-P4-5.1+b1-Raspbian <<>> v.oui.sncf
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 1153
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;v.oui.sncf. IN A
;; ANSWER SECTION:
v.oui.sncf. 3600 IN CNAME voyages-sncf.eulerian.net.
;; AUTHORITY SECTION:
eulerian.net. 10800 IN SOA localhost. nobody.invalid. 1 3600 1200 604800 10800
;; Query time: 250 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: dim. nov. 10 23:42:11 CET 2019
;; MSG SIZE rcvd: 137</pre>
<p>Et un domaine au hasard :</p>
<pre>
$ dig le.rgpd.pour.les.nuls.eulerian.net
; <<>> DiG 9.11.5-P4-5.1+b1-Raspbian <<>> le.rgpd.pour.les.nuls.eulerian.net
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 30147
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;le.rgpd.pour.les.nuls.eulerian.net. IN A
;; AUTHORITY SECTION:
eulerian.net. 10800 IN SOA localhost. nobody.invalid. 1 3600 1200 604800 10800
;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: dim. nov. 10 23:43:31 CET 2019
;; MSG SIZE rcvd: 122</pre>
<p>Dans ce deuxième exemple, on voit bien le drapeau <code>aa</code> dans la réponse, ce qui signifie que le serveur qui a répondu (le <code>localhost</code>) fait autorité (<code>aa</code> signifiant <code>Authoritative Answer</code>). Il n'est pas présent dans la précédente requête car la question porte sur <code>v.oui.sncf.</code> et que la réponse a été fournie par les serveurs de noms de la SNCF : <code>voyages-sncf.eulerian.net.</code> est le nom canonique de ce domaine. Le reste a été intercepté par nos soins.</p>
<p>Eulerian ne sont pas les seuls à pratiquer cette mise sous tapis des mouchards. Critéo pratique également la chose (via le domaine <code>dnsdelegation.io</code>, que je vous invite à bloquer la même façon) et d'autres boîtes s'y mettent aussi. En clair, les vendeurs de traqueurs semblent avoir déclaré la guerre aux bloqueurs de publicités et les développeurs de navigateurs – qui commencent à hausser la voix sur le sujet et agir en conséquence (<a href="https://www.nextinpact.com/brief/firefox-70-detaille-les-trackers-bloques--de-nouvelles-protections-en-place-10074.htm">voir notamment Mozilla qui travaille dans ce sens</a>) – le tout bien sûr en s'essuyant le derrière avec le RGPD (on avait l'habitude sur ce point, <a href="https://www.nextinpact.com/news/108313-cookies-tolerance-cnil-passe-cap-conseil-detat.htm">la CNIL fait un peu pareil</a>.). Cela reste inquiétant, car cette escalade dans la dégueulasserie laisse pour l'instant une majorité de personnes démunies pour bloquer ces traqueurs.</p>
<h3>Addendum</h3>
<p>Premièrement, sous Debian et dérivés, où Unbound à un profil <a href="https://fr.wikipedia.org/wiki/AppArmor">AppArmor</a> actif, il est préférable de placer les fichiers de zone dans un répertoire accessible au résolveur, typiquement <code>/var/lib/unbound/</code>.</p>
<p>Ensuite, il est possible de simplifier la configuration d'Unbound en ne créant qu'un seul fichier de zone et le donnant au résolveur pour chaque domaine à bloquer. Cela donne pour le fichier de zone :</p>
<pre><code> $TTL 10800
@ IN SOA localhost. nobody.invalid. (
1
3600
1200
604800
10800
)</code></pre>
<p>Et la configuration d'Unbound :</p>
<pre><code> auth-zone:
name: "eulerian.net."
zonefile: "/var/lib/unbound/blocked.zone"
auth-zone:
name: "eulerian.fr."
zonefile: "/var/lib/unbound/blocked.zone"
auth-zone:
name: "eulerian.com."
zonefile: "/var/lib/unbound/blocked.zone"</code></pre>
<p>Enfin, <code>auth-zone</code> est un ajout récent d'Unbound. Il a été ajouté dans la version 1.7.0 (afin de pouvoir mettre en œuvre le <a href="https://www.bortzmeyer.org/7706.html">RFC 7706</a>). Pour les versions plus anciennes, il va falloir passer par le paramètre <code>stub-zone</code>. On oublie alors le fichier de zone et on met dans la configuration d'Unbound :</p>
<pre><code> stub-zone:
name: "eulerian.net."
stub-addr: 127.0.0.1
stub-zone:
name: "eulerian.fr."
stub-addr: 127.0.0.1
stub-zone:
name: "eulerian.com."
stub-addr: 127.0.0.1</code></pre>
<p>Si jamais Unbound sort une erreur ou un avertissement indiquant qu'il n'est pas content de l'emploi de l'adresse 127.0.0.1, changez là par une adresse privée inutilisée, par exemple dans 192.168.0.0/16.</p>
<p>Merci à <a href="https://social.imirhil.fr/@aeris">Aeris</a>, <a href="https://true.kawi.fr/@ycawidro">Yanik Cawidrone</a> et <a href="https://mamot.fr/@Lapineige">Lapineige</a> pour leurs remarques.</p>
Automatisation de tâches avec les timers systemd
2019-09-10T00:45:00+02:00
2022-01-30T22:10:00+01:00
https://www.shaftinc.fr/automatisation-taches-systemd.html
<h2>systemd-crontabd</h2>
<p>Encore un billet <q>d'auto-documentation</q>, voyons voir comment utiliser les <a href="https://www.freedesktop.org/software/systemd/man/systemd.timer.html"><i>timers</i></a> de <code>systemd</code> en lieu et place des vénérables <a href="https://fr.wikipedia.org/wiki/Cron">cron</a> : ils offrent en effet quelques sympathiques avantages que la <code>crontab</code> ne possède pas.</p>
<h3>La base</h3>
<p>Sous <code>systemd</code>, un <i>timer</i> est simplement une unité tout comme les <i>services</i>, <i>target</i>, <i>slice</i>... et sert... de minuteur. Il est nécessairement lié à un service et va le lancer selon des conditions temporelles, typiquement un chronomètre ou un calendrier. Donc, en gros oui : <code>systemd</code> réinvente la <code>crontab</code>. Voyons voir un cas simple de timer, un script de surveillance. Une de mes machines à la facheuse tendance de perdre épisodiquement sa connectivité IPv6. J'ai donc bricolé un sript qui fait 2-3 tests et relance le nécessaire au besoin. Pour intégrer cette surveillance à <code>systemd</code> on va créer 2 fichiers : un <code>.timer</code> et un <code>.service</code>. Le service est tout ce qu'il y a de plus classique :</p>
<pre><code> [Unit]
Description=Surveillance IPv6
[Service]
User=root
ExecStart=/usr/local/bin/test-ip6
[Install]
WantedBy=basic.target</code></pre>
<p>Et on crée un fichier <code>.timer</code> avec le même nom (si on crée <code>test.service</code>, il faut un <code>test.timer</code>) :</p>
<pre><code> [Unit]
Description=Run test-ip6 every 10 minutes
[Timer]
OnBootSec=10min
OnUnitActiveSec=10min
Unit=test.service
[Install]
WantedBy=timers.target</code></pre>
<p>Dans le détail :</p>
<ul>
<li><code>OnBootSec</code> indique la durée à attendre après le démarrage de la machine.</li>
<li><code>OnUnitActiveSec</code> définit la durée à attendre depuis la dernière activation du service que le <i>timer</i> contrôle.</li>
<li><code>Unit</code> définit l'unité à contrôler. Il pourrait être implicite ici : par défaut, <code>systemd</code> va chercher une unité de type <i>service</i> avec le même nom que le <i>timer</i></li>
</ul>
<p>On active uniquement le <i>timer</i> via :</p>
<pre>
# systemctl enable --now test.timer</pre>
<p>Le <code>--now</code> permet de démarrer immédiatement le <i>timer</i>. S'il est omis, il faut bien penser à le faire via :</p>
<pre>
# systemctl start test.timer</pre>
<p>Voilà, on a un équivalent <code>systemd</code> d'une entrée :</p>
<pre><code> */10 * * * * root /usr/local/bin/test-ip6</code></pre>
<p>dans la <code>crontab</code>. À la différence que le <code>cron</code> est absolu : il s'exécute quoiqu'il arrive toutes les 10 min (18h00, 18h10, 18h20...), là où l'exécution du <i>timer</i> est relative à un évenement et peut être restreinte par des conditions (parmi celles que j'aime bien, le changement de fuseau horaire). Regardons un cas plus évolué : la rotation des journaux, gérées via <code>logrotate.timer</code> :</p>
<pre><code> [Unit]
Description=Daily rotation of log files
Documentation=man:logrotate(8) man:logrotate.conf(5)
[Timer]
OnCalendar=daily
AccuracySec=1h
Persistent=true
[Install]
WantedBy=timers.target</code></pre>
<p>On a donc les conditions suivantes :</p>
<ul>
<li><code>OnCalendar</code> permet de contrôler le <i>timer</i> via une base calendaire, ici un déclechement quotidien (à minuit). Comme pour les <code>cron</code>, il est possible de définir des heures ou jours de la semaine (ou du mois) particulier. La <a href="https://www.freedesktop.org/software/systemd/man/systemd.time.html">documentation</a> précise la syntaxe.</li>
<li><code>AccuracySec</code> définit la précision de l'exécution. Ici, on définit une fenêtre de ±1h et le système exécutera la tâche durant cette dernière, à une valeur aléatoire mais fixe pour tous les <i>timers</i>, afin de ne pas réveiller le CPU inutilement (<i>dixit</i> le manuel).</li>
<li><code>Persistent</code> est un booléen. Quand il est vrai (ce qui n'est pas le comportement par défaut), l'heure de la dernière exécution du service contrôlé par le <i>timer</i> est stockée. À l'activation du <i>timer</i>, le service est déclenché immédiatement s'il aurait du être déclenché durant l'inactivité du timer. Cela permet de rattraper des déclechements ratés (pour cause de machine éteinte par exemple). En gros, <a href="https://fr.wikipedia.org/wiki/Anacron"><code>anacron</code></a> version <code>systemd</code>.</li>
</ul>
<p>D'autres conditions existent et sont listés dans le <a href="https://www.freedesktop.org/software/systemd/man/systemd.timer.html">manuel</a>. On va en voir une autre, que je trouve intéressante : <code>OnUnitInactiveSec</code>.</p>
<h3>Surveillance de service</h3>
<p>L'idée de ce billet m'est venu en cherchant une solution au problème que je rencontre avec mon script <code>unbound-adblock</code> et la combinaison <a href="https://fr.wikipedia.org/wiki/NetworkManager">NetworkManager</a> et Wi-Fi (<a href="https://framagit.org/Shaft/unbound-adblock/issues/3">voir ce ticket pour le détail</a>). J'ai trouvé une solution correcte ne nécessitant ni de modifier le script, ni de modifier le fichier <code>.service</code>. Ce qui me satisfait, même si ce n'est pas parfait.</p>
<p><code>OnUnitInactiveSec</code> donc définit la durée (en secondes si aucune unité n'est précisée) à attendre depuis la dernière activation du service que le <i>timer</i> contrôle (l'inverse de <code>OnUnitActiveSec</code>). Dans le cas de <code>unbound-adblock</code>, on créé donc le <code>adblock.timer</code> :</p>
<pre><code> [Unit]
Description=Reload failed adblock
[Timer]
OnUnitInactiveSec=60
Unit=adblock.service
[Install]
WantedBy=timers.target</code></pre>
<p>Une fois actif et démarré, ce <i>timer</i> va donc attendre 1 minute avant de relancer le service <code>adblock</code> si ce dernier est inactif (le status <code>failed</code> rentrant dans ce cas).</p>
<pre>
$ systemctl status adblock.*
● adblock.service - Unbound AdBlock List Making
Loaded: loaded (/etc/systemd/system/adblock.service; enabled; vendor preset: enabled)
Active: <b>failed</b> (Result: exit-code) since Mon 2019-09-09 19:59:06 CEST; 58s ago
Process: 981 ExecStart=/usr/local/bin/unbound-adblock /home/john/liste-adblock.json (code=exited, status=1/FAILURE)
Main PID: 981 (code=exited, status=1/FAILURE)
sept. 09 19:59:06 SHAFT-NETBOOK systemd[1]: Starting Unbound AdBlock List Making...
sept. 09 19:59:06 SHAFT-NETBOOK unbound-adblock[981]: grep: /var/log/adblock.log.1: Aucun fichier ou dossier de ce type
sept. 09 19:59:06 SHAFT-NETBOOK unbound-adblock[981]: Le téléchargement de 2 listes sur 4 a échoué, voir /var/log/adblock.log. Arrêt du script
sept. 09 19:59:06 SHAFT-NETBOOK systemd[1]: adblock.service: Main process exited, code=exited, status=1/FAILURE
sept. 09 19:59:06 SHAFT-NETBOOK systemd[1]: adblock.service: Failed with result 'exit-code'.
sept. 09 19:59:06 SHAFT-NETBOOK systemd[1]: Failed to start Unbound AdBlock List Making.
● adblock.timer - Reload failed adblock
Loaded: loaded (/etc/systemd/system/adblock.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Mon 2019-09-09 19:58:59 CEST; 1min 5s ago
Trigger: Mon 2019-09-09 20:00:06 CEST; <b>1s left</b></pre>
<p>On voit donc <code>adblock.service</code> planté, car démarré sans connexion Wi-Fi active et <code>adblock.timer</code> expirant dans une seconde et relançant le service ce faisant. Si la nouvelle exécution du script échoue, le timer se redéclenchera. Sinon, il s'arrête. Cette solution est sans doute moins précise et efficace que le <code>Restart</code> <a href="https://www.freedesktop.org/software/systemd/man/systemd.service.html#">disponible pour les services</a> (On ne peut pas exclure un code d'erreur ou définir une limite de tentative par exemple), mais il y a quelques avantages :</p>
<ul>
<li>Ne nécessite pas de modifier le fichier <code>.service</code> de l'unité. On peut donc surveiller un service installé via le gestionnaire de paquets (un serveur Web par exemple) sans crainte de voir la modification écrasée lors d'une mise à jour.</li>
<li>Cette indépendence du <i>timer</i> le rend optionnel et débrayable facilement.</li>
</ul>
<p>Dans tous les cas, l'avantage comparé à une solution passant par la <code>crontab</code> et de ne pas avoir à faire un script testant le service à surveiller.</p>
DoT, DoH : le chiffrement du DNS en pratique
2019-06-28T15:45:00+02:00
2022-01-30T22:10:04+01:00
https://www.shaftinc.fr/chiffrement-dns-pratique.html
<h2>Les mains dans le cambouis</h2>
<p>En complément de ma causerie à <a href="https://passageenseine.fr">Pas Sage En Seine</a> ce 28 juin (et dont <a href="misc/dot-doh-dns-chiffrement.pdf">les diapos sont ici</a>, je vous conseille d'y jeter un œil avant de lire texte – ou bien lire les RFC <a href="https://www.bortzmeyer.org/7858.html">7858</a>, <a href="https://www.bortzmeyer.org/8310.html">8310</a> et <a href="https://www.bortzmeyer.org/8484.html">8484</a> 🙂), un billet donc pour parler de ce que je n'ai pas pu placer dans une conférence d'une heure : l'utilisation d'un résolveur DNS sur TLS (DoT) dans la pratique, principalement en tant que client mais aussi quelques billes pour administrer un résolveur complet. J'en profiterai pour causer un peu de DNS sur HTTPS (DoH). Comme d'habitude, les configurations que je donne sont pour des systèmes Debian et dérivés. Les chemins peuvent donc changer si vous êtes sous Arch, une *BSD ou que-sais-je 🙂. Une bonne partie des bouts de configuration présentés ici viennent de ma présentation chez <a href="https://root66.net/index.php?post/2019/04/09/DNS-sur-TLS-dans-la-pratique">Root66</a> en avril dernier.</p>
<h3>Utiliser un client DoT</h3>
<p>Quelques généralités avant de rentrer dans le vif du sujet. Sous Linux, depuis sa version 239, <code>systemd</code> incorpore un résolveur minimum capable d'utiliser DoT, mais nous n'en parlerons pas car ce n'est pas son boulot et par ailleurs, il n'est à ma connaissance pas capable d'utiliser des profils d'authentification stricts</p>
<p>Avant de commencer, il faut bien évidemment choisir un résolveur DoT, récupérer son ou ses adresses IPs, son nom de domaine ainsi que sa clé publique (SPKI ou <i>Subject Public Key Info</i>). Pour vérifier si le SPKI fourni est correct, on peut utiliser <code>gnutls</code> en se connectant directement au serveur (à noter que faire la même chose avec OpenSSL demandera l'absorption d'aspirine à la vue de la commande).</p>
<pre class="scroll">
# gnutls-cli --dane --verify-hostname dot.example.org 2001:db8:7858::853 -p 853
Processed 130 CA certificate(s).
Resolving '2001:db8:7858::853'...
Connecting to '2001:db8:7858::853'...
- Certificate type: X.509
- Got a certificate list of 2 certificates.
- Certificate[0] info:
- subject 'CN=dot.example.org',...
Public Key ID:sha1:52a55c564dfd4ee38a6a51ad6962fc0a6dcc7b4asha256:f504504952e7fd8edbd444dd102ad707c74ff8c6af9a0f7f66fc85e237d2b7fc
<b>Public Key PIN:pin-sha256:9QRQSVLn/Y7b1ETdECrXB8dP+Mavmg9/ZvyF4jfSt/w=</b>
...
- Status: The certificate is trusted.
- DANE: Certificate matches.
- Description: (TLS1.3)-(ECDHE-SECP256R1)-(RSA-PSS-RSAE-SHA256)-(AES-256-GCM)
</pre>
<p>Le SPKI correspond au <code>Public Key PIN</code>. L'option <code>--dane</code> n'est pas obligatoire et s'il n'y a pas d'enregistrement <code>TLSA</code> pour le service, <code>gnutls</code> retournera un message d'erreur. Pour vérifier l'existence d'un enregistrement <code>TLSA</code>, on utilise :</p>
<pre>
# dig TLSA _853._tcp.dot.example.org</pre>
<h4>Stubby</h4>
<p>Après avoir été <a href="https://en.wikipedia.org/wiki/Sergeant_Stubby">le chien le plus décoré de la Première Guerre Mondiale</a>, Stubby est devenu un résolveur minimum pensé pour le chiffrement et basé sur <code><a href="https://getdnsapi.net/">getdns</a></code>. Il est disponible dans le gestionnaire de paquets de la plupart des distributions Linux (à partir de Buster uniquement concernant Debian) et fonctionne parfaitement avec sa configuration de base. Cette dernière contient toute une liste de résolveurs DoT déjà configurés (il s'agit de ceux listés sur le site du projet <a href="https://dnsprivacy.org/wiki/">DNS Privacy</a>). DoT étant avant tout une question de confiance dans l'administrateur·trice à qui l'on confie sa résolution DNS, ne pas les utiliser est parfaitement légitime.</p>
<p>Sous Debian, la configuration se fait dans <code>/etc/stubby/stubby.yml</code>. Pensez à commenter tous les résolveurs pré-configurés et si le résolveur que vous utilisez est accessible via IPv4 et IPv6, une entrée par protocole (ou gardez juste l'entrée IPv6). La configuration initiale de Stubby étant déjà optimale concernant les techniques de protection de vie privée, il n'y a qu'à donner les informations du résolveur :</p>
<pre><code> upstream_recursive_servers:
- address_data: 2001:db8:7858::853
tls_auth_name: "dot.example.org"
tls_pubkey_pinset:
- digest: "sha256"
value: 9QRQSVLn/Y7b1ETdECrXB8dP+Mavmg9/ZvyF4jfSt/w=</code></pre>
<p>Et voilà, vous avez un résolveur minimum qui fait suivre les requêtes DNS à un résolveur complet via TLS. Nous verrons plus bas comment coupler Stubby à Unbound afin de profiter des avantages de ce dernier, à commencer par un cache, ce qui diminuera le nombre de requêtes et améliorera les performances.</p>
<h4>Knot Resolver</h4>
<p>Knot Resolver est un résolveur complet avec cache. Il a la particularité de se configurer en utilisant du LUA, ce qui le rends particulièrement puissant, mais aussi beaucoup plus complexe que Stubby ou Unbound. <a href="https://knot-resolver.readthedocs.io/en/stable/">Sa documentation</a> est également assez touffue. Son intégration à <code>systemd</code> est par ailleurs particulière (on y reviendra sur la partie <q>Configurer un serveur DoT</q>). Pour activer le service qui va bien, il faut utiliser :</p>
<pre>
systemctl enable --now kresd@1.service</pre>
<p>Ensuite, la configuration se fait dans <code>/etc/knot-resolver/kresd.conf</code>. Attention, tout comme Stubby, il faut une entrée par IP.</p>
<pre><code> policy.add(policy.all(
policy.TLS_FORWARD({
{'192.0.2.13', pin_sha256='9QRQSVLn/Y7b1ETdECrXB8dP+Mavmg9/ZvyF4jfSt/w='},
{'2001:db8:7858::853', hostname='dot.example.org', ca_file='/etc/ssl/certs/ca-certificates.crt'}
})
))</code></pre>
<p>Quelques remarques. Cet exemple donne la configuration pour un même serveur accessible en IPv4 et en IPv6. Contrairement à Stubby qui est capable de faire une double vérification ADN + IP et SPKI + IP, ce n'est pas le cas ici. Si l'on veut utiliser une authentification via ADN, il faut donner le chemin du magasin d'autorités de certification (c'est ici celui de Debian).</p>
<h4>Unbound</h4>
<p>Avec la version actuelle (1.9.2, en juin 2019) et l'incapacité d'Unbound de garder une connexion TCP ouverte quand il est utilisé en tant que <i>forwarder</i>, je déconseille fortement l'utilisation d'Unbound en tant que client DoT. En attendant que ses développeurs se mettent à la page du <a href="https://www.bortzmeyer.org/8490.html">RFC 8490</a>, la configuration est donnée à titre indicatif. Unbound ne permet actuellement qu'une authentification ADN + IP.</p>
<p>Sous Debian, créer un fichier <code>.conf</code> dans <code>/etc/unbound/unbound.conf.d/</code></p>
<pre><code> server:
chroot: ""
auto-trust-anchor-file: "/var/lib/unbound/root.key"
# Les 2 lignes précédentes sont inutiles sous Debian
do-not-query-localhost: no
tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt
forward:
name: "."
forward-addr: 2001:db8:7858::853@853#dot.example.org
forward-tls-upstream: yes</code></pre>
<p>À noter une petite subtilité dans la verson pour Windows, utiliser le magasin d'autorités de certification du système se fait via :</p>
<pre><code> tls-win-cert: yes</code></pre>
<h4>Unbound + Stubby</h4>
<p>Je rappelle le principe : sur la machine locale, Unbound est le résolveur utilisé par le système mais transmet tout à Stubby qui va gérer la partie TLS et discuter avec le serveur DoT.</p>
<img alt="" src="images/unbound-stubby.png">
<p>L'avantage est de profiter de la qualité de Stubby pour ce qui concerne DoT, et de celle d'Unbound pour le reste (cache, possibilité de faire facilement <a href="blocage-pubs-unbound.html">un bloqueur de publicités/traqueurs</a>...). On commence donc par configurer Unbound pour tout envoyer à Stubby :</p>
<pre><code> server:
chroot: ""
auto-trust-anchor-file: "/var/lib/unbound/root.key"
# Les 2 lignes précédentes sont inutiles sous Debian
do-not-query-localhost: no
forward-zone:
name: "."
forward-addr: 127.0.0.1@8053
forward-addr: ::1@8053</code></pre>
<p>Et on configure Stubby :</p>
<pre><code> ...
listen_addresses:
- 127.0.0.1@8053
- 0::1@8053
upstream_recursive_servers:
- address_data: 2001:db8:7858::853
tls_auth_name: "dot.example.org"
tls_pubkey_pinset:
- digest: "sha256"
value: 9QRQSVLn/Y7b1ETdECrXB8dP+Mavmg9/ZvyF4jfSt/w=</code></pre>
<h3>Configurer un serveur DoT</h3>
<p>Voici quelques éléments de configuration de serveurs DoT. 2 principaux logiciels ici : Unbound et Knot Resolver. BIND (dans sa branche 9.x tout du moins) ne gère pas DoT et nécessite de passer par des techniques détournées type <code>stunnel</code> (ce que fait LDN avec <a href="https://ldn-fai.net/serveur-dns-recursif-ouvert/">son service</a>) ou HAProxy. On va donc l'éviter.</p>
<p>Avant de se lancer, il y a quelques éléments à avoir en sa possession : un certificat X.509 et le SPKI de ce dernier. Je vous laisse voir pour récupérer un certificat (je vous laisse choisir votre autorité de certification, ceci dit attention avec Let's Encrypt : il est préférable de ne pas changer de la clé à chaque renouvellement du certificat, avec <code>certbot</code> par exemple). Pour calculer le SPKI, on peut prendre la clé privée et la passer dans cette moulinette OpenSSL :</p>
<pre>
# openssl rsa -in /path/to/private.key -outform der -pubout | \
openssl dgst -sha256 -binary | \
openssl enc -base64</pre>
<p>Attention à changer le <code>rsa</code> en <code>ec</code> si vous utilisez une clé ECDSA.</p>
<h4>Unbound</h4>
<p>La configuration de base d'Unbound pour le transformer en serveur DoT est relativement simple : on lui donne une clé privée et un certificat, on lui dit d'écouter sur le bon port et c'est tout.</p>
<pre><code> server:
chroot : ""
auto-trust-anchor-file: "/var/lib/unbound/root.key"
# Les 2 lignes précédentes sont inutiles sous Debian
interface: 127.0.0.1 # Pour que le système ait un résolveur
interface: ::1 # Pour que le système ait un résolveur
interface: 192.0.2.53@853
interface: 2001:db8:dead:cafe::853@853
udp-upstream-without-downstream: yes
tls-service-key: /path/to/private.key
tls-service-pem: /path/to/cert.pem
remote-control:
# Écoute sur localhost:8953 par défaut
control-enable: yes
control-use-cert: no</code></pre>
<p><code>remote-control</code> permet d'activer <code>unbound-control</code>, l'utilitaire d'administration du serveur (capable de fonctionner à distance, mais nous laissons ici les paramètres de base ne fonctionnant que sur le localhost). <code>udp-upstream-without-downstream</code> n'est pas vraiment utile ici, il permet quand Unbound est configuré pour ne pas être accessible via UDP (ce qui devrait être le cas en utilisant TLS) de pouvoir tout de même utiliser UDP pour la résolution. De ce que j'ai pu constater, désactiver UDP et activer cette option ne pose pas de problèmes pour la résolution des requêtes des clients du serveur, sauf le localhost, pour lequel cela pose des soucis.</p>
<p>Attention, à partir de Debian Buster, <a href="https://en.wikipedia.org/wiki/AppArmor">AppArmor</a> est installé et actif sur le système et Unbound vient avec un profil. Il faut éditer <code>/etc/apparmor.d/local/usr.sbin.unbound</code> et ajouter :</p>
<pre><code> /path/to/private.key r,
/path/to/cert.pem r</code></pre>
<p>afin qu'Unbound puisse accèder à ces fichiers.</p>
<p>Une configuration plus avancée, pour un résolveur faisant face à un trafic plus conséquent, pourrait être :</p>
<pre><code> server:
verbosity: 1
use-syslog: no
logfile: "/var/log/unbound.log"
log-time-ascii: yes
num-threads: 4 # On crée un thread par cœur
chroot : ""
auto-trust-anchor-file: "/var/lib/unbound/root.key"
# Les 2 lignes précédentes sont inutiles sous Debian
interface: 127.0.0.1 # Pour que le système ait un résolveur
interface: ::1 # Pour que le système ait un résolveur
interface: 192.0.2.53@853
interface: 2001:db8:dead:cafe::853@853
# Taile du cache. Estimation raisonnable pour un système avec 4 Gio de RAM
rrset-cache-size: 256m
msg-cache-size: 128m
# Limite de requêtes par secondes par IPs
# Si atteint, ne jette pas tout mais limite le client à 1/10 de ip-ratelimit
ip-ratelimit: 300
ip-ratelimit-factor: 10
incoming-num-tcp: 1000
udp-upstream-without-downstream: yes
edns-tcp-keepalive: yes
# Qname minimization activée par défaut, harden-below-nxdomain (RFC 8020) est recommandé
# qname-minimisation-strict est désactivé car casse pas encore pas mal de services
# (Imdb, service de mise à jour de la Switch de Nintendo...)
harden-below-nxdomain: yes
# Diverses mesures de sécurité
harden-glue: yes
harden-dnssec-stripped: yes
harden-referral-path: yes
# Aggressive use of NSEC. RFC 8198.
aggressive-nsec: yes
use-caps-for-id: yes
prefetch: yes
prefetch-key: yes
tls-service-key: /path/to/private.key
tls-service-pem: /path/to/cert.pem
tls-port: 853 # Donné à titre indicatif, c'est la valeur par défaut
# TLS 1.2 ciphers
tls-ciphers: ECDHE-RSA-CHACHA20-POLY1305:EECDH+AES:+AES128:+AES256:+SHA256:+SHA384:!SHA
# TLS 1.3 ciphers
tls-ciphersuites: TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384
remote-control:
# Écoute sur localhost:8953 par défaut
control-enable: yes
control-use-cert: no</code></pre>
<h4>Knot Resolver</h4>
<p>Je maîtrise beaucoup moins ce logiciel, je donne donc uniquement une configuration de base :</p>
<pre><code> -- Default empty Knot DNS Resolver configuration in -*- lua -*-
-- Bind ports as privileged user (root) --
-- net = { '127.0.0.1', '::1' }
net.tls('/etc/knot-resolver/cert.pem', '/etc/knot-resolver/key.key')
-- On active le remplissage
net.tls_padding(true)
net.listen('2001:db8:dead:cafe::853', 853)
net.listen('192.0.2.53', 853)
-- Switch to unprivileged user --
user('knot-resolver','knot-resolver')
-- Unprivileged
-- cache.size = 100*MB # À augmenter éventuellement</code></pre>
<p>Sur les systèmes utilisant <code>systemd</code>, il faut indiquer les sockets.</p>
<pre>
# systemctl edit kresd-tls.socket</pre>
<p>et mettre :</p>
<pre><code> [Socket]
ListenStream=192.168.2.53:853
ListenStream=[2001:db8:dead:cafe::853]:853</code></pre>
<p>Par ailleurs, pour pouvoir faire tourner Knot sur plusieurs threads, on utilise encore <code>systemd</code>. Ils partageront la même configuration :</p>
<pre>
# systemctl enable --now kresd@{1..n}.service</pre>
<p>et on les démarre :</p>
<pre>
# systemctl start kresd@{1..n}.service</pre>
<h4>Surveillez !</h4>
<p>Le DNS étant un service critique, il est nécessaire de monitorer ses serveurs DoT. L'équipe de GetDNS propose <code>getdns_server_mon</code>, un utilitaire présent dans le paquet <code>getdns-utils</code> à partir de sa version 1.4.0. Il est compatible avec Icinga, Naemon, Nagios, Shinken et Sensu. Sa documentation se trouve <a href="https://github.com/getdnsapi/getdns/tree/develop/src/tools">sur Github</a>. Tous les tests qu'il propose sont à faire.</p>
<h3>DNS sur HTTPS (DoH)</h3>
<p>DoH est récent (octobre 2018) et les logiciels ne sont pas encore forcément compatibles. Outre les bouts de code traînant à droite à gauche, le principal serveur disponible est Knot Resolver, compatible de manière expérimentale depuis la version 4.0.0 sortie en avril dernier et niveau client, Firefox depuis la version 62. Un vrai client sera disponible avec la version 0.3 de Stubby, dont la sortie ne devrait plus trop tarder.</p>
<h4>Knot Resolver</h4>
<p>Je n'ai pas vraiment testé DoH dans Knot Resolver (j'attends Stubby pour faire de vrais tests), le bout de configuration est donc piqué à <a href="https://knot-resolver.readthedocs.io/en/v4.0.0/modules.html#mod-http">la documentation du logiciel</a> 😬</p>
<pre><code> modules.load('http')
http.config({
tls = true,
cert = '/etc/knot-resolver/cert.pem',
key = '/etc/knot-resolver/key.key',
}, 'doh')</code></pre>
<h4>Firefox</h4>
<p>À l'heure actuelle, Firefox est donc malheureusement le principal client DoH disponible dans la nature. Sur la version stable (67 actuellement), l'interface amicale n'est pas encore implémentée et il faut donc aller dans <code>about:config</code> pour trouver les paramètres (il s' agit de ceux contenant <code>trr</code>).</p>
<p>Les deux paramètres qui nous intéressent sont <code>network.trr.mode</code> et <code>network.trr.uri</code>. Le premier commande le comportement du module DoH de Firefox, le deuxième est l'URL du résolveur DoH à utiliser (on constate que c'est le résolveur de CloudFlare qui est configuré par défaut. Vous pouvez le supprimer 🙂).</p>
<img alt="" src="images/firefox-trr.png">
<p>Les valeurs possibles de <code>network.trr.mode</code> sont :</p>
<ul>
<li>0 : DoH est désactivé. Valeur par défaut.</li>
<li>1 : Envoie la requête au résolveur DoH configuré et au résolveur configuré au niveau du système. Utilise la première réponse qui arrive.</li>
<li>2 : Utilise DoH en premier et si la résolution échoue, se repli sur le résolveur du système.</li>
<li>3 : Utilise uniquement DoH.</li>
<li>4 : Utilise DoH et le résolveur du système en parallèle mais n'utilise pas les réponses obtenues via DoH. Ce dernier ne sert qu'à faire des mesures de performances.</li>
<li>5 : Désactive explicitement DoH. C'est le paramètre que je recommande si vous ne comptez pas utiliser DoH.</li>
</ul>
<p>Voilà, vous êtes armé·e·s pour chiffrer votre trafic DNS. Comme je le disais dans ma présentation, ce n'est malheureusement pas vraiment accessible pour un large public (celui capable de changer le résolveur à utiliser dans Windows à peu près), la faute au manque de logiciels simples d'accès. En attendant, que les OS intègrent nativement ces techniques via des interfaces graphiques (seul Android 9 le fait pour l'instant), cela risque de ne pas changer...</p>
Mon blog utilise TLS 1.3
2018-12-22T22:20:00+01:00
2022-09-06T21:50:05+02:00
https://www.shaftinc.fr/blog-tls1-3.html
<h2>Le votre aussi bientôt j'espère</h2>
<p>10 ans après la normalisation de TLS 1.2 dans le RFC 5246, TLS 1.3 a finalement été finalisé en août 2018 dans le <a href="https://www.rfc-editor.org/rfc/rfc8446.txt">RFC 8446</a> après une gestation particulièrement longue et mouvementée (le premier brouillon date <a href="https://datatracker.ietf.org/doc/draft-ietf-tls-tls13/">d'avril 2014</a>). Dans la foulée, en septembre, la <a href="https://www.openssl.org/blog/blog/2018/09/11/release111/">version d'OpenSSL</a> le prenant en charge (1.1.1) sort. Le temps de vérifier et corriger les logiciels qui en dépendent, cette version est arrivée dans Debian Buster (actuelle branche de test) à la toute fin octobre. La <a href="https://archive.apache.org/dist/httpd/CHANGES_2.4.37">version d'Apache</a> compatible (2.4.36) n'a pas été mise à disposition dans Buster, qui est passé directement à la 2.4.37 quelques jours après OpenSSL 1.1.1.</p>
<p>Bref, ce site est désormais capable d'utiliser TLS 1.3 depuis novembre. Si les protocoles sont gérés de la sorte dans la configuration TLS de votre serveur Apache :</p>
<pre><code> SSLProtocol ALL -SSLv3 -TLSv1 -TLSv1.1</code></pre>
<p>Bravo, vous n'avez rien à faire : Apache est capable d'utiliser TLS 1.3 tout seul comme un grand pour peu que vos logiciels soient à jour 🙂. Regardons l'ordre défini pour les suites de chiffrements du protocole dans OpenSSL :</p>
<pre>
# openssl ciphers -v | grep TLSv1.3
TLS_AES_256_GCM_SHA384 TLSv1.3 Kx=any Au=any Enc=AESGCM(256) Mac=AEAD
TLS_CHACHA20_POLY1305_SHA256 TLSv1.3 Kx=any Au=any Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_AES_128_GCM_SHA256 TLSv1.3 Kx=any Au=any Enc=AESGCM(128) Mac=AEAD</pre>
<p>La machine hébergeant ce blog n'ayant pas les <a href="https://fr.wikipedia.org/wiki/Jeu_d%27instructions_AES">instructions AES-NI</a>, je préfère mettre <a href="https://fr.wikipedia.org/wiki/Salsa20">ChaCha20-Poly1305</a> en premier, puis AES-128 en deuxième. Pour dire à Apache d'utiliser un ordre différent, on passe toujours par la directive <code>SSLCipherSuite</code> du <code><a href="https://httpd.apache.org/docs/2.4/mod/mod_ssl.html">mod_ssl</a></code>. Ceci dit les suites de chiffrements étant gérées par <a href="https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_ciphersuites.html">un paramètre spécifique</a> dans OpenSSL, il y a un <i>twist</i> et il faut indiquer à Apache qu'il s'agit des algorithmes de TLS 1.3. Il faut donc explicitement mettre <code>TLSv1.3</code> après le nom de la directive. Ce qui implique qu'il faut désormais indiquer 2 fois la directive, une première fois pour TLS 1.2 et une deuxième pour TLS 1.3 :</p>
<pre><code> # Suite de chiffrements TLSv1.2
SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384
# Suite de chiffrements TLSv1.3
SSLCipherSuite TLSv1.3 TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384</code></pre>
<p>Attention, TLS 1.3 interdisant la renégociation, il est impossible de spécifier l'ordre des algorithmes dans un contexte de répertoire (<code><Directory></code>). Une <a href="https://web.archive.org/web/20190914231135/https://www.shaftinc.fr/blog-tls1-3.html">ancienne version de cet article</a> disait que <code>SSLCipherSuite</code> n'était pas capable de gérer l'ordre de chiffrement pour TLS 1.3 et qu'il fallait utiliser par l'API <code>SSL_CONF</code> d'OpenSSL via la directive <a href="https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslopensslconfcmd"><code>SSLOpenSSLConfCmd</code></a> et ainsi passer au <code>mod_ssl</code> la commande souhaitée. La technique fonctionnait mais le billet était faux sur ce point.</p>
<p>Petite astuce tout de même autour de la directive <code>SSLOpenSSLConfCmd</code>, c'est grâce à elle que je peux forcer les courbes elliptiques à utiliser pour <a href="https://fr.wikipedia.org/wiki/%C3%89change_de_cl%C3%A9s_Diffie-Hellman_bas%C3%A9_sur_les_courbes_elliptiques">ECDH</a> dans la configuration d'Apache :</p>
<pre><code> SSLOpenSSLConfCmd Curves X25519:sect571r1:secp521r1:secp384r1</code></pre>
<p><code>Curves</code> est ici un paramètre d'OpenSSL et non d'Apache. Vous remarquerez que <code>prime256v1</code> est absente (et ce depuis fort longtemps) et que ça ne semble gêner personne. <a href="https://www.ssllabs.com/ssltest/">L'outil de test de SSLLabs</a> est fort pratique pour tester la compatibilité théorique des clients et prendre des décisions en fonction.</p>
<p>Et côté client justement ? TLS 1.3 est activé par défaut depuis <a href="https://www.mozilla.org/en-US/firefox/60.0/releasenotes/">Firefox 60</a> (Il était présent depuis la version 52 mais devait être <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1310516)">activé manuellement</a>). Dans Chrome, il est activé par défaut depuis <a href="https://www.chromestatus.com/feature/5934924745932800">Chrome 70</a> – après une brève activation en 2017 (La Pieuvre du faire machine arrière à cause de <a href="https://en.wikipedia.org/wiki/Middlebox"><i>middleboxes</i></a> dangeuresement débiles, notamment celles des <a href="https://reflets.info/articles?search=BlueCoat">grands défenseurs des droits humains</a> de chez <a href="https://web.archive.org/web/20170912061432/http://bluecoat.force.com/knowledgebase/articles/Technical_Alert/000032878">BlueCoat</a>).</p>
<p>Ceci étant dit, ces 2 navigateurs ne sont pas les seuls clients se connectant à ce site. Une grande majorité des requêtes vient plutôt de robots, que ce soit les <a href="https://fr.wikipedia.org/wiki/Robot_d%27indexation">crawlers</a> des moteurs de recherches et surtout les agrégateurs de flux RSS/Atom tel que <a href="https://tt-rss.org/">Tiny Tiny RSS</a>.</p>
<p>J'ai donc activé le log des protocoles de chiffrement (j'en parlais dans mon <a href="rgpd-compliant.html">précédent billet</a>), écrit un petit script qui compte le nombre de connexions quotidiennes et le protocole utilisé, en dédoublonnant par IP (elles ne sont pas conservées outre la politique habituelle de log), agrégé le tout par semaine et passé le tout à <a href="http://gnuplot.info"><code>gnuplot</code></a> afin d'avoir un beau graphique permettant de suivre l'évolution de l'adoption de TLS 1.3 se connectant au site. Il sera mis à jour tous les dimanches, lors de la rotation de logs du serveur.</p>
<p>Une connexion correspond donc à la venue d'un client sur une journée de la semaine, quel que soit le nombre de requêtes effectuées par la suite (si le comptage se faisait par requêtes, mes appareils seraient sur-représentés). Les statistiques commencent au 18 novembre, dernier jour de la semaine 46. Les données pour cette semaine ne sont donc pas nécessairement fiables.</p>
<a href="images/stat-tls.png"><img alt="" title="Statistiques hebdomadaire des protocoles TLS utilisés" src="images/stat-tls.png"></a>
<a href="images/stat-tls-1.png"><img alt="" title="Statistiques hebdomadaire des protocoles TLS utilisés" src="images/stat-tls-1.png"></a>
<a href="images/stat-tls-2.png"><img alt="" title="Statistiques hebdomadaire des protocoles TLS utilisés" src="images/stat-tls-2.png"></a>
<p>Il est aussi accessible via une <a href="tls13.html">page dédiée</a> (regardez le menu 🙂).</p>
Un blog « RGPD compliant »
2018-05-25T23:50:00+02:00
2022-01-30T22:10:06+01:00
https://www.shaftinc.fr/rgpd-compliant.html
<h2>Enfin j'espère</h2>
<p>Chez Shaft Inc, votre vie privée est au centre de nos préoccupations. Afin d'être transparents sur les données que nous traitons, nous vous présentons une nouvelle version de nos conditions d'utilisation, entrants en vigueur le 25 mai 2018...</p>
<p>Ce type d'<a href="https://fr.wiktionary.org/wiki/amphigouri">amphigouri</a> pullule sur nombre de sites et services Web en cette journée <a href="https://www.nextinpact.com/news/106618-le-rgpd-entre-en-application-10-questions-10-reponses.htm">d'entrée en vigueur du Règlement général sur la protection des données personnelles</a> (le fameux RGPD). Étant mine de rien, administrateur de ce site, je me retrouve donc responsable des données que vous pouvez y laisser. L'occasion de voir donc ce que j'en fait (spoiler : rien) et comment je les sécurise (puisque la plus grosse menace, à ne jamais négliger, est la compromission de la machine par un tiers et la copie frauduleuse des données qu'elle contient)</p>
<h3>Logs</h3>
<p>Le serveur Web affichant cette belle page est un Apache, configuré de manière a priori normale niveau log :</p>
<pre><code> <VirtualHost *:443>
...
CustomLog ${APACHE_LOG_DIR}/access.log combined</code></pre>
<p>J'ai donc des logs de cette forme sur le port 443 :</p>
<pre>
2001:db8::2505:2018 - - [25/May/2018:15:44:21 +0000] "GET / HTTP/2.0" 200 13377 "https://example.org/shaft-inc-trop-fort" "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0"</pre>
<p>Sur le port 80, le log est quasiment identique (il n'y a a priori que des robots discutant avec le port 80, HTTPS n'étant pas une option)</p>
<pre>
www.shaftinc.fr:80 2001:db8::2505:2018 - - [25/May/2018:15:44:21 +0000] "GET / HTTP/1.1" 308 234 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0"</pre>
<p>Votre IP peut également se retrouver dans le log d'erreur, si vous tentez de faire des bêtises :</p>
<pre>
[Sun May 20 19:42:00.138363 2018] [authz_core:error] [pid 8255:tid 150448084659000] [client 2001:db8::2505:2018:51584] AH01630: client denied by server configuration: /chemin/blog/index.html</pre>
<p>À moins d'utiliser le User-Agent d'un bot que j'ai identifié comme non désirable, peu de chances de tomber sur ce cas.</p>
<p>À l'occasion, typiquement lorsque j'introduis de la nouveauté dans la configuration TLS du serveur (pas de changement prévu avant l'arrivée de TLS 1.3 je pense), il m'arrive d'ajouter un autre log, configuré comme suit :</p>
<pre><code> CustomLog ${APACHE_LOG_DIR}/tls.log "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"</code></pre>
<p>Log qui a la particularité de donner des détails sur les protocoles de chiffrement utilisés (ça me fut bien pratique pour me décider à couper définitivement TLS 1.0 et dégager des suites de chiffrements sans remords) :</p>
<pre>
[25/May/2018:15:44:21 +0000] 2001:db8::2505:2018 TLSv1.2 ECDHE-RSA-CHACHA20-POLY1305 "GET / HTTP/2.0" 13377</pre>
<p>Les seules données personnelles que vous laissez sont donc votre adresse IP, la page consultée ici bas, et éventuellement le référent (<i>referrer</i>) si vous venez d'un autre site. C'est à la fois peu et énorme. J'ai donc pour politique de ne garder ces données au maximum 2 semaines (je suis ce que fait la Quadrature du Net avec <a href="https://mamot.fr/terms">son instance Mastodon</a> par exemple). L'effacement se fait à la rotation des logs, configurées comme-ci avec <code>logrotate</code> :</p>
<pre><code> /var/log/apache2/*.log {
weekly
missingok
rotate 1
notifempty
create 640 root adm
sharedscripts
postrotate
if /etc/init.d/apache2 status > /dev/null ; then \
/etc/init.d/apache2 reload > /dev/null; \
fi;
endscript
prerotate
if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
run-parts /etc/logrotate.d/httpd-prerotate; \
fi; \
endscript
}</code></pre>
<p>Une instance Nextcloud est également hébergée sur ce site et quelques fichiers sont accessibles librement, notamment depuis d'autres articles de blogs (<a href="https://www.bortzmeyer.org/7816.html">ici par exemple</a>). La politique de log, définie dans <code>config.php</code>, de cette instance est la suivante :</p>
<pre><code> 'log_type' => 'owncloud',
'logdateformat' => 'd-M-Y H:i:s O',
'logfile' => '/var/log/nextcloud.log',
'loglevel' => 2,
'log_authfailip' => true,</code></pre>
<p>Le niveau 2 correspondant au log d'avertissement (<q>WARN</q>) selon <a href="https://docs.nextcloud.com/server/13/admin_manual/configuration_server/logging_configuration.html">la documentation</a>, donc votre IP n'est pas logguée ici si vous tentez de télécharger un fichier librement accessible. Le seul cas où cela peut arriver est normalement, encore une fois, en faisant une bêtise et en spammant l'authentification sur la page d'accueil de Nextcloud.</p>
<p>Parmi les autres logs pouvant contenir votre adresse IP, les logs Fail2ban. A moins de faire des bêtises, peu de chances de s'y retrouver. Les logs Nextcloud et Fail2ban sont conservés selon la même politique que pour Apache.</p>
<p>Enfin dernier log pouvant révéler une visite ici, le pare-feu. A raison de 5 entrées maximum par minute, les paquets jetés par <code>iptables</code> sont inscrits dans un fichier. Les données étant conservées une semaine.<p>
<p>Il reste entendu qu'un malveillant s'emparant du disque dur du serveur peut tenter une récupération de données effacées et donc des logs normalement supprimés.</p>
<h3>Base de données</h3>
<p>Le gros morceau, c'est la galerie Photos tournant sous <a href="https://fr.piwigo.org/">Piwigo</a>. Il y a derrière une base de données et cette dernière contient notamment par défaut deux tables, <code>history</code> et <code>sessions</code>, pouvant être problématique. <code>sessions</code> contient les id de session contenus dans les cookies envoyés par Piwigo aux visiteurs ainsi que quelques données (qui ne sont pas systématiquement les mêmes, la plus explicite que j'ai pu trouver est la taille de la fenêtre du navigateur. Ceci dit l'id de session reste problématique car si ce serveur et votre PC sont compromis, un lien peut être établi. <code>history</code> contient quand à elle notamment l'IP des visiteurs et les pages visitées :</p>
<pre class="scroll">
MariaDB [piwigo]> SELECT * FROM piwi_history LIMIT 10;
+-------+------------+----------+---------+-----------------+--------------+-------------+---------+----------+------------+------------+-----------+-------------+
| id | date | time | user_id | IP | section | category_id | tag_ids | image_id | summarized | image_type | format_id | auth_key_id |
+-------+------------+----------+---------+-----------------+--------------+-------------+---------+----------+------------+------------+-----------+-------------+
| 17315 | 2018-05-24 | 22:47:18 | 2 | 2001:db8:2505:1 | categories | NULL | NULL | NULL | false | NULL | NULL | NULL |
| 17316 | 2018-05-24 | 22:47:21 | 2 | 2001:db8:2505:1 | categories | 4 | NULL | NULL | false | NULL | NULL | NULL |
| 17317 | 2018-05-24 | 22:47:23 | 2 | 2001:db8:2505:1 | categories | 4 | NULL | 385 | false | picture | NULL | NULL |
| 17318 | 2018-05-24 | 22:56:26 | 2 | 2001:db8:2505:1 | categories | NULL | NULL | NULL | false | NULL | NULL | NULL |
| 17319 | 2018-05-24 | 22:56:28 | 2 | 2001:db8:2505:1 | most_visited | NULL | NULL | NULL | false | NULL | NULL | NULL |
| 17320 | 2018-05-24 | 22:56:31 | 2 | 2001:db8:2505:1 | most_visited | NULL | NULL | 1019 | false | picture | NULL | NULL |
| 17321 | 2018-05-24 | 22:56:36 | 2 | 192.0.2.128 | most_visited | NULL | NULL | NULL | false | NULL | NULL | NULL |
| 17322 | 2018-05-24 | 22:56:37 | 2 | 192.0.2.128 | most_visited | NULL | NULL | 627 | false | picture | NULL | NULL |
| 17323 | 2018-05-24 | 22:56:38 | 2 | 192.0.2.128 | most_visited | NULL | NULL | NULL | false | NULL | NULL | NULL |
| 17324 | 2018-05-24 | 22:56:40 | 2 | 192.0.2.128 | most_visited | NULL | NULL | 952 | false | picture | NULL | NULL |
+-------+------------+----------+---------+-----------------+--------------+-------------+---------+----------+------------+------------+-----------+-------------+</pre>
<p>Le <code>user_id</code> correspond à l'utilisateur <q>Guest</q>. À noter que si vous utilisez IPv6, la nullité de MySQL et MariaDB (qui ne proposent toujours pas de types spécifiques pour manier les IPs) et le manque de sérieux des dévs de Piwigo vous protègent en partie, l'IP étant pour eux une chaine de caractères de longueur 15 au maximum :</p>
<pre><code> CREATE TABLE `piwi_history` (
...
`IP` VARCHAR(15) NOT NULL DEFAULT '',</code></pre>
<p>Bref, trêve de péroraisons : ces données sont inutiles (voir redondantes avec les logs), je ne les conserve qu'une journée. Une purge des tables est possible via la console d'administration de Piwigo, mais ce n'est pas la panacée. Par conséquent, tous les jours à 06:20, la table est purgée via un événement SQL :</p>
<pre><code> CREATE EVENT `purge_piwi`
ON SCHEDULE
EVERY 1 DAY STARTS '2018-05-20 06:20:00'
ON COMPLETION PRESERVE
ENABLE
COMMENT 'Purge quotidienne de la base Piwigo'
DO BEGIN
TRUNCATE piwi_history;
TRUNCATE piwi_sessions;
END</code></pre>
<p>(avant le 20 mai, je conservais 2 semaines de données comme pour les logs mais à la réflexion cela est inutile)</p>
<p>La base est <q>dumpée</q> tous les jours à 06h29 et envoyée en tant que backup sur une autre machine. Là, ce dump est monté dans ce qu'on pourrait appeler une base miroir. Afin d'éviter les soucis entre l'heure de la suppression des données (06h20) et celle du dump (06h29 modulo les tâches en cours à cet instant), l'événement est répété sur la base miroir à 06h45.</p>
<p>Pour Nextcloud, point de requêtes en base de données à faire : pas de tables contenant l'historique des visiteurs et pas non plus, a priori, de table avec les cookies de session. Étant le seul utilisateur enregistré de l'instance, si les données personnelles qui y sont stockées se retrouvent dans la nature, c'est à moi-même que je devrais m'en prendre. Une petite remarque cependant si vous administrez une instance et que cette dernière possède quelques utilisateurs : par défaut l'application Activity, qui permet entre autre choses d'envoyer des notifications aux membres en cas de partages de fichiers, répertoires..., est activée et elle conserve en base des données personnelles (du type tel utilisateur a téléversé/effacé/partagé un fichier, ce genre de chose). Il est possible de configurer Nextcloud pour faire une purge de cette table. Dans le fichier <code>config.php</code>, ajouter :<p>
<pre><code> 'activity_expire_days' => 7,</code></pre>
<p>(ou moins si vous voulez garder les données moins d'une semaine)</p>
<h3>Cookies et autres joyeusetés</h3>
<p>On l'a vu, Piwigo laisse un cookie de session sur votre machine. Ils y en a typiquement 4 (mais peut-être plus). Le premier nommé <code>pwg_display_thumbnail</code> qui vaut en général <code>no_thumbnail_display</code>, <code>cap</code> qui indique la taille de la fenêtre du navigateur et <code>pwg_id</code> du type <code>a42d0riyyleetv5p33keujrgpd</code>. Ils sont normalement valides jusqu'à la fin de votre session.</p>
<p>Nextcloud va lui déposer 4 cookies : le premier dont le nom (et le contenu) sont un identificateur technique, le deuxième nommé <code>oc_sessionPassphrase</code> est dont la valeur est une longue phrase de passe. Ces deux là sont censés expirer à la fin de la session. Les deux derniers <code>nc_sameSiteCookielax</code> et <code>nc_sameSiteCookiestrict</code> valent tous les deux <code>true</code> et expirent en 2101 (!). Dans tous les cas, pensez à configurer votre navigateur pour effacer ces petits gâteaux à sa fermeture.<p>
<p>Enfin, comme précisé sur la page <a href="shaft.html">À Propos</a> :</p>
<blockquote cite="shaft.html">Les seules requêtes externes que ce site fera sont la récupération de tuiles <a href="https://fr.wikipedia.org/wiki/OpenStreetMap">OpenStreetMap</a> sur la partie Photos et rien d'autre. Voir la <a href="https://wiki.osmfoundation.org/wiki/Privacy_Policy#Log_files_and_Information_submitted_to_OSMF_Services">politique d'OSM</a> concernant la vie privée. Si elle vous semble insupportable, bloquez toutes requêtes vers <code>*.tile.openstreetmap.org</code> avec une extension telle que <a href="https://www.eff.org/privacybadger">Privacy Badger</a> ou <a href="https://addons.mozilla.org/fr/firefox/addon/umatrix/">uMatrix</a>. À noter que ce site utilise désormais l'entête <q><a href="https://w3c.github.io/webappsec/specs/referrer-policy/">Referrer-Policy</a></q> de manière à ne pas envoyer d'entête <code><a href="https://fr.wikipedia.org/wiki/R%C3%A9f%C3%A9rent_(informatique)">referer</a></code> vers d'autres sites (valeur : <code>same-origin</code>). Attention, la chose est récente et seul un navigateur compatible (Firefox l'est à partir de la version 52) sera en mesure de l'utiliser.</blockquote>
<p>Je pense avoir fait le tour des données personnelles vous appartenant qui sont collectées en ces lieux et la manière dont je les gère. Cette politique me semble être raisonnable dans le compromis à faire entre rétention nécessaire de données pour les tâches d'administration et respect de la vie privée. Si ça ne vous semble pas le cas, on peut en discuter sur <a href="https://mamot.fr/@shaft">Mastodon</a> ou autour d'une bière à l'occasion 🙂.</p>