#09 — rgz-dns
EN PRODUCTION
Priorité: 🔴 CRITIQUE · Type: TYPE A · Conteneur: rgz-dns · Code: config/unbound/
Dépendances: Aucune
Description
rgz-dns est le résolveur DNS de la plateforme RGZ. Il s'agit d'Unbound (nlnetlabs/unbound), un résolveur DNS récursif validant performant et sécurisé. Son rôle dépasse la simple résolution de noms : il est au cœur du mécanisme de portail captif (redirection des non-authentifiés), du blocage de domaines malveillants (protection des abonnés), et du contrôle d'accès par VLAN (les abonnés de VLAN 142 peuvent avoir des règles DNS différentes de ceux du VLAN 150).
Le sinkhole DNS (outil #34) est la fonctionnalité la plus critique : Unbound intercepte toutes les requêtes DNS des appareils non-authentifiés et répond avec l'IP du portail captif (access-rgz.duckdns.org). Ainsi, quel que soit le domaine que l'abonné tente de résoudre, il est redirigé vers le portail d'inscription. Cette technique est plus robuste que la redirection purement nftables car elle fonctionne pour les applications qui essaient directement leur DNS avant d'ouvrir une connexion HTTP.
Les blocklists de sécurité sont chargées au démarrage depuis des sources connues : URLhaus (phishing actif), MalwareDomainList (C&C de malwares), et une liste locale RGZ (domaines bloqués sur décision NOC). Ces listes sont actualisées via une tâche Celery planifiée ou un script cron. Quand un abonné tente de résoudre un domaine malveillant, Unbound répond avec NXDOMAIN ou l'IP d'une page de blocage.
Le contrôle d'accès DNS est configuré par sous-réseau VLAN : les VLAN abonnés (10.100.0.0/8) peuvent interroger le serveur DNS, le réseau MGMT Docker (172.23.0.0/16) peut également l'utiliser, mais les requêtes depuis des sources non-reconnues sont rejetées silencieusement. DNSSEC validation est activée pour protéger les réponses DNS contre l'empoisonnement de cache.
Architecture Interne
rgz-dns (nlnetlabs/unbound)
│
├── Port: 53/UDP + 53/TCP (interne rgz-net)
│
├── Résolution normale (abonnés authentifiés)
│ ├── Requête DNS → cache local Unbound → résolution récursive
│ ├── DNSSEC validation activée
│ └── Forwarding vers 1.1.1.1 / 8.8.8.8 (upstream)
│
├── Sinkhole (abonnés non-authentifiés — outil #34)
│ ├── config/unbound/unbound.conf.d/sinkhole.conf
│ ├── Wildcard: *.* → 10.200.0.1 (IP portail captif)
│ └── Exceptions: access-rgz.duckdns.org, api-rgz.duckdns.org (portail lui-même)
│
├── Blocklists (protection malwares)
│ ├── config/unbound/unbound.conf.d/blocklist.conf
│ ├── Sources: URLhaus, MalwareDomainList, liste locale RGZ
│ └── Action: NXDOMAIN ou redirect vers page blocage RGZ
│
└── Contrôle accès
├── config/unbound/unbound.conf.d/access-control.conf
├── allow: 10.100.0.0/8 (VLANs abonnés)
├── allow: 172.23.0.0/16 (réseau Docker MGMT)
└── refuse: 0.0.0.0/0 (tout le reste)
Flux non-authentifié:
Device → DNS query "google.com" → Unbound sinkhole → répond 10.200.0.1
Device → HTTP/HTTPS vers 10.200.0.1 → rgz-portal → portail captif affiché
Flux authentifié:
Device → DNS query "google.com" → Unbound résolution normale → 142.250.X.X
Device → HTTP vers 142.250.X.X → Internet direct (via rgz-gateway NAT)Configuration
Variables d'environnement
| Variable | Exemple | Description |
|---|---|---|
PORTAL_IP | 10.200.0.1 | IP vers laquelle le sinkhole redirige |
UPSTREAM_DNS_1 | 1.1.1.1 | DNS upstream primaire |
UPSTREAM_DNS_2 | 8.8.8.8 | DNS upstream secondaire |
Fichiers de configuration
config/unbound/
├── unbound.conf # Configuration principale Unbound
└── unbound.conf.d/
├── sinkhole.conf # #34 — Wildcard → portail captif
├── blocklist.conf # Domaines malveillants bloqués
└── access-control.conf # ACL par VLAN/sous-réseau
docker/dns/
└── Dockerfile # nlnetlabs/unbound:latest + curl (pour téléchargement blocklists)unbound.conf (structure principale)
server:
# Écoute sur toutes les interfaces Docker
interface: 0.0.0.0
port: 53
# Taille du cache DNS
cache-min-ttl: 60
cache-max-ttl: 86400
msg-cache-size: 50m
rrset-cache-size: 100m
num-threads: 2
# Sécurité
val-permissive-mode: no # DNSSEC strict
qname-minimisation: yes # Protection vie privée
hide-identity: yes
hide-version: yes
# Accès (défini dans access-control.conf)
access-control: 0.0.0.0/0 refuse
# Logs
verbosity: 1
log-queries: no # Désactivé en prod (RGPD)
log-replies: no
# Forwarding vers DNS upstream (Cloudflare + Google)
forward-zone:
name: "."
forward-addr: 1.1.1.1@853#cloudflare-dns.com
forward-addr: 8.8.8.8@853#dns.google
forward-tls-upstream: yes # DNS over TLS
# Inclure les configs modulaires
include: "/etc/unbound/unbound.conf.d/*.conf"sinkhole.conf (#34)
# Sinkhole pour appareils non-authentifiés
# Toutes les requêtes → IP portail captif
# EXCEPTION: les domaines du portail lui-même
# Domaines RGZ exemptés du sinkhole
local-zone: "access-rgz.duckdns.org" transparent
local-zone: "api-rgz.duckdns.org" transparent
local-zone: "registre-rgz.duckdns.org" transparent
# Wildcard sinkhole : tout le reste → portail
# Note: activé/désactivé dynamiquement par outil #32 (nftables-generator)
# selon si l'abonné est authentifié ou non
local-zone: "." redirect
local-data: ". A 10.200.0.1"
local-data: ". AAAA ::1"blocklist.conf (extrait)
# Domaines malveillants — mis à jour périodiquement
# Format: local-zone: "domaine." always_nxdomain
local-zone: "malware-example.com." always_nxdomain
local-zone: "phishing-bj.net." always_nxdomain
local-zone: "cryptominer-pool.io." always_nxdomain
# Domaines de tracking publicitaire (optionnel)
# local-zone: "doubleclick.net." always_nxdomainaccess-control.conf
# Refus par défaut
access-control: 0.0.0.0/0 refuse
# VLANs abonnés RGZ (100-499 → 10.100.0.0/8 à 10.243.0.0/8)
access-control: 10.100.0.0/8 allow
# Réseau Docker MGMT
access-control: 172.23.0.0/16 allow
# Localhost
access-control: 127.0.0.0/8 allow
access-control: ::1 allowHealthcheck
# Healthcheck Docker
unbound-control status
# Depuis l'hôte
docker exec rgz-dns unbound-control status
# Attendu: "unbound is running"
# Test de résolution standard
docker exec rgz-dns drill google.com @127.0.0.1
# Vérifier le sinkhole (domaine non-existent → portail IP)
docker exec rgz-dns drill randomdomain12345.com @127.0.0.1
# Attendu: 10.200.0.1 (IP portail)
# Vérifier qu'un domaine RGZ est exempté du sinkhole
docker exec rgz-dns drill access-rgz.duckdns.org @127.0.0.1
# Attendu: IP publique du portail (pas 10.200.0.1)
# Vérifier le blocage d'un domaine malveillant
docker exec rgz-dns drill malware-example.com @127.0.0.1
# Attendu: NXDOMAIN
# Stats du cache
docker exec rgz-dns unbound-control stats | grep -E "num.queries|cache.hits"Sécurité
| Règle | Implémentation |
|---|---|
| DNSSEC | Validation activée (val-permissive-mode: no) — rejette les réponses non-validées |
| DNS over TLS | Forwarding upstream via port 853 TLS (Cloudflare, Google) |
| ACL stricte | refuse par défaut, allow uniquement pour VLANs connus |
| Confidentialité | log-queries: no en production (conformité RGPD/APDP) |
| Sinkhole | Redirection captive pour non-authentifiés, pas de résolution libre |
| hide-identity | Unbound ne révèle pas sa version ni son hostname |
Commandes Utiles
# Démarrer
docker compose -f /home/claude-dev/RGZ/docker-compose.core.yml up -d rgz-dns
# Logs Unbound
docker logs rgz-dns -f --tail=100
# Recharger la configuration sans restart
docker exec rgz-dns unbound-control reload
# Vider le cache DNS
docker exec rgz-dns unbound-control flush_zone .
# Ajouter un domaine à la blocklist à chaud
docker exec rgz-dns unbound-control local_zone "malware-new.com." always_nxdomain
docker exec rgz-dns unbound-control local_data "malware-new.com. 3600 IN A 0.0.0.0"
# Supprimer une zone locale (débloquer un domaine)
docker exec rgz-dns unbound-control local_zone_remove "malware-new.com."
# Mettre à jour les blocklists (script)
docker exec rgz-dns sh /etc/unbound/update-blocklists.sh
# Voir les statistiques de cache
docker exec rgz-dns unbound-control stats_noreset
# Résolution de test avec trace DNSSEC
docker exec rgz-dns drill -D google.com @127.0.0.1Implémentation TODO
- [x] Service défini dans
docker-compose.core.yml - [x] Dockerfile créé (
docker/dns/Dockerfile) - [ ]
config/unbound/unbound.conf— Config principale (threads, cache, DNSSEC, DoT) - [ ]
config/unbound/unbound.conf.d/sinkhole.conf— Wildcard sinkhole (#34) - [ ]
config/unbound/unbound.conf.d/blocklist.conf— Domaines malveillants - [ ]
config/unbound/unbound.conf.d/access-control.conf— ACL par VLAN - [ ] Script
update-blocklists.sh— Téléchargement URLhaus + MalwareDomainList - [ ] Intégration Prometheus (unbound-exporter sur :9167)
- [ ] Dashboard Grafana queries DNS / hits blocklist (#37)
- [ ] Tests sinkhole (abonné non-authentifié redirigé)
- [ ] Tests DNSSEC (validation des zones signées)
Dernière mise à jour: 2026-02-21