lid's Wiki

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:

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:

DORA

Quelles informations le serveur DHCP peut-il envoyer?

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:

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 &ecirc;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:

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 &ecirc;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:

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.

./martineutf8.png

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:

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:

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:

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 😃