title: Linux: Services d'infrastructure
description: Setup DNS, DHCP dans un environnement Debian GNU/Linux
published: true
date: 2025-09-01T21:06:00.797Z
tags: linux, networking, dns, dhcp
editor: markdown
dateCreated: 2023-09-25T12:21:39.185Z
DNS: Domain Name System
Historique
Organisation de l'espace de nom de domaine
L'espace des noms de domaines est organisé dans une hiérarchie dont le sommet est la /racine/, représentée par un point.
En fait, quand vous tapez:
$ ping google.com
En vrai votre client "ping" s'occupe, tout comme votre navigateur, d'ajouter le point que vous avez omis:
$ ping google.com.
# si si, tapez-le!
La racine, comme tout domaine, est ensuite découpée en plusieurs sous-domaines (com., fr., etc) qu'on appelle les domaines de premier niveau (TLD, /Top Level Domains/) dont la gestion est déléguée à d'autres entités.
Ces /Top Level Domains/ sont eux aussi ensuite découpés en plusieurs sous-domaines (lemonde.fr., google.com., etc), dont la gestion est attribuée à différents registrars (les OVH, 1&1, Gandi, and co). Par exemple le domaine epsi.fr. est un sous-domaine du TLD fr..
Enfin, l'utilisateur final peut créer tous les sous-domaines qu'il veut (par exemple www.lemonde.fr., eu-west-1.admin.google.com. (il n'existe pas lui), dans sa délégation. On appelle cet ensemble de sous-domaines attachés à un domaine.tld. une /zone/ DNS.
De même, on appelle l'ensemble sous-domaine(s), domaine, tld et point final un /FQDN/, /Fully Qualified Domain Name/. Par exemple: www.epsi.fr..
Les noms de domaines sont donc résolus en parcourant la hiérarchie depuis le sommet (.) et en suivant les délégations successives: TLD, puis domaine.
Un bon exemple est le fonctionnement de /whois/, qu'on peut "simuler" avec /nc/ (netcat), par exemple avec le domaine epsi.fr.:
# on commence par demander qui est en charge de .fr à l'IANA (racine):
$ nc whois.iana.org 43
fr
% IANA WHOIS server
% for more information on IANA, visit http://www.iana.org
% This query returned 1 object
domain: FR
organisation: Association Française pour le Nommage Internet en Coopération (A.F.N.I.C.)
...
whois: whois.nic.fr
status: ACTIVE
remarks: Registration information: http://www.nic.fr/
created: 1986-09-02
changed: 2020-03-23
source: IANA
La ligne qui nous intéresse ici est la ligne "whois: whois.nic.fr" qui nous indique le serveur whois en charge de la délégation FR:
$ nc whois.nic.fr 43
epsi.fr
domain: epsi.fr
status: ACTIVE
hold: NO
holder-c: EA10427-FRNIC
admin-c: EA12652-FRNIC
tech-c: OVH5-FRNIC
zone-c: NFC1-FRNIC
nsl-id: NSL18490-FRNIC
registrar: OVH
...
nserver: dns12.ovh.net
nserver: ns12.ovh.net
Maintenant, je sais quels serveurs interroger pour obtenir le SOA d'epsi.fr: les NS d'OVH:
$ dig @dns12.ovh.net www.epsi.fr
; <<>> dig 9.10.8-P1 <<>> @dns12.ovh.net www.epsi.fr
; (2 servers found)
;; global options: +cmd
... snip ...
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;www.epsi.fr. IN A
;; ANSWER SECTION:
www.epsi.fr. 3600 IN A 149.255.137.13
;; Query time: 18 msec
;; SERVER: 213.251.188.131#53(213.251.188.131)
;; WHEN: Mon Nov 02 16:26:02 CET 2020
;; MSG SIZE rcvd: 56
Et voilà, je peux résoudre www.epsi.fr sans souci.
Mécanismes de résolution de noms
Résolution directe
La résolution directe, c'est le type de résolution qui nous permet d'associer un FQDN à une (ou plusieurs) adresse(s) IP.
Il faut bien distinguer deux modes de résolution de noms:
- La résolution de noms récursive effectuée par les hôtes (typiquement: votre poste de travail, un serveur qui ne soit pas un serveur DNS, etc) vers un serveur DNS récursif
- La résolution de noms itérative effectuée par les serveurs DNS récursifs sur réception d'une requête d'un client: il contactera d'abord les serveurs racines pour obtenir les informations de /Start Of Authority/ (SOA) du tld, puis les serveurs délégués à ce tld pour connaître les informations de SOA des serveurs DNS délégués au domaine, et enfin, interrogera les serveurs SOA du domaine pour obtenir l'adresse - ou les adresses - IP des sous-domaines demandés.
Résolution inverse
La résolution inverse nous permet de trouver le nom de domaine associé à une adresse IP. Dans un nom de domaine classique, comme epsi.fr., la partie la plus générique est tout à droite (le point final) et le mécanisme de résolution parcourt donc le nom de domaine de la droite vers la gauche: je résouds ., puis fr., puis epsi.fr. et finalement www.epsi.fr..
Dans une adresse IP, la partie la plus générique est au début de l'adresse IP, tout à gauche. Le mécanisme de résolution parcourerait alors une adresse IP en sens inverse d'un nom de domaine s'il voulait aller de la partie la plus générique (tout à gauche) à la partie la plus précise (tout à droite). Dès lors, on inverse les 4 octets de notre adresse IP qu'on concatène au pseudo domaine in-addr.arpa.
Par exemple, si je demande à /dig/ sous GNU/Linux ou UNIX de m'effectuer une résolution inverse sur 89.234.176.135:
$ dig -x 89.234.176.135
; <<>> dig 9.10.8-P1 <<>> -x 89.234.176.135
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 26050
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;135.176.234.89.in-addr.arpa. IN PTR
;; ANSWER SECTION:
135.176.234.89.in-addr.arpa. 86400 IN PTR miskatonic.lidstah.info.
;; Query time: 104 msec
;; SERVER: 192.168.42.253#53(192.168.42.253)
;; WHEN: Mon Nov 02 16:57:11 CET 2020
;; MSG SIZE rcvd: 93
On voit bien que l'enregistrement inverse 135.176.234.89.in-addr.arpa. pointe bien vers miskatonic.lidstah.info., qui est une VM hébergée chez FAImaison m'appartenant , et que cet enregistrement DNS est de type PTR. Ce qui m'amène aux types d'enregistrements DNS:
Types d'enregistrements DNS
| Type | Utilisation |
|---|---|
| A | Enregistrement d'hôte. fait correspondre domaine ou sous-domaine et adresse IPv4 |
| AAAA | Enregistrement d'hôte. fait correspondre domaine ou sous-domaine et adresse IPv6 |
| CNAME | Alias d'un FQDN vers un autre FQDN ou Nom de domaine |
| MX | Mail eXchange, définit les serveurs de mails pour ce NDD (ils doivent avoir un PTR) |
| PTR | PoinTer Record, /reverse/, associe une IP à un enregistrement de domaine |
| NS | NameServer record, définit les serveurs DNS du domaine |
| SOA | Start Of Authority: donne les infos génériques du domaine: DNS principal, TTL, Serial, etc |
| SRV | Généralise la notion de MX à d'autres types de services. Peu utilisé. |
| TXT | TeXT: permet d'associer du texte à un enregistrement DNS (ex: SPF, DKIM) |
Installation et configuration d'un serveur de noms.
On utilisera le logiciel BIND, développé par l'ISC (/Internet Systems Consortium/, si c'est pas un nom qui claque ça ) qui est un des logiciels les plus connus. Il en existe beaucoup d'autres: NSD, PowerDNS, UnBound, mais BIND est à la fois un serveur autoritaire non-récursif sur les zones qui lui sont déléguées, et un résolveur DNS via ses options de /global forwarding/. Il est donc plutôt indiqué dans ce TD car il va nous permettre de mettre en place une zone DNS et sa zone inverse utilisable "en interne" sur votre LAN, et de répondre aux requêtes DNS récursives de nos clients, par exemple pour résoudre fr.wikipedia.org ou www.reddit.com (au hasard).
Installation de BIND
Comme d'habitude, on va passer par notre gestionnaire de paquets:
$ sudo apt install bind9
Configuration de base de BIND
La configuration de base de BIND s'effectue dans le répertoire:
/etc/bind/
Et la configuration de nos fichiers de zones directes et inverses dans:
/var/cache/bind/
Global Forwarder
Tout d'abord, on va activer le /Global Forwarding/ afin que notre serveur DNS puisse se comporter comme un serveur DNS récursif:
$ sudo vim /etc/bind/named.conf.options
options {
directory "/var/cache/bind";
// Ici on utilise les NS publics de Google
// Mais on peut bien sûr utiliser d autres NS
forwarders {
8.8.8.8;
8.8.4.4;
};
dnssec-validation auto;
listen-on-v6 { any; };
};
On peut maintenant relancer BIND, et tester le bon fonctionnement de la résolution récursive de noms de domaines depuis notre VM:
$ sudo systemctl restart bind9
$ dig @127.0.0.1 google.fr
; <<>> DiG 9.11.5-P4-5.1+deb10u2-Debian <<>> @127.0.0.1 google.fr
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2236
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 13, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: c62d4e9e2e06ccbfd7b1b9325fa03eff05b7b4bc6f5bd69a (good)
;; QUESTION SECTION:
;google.fr. IN A
;; ANSWER SECTION:
google.fr. 71 IN A 216.58.214.67
;; AUTHORITY SECTION:
. 85906 IN NS h.root-servers.net.
. 85906 IN NS a.root-servers.net.
. 85906 IN NS j.root-servers.net.
. 85906 IN NS d.root-servers.net.
. 85906 IN NS l.root-servers.net.
. 85906 IN NS b.root-servers.net.
. 85906 IN NS c.root-servers.net.
. 85906 IN NS m.root-servers.net.
. 85906 IN NS e.root-servers.net.
. 85906 IN NS k.root-servers.net.
. 85906 IN NS i.root-servers.net.
. 85906 IN NS f.root-servers.net.
. 85906 IN NS g.root-servers.net.
;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: lun. nov. 02 18:16:47 CET 2020
;; MSG SIZE rcvd: 293
Puis depuis une machine de notre réseau local:
# ici mon serveur DNS a l'IP 192.168.1.175
$ dig @192.168.1.175 epsi.fr
; <<>> DiG 9.11.5-P4-5.1+deb10u2-Debian <<>> @192.168.1.175 epsi.fr
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 34093
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 13, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: e21e6ecf39d62987593d91b45fa03f75a470c17750bd88be (good)
;; QUESTION SECTION:
;epsi.fr. IN A
;; ANSWER SECTION:
epsi.fr. 3599 IN A 149.255.137.13
;; AUTHORITY SECTION:
. 85788 IN NS f.root-servers.net.
. 85788 IN NS d.root-servers.net.
. 85788 IN NS i.root-servers.net.
. 85788 IN NS g.root-servers.net.
. 85788 IN NS h.root-servers.net.
. 85788 IN NS a.root-servers.net.
. 85788 IN NS j.root-servers.net.
. 85788 IN NS b.root-servers.net.
. 85788 IN NS m.root-servers.net.
. 85788 IN NS c.root-servers.net.
. 85788 IN NS l.root-servers.net.
. 85788 IN NS k.root-servers.net.
. 85788 IN NS e.root-servers.net.
;; Query time: 67 msec
;; SERVER: 192.168.1.175#53(192.168.1.175)
;; WHEN: lun. nov. 02 18:18:45 CET 2020
;; MSG SIZE rcvd: 291
Préparation de notre zone
Ok, donc, on peut résoudre des noms de domaines avec notre serveur DNS. Youpi.
Maintenant, on va configurer NOTRE nom de domaine, qui sera servi sur NOTRE LAN
Choisissez un nom de domaine qui vous plaît, évitez quand même de vous appeler google.com, ça pourrait être embêtant
dans mon cas, je vais appeler mon nom de domaine cogip.fr. Parcequ'à la COGIP on est des winners! On partira aussi du principe que les machines de notre zone ont des IPs sur le sous-réseau 192.168.100.0/24 (qu'on utilisera aussi sur la partie DHCP! Après, vous pouvez prendre le sous-réseau que vous voulez)
On va d'abord renseigner cette zone - et sa zone inverse! - dans notre configuration BIND:
$ sudo vim /etc/bind/named.conf.local
include "/etc/bind/rndc.key"
zone "cogip.fr." {
type master;
file "cogip.fr";
};
zone "100.168.192.in-addr.arpa." {
type master;
file "100.168.192.rev";
};
Pour le moment, on ne va pas recharger la configuration de bind, car nous n'avons pas créé nos fichiers de zones directe et inverse!
Zone directe
On va créer notre fichier de zone, ici, je vais l'appeler cogip.fr, comme mon "NDD":
ATTENTION : il y a un espace dans le bloc SOA avant les différentes valeurs annoncées par notre SOA (Serial, refresh, retry, expire, cache) et il n'est pas là pour rien! (bind refusera de charger une configuration ne respectant pas sa syntaxe!)
$ sudo vim /var/cache/bind/cogip.fr
$TTL 86400 // TTL par defaut de notre zone
@ IN SOA cogip.fr. hostmaster.cogip.fr. (
1; //Serial, on l incrémente à chaque modification
3600; //refresh 1h
1800; //Retry 30m
7200; //expire 2h
7200; //cache TTL 2h
)
//Il nous faut bien sûr un NS
@ NS b2-dns.cogip.fr.
b2-dns IN A 192.168.100.175
router IN A 192.168.100.1
polaris IN A 192.168.100.190
proxmox IN CNAME polaris.cogip.fr.
gateway IN CNAME router.cogip.fr.
Zone inverse
$ sudo vim /var/cache/bind/100.168.192.rev
$TTL 86400
@ IN SOA 100.168.192.in-addr.arpa. hostmaster.cogip.fr. (
1;
3600;
1800;
7200;
7200;
)
@ NS b2-dns.cogip.fr.
175 IN PTR b2-dns.cogip.fr.
1 IN PTR router.cogip.fr.
190 IN PTR polaris.cogip.fr.
On peut maintenant recharger la configuration de bind9 et tester son bon fonctionnement:
$ sudo systemctl reload bind9
$ dig @192.168.1.175 gateway.cogip.fr
; <<>> DiG 9.11.5-P4-5.1+deb10u2-Debian <<>> @192.168.1.175 gateway.cogip.fr
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 3843
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 1, ADDITIONAL: 2
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 37a180166b0ee33509656f775fa04a604545eac557cd3fb2 (good)
;; QUESTION SECTION:
;gateway.cogip.fr. IN A
;; ANSWER SECTION:
gateway.cogip.fr. 86400 IN CNAME router.cogip.fr.
router.cogip.fr. 86400 IN A 192.168.100.254
;; AUTHORITY SECTION:
cogip.fr. 86400 IN NS b2-dns.cogip.fr.
;; ADDITIONAL SECTION:
b2-dns.cogip.fr. 86400 IN A 192.168.100.175
;; Query time: 3 msec
;; SERVER: 192.168.1.175#53(192.168.1.175)
;; WHEN: lun. nov. 02 19:05:20 CET 2020
;; MSG SIZE rcvd: 147
Sécurité des infrastructures de résolution de noms
Alors, on a un serveur DNS fonctionnel, qui sert notre (ou nos) zone(s) DNS tout en nous permettant de résoudre récursivement des noms de domaines n'appartenant pas à nos zones. Ce faisant, on a certes amélioré la vie de la compta, car c'est plus facile de taper https://erp.cogip.fr que https://192.168.1.142, mais on a aussi créé un gros SPOF (/Single Point Of Failure/): notre serveur DNS. En effet, s'il tombe, non seulement la compta ne trouvera plus erp.cogip.fr, mais en prime elle ne trouvera plus rien du tout (google, etc).
En environnement de production, on aura toujours au moins deux serveurs DNS: un DNS primaire et un DNS secondaire. Et en général on en aura un peu plus que deux.
Serveurs DNS secondaires
Alors, comme créer un serveur DNS secondaire impliquerait de vous faire créer une VM supplémentaire, ainsi que quelques modifications de config tant au niveau du master que du slave, on n'aurait tout simplement pas le temps de traiter les autres sujets de ce cours. Ce qui serait embêtant.
Aussi, pour celles et ceux qui voudraient tenter l'aventure, je vous propose de créer vous mêmes un DNS secondaire en vous basant sur ce tutorial - qu'il faudra adapter, car il est pour CentOS et qu'on travaille sur Debian ! - https://kifarunix.com/how-to-setup-master-slave-dns-server-using-bind-on-centos-7/
UDP = Problèmes /!\
Les attaques par réflexion UDP.
UDP est un protocole de couche 4, de type "fire and forget". C'est à dire que j'envoie un paquet UDP sans attendre de réponse d'aquittement.
Par défaut, DNS utilise UDP comme protocole de transport. On peut imaginer un attaquant envoyant des paquets UDP malformés (il s'en fiche, il n'attend pas de réponse) à notre serveur DNS. Il y a plus vicieux: mon serveur DNS peut devenir partie d'un DDoS. En effet, je peux envoyer un message UDP à mon serveur DNS lui demandant, par exemple, une requête ANY. Mais rien ne m'empêche de modifier l'IP source (c'est à dire l'IP avec laquelle j'envoie mon segment UDP) de mon paquet IP avec l'IP d'une cible. Le serveur DNS enverra alors ses réponses à la cible. Si on multiplie ça par beaucoup de serveurs DNS, on produit un DDoS.
Exemple avec memcached au lieu de DNS: https://blogs.akamai.com/2018/03/memcached-fueled-13-tbps-attacks.html
iptables: limiter le nombre de requêtes par seconde en udp.
Dès lors, une première mesure serait de limiter le nombre de requêtes par seconde, en udp sur notre port 53, via iptables:
$ sudo iptables -A INPUT -i eth0 -p udp --dport 53 -m limit --limit 5/s -j ACCEPT
dnsdist: load-balancer DNS avec rate-limit, cache, etc.
Les mêmes personnes à l'origine de l'excellent PowerDNS ont aussi développé /dnsdist/ qui est un load-balancer UDP spécialisé DNS.
https://www.powerdns.com/dnsdist.html
dnsdist nous permet de répartir la charge sur nos serveurs DNS (un peu comme HAProxy pour TCP et HTTP), mais aussi de limiter, ignorer, ou bannir le trafic illégitime.
Sa configuration s'effectue en lua (et ça, c'est plutôt cool):
-- disable security status polling via DNS
setSecurityPollSuffix("")
-- listen on the goddamn internet
setLocal("IP_PUBLIQUE:53")
-- whitelist
acl = newNMG()
acl:addMask("10.29.0.0/24")
acl:addMask("10.53.0.0/24")
-- actions, filtering
-- AXFR and IXFR to authoritative server
addAction(OrRule({QTypeRule(dnsdist.SOA), QTypeRule(dnsdist.AXFR), QTypeRule(dnsdist.IXFR)}), PoolAction("auth"))
-- abusers
addAction(MaxQPSIPRule(5, 32, 48), DelayAction(100))
-- delay query on consecutives digits after 4 digits
addAction(RegexRule("[0-9]{4,}"), DelayAction(750))
-- truncate long ANY queries over UDP, forcing TCP retry (anti-reflection)
addAction(AndRule({QTypeRule(dnsdist.ANY), TCPRule(false)}), TCAction())
newServer({address="10.53.0.247", name="ns1", qps=100, pool='auth'})
newServer({address="10.53.0.248", name="ns2", qps=100, pool='slave'})
newServer({address="10.53.0.243", name="nscache1", qps=500})
newServer({address="10.53.0.244", name="nscache2", qps=500})
newServer({address="10.53.0.245", name="nscache3", qps=500})
newServer({address="10.53.0.246", name="nscache4", qps=500})
setServerPolicy(firstAvailable)
controlSocket('127.0.0.1:5199')
setKey("une clef bien balaise :)")
-- caching most common requests
pc = newPacketCache(1000000)
getPool(""):setCache(pc)
DNSSEC
DNSSEC a été développé par l'/Internet Engineering Task Force/ (IETF) afin de palier aux problèmes inhérents de DNS. Il permet de protéger de bout en bout les informations DNS en les chiffrant. Il propose une signature (un enregistrement TXT NSEC) directement dans la zone DNS ainsi qu'une clef de chiffrement à récupérer de préférence par un autre moyen que le serveur DNS lui même (clef signée en PGP via le Web par exemple).
Assez compliqué à mettre en oeuvre, il est cependant de plus en plus utilisé. Là encore, je vous invite chaudement à vous documenter à ce sujet.
DHCP: Dynamic Host Configuration Protocol
Fonctionnement du protocole
DHCP permet de résoudre plusieurs problèmes:
- Provisionnement automatique des informations de configuration réseau des hôtes connectés à un réseau (passerelle, DNS, attribution automatique d'une adresse IP dans le sous-réseau)
- Il est plus simple de mettre à jour la configuration du serveur DHCP pour propager de nouvelles informations réseau (nouveaux serveurs DNS, nouvelle passerelle) plutôt que de devoir le configurer sur chaque poste de travail.
DORA
- Le client /broadcaste/ sur le port 67 une requête DHCP Discover
- Le(s) serveur(s) DHCP répond(ent) sur le port 68 du client en lui envoyant une requête DHCP Offer
- Le client choisit l'offre la plus "intéressante" (en général la première reçue) et répond par une requête DHCP Request
- Le serveur prend en note la réponse du client et lui envoie un paquet DHCP ACK
Quelles informations le serveur DHCP peut-il envoyer?
- Adresse IP et masque de sous-réseau assignés à l'adresse MAC de l'interface réseau du client, attribués pendant un temps donné: bail DHCP (/lease/ en anglais)
- Le client doit renouveler son bail à intervalle régulier (T1, default lease time)
- à 50% du temps du bail, le client envoie un message DHCP Request en unicast au serveur DHCP - qui doit renvoyer DHCP ACK pour renouveler le bail (ou NACK si le client n'est plus disponible)
- à 87.5% du temps du bail, s'il n'a toujours pas eu de réponse, le client broadcaste un DHCP Request.
- S'il n'a pas obtenu de réponse, le client broadcaste un DHCP Discover.
- Nom de domaine du réseau
- Résolveurs DNS du réseau
- Passerelles du réseau
De même, un serveur DHCP peut couvrir plusieurs réseaux séparés tant que les routeurs entre ces réseaux supportent la RFC1542, ou sinon en utilisant des agents relais DHCP.
DHCP: ISC Kea DHCP Server
Prérequis
Trois VMs Debian Trixie (13), 1 coeur, 1Go de RAM, avec deux interfaces réseau: une sur le réseau du labo, une sur un VLAN attribué à votre groupe (ex: vmbr1014). En effet, on ne veut pas ajouter un serveur DHCP sur le réseau du labo (on a déjà tout ce qu'il faut de ce côté!), mais uniquement dans le VLAN de votre groupe. Une VM sera configurée en tant que passerelle (donc, net.ipv4.ip_forward = 1 et règles iptables pour NATter le trafic).
On créera, une fois notre serveur DHCP configuré et actif, une VM uniquement sur le VLAN attribué à votre groupe, afin de vérifier le bon fonctionnement de notre setup.
Installation
Comme Debian 12 et 13 packagent des versions assez anciennes de Kea DHCP Server, nous allons donc:
- Ajouter le dépôt officiel d'ISC Kea DHCP
- L'installer
Pour l'ajout du dépôt, bien que je ne sois pas spécialement fan de la méthode "curl | bash", l'ISC fournissant un script du style qui va nous faciliter la tâche (dans notre cas, le but est de rapidement mettre en place un serveur DHCP), on va quand même l'utiliser:
curl -1sLf 'https://dl.cloudsmith.io/public/isc/kea-3-0/setup.deb.sh' | sudo -E bash
Ensuite, un classique sudo apt update va mettre à jour la base de données des paquets de notre distribution.
On peut donc procéder à l'installation de Kea à proprement parler. Il se compose de deux paquets: isc-kea-dhcp4 et isc-kea-dhcp6, respectivement pour IPv4 et IPv6. Dans notre cas, nous allons nous contenter d'installer isc-kea-dhcp4:
sudo apt install isc-kea-dhcp4
Pour le moment, nous n'allons pas lancer le service, mais nous allons quand même l'activer:
sudo systemctl enable isc-kea-dhcp4-server.service
Configuration
Nous allons maintenant configurer Kea DHCP. Pour cela, on édite le fichier de configuration correspondant: /etc/kea/kea-dhcp4.conf.
Le fichier de configuration, comme vous pouvez le voir, est bien documenté.
La première étape sera de configurer l'interface réseau sur laquelle notre serveur DHCP va écouter. Si vous utilisez des VMs créées à partir du template cloudinit debian-trixie, l'interface voulue devrait se nommer eth1.
Ensuite, les parties qui vont nous intéresser sont "options-data", qui fournit les options par défaut (noms de domaine, serveurs DNS, etc) fournies par le serveur DHCP et "subnet4", dans lequel nous pouvons configurer des options spécifiques à chaque subnet, les "pools" d'adresses qui seront attribuées aux clients, etc. Bien évidemment, adaptez votre configuration aux choix que nous aurons défini pour chaque groupe (notamment sous-réseau, passerelle et pool d'adresses).
Notre fichier de configuration devrait ressembler à ça (expurgé des commentaires):
{
"Dhcp4": {
"interfaces-config": {
"interfaces": [ "eth1" ]
},
"control-socket": {
"socket-type": "unix",
"socket-name": "kea4-ctrl-socket"
},
"lease-database": {
"type": "memfile",
"lfc-interval": 3600
},
"expired-leases-processing": {
"reclaim-timer-wait-time": 10,
"flush-reclaimed-timer-wait-time": 25,
"hold-reclaimed-time": 3600,
"max-reclaim-leases": 100,
"max-reclaim-time": 250,
"unwarned-reclaim-cycles": 5
},
"renew-timer": 900,
"rebind-timer": 1800,
"valid-lifetime": 3600,
"option-data": [
{
"name": "domain-name-servers",
"data": "192.168.1.187, 192.168.1.188"
},
{
"name": "domain-name",
// alternativement, on peut utiliser le code numérique de l'option:
// "code": 15,
"data": "lidstah.info"
},
{
"name": "domain-search",
"data": "lidstah.info"
},
{
"name": "boot-file-name",
"data": "EST5EDT4\\,M3.2.0/02:00\\,M11.1.0/02:00"
},
{
"name": "default-ip-ttl",
"data": "0xf0"
}
],
"subnet4": [
{
"id": 1,
"subnet": "192.168.66.0/24",
"pools": [ { "pool": "192.168.66.10 - 192.168.66.50" } ],
"interface": "eth1",
"option-data": [
{
"name": "routers",
"data": "192.168.66.100"
}
],
"reservations": [
]
}
],
"loggers": [
{
"name": "kea-dhcp4",
"output-options": [
{
"output": "stdout",
"pattern": "%-5p %m\n"
}
],
"severity": "INFO",
"debuglevel": 0
}
]
}
}
Il ne nous reste plus qu'à lancer le service: sudo systemctl start isc-kea-dhcp4-server.service. On peut alors créer notre VM de test sur ce réseau, configurer cloudinit pour qu'elle utilise DHCP et bien vérifier qu'elle a obtenu une adresse IP sur le pool d'adresses que nous avons configuré.
Baux Statiques
Un bail statique permet de toujours attribuer la même adresse IP à la même adresse MAC. Cette adresse doit être hors de nos pools DHCP. Dans notre fichier de configuration, ces baux statiques sont configurés dans la partie "reservations" de notre section "subnet4".
Un exemple assez complet (adresse mac, adresse IP, hostname et options DHCP spécifiques) ci-dessous:
"reservations" : [
{
"hw-address": "01:23:45:67:89:ab",
"ip-address": "192.168.66.134",
"hostname": "hypervisor-1",
"option-data": [ {
"name": "domain-name-servers",
"data": "8.8.8.8, 8.8.4.4"
} ]
]
Dans ce cas, la machine avec l'adresse MAC décrite ci-dessus se verra toujours attribuer la même adresse IP, aura pour nom (du point de vue DHCP) "hypervisor-1", et aura pour serveurs DNS les DNS de Google©.
Failover
Le but, ici, est d'avoir un service DHCP à haute disponibilité. En effet, si mon serveur DHCP est down… eh bien mes clients ne peuvent plus obtenir de configuration réseau (c'est embêtant ). La solution est donc d'avoir deux serveurs DHCP, notre serveur actif, et un serveur passif, qui sera capable de prendre le relai si le premier serveur DHCP venait à rencontrer un problème.
On va d'abord, sur nos deux machines, installer le paquet isc-kea-hooks, qui va nous fournir les bibliothèques de fonction nécessaires.
Serveur Primaire
Sur notre premier serveur DHCP, nous allons ajouter un bloc "hooks-libraries" (juste avant la configuration de notre subnet):
"hooks-libraries": [
{
"library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_lease_cmds.so"
},
{
"library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_ha.so",
"parameters": {
"high-availability": [
{
"this-server-name": "server1",
"max-unacked-clients": 0,
"mode": "hot-standby",
"peers": [
{
"name": "server1",
"url": "http://192.168.66.201:8000/",
"role": "primary"
},
{
"name": "server2",
"url": "http://192.168.66.202:8000/",
"role": "standby"
}
]
}
]
}
}
],
Puis on redémarre notre serveur DHCP via systemctl.
Serveur Secondaire
Ici, nous allons avoir exactement la même configuration que notre premier serveur, seule l'option "this-server-name" devrait différer, ainsi que le pool d'adresses à assigner qui doit être différent (par exemple, 192.168.66.51-192.168.66.90 au lieu de 192.168.66.10-192.168.66.50 dans le fichier de configuration du primaire).
"hooks-libraries": [
{
"library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_lease_cmds.so"
},
{
"library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_ha.so",
"parameters": {
"high-availability": [
{
"this-server-name": "server2",
"max-unacked-clients": 0,
"mode": "hot-standby",
"peers": [
{
"name": "server1",
"url": "http://192.168.66.201:8000/",
"role": "primary"
},
{
"name": "server2",
"url": "http://192.168.66.202:8000/",
"role": "standby"
}
]
}
]
}
}
],
On relance là aussi notre serveur DHCP. Il ne nous reste plus qu'à tester: on stoppe notre serveur primaire, on reboote notre VM client de test, et on regarde ce qu'il se passe!
Relai DHCP
Lorsque votre serveur DHCP doit être capable d'assigner des adresses IPs en dehors des réseaux auquel il est directement connecté, on fait alors appel à un Relai DHCP. C'est un peu hors du syllabus de ce module, mais il est bon de le savoir. Un relai DHCP va, pour simplifier, servir de proxy aux requêtes DHCP des clients, les transmettre au serveur DHCP, et enfin transmettre la réponse aux clients.
Fonctionnement du protocole HTTP /HyperText Transfer Protocol/)
/HyperText Transfer Protocol/, HTTP, a été créé par Tim Berners-Lee, en même temps que les URLs et le langage HTML, en 1990 car FTP (/File Transfer Protocol/), s'il permettait de transférer des données, ne prenait pas en compte le format de ces données (défini par le standard /Multipurpose Internet Mail Extensions/ (MIME)). MIME permettant de décrire le type des données que l'on transmet (est-ce que c'est du texte, une image JPEG, PNG, une vidéo x264, un pdf, un mp3?), le logiciel client peut choisir de les afficher, de les lire, ou de les transmettre à un logiciel permettant de lire ce média. Même de nos jours cela peut être utile: cela permet par exemple au navigateur Web d'envoyer un lien vidéo à un lecteur média (VLC, mpv, etc) utilisant moins de CPU pour lire ce flux que le navigateur. Idéal pour utiliser youtube relativement correctement sur une machine un peu ancienne. C'est grâce à ces types MIME que l'on peut lire un article Wikipedia avec des images d'illustration, vidéos, et sons.
Bien sûr, entre HTTP/0.9 (1990), HTTP/1.0 en mai 1996, HTTP/1.1 en janvier 1997 (notez l'accélération), HTTP/2.0 en 2012 (elle aura pris son temps cette version) et bientôt HTTP/3 (à l'état de brouillon pour le moment, mais déjà implémenté (nginx 1.19 par exemple côté serveurs), HTTP a subit plusieurs évolutions importantes (notamment avec HTTP/2 et HTTP/3) mais son fonctionnement côté client est resté similaire.
Analyse d'une requête
D'ailleurs, il est resté suffisamment similaire pour que même de nos jours on puisse encore utiliser /telnet/ (en HTTP uniquement, en HTTPS c'est un peu plus compliqué) pour effectuer des requêtes sur un serveur Web:
$ sudo apt update && sudo apt install telnet
$ telnet www.perdu.com 80
GET / HTTP/1.1
Host: www.perdu.com
# Appuyez sur entrée pour afficher la réponse ;)
HTTP/1.1 200 OK
Date: Wed, 11 Nov 2020 14:41:14 GMT
Server: Apache
Upgrade: h2
Connection: Upgrade
Last-Modified: Thu, 02 Jun 2016 06:01:08 GMT
ETag: "cc-5344555136fe9"
Accept-Ranges: bytes
Content-Length: 204
Cache-Control: max-age=600
Expires: Wed, 11 Nov 2020 14:51:14 GMT
Vary: Accept-Encoding,User-Agent
Content-Type: text/html
<html><head><title>Vous Etes Perdu ?</title></head><body><h1>Perdu sur l'Internet ?</h1><h2>Pas de panique, on va vous aider</h2><strong><pre> * <----- vous êtes ici</pre></strong></body></html>
Connection closed by foreign host.
Il existe plusieurs types de requêtes HTTP, qu'on appelle des méthodes, que le client peut transmettre au serveur, permettant au client de demander la transmission du contenu d'une page web, de télécharger un fichier, de /téléverser/ (upload) un fichier, ou les éléments de réponse d'un formulaire, etc. Ci-dessous un petit tableau récapitulatif:
| Méthode | Signification | Privilège requis |
|---|---|---|
| GET | Demande l'accès à une ressource | |
| HEAD | Demande uniquement des informations sur la ressource | |
| POST | Transmet des données à une ressource (ex: formulaire) | |
| PUT | Permet de remplacer ou ajouter une ressource au serveur | Oui |
| PATCH | Permet de faire une modification partielle d'une ressource | Oui |
| DELETE | Supprime une ressource du serveur | Oui |
| OPTIONS | Permet d'obtenir les options de communication du serveur | |
| CONNECT | Permet d'utiliser un proxy | |
| TRACE | Le serveur renvoie ce qu'il a reçu à fin de diagnostic | Oui |
Quelques exemples (ici avec l'IP d'une VM perso faisant tourner un apache par défaut) avec OPTIONS et HEAD:
lidstah@toolbox-lidstah-user-dev:~> telnet 192.168.1.175 80
Trying 192.168.1.175...
Connected to 192.168.1.175.
Escape character is '^]'.
OPTIONS / HTTP/1.1
Host: 192.168.1.175
HTTP/1.1 200 OK
Date: Wed, 11 Nov 2020 15:06:32 GMT
Server: Apache/2.4.38 (Debian)
Allow: GET,POST,OPTIONS,HEAD
Content-Length: 0
Content-Type: text/html
Connection closed by foreign host.
lidstah@toolbox-lidstah-user-dev:~> telnet 192.168.1.175 80
Trying 192.168.1.175...
Connected to 192.168.1.175.
Escape character is '^]'.
HEAD / HTTP/1.1
Host: 192.168.1.175
HTTP/1.1 200 OK
Date: Wed, 11 Nov 2020 15:08:35 GMT
Server: Apache/2.4.38 (Debian)
Last-Modified: Wed, 11 Nov 2020 14:59:49 GMT
ETag: "29cd-5b3d60bfc8e75"
Accept-Ranges: bytes
Content-Length: 10701
Vary: Accept-Encoding
Content-Type: text/html
Connection closed by foreign host.
Une requête HTTP est donc composée:
- D'une ligne de commande de la forme Méthode URL Version du protocole HTTP (ex: GET /plop.html HTTP/1.1)
- D'une ou plusieurs lignes contenant des en-têtes, /Headers/ (Host, User-Agent, etc)
- NOTE : l'en-tête Host est obligatoire depuis HTTP/1.1!
- D'une ligne vide
Analyse d'une réponse
Regardons de plus près ce que le serveur /perdu.com/ nous a répondu lors de notre premier test avec telnet:
$ telnet www.perdu.com 80
GET / HTTP/1.1
Host: www.perdu.com
# Réponse du serveur:
HTTP/1.1 200 OK
Date: Wed, 11 Nov 2020 14:41:14 GMT
Server: Apache
Upgrade: h2
Connection: Upgrade
Last-Modified: Thu, 02 Jun 2016 06:01:08 GMT
ETag: "cc-5344555136fe9"
Accept-Ranges: bytes
Content-Length: 204
Cache-Control: max-age=600
Expires: Wed, 11 Nov 2020 14:51:14 GMT
Vary: Accept-Encoding,User-Agent
Content-Type: text/html
<html><head><title>Vous Etes Perdu ?</title></head><body><h1>Perdu sur l'Internet ?</h1><h2>Pas de panique, on va vous aider</h2><strong><pre> * <----- vous êtes ici</pre></strong></body></html>
Connection closed by foreign host.
Ici, on voit qu'avant de nous transmettre le contenu demandé, le serveur nous a transmis plusieurs lignes d'information:
- Un code de réponse HTTP/1.1, en l'occurence le code 200 OK (tout s'est bien passé)
- Un ensemble d'en-têtes de réponse, on peut noter:
- La date
- Le serveur utilisé (qui a été configuré pour ne pas afficher sa version), ici Apache
- L'upgrade vers HTTP/2 (h2)
- Le temps de mise en cache côté navigateur (600 secondes)
- Le Type de contenu MIME, ici du texte au format HTML (text/html)
- Une ligne vide (tout comme pour notre requête)
- Et enfin le contenu demandé, le corps (/body/) de la réponse
- On remarque que le serveur ferme la connexion une fois le contenu transmis
Types MIME
Au tout début d'ARPANET, on a commencé à envoyer les premiers emails. Comme la langue utilisée était l'anglais, qui n'utilise pas, par exemple, de caractères diacritiques comme à, è, é, etc, l'encodage utilisé par défaut était le bon vieux ASCII codé sur 7 bits.
Au fur et à mesure qu'ARPANet devenait Internet, de plus en plus de personnes d'horizons - et donc de langue - différents ont commencé à communiquer, en général en utilisant un encodage de caractères leur permettant d'utiliser tous les caractères de leur langue, par exemple ISO-8859-1 ou UTF-8.
Afin que tout le monde puisse lire les mails de tout le monde avec le bon jeu de caractères, l'/Internet Engineering Task Force/ (encore un nom classe) a mis au point le standard MIME permettant de spécifier le type de fichier transmis ainsi que son encodage.
Un message d'en-têtes MIME contiendra en général:
- une ligne d'en-tête MIME-Version (par ex: MIME-Version: 1.0), qui indique quelle version du standard est utilisée
- une ligne contenant le header Content-Type (par ex: Content-Type: text/plain)
- une ligne contenant le header Content-Transfer-Encoding (7bit, base64, binary, quoted-printable, 8bit), qui nous indique sous quelle forme le contenu est encodé (on utilisera beaucoup base64, pour encoder n'importe quel type de contenu en caractères compatibles ASCII 7 bits).
Enfin, on peut préciser l'encodage du texte dans le /header/ Content-Type:
Content-Type: text/plain; charset=utf-8
Codes de réponse
Il existe bien évidemment un nombre important de codes HTTP (dont la liste complète est disponible notamment sur Wikipedia: https://fr.wikipedia.org/wiki/Liste_des_codes_HTTP ), mais on peut déjà dégager les grands types de codes de retour:
| Type de code de retour | Signification générique |
|---|---|
| 1xx | Information |
| 2xx | Succès |
| 3xx | Redirection |
| 4xx | Erreur du client |
| 5xx | Erreur du serveur (fear) |
On peut dégager les codes HTTP les plus connus (et courants):
| Code | Signification |
|---|---|
| 200 | OK, requête traitée avec succès |
| 301 | Redirection permanente (ex: http -> https) |
| 302 | Redirection temporaire |
| 400 | Bad Request |
| 401 | Utilisateur non authentifié |
| 403 | Accès interdit à la ressource |
| 404 | Not found |
| 500 | erreur du serveur |
| 503 | erreur du serveur (souvent, base de données down) |
| 504 | timeout, le serveur n'a pas répondu |
Entêtes (/Headers/) HTTP
Entêtes Client
| Header | Signification |
|---|---|
| Host | FQDN du site web visité (obligatoire depuis 1.1) |
| Referer | URI du document qui a donné un lien vers la ressource demandée (ex: autre site) |
| User-Agent | Logiciel (navigateur, CLI (curl...), bot) utilisé par le client |
| Connection | options à utiliser avec la connexion actuelle (ex: Keep-Alive) |
| Accept | liste les types MIME supportés par le client |
| Accept-Charset | liste des encodages de caractères supportés par le client |
| Accept-Language | Langues acceptées par le client |
| TE | Spécifie les encodages de contenus supportés et le support du header Trailer |
Entêtes Serveur
| Header | Signification |
|---|---|
| Date | Date de génération du message |
| Server | Logiciel serveur Web utilisé |
| Content-Type | Type MIME de la ressource |
| Content-Length | Taille en octets de la ressource |
| Expires | Temps de mise en cache client (navigateur) |
| Last-Modified | Date de dernière modification de la ressource |
| Connection | options à utiliser avec la connexion actuelle (ex: Keep-Alive) |
| Transfer-Encoding | Encodage utilisé pour le transfert (toujours "chunked") |
| Trailer | Liste tous les headers situés après le contenu transféré |
Il existe aussi de nombreux /headers/ ne faisant pas partie du protocole HTTP, en général préfixés par un X-, quelques exemples:
| Header | signification |
|---|---|
| X-Forwarded-For | transmet (reverse proxy) l'IP du client en backend |
| X-Forwarded-Proto | Détermine le proto utilisé (http ou https) en frontent |
| X-Cache | HIT ou MISS du cache Web utilisé (ex: Varnish) |
Sécurisation des accès: authentification et TLS (https)
Authentification /basic/ et /digest/
HTTPS
Fonctionnement du protocole FTP
Historique FTP
FTP, /File Transfer Protocol/ est un protocole de transfert de fichiers antédiluvien mais toujours utilisé. Ses avantages principaux sont sa simplicité et sa rapidité, ses défauts principaux étant son âge et, par défaut, son absence de sécurité (mis à part un couple login/mot de passe transmis en clair) et de chiffrement des communications entre le serveur et le client. Il écoute par défaut sur le port 21, et transmet les données depuis son port numéro 20.
Il a été défini pour la première fois dans la RFC 114 (!) en 1971. Sa spécification a évolué en 1980 avec la RFC 765, puis la RFC 959 en 1985 pour finir par les RFC 2228 et 2428 en 1997 et 1998 respectivement (ajoutant la prise en charge d'IPv6, et les extensions de sécurité (chiffrement) notamment).
Mode Actif/Passif
FTP fonctionne selon deux modes: Actif et Passif. En mode actif, c'est le client FTP qui détermine quel port utiliser - ce qui peut nécessiter d'ouvrir le(s) port(s) correspondants côté pare-feu client, car le serveur est obligé d'initialiser une connexion sur le port du client.
À l'inverse, en mode passif, c'est le serveur qui indique au client le port à utiliser pour la transmission des données. Ici, c'est le client qui initialise la connexion de transfert de données, et donc, il ne rencontrera pas les problématiques liées au NAT et au pare-feu du mode actif.
Modes de transfert
FTP propose deux modes de transfert des données:
- Le mode binaire: le fichier est transmis tel quel.
- Le mode ASCII: il ne s'applique qu'aux fichiers texte. Il adapte le fichier aux formats gérés par le client (par exemple, pour passer à la ligne suivante sous UNIX on utilise /Line Feed/, alors que sous Windows on utilise /Carriage Return/ puis /Line Feed/. (les fameux LF/CR, l'enfer des développeurs Windows qui programment pour UNIX. Concommitamment, c'est infernal pour les adminsys UNIX qui étaient obligés de corriger les LF/CR Windows du code poussé sur l'environnement UNIX. D'expérience, c'est insupportable, même si /sed/, /vim/ ou /emacs/ permettent d'alléger l'horreur de la chose)
Setup basique ProFTPd
On va commencer par installer notre serveur FTP (ProFTPd) ainsi que sa documentation sur notre VM:
$ sudo apt install proftpd-basic proftpd-doc
Maintenant, si j'essaye de me connecter en FTP (par ex: ftp://192.168.1.198:21) avec mon login et mot de passe utilisateur... ça marche. On va vouloir limiter, pour commencer, les accès, par exemple aux membres d'un groupe /ftpgroup/ dédié.
On va se créer un utilisateur "ftpclient" membre du groupe "ftpgroup", sans shell de login:
$ sudo useradd -ms /bin/false ftpclient
$ sudo passwd ftpclient
$ sudo addgroup ftpgroup
$ sudo usermod -aG ftpgroup ftpclient
On va ensuite limiter l'accès FTP à ce groupe, en modifiant notre fichier de configuration ProFTPd pour:
- "chrooter" les utilisateurs dans leur répertoire (ils ne peuvent pas parcourir l'arborescence "au-dessus" de leur répertoire
- Limiter le login FTP aux utilisateurs du groupe /ftpgroup/
- S'assurer que le login root est bien désactivé...
- S'assurer qu'on n'a pas besoin d'un shell de login fonctionnel (genre bash) pour nos utilisateurs FTP (on préfèrera qu'ils n'aient pas de shell du tout
)
On va donc éditer le fichier de configuration, et vérifier (ou ajouter) la présence des lignes suivantes:
RootLogin off
RequireValidShell off
DefaultRoot ~
<Limit LOGIN>
DenyGroup !ftpgroup
</Limit>
On n'a plus qu'à relancer ProFTPd:
$ sudo systemctl restart proftpd
Sécurisation
Alors pour le moment notre serveur FTP parle en clair sur Internet, c'est embêtant. It works©, mais c'est dangereux: nos identifiants ainsi que les données qu'on transfère passent en clair sur le réseau.
On va donc le configurer pour qu'il puisse papoter en TLS, en utilisant un certificat auto-signé:
$ sudo mkdir /etc/proftpd/certs
$ sudo openssl req -new -x509 -keyout /etc/proftpd/certs/proftpd.key.pem \
-days 365 -nodes -out /etc/proftpd/certs/proftpd.crt.pem
$ sudo chmod 600 /etc/proftpd/certs/*
On va ensuite éditer le fichier de configuration tls de ProFTPd:
$ sudo vim /etc/proftpd/tls.conf
TLSEngine on
TLSLog /var/log/proftpd/tls.log
TLSProtocol TLSv1.2
TLSRSACertificateFile /etc/proftpd/certs/proftpd.crt.pem
TLSRSACertificateKeyFile /etc/proftpd/certs/proftpd.key.pem
TLSOptions NoCertRequest EnableDiags NoSessionReuseRequired
TLSOptions AllowClientRenegotiations
TLSVerifyClient off
TLSRequired on
RequireValidShell no
On relance proftpd:
$ sudo systemctl restart proftpd
Et on peut tester de se connecter en FTPS à notre VM via un client FTP comme Filezilla