Mise en place d'un serveur de filtrage Web avec Unbound et RPZ sur Linux
- Mise à jour le 04 déc. 2024
Je cherchais un moyen de mettre en place un filtrage Web sur GNU/Linux. Je connaissais la méthode avec l'utilisation de squidguard, mais je la trouve un peu pénible à configurer (les paramètres proxy) et assez difficile à déployer automatiquement sur chaque poste de travail, surtout lorsqu'ils ne sont pas gérés par un domaine Active Directory. Bref j'ai découvert en lisant un article sur le pare-feu open source DynFi: https://dynfi.com/, que ce dernier faisait du filtrage Web avec RPZ: https://fr.wikipedia.org/. J'ai donc commencé à étudier cette solution et j'ai trouvé un moyen de mettre en place le filtrage Web avec RPZ.
- Ce que fait cette solution :
- Empêche les utilisateurs d'accéder à une liste de sites interdits
- Affiche une page de blocage lorsque l'accès à un site est bloqué
Schéma réseau
Dans cette architecture, nous aurons un serveur Debian agissant à la fois en tant que serveur DNS et serveur web. Lorsqu'un client fait une demande pour un site interdit (selon une liste de blocage préétablie), il sera redirigé vers le serveur web, avec une page web l'avertissant que le contenu est bloqué.
- Prérequis :
- Bloquer le port 53 (UDP et TCP) sur votre passerelle pour empêcher les postes de travail d'effectuer des requêtes vers des serveurs DNS externes.

Serveur Debian
Comme décrit précédemment, nous aurons deux services sur notre serveur Debian : un serveur web pour afficher un message texte simple informant les utilisateurs qu'ils tentent de se connecter à un site web interdit, et un service DNS pour fournir aux clients des réponses DNS correctes ou modifiées. Pour le serveur web, j'utiliserai micro-httpd, qui est un serveur HTTP léger parfaitement adapté à nos besoins ici, et Unbound en tant que serveur DNS.
micro-httpd
Pour informer les utilisateurs que la page demandée est bloquée, nous aurons besoin d'un serveur web qui affichera une page web les informant que le site web auquel ils essaient d'accéder est interdit.
Installation
- Installer le paquet micro-httpd :
root@host:~# apt install micro-httpd
Configuration
- La configuration de micro-httpd se trouve dans le fichier
/lib/systemd/system/micro-httpd@.service
:
[Unit]
Description=micro-httpd
Documentation=man:micro-httpd(8)
[Service]
User=nobody
Group=www-data
ExecStart=-/usr/sbin/micro-httpd /var/www/html
StandardInput=socket
- Et le fichier
/lib/systemd/system/micro-httpd.socket
:
[Unit]
Description=micro-httpd
Documentation=man:micro-httpd(8)
[Socket]
ListenStream=0.0.0.0:80
Accept=true
[Install]
WantedBy=sockets.target
- Créer un fichier
/var/www/html/index.html
et définir les autorisations appropriées :
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Access Forbidden</title>
</head>
<body>
<h1>Access Forbidden</h1>
<p>Sorry, but you do not have permission to access this page.</p>
</body>
</html>
root@host:~# chown -R www-data:www-data /var/www/html
- Si nécessaire, pour redémarrer micro-httpd on utilisera la commande
systemctl
suivante :
root@host:~# systemctl restart micro-httpd.socket
Ouvrir un navigateur web et accéder à l'adresse http://192.168.0.200/
pour vérifier que l'on accède bien à la page web bloquée.
Unbound
Installation
- Installer le paquet unbound:
root@host:~# apt install unbound
Configuration
- Créer un fichier
/etc/unbound/unbound.conf.d/rpz.conf
:
server:
module-config: "respip validator iterator" # Load respip, validator, and iterator modules
interface: 192.168.0.200 # Network interface used for DNS queries
interface: 127.0.0.1 # Loopback interface used for local DNS queries
do-ip4: yes # Enable IPv4 support
do-ip6: no # Disable IPv6 support
do-udp: yes # Enable UDP support for DNS queries
do-tcp: yes # Enable TCP support for DNS queries
do-daemonize: yes # Run Unbound as a daemon (in the background)
access-control: 0.0.0.0/0 allow # Allow all IP addresses to make DNS queries
local-zone: "std.priv." static # Define a local zone for DNS queries
local-data: "denied.std.priv. IN A 192.168.0.200" # Define a local DNS entry for a specific domain
local-data-ptr: "192.168.0.200 denied.std.priv." # Define a local DNS PTR entry for a specific IP address
rpz:
name: rpz.std.rocks # RPZ zone name
zonefile: /etc/unbound/blacklist.zone # RPZ zone file used for DNS query filtering
- Créer un fichier
/etc/unbound/blacklist.zone
où, à des fins de test, nous redirigerons chaque requête pourorange.fr
etgoogle.fr
vers notre page web bloquée :
*.orange.fr IN A 192.168.0.200
*.google.fr IN A 192.168.0.200
- Redémarrer le service unbound :
root@host:~# systemctl restart unbound
Workstation
- Depuis la station de travail, essayer d'ouvrir
www.google.fr
. Si la redirection fonctionne on devrait être redirigé vers la page de blocage :

- Lorsque nous effectuons une requête DNS, nous constatons que
www.google.fr
etorange.fr
sont redirigés vers l'adresse192.168.0.200
:

Téléchargement et application d'une liste de blocage
Maintenant que nous avons construit notre système de filtrage web, il est temps de le rendre utile. Pour ce faire, nous allons télécharger un fichier de liste de blocage et le formater pour le faire fonctionner avec notre architecture. Il existe de nombreuses listes différentes disponibles sur Internet (chercher avec le terme liste de blocage RPZ). Essayons l'une des listes disponibles sur https://github.com/hagezi/dns-blocklists.
- Les listes RPZ disponibles ont ce format :
website.to.block CNAME .
- Cependant, pour être utile dans notre cas, nous avons besoin de les reformater en :
website.to.block IN A 192.168.0.200
Alors, comment traduire le format par défaut en celui utile dans notre architecture, tel qu'indiqué ci-dessus ? Il existe différentes approches, mais personnellement, j'utiliserai l'éditeur sed. Voyons comment faire !
- Tout d'abord, télécharger l'une des listes RPZ :
root@host:~# wget https://raw.githubusercontent.com/hagezi/dns-blocklists/main/rpz/multi.txt
- Ensuite, formater-la en utilisant
sed
:
root@host:~# sed -i 's/CNAME.*/IN A 192.168.0.200/' multi.txt
Note : Pour empêcher les utilisateurs de contourner la politique, je recommande d'ajouter une liste DoH/VPN/TOR/Proxy et de bloquer les adresses IP DoH sur votre pare-feu. (Consultez cette liste : https://github.com/crypt0rr/public-doh-servers).
Aller plus loin
Une chose qui peut être utile est d'utiliser le contrôle d'accès afin de pouvoir appliquer des filtres différents à des réseaux ou hôtes. Voyons comment cela fonctionne.
- Modifiez le fichier de configuration
/etc/unbound/unbound.conf.d/rpz.conf
:
server:
module-config: "respip validator iterator" # Load respip, validator, and iterator modules
interface: 192.168.0.200 # Network interface used for DNS queries
interface: 127.0.0.1 # Loopback interface used for local DNS queries
do-ip4: yes # Enable IPv4 support
do-ip6: no # Disable IPv6 support
do-udp: yes # Enable UDP support for DNS queries
do-tcp: yes # Enable TCP support for DNS queries
do-daemonize: yes # Run Unbound as a daemon (in the background)
access-control: 0.0.0.0/0 allow # Allow all IP addresses to make DNS queries
local-zone: "std.priv." static # Define a local zone for DNS queries
local-data: "denied.std.priv. IN A 192.168.0.200" # Define a local DNS entry for a specific domain
local-data-ptr: "192.168.0.200 denied.std.priv." # Define a local DNS PTR entry for a specific IP address
define-tag: "social adult dnsbypass"
access-control-tag: 192.168.10.0/24 "social adult dnsbypass"
access-control-tag: 192.168.10.200/32 "social adult"
access-control-tag: 192.168.20.0/24 "adult dnsbypass"
rpz:
name: rpz.social.std.rocks # RPZ zone name
zonefile: /var/lib/unbound/social_networks/blacklist.zone
tags: "social"
rpz:
name: rpz.adult.std.rocks # RPZ zone name
zonefile: /var/lib/unbound/adult/blacklist.zone
tags: "adult"
rpz:
name: rpz.dnsbypass.std.rocks # RPZ zone name
zonefile: /var/lib/unbound/dns_bypass/blacklist.zone
tags: "dnsbypass"
Comme nous pouvons le voir ci-dessus, nous avons créé trois types de listes de filtrage web : social (réseaux sociaux), adulte et dnsbypass. Nous avons appliqué les listes social adulte dnsbypass au réseau 192.168.10.0/24, les listes social adulte à l'hôte 192.168.10.200 et les listes adulte dnsbypass au réseau 192.168.20.0/24.
Dépannage
L'utilisation de listes comportant plus de centaines de milliers d'entrées peut provoquer une erreur lors du démarrage d'unbound. En général, cela est dû au fait que le service met trop de temps à démarrer et qu'il est automatiquement arrêté par le système. Voici comment modifier le délai de démarrage du service unbound.
- Message d'erreur lors du démarrage du service unbound:
root@host:~# systemctl restart unbound
Job for unbound.service failed because a timeout was exceeded.
See "systemctl status unbound.service" and "journalctl -xeu unbound.service" for details.
- (Optionnel) On paramètre notre éditeur, personnelement j'utilise
vim
:
root@host:~# export EDITOR=vim
- Modifier les paramètres systemd pour le service unbound:
root@host:~# systemctl edit unbound.service
- Ajouter ces trois lignes afin d'augmenter la durée que le système permet au service unbound pour démarrer:
### Editing /etc/systemd/system/unbound.service.d/override.conf
### Anything between here and the comment below will become the new contents of the file
[Service]
TimeoutStartSec=300
TimeoutStopSec=300
### Lines below this comment will be discarded
### /lib/systemd/system/unbound.service
# [Unit]
# Description=Unbound DNS server
# Documentation=man:unbound(8)
# After=network.target
# Before=nss-lookup.target
# Wants=nss-lookup.target
#
# [Service]
# Type=notify
# Restart=on-failure
# EnvironmentFile=-/etc/default/unbound
# ExecStartPre=-/usr/libexec/unbound-helper chroot_setup
# ExecStartPre=-/usr/libexec/unbound-helper root_trust_anchor_update
# ExecStart=/usr/sbin/unbound -d -p $DAEMON_OPTS
# ExecStopPost=-/usr/libexec/unbound-helper chroot_teardown
# ExecReload=+/bin/kill -HUP $MAINPID
#
# [Install]
# WantedBy=multi-user.target
- Le service unbound devrait maintenant pouvoir se lancer sans erreur:
root@host:~# systemctl restart unbound