Initial commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
file: images/DTaMGE.webp
|
||||
title: Illustration par DALL·E
|
||||
@@ -0,0 +1,2 @@
|
||||
file: images/mMfOpQ.png
|
||||
title: Utilisez le bouton "Brut" pour obtenir l'URL du fichier à rajouter à blocky
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 147 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
@@ -0,0 +1,429 @@
|
||||
---
|
||||
cover: images/DTaMGE.webp
|
||||
date: "2024-05-21T00:10:53+02:00"
|
||||
title: Blocky et Unbound sous NixOS
|
||||
---
|
||||
|
||||
## Contexte
|
||||
|
||||
### Hardware
|
||||
|
||||
J'en ai marre des Raspberry Pi.
|
||||
Ça fait depuis longtemps que [je le dis](/interets/informatique/2021/02/28/rant-raspberry-pi-4/), et le temps n'a malheureusement pas corrigé certains défauts.
|
||||
Je vais oser l'affirmation :
|
||||
|
||||
**Il est impossible d'avoir un uptime décent pour faire un serveur d'un Raspberry Pi**, peu importe le type de serveur ou la génération de Raspberry Pi dont on parle.
|
||||
Ce sont des machines d'_expérimentation_, et malgré les soins que je leur apporte, je ne peux pas les utiliser comme des serveurs pour deux raisons :
|
||||
|
||||
- le réseau est instable (en particulier en Wifi mais c'est aussi vrai en ethernet pour les RPi qui en sont dotés, en tout cas le 4 comme on va le voir)
|
||||
- le système de stockage aux fraises (je ne parle pas des cartes SD mais du stockage USB)
|
||||
|
||||
Pour les caméras, ce n'est pas trop grave si l'une d'elles se déconnecte pendant quelques minutes.
|
||||
C'est beaucoup plus embêtant pour un serveur DNS, en substance [pi-hole](https://pi-hole.net/), hébergé depuis quelque temps sur un [Raspberry Pi 4](https://www.raspberrypi.com/products/raspberry-pi-4-model-b/).
|
||||
La machine utilise l'[ISO officielle](https://www.raspberrypi.com/software/operating-systems/#raspberry-pi-os-64-bit) de la fondation et il n'y a que pi-hole qui y soit installé.
|
||||
**Aucune configuration supplémentaire** n'est faite sur la machine.
|
||||
L'installation est parfaitement _vanilla_, sauf ce qui est installé par pi-hole.
|
||||
|
||||
Le Pi 4 est connecté en ethernet, et il boote depuis un SSD SATA en USB3.
|
||||
Je ne me fous pas de la gueule de cette machine quand même.
|
||||
|
||||
Occasionnellement, une fois par semaine ou peut-être plus, la machine _disparaît_ du réseau.
|
||||
Comme ça : pouf, plus de Raspberry Pi.
|
||||
Plus de DNS.
|
||||
Plus de SSH.
|
||||
Plus de pi-hole.
|
||||
Plus _rien_.
|
||||
Sans explication : pas de log, rien, que dalle, _peanuts_.
|
||||
|
||||
### Software
|
||||
|
||||
J'adore pi-hole.
|
||||
L'application fonctionne exactement comme je veux qu'elle le fasse.
|
||||
Je vois en temps réel les domaines demandés par le réseau, je peux black/whitelister un domaine en un clic, c'est très frugal, ça répond bien, bref, c'est un excellent logiciel.
|
||||
|
||||
Malgré ces éloges, j'ai trois "problèmes" :
|
||||
|
||||
- installer un upstream récursif sur la même machine n'est pas "simple" (c'est-à-dire que [c'est facile de copier/coller des commandes](https://docs.pi-hole.net/guides/dns/unbound/), mais les comprendre — et surtout comprendre pourquoi ça ne fonctionne pas — est une autre histoire)
|
||||
- installer un cluster de pi-hole est tout sauf trivial ([il y a moyen](https://discourse.pi-hole.net/t/clustered-pihole-ive-done-it/12716/7) mais ce n'est absolument pas propre)
|
||||
- pas d'installation sous NixOS (selon ma politique personnelle, docker n'est pas une option)
|
||||
|
||||
## Perdre pour gagner avec Blocky, Unbound et NixOS
|
||||
|
||||
Hors de question de re-tenter le coup avec AdGuard Home pour les raisons que j'ai déjà détaillées [ici](/interets/informatique/2023/06/21/migration-de-pi-hole-vers-adguard-home-et-digression/).
|
||||
Une alternative (parmi d'autres) est [blocky](https://github.com/0xERR0R/blocky).
|
||||
Une "petite" application écrite en Go, qui fait office de proxy DNS et de blocklist.
|
||||
Avec unbound en upstream, on obtient une solution _similaire_ à pi-hole.
|
||||
C'est la "stack" que l'on va installer sur un "vrai" serveur.
|
||||
|
||||
_Similaire_ parce que, "malheureusement", on va perdre l'UI, mais est-ce que ça sera vraiment un problème ?
|
||||
Pour l'instant, je ne sais pas...
|
||||
|
||||
### Pré-requis
|
||||
|
||||
Partons du principe qu'on a un serveur DNS fonctionnel sur le réseau (typiquement, le routeur ou le serveur DNS à remplacer).
|
||||
|
||||
On va installer Blocky et Unbound sur un serveur sous [NixOS](https://nixos.org/).
|
||||
|
||||
### Unbound
|
||||
|
||||
```nix
|
||||
{
|
||||
services.unbound = {
|
||||
enable = true;
|
||||
resolveLocalQueries = false;
|
||||
|
||||
settings = {
|
||||
server = {
|
||||
interface = "127.0.0.1@5353";
|
||||
access-control = [ "127.0.0.1/0 allow" ];
|
||||
verbosity = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
On passe `resolveLocalQueries` à `false` pour qu'unbound ne pirate pas le `resolv.conf` : étant donné qu'il n'écoute pas sur le port standard, si on ne met pas cette directive, NixOS ne pourra plus résoudre des domaines après l'installation d'unbound.
|
||||
|
||||
`interface` et `access-control` ne servent respectivement qu'à indiquer comment le serveur doit écouter (en l'occurrence, uniquement depuis `127.0.0.1` sur le port `5353`) et n'autoriser que la machine locale à l'utiliser.
|
||||
|
||||
- [Consulter toutes les directives de configuration d'unbound pour NixOS](https://search.nixos.org/options?channel=23.11&from=0&size=50&sort=alpha_asc&type=packages&query=services.unbound).
|
||||
|
||||
Et c'est **tout** : tous les autres paramètres par défaut sont suffisants, nous n'avons besoin de rien d'autre.
|
||||
|
||||
```shell
|
||||
nix-os rebuild switch
|
||||
```
|
||||
|
||||
Et on s'assure que ça fonctionne :
|
||||
|
||||
```shell
|
||||
nix-shell -p dig # Shell temporaire pour lancer dig
|
||||
dig free.fr @127.0.0.1 -p 5353
|
||||
```
|
||||
|
||||
<pre><samp>
|
||||
; <<>> DiG 9.18.26 <<>> free.fr @127.0.0.1 -p 5353
|
||||
;; global options: +cmd
|
||||
;; Got answer:
|
||||
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 22474
|
||||
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
|
||||
|
||||
;; OPT PSEUDOSECTION:
|
||||
; EDNS: version: 0, flags:; udp: 1232
|
||||
;; QUESTION SECTION:
|
||||
;free.fr. IN A
|
||||
|
||||
;; ANSWER SECTION:
|
||||
free.fr. 600 IN A 212.27.48.10
|
||||
|
||||
;; Query time: 67 msec
|
||||
;; SERVER: 127.0.0.1#5353(127.0.0.1) (UDP)
|
||||
;; WHEN: Mon May 20 00:49:10 CEST 2024
|
||||
;; MSG SIZE rcvd: 52
|
||||
</samp></pre>
|
||||
|
||||
On peut passer à la suite.
|
||||
|
||||
L'avantage avec NixOS (et plus généralement, les distros du même genre) c'est qu'à ce stade, si ça ne fonctionne pas, ça ne vient pas de la configuration 😁
|
||||
|
||||
### Blocky
|
||||
|
||||
```nix
|
||||
{
|
||||
services.blocky = {
|
||||
enable = true;
|
||||
settings = {
|
||||
# On peut utiliser plusieurs upstreams dans plusieurs groupes...
|
||||
upstreams = {
|
||||
groups = {
|
||||
default = [
|
||||
# Mais en l'occurrence, on va se contenter d'utiliser notre unbound
|
||||
"127.0.0.1:5353"
|
||||
];
|
||||
};
|
||||
};
|
||||
conditional = {
|
||||
# Si Blocky n'a pas déjà résolu un domaine, il demande à l'upstream.
|
||||
# Si on mettait false, on obtiendrait une erreur de résolution.
|
||||
fallbackUpstream = true;
|
||||
mapping = {
|
||||
# Ça c'est personnel : c'est 10.0.0.1 qui répond pour le domaine
|
||||
# home.arpa sur mon réseau parce que c'est le DHCP et qu'il stocke les
|
||||
# noms d'hôtes du réseau local
|
||||
"home.arpa" = "10.0.0.1";
|
||||
};
|
||||
};
|
||||
blocking = {
|
||||
blackLists = {
|
||||
ads = [
|
||||
# Là on ajoute les listes que l'on veut, à commencer par la plus
|
||||
# célèbre
|
||||
https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
|
||||
];
|
||||
};
|
||||
clientGroupsBlock = {
|
||||
default = [
|
||||
# Sans cette ligne, rien ne sera bloqué, tout sera envoyé à
|
||||
# l'upstream. Le nom doit correspondre à au moins une liste créée
|
||||
# dans `blackLists`
|
||||
"ads"
|
||||
];
|
||||
};
|
||||
};
|
||||
# Les ports d'écoute
|
||||
ports = {
|
||||
dns = 53;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Voyez que si l'on virait les lignes ne servant qu'à ouvrir et fermer des blocs de config, on n'aurait moins d'une dizaine de lignes de configuration.
|
||||
Si vous préférez quelque chose de plus condensé :
|
||||
|
||||
```nix
|
||||
{
|
||||
services.blocky.enable = true;
|
||||
services.blocky.settings.upstreams.groups.default = [ "127.0.0.1:5353" ];
|
||||
services.blocky.settings.conditional.fallbackUpstream = true;
|
||||
services.blocky.settings.conditional.mapping."home.arpa" = "10.0.0.1";
|
||||
services.blocky.settings.blocking.blackLists.ads = [
|
||||
https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
|
||||
];
|
||||
services.blocky.settings.clientGroupsBlock.default = [ "ads" ];
|
||||
services.blocky.settings.ports.dns = 53;
|
||||
}
|
||||
```
|
||||
|
||||
**On répète exactement la même configuration sur un deuxième serveur.**
|
||||
On obtient deux serveurs DNS capables de bloquer des domaines sur le réseau, mais, pour obtenir un véritable effet "cluster", il faut aller un peu plus loin.
|
||||
|
||||
Les directives de configuration de Blocky pour NixOS se résument à `enable` et `settings`, cette dernière acceptant une notation équivalent à celle de Blocky.
|
||||
On pourra donc utiliser "directement" [la documentation de Blocky](https://0xerr0r.github.io/blocky/v0.23/configuration/) pour le configurer dans NixOS.
|
||||
|
||||
### redis
|
||||
|
||||
J'utilise de toute façon redis sur mon serveur web, ce n'est pas bien compliqué de l'installer sur le reverse-proxy (qui est aussi une machine sous NixOS).
|
||||
|
||||
Je vais d'ailleurs considérer que le redis du serveur web (`10.0.2.2`) sera le maître du réseau, tandis que celui du reverse-proxy (`10.0.2.1`) sera l'esclave.
|
||||
|
||||
Le redis maître sera configuré comme suit :
|
||||
|
||||
```nix
|
||||
{
|
||||
services.redis.vmOverCommit = true;
|
||||
|
||||
services.redis.servers.blocky = {
|
||||
enable = true;
|
||||
bind = "0.0.0.0";
|
||||
port = 16379
|
||||
masterAuth = "secret";
|
||||
requirePass = "secret";
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Notons que nous créons un service redis spécialement dédié à blocky.
|
||||
On applique [la modification que redis recommande](https://redis.io/docs/latest/develop/get-started/faq/#background-saving-fails-with-a-fork-error-on-linux) avec `services.redis.vmOverCommit = true;`.
|
||||
En outre, on fait écouter ce serveur sur un port différent du port original (`6379`) pour pouvoir continuer d'utiliser une autre instance de redis dédiée à d'autres usages.
|
||||
|
||||
Enfin, n'oubliez pas de changer le mot de passe (`secret`).
|
||||
|
||||
Et l'esclave :
|
||||
|
||||
```nix
|
||||
{
|
||||
services.redis.vmOverCommit = true;
|
||||
|
||||
services.redis.servers.blocky = {
|
||||
enable = true;
|
||||
bind = "0.0.0.0";
|
||||
masterAuth = "secret";
|
||||
requirePass = "secret";
|
||||
|
||||
slaveOf = {
|
||||
ip = "10.0.2.2";
|
||||
port = 16379;
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Même configuration, mais en précisant simplement quel est le serveur maître.
|
||||
|
||||
Après quelques `nixos rebuild switch`, on devrait voir la sortie suivante sur le serveur maître :
|
||||
|
||||
```shell
|
||||
journalctl -u redis-blocky.service -f
|
||||
```
|
||||
|
||||
<pre><samp>
|
||||
May 20 10:27:08 mac-mini-m1-de-richard redis[97580]: Running mode=standalone, port=16379.
|
||||
May 20 10:27:08 mac-mini-m1-de-richard redis[97580]: Server initialized
|
||||
May 20 10:27:08 mac-mini-m1-de-richard redis[97580]: Loading RDB produced by version 7.2.4
|
||||
May 20 10:27:08 mac-mini-m1-de-richard redis[97580]: RDB age 0 seconds
|
||||
May 20 10:27:08 mac-mini-m1-de-richard redis[97580]: RDB memory usage when created 0.98 Mb
|
||||
May 20 10:27:08 mac-mini-m1-de-richard redis[97580]: Done loading RDB, keys loaded: 0, keys expired: 0.
|
||||
May 20 10:27:08 mac-mini-m1-de-richard redis[97580]: DB loaded from disk: 0.000 seconds
|
||||
May 20 10:27:08 mac-mini-m1-de-richard redis[97580]: Ready to accept connections tcp
|
||||
May 20 10:27:08 mac-mini-m1-de-richard redis[97580]: Ready to accept connections unix
|
||||
May 20 10:27:08 mac-mini-m1-de-richard systemd[1]: Started Redis Server - redis-blocky.
|
||||
May 20 10:27:42 mac-mini-m1-de-richard redis[97580]: Replica 10.0.2.1:16379 asks for synchronization
|
||||
May 20 10:27:42 mac-mini-m1-de-richard redis[97580]: Partial resynchronization request from 10.0.2.1:16379 accepted. Sending 0 bytes of backlog starting from offset 2087.
|
||||
</samp></pre>
|
||||
|
||||
Et sur l'esclave :
|
||||
|
||||
```shell
|
||||
journalctl -u redis-blocky.service -f
|
||||
```
|
||||
|
||||
<pre><samp>
|
||||
May 20 10:27:42 reverse-proxy redis[48394]: Ready to accept connections unix
|
||||
May 20 10:27:42 reverse-proxy systemd[1]: Started Redis Server - redis-blocky.
|
||||
May 20 10:27:42 reverse-proxy redis[48394]: Connecting to MASTER 10.0.2.2:16379
|
||||
May 20 10:27:42 reverse-proxy redis[48394]: MASTER <-> REPLICA sync started
|
||||
May 20 10:27:42 reverse-proxy redis[48394]: Non blocking connect for SYNC fired the event.
|
||||
May 20 10:27:42 reverse-proxy redis[48394]: Master replied to PING, replication can continue...
|
||||
May 20 10:27:42 reverse-proxy redis[48394]: Trying a partial resynchronization (request c55ad3ed1037641cbbf6f8cc36a7e76bc64b59e5:2087).
|
||||
May 20 10:27:42 reverse-proxy redis[48394]: Successful partial resynchronization with master.
|
||||
May 20 10:27:42 reverse-proxy redis[48394]: Master replication ID changed to 00484c469b8a3de337df088cadefc5e2687f2338
|
||||
May 20 10:27:42 reverse-proxy redis[48394]: MASTER <-> REPLICA sync: Master accepted a Partial Resynchronization.
|
||||
</samp></pre>
|
||||
|
||||
Donc, a priori tout fonctionne.
|
||||
|
||||
Il ne reste plus qu'à intégrer redis à Blocky en modifiant sa configuration sur les deux serveurs :
|
||||
|
||||
```nix
|
||||
{
|
||||
services.blocky = {
|
||||
enable = true;
|
||||
settings = {
|
||||
# [...]
|
||||
redis = {
|
||||
address = "127.0.0.1:16379";
|
||||
password = "secret";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Dans un monde idéal, on aurait trois serveurs redis et on utiliserait [sentinel](https://redis.io/docs/latest/operate/oss_and_stack/management/sentinel/), mais dans le cas présent, un système maître/esclave devrait suffire : c'est pourquoi on indique à chaque serveur blocky d'utiliser son propre serveur redis qui sera de toute façon répliqué sur l'autre serveur physique.
|
||||
|
||||
Après quelques instants, on devrait pouvoir voir la base de données de redis se remplir :
|
||||
|
||||
```shell
|
||||
redis-cli -p 16379 -a secret
|
||||
```
|
||||
|
||||
```ansi
|
||||
127.0.0.1:16379> KEYS *
|
||||
1) "blocky:cache:\x00\x01hosts.tweedge.net"
|
||||
2) "blocky:cache:\x00\x01blocklistproject.github.io"
|
||||
3) "blocky:cache:\x00\x1cgit.dern.ovh"
|
||||
4) "blocky:cache:\x00\x01git.dern.ovh"
|
||||
5) "blocky:cache:\x00\x1craw.githubusercontent.com"
|
||||
6) "blocky:cache:\x00\x1cblocklistproject.github.io"
|
||||
7) "blocky:cache:\x00\x01raw.githubusercontent.com"
|
||||
8) "blocky:cache:\x00\x1chosts.tweedge.net"
|
||||
```
|
||||
|
||||
Et on devrait voir plus ou moins la même chose sur les deux serveurs.
|
||||
|
||||
### Blacklist/whitelist personnelles
|
||||
|
||||
Évidemment, on peut modifier la configuration de Blocky pour ajouter ses propres black/whitelists :
|
||||
|
||||
```nix
|
||||
{
|
||||
services.blocky = {
|
||||
enable = true;
|
||||
settings = {
|
||||
# [...]
|
||||
blocking = {
|
||||
blackLists = {
|
||||
ads = [
|
||||
"someadsdomain.com"
|
||||
# [...]
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Mais cela impose de modifier les deux serveurs à chaque fois, puis `nixos rebuild switch`, etc.
|
||||
C'est chiant.
|
||||
Donc, la solution, c'est d'héberger ses listes dans un dépôt git.
|
||||
Du coup, on peut faire ça :
|
||||
|
||||
```nix
|
||||
{
|
||||
services.blocky = {
|
||||
enable = true;
|
||||
settings = {
|
||||
# [...]
|
||||
blocking = {
|
||||
blackLists = {
|
||||
externalLists = [
|
||||
https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
|
||||
];
|
||||
internalLists = [
|
||||
https://git.dern.ovh/Infrastructure/dns/raw/branch/main/blacklist.txt
|
||||
];
|
||||
};
|
||||
whiteLists = {
|
||||
internalLists = [
|
||||
https://git.dern.ovh/Infrastructure/dns/raw/branch/main/whitelist.txt
|
||||
];
|
||||
};
|
||||
};
|
||||
# [...]
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Il n'y a qu'à mettre à jour le dépôt git pour que, ponctuellement, Blocky mette à jour ses listes.
|
||||
Et si on est vraiment pressé, on peut toujours faire `systemctl restart blocky.service`.
|
||||
|
||||
On écrira alors notre blacklist comme un fichier `hosts` :
|
||||
|
||||
```txt
|
||||
0.0.0.0 cache.consentframework.com
|
||||
0.0.0.0 cdn.stripcash.com.c.footprint.net
|
||||
0.0.0.0 consentframework.com
|
||||
0.0.0.0 cookieless-data.com
|
||||
# etc
|
||||
```
|
||||
|
||||
Alors que la whitelist contiendra un domaine par ligne, tout simplement.
|
||||
|
||||
On utilisera alors le chemin vers le fichier brut, fourni par notre forge logicielle.
|
||||
|
||||

|
||||
|
||||
Il y a d'autres façons de faire : blocky peut aussi interpréter des fichiers locaux[^1].
|
||||
Mais j'ai estimé que pour mon cas d'usage, stocker mes black/whitelists dans Gitea était plus intéressant.
|
||||
|
||||
[^1]: <https://0xerr0r.github.io/blocky/v0.23/configuration/#blocking-and-whitelisting>
|
||||
|
||||
Je regrette juste de ne pas encore avoir compris s'il était possible de fournir à blocky la liste des listes à parser : j'aurais aimé stocker dans Gitea un fichier qui contient une liste d'URLs (contenant notamment <https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts> mentionné dans mes exemples, mais pas seulement), mais je n'ai pas — encore — compris comment faire ni même si c'était possible.
|
||||
|
||||
## Conclusion
|
||||
|
||||
La gestion des black/whitelists est un peu plus pénible que sous pi-hole : je ne peux plus afficher en temps réel la liste des domaines demandés, et cliquer sur l'un d'eux pour le (dé)bloquer.
|
||||
Il est possible de partiellement remédier à cela en installant une stack [Prometheus/Grafana](https://0xerr0r.github.io/blocky/v0.23/prometheus_grafana/), ce que je n'ai pas envie de faire pour le moment, pas juste pour blocky.
|
||||
|
||||
Pour le moment, cette solution me satisfait, et, finalement, l'interface web ne me manque pas.
|
||||
Après tout, une fois que tout est en place, on ne touche plus à rien, et la supervision se fait aussi bien en ligne de commande.
|
||||
Et je ne trifouille pas tous les jours les black/whitelists.
|
||||
|
||||
Notons tout de même que tout cela est bien plus simple et rapide à installer, et surtout [à sauvegarder et à restaurer](/interets/informatique/2024/05/04/maintenance-terminee/) que pi-hole sur un Raspberry Pi.
|
||||
|
||||
Deux instances de blocky couplées à deux instances de redis sur deux serveurs physiques "fiables" devraient m'éviter quelques déconvenues à l'avenir...
|
||||
Reference in New Issue
Block a user