Services réseau#

Hide code cell source

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.gridspec as gridspec
import numpy as np
import pandas as pd
import seaborn as sns
import socket
import re
from collections import defaultdict

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

Nginx — serveur web et reverse proxy#

Nginx (prononcé « engine-x ») est un serveur HTTP haute performance, proxy inverse et répartiteur de charge. Son architecture événementielle non-bloquante lui permet de gérer des milliers de connexions simultanées avec une faible empreinte mémoire.

Installation et structure de configuration#

# Installation
apt install nginx          # Debian/Ubuntu
dnf install nginx          # RHEL/Fedora

# Structure des répertoires
/etc/nginx/
├── nginx.conf             # configuration globale
├── conf.d/                # fragments inclus
├── sites-available/       # serveurs virtuels disponibles
└── sites-enabled/         # liens symboliques vers les actifs

Configuration de base — server blocks#

# /etc/nginx/sites-available/mon-site.conf

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    root /var/www/example.com/html;
    index index.html index.htm;

    # Compression gzip
    gzip on;
    gzip_types text/plain text/css application/json application/javascript;

    # Headers de sécurité
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";
    add_header X-XSS-Protection "1; mode=block";

    location / {
        try_files $uri $uri/ =404;
    }

    location /static/ {
        alias /var/www/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # Logs
    access_log /var/log/nginx/example.com.access.log;
    error_log  /var/log/nginx/example.com.error.log warn;
}
# Activer le site
ln -s /etc/nginx/sites-available/mon-site.conf /etc/nginx/sites-enabled/

# Tester la syntaxe
nginx -t

# Recharger
systemctl reload nginx

Reverse proxy#

upstream backend_app {
    server 127.0.0.1:8000;
    server 127.0.0.1:8001;
    server 127.0.0.1:8002;
    keepalive 32;
}

server {
    listen 443 ssl http2;
    server_name api.example.com;

    location / {
        proxy_pass         http://backend_app;
        proxy_http_version 1.1;
        proxy_set_header   Host              $host;
        proxy_set_header   X-Real-IP         $remote_addr;
        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_set_header   Connection        "";

        proxy_connect_timeout  10s;
        proxy_read_timeout     60s;
        proxy_buffering        on;
        proxy_buffer_size      8k;
    }
}

Load balancing#

Nginx supporte plusieurs algorithmes de répartition de charge :

# Round-robin (défaut)
upstream backend { server 10.0.1.1; server 10.0.1.2; }

# Weighted round-robin
upstream backend {
    server 10.0.1.1 weight=3;
    server 10.0.1.2 weight=1;
}

# Least connections
upstream backend {
    least_conn;
    server 10.0.1.1;
    server 10.0.1.2;
}

# IP hash (persistance de session)
upstream backend {
    ip_hash;
    server 10.0.1.1;
    server 10.0.1.2;
}

# Avec health checks (Nginx Plus ou ngx_upstream_module)
upstream backend {
    server 10.0.1.1 max_fails=3 fail_timeout=30s;
    server 10.0.1.2 max_fails=3 fail_timeout=30s;
    server 10.0.1.3 backup;        # serveur de secours
}

TLS avec Nginx#

Certificat auto-signé (développement/test)#

# Générer une clé et un certificat auto-signé (10 ans)
openssl req -x509 -nodes -newkey rsa:4096 \
    -keyout /etc/nginx/ssl/example.key \
    -out    /etc/nginx/ssl/example.crt \
    -days 3650 \
    -subj "/C=FR/ST=IDF/L=Paris/O=MaSociete/CN=example.com"

# Générer un certificat avec SAN (Subject Alternative Names)
openssl req -x509 -nodes -newkey rsa:4096 \
    -keyout /etc/nginx/ssl/example.key \
    -out    /etc/nginx/ssl/example.crt \
    -days 365 \
    -addext "subjectAltName=DNS:example.com,DNS:www.example.com,IP:203.0.113.5"

Let’s Encrypt avec Certbot#

# Installation
apt install certbot python3-certbot-nginx

# Obtenir un certificat et configurer Nginx automatiquement
certbot --nginx -d example.com -d www.example.com

# Certificat seul (sans modification Nginx)
certbot certonly --nginx -d example.com

# Renouvellement automatique (déjà configuré via systemd timer)
systemctl status certbot.timer
certbot renew --dry-run

Configuration TLS moderne (A+ sur SSL Labs)#

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Protocoles et ciphers modernes
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;

    # HSTS (2 ans)
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 1.1.1.1 8.8.8.8 valid=300s;

    # Session cache
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;
}

# Redirection HTTP → HTTPS
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

DNS avec Bind9#

Bind9 (Berkeley Internet Name Domain) est le serveur DNS le plus répandu. En production, il peut être serveur faisant autorité (répond pour ses zones) ou résolveur récursif (interroge d’autres serveurs).

Configuration principale#

# /etc/bind/named.conf.options
options {
    directory "/var/cache/bind";

    listen-on { 127.0.0.1; 192.168.1.1; };
    listen-on-v6 { ::1; };

    # Autoriser les requêtes depuis le LAN
    allow-query { localhost; 192.168.0.0/16; };

    # Forwarders (upstream DNS)
    forwarders { 1.1.1.1; 8.8.8.8; };
    forward first;

    # DNSSEC validation
    dnssec-validation auto;

    # Masquer la version Bind
    version "not available";
};
# /etc/bind/named.conf.local
zone "example.com" {
    type master;
    file "/etc/bind/zones/db.example.com";
    allow-transfer { 192.168.1.2; };   # serveur DNS secondaire
};

zone "1.168.192.in-addr.arpa" {
    type master;
    file "/etc/bind/zones/db.192.168.1";
};

Fichier de zone#

; /etc/bind/zones/db.example.com
$ORIGIN example.com.
$TTL    3600

@   IN  SOA  ns1.example.com.  hostmaster.example.com. (
            2024030101  ; Serial (YYYYMMDDNN)
            3600        ; Refresh
            900         ; Retry
            604800      ; Expire
            300 )       ; Negative TTL

; Serveurs de noms
@       IN  NS   ns1.example.com.
@       IN  NS   ns2.example.com.

; Enregistrements A et AAAA
ns1         IN  A       203.0.113.1
ns2         IN  A       203.0.113.2
@           IN  A       203.0.113.5
www         IN  CNAME   @
mail        IN  A       203.0.113.10
api         IN  A       203.0.113.15

; IPv6
@           IN  AAAA    2001:db8::5

; Messagerie
@           IN  MX  10  mail.example.com.
@           IN  MX  20  mail2.example.com.

; SPF, DKIM, DMARC
@           IN  TXT "v=spf1 mx a -all"
@           IN  TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com"
# Vérifier la syntaxe
named-checkzone example.com /etc/bind/zones/db.example.com
named-checkconf

# Recharger les zones sans redémarrer
rndc reload
rndc reload example.com

DNS avec Unbound#

Unbound est un résolveur DNS récursif validant DNSSEC, conçu pour être déployé en local ou comme cache DNS sur un réseau. Plus léger que Bind9 pour ce rôle.

# /etc/unbound/unbound.conf
server:
    interface: 127.0.0.1
    interface: 192.168.1.1
    port: 53

    do-ip4: yes
    do-ip6: yes
    do-udp: yes
    do-tcp: yes

    access-control: 127.0.0.0/8 allow
    access-control: 192.168.0.0/16 allow
    access-control: 0.0.0.0/0 refuse

    # Cache
    cache-min-ttl: 60
    cache-max-ttl: 86400
    msg-cache-size: 128m
    rrset-cache-size: 256m

    # DNSSEC
    auto-trust-anchor-file: "/var/lib/unbound/root.key"

    # DNS-over-TLS vers les forwarders
    tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt

    # Confidentialité
    hide-identity: yes
    hide-version: yes
    qname-minimisation: yes

forward-zone:
    name: "."
    forward-tls-upstream: yes
    forward-addr: 1.1.1.1@853#cloudflare-dns.com
    forward-addr: 8.8.8.8@853#dns.google
# Vérifier la configuration
unbound-checkconf

# Contrôle du service
unbound-control status
unbound-control stats
unbound-control flush example.com        # vider le cache pour un domaine
unbound-control flush_zone example.com

NFS — partage de fichiers réseau#

NFS (Network File System) permet de monter un système de fichiers distant comme s’il était local. Il est natif sur Linux et ne nécessite aucun client spécial.

Configuration du serveur NFS#

# Installation
apt install nfs-kernel-server

# /etc/exports
/srv/nfs/data       192.168.1.0/24(rw,sync,no_subtree_check)
/srv/nfs/backups    192.168.1.5(ro,sync,no_subtree_check,no_root_squash)
/home               192.168.1.0/24(rw,sync,root_squash,no_subtree_check)
/srv/public         *(ro,sync,insecure)

Options importantes des exports :

Option

Signification

rw / ro

Lecture-écriture / lecture seule

sync

Écriture confirmée sur disque avant réponse (sûr)

async

Écriture asynchrone (plus rapide, risqué)

root_squash

Mappe root client → nobody (sécurité par défaut)

no_root_squash

root client = root serveur (dangereux)

all_squash

Mappe tous les utilisateurs → nobody

anonuid/anongid

UID/GID du compte anonyme

no_subtree_check

Désactive la vérification de sous-arbre (recommandé)

insecure

Autorise les ports > 1024 (clients non-root)

# Appliquer les exports sans redémarrage
exportfs -ra
exportfs -v        # vérifier ce qui est exporté

# Redémarrer le service
systemctl enable --now nfs-kernel-server

Client NFS#

# Montage manuel
mount -t nfs 192.168.1.1:/srv/nfs/data /mnt/data

# Avec options (recommandé)
mount -t nfs -o rw,hard,intr,rsize=65536,wsize=65536,timeo=14 \
    192.168.1.1:/srv/nfs/data /mnt/data

# /etc/fstab pour montage automatique
192.168.1.1:/srv/nfs/data  /mnt/data  nfs  rw,hard,intr,rsize=65536,wsize=65536  0 0

# Montage automatique via systemd (lazy mount)
# → utiliser automount units systemd

NFSv4 et Kerberos

NFSv4 est la version recommandée pour les nouvelles installations. Elle utilise un seul port TCP (2049), supporte l’authentification Kerberos (sec=krb5i pour l’intégrité, sec=krb5p pour le chiffrement) et offre un ACL plus riche. idmapd est nécessaire pour la correspondance UID/nom entre client et serveur.

Samba — partage SMB/CIFS#

Samba implémente le protocole SMB/CIFS et permet aux systèmes Linux d’exposer des partages accessibles par Windows, macOS et d’autres Linux.

Configuration de base#

# Installation
apt install samba

# /etc/samba/smb.conf
[global]
    workgroup = WORKGROUP
    server string = Serveur Samba %v
    netbios name = FILESERVER
    security = user
    map to guest = bad user
    dns proxy = no

    # Protocoles modernes uniquement
    server min protocol = SMB2
    client min protocol = SMB2

    # Logs
    log file = /var/log/samba/log.%m
    max log size = 1000

[partage-public]
    comment = Partage public en lecture seule
    path = /srv/samba/public
    browseable = yes
    read only = yes
    guest ok = yes

[partage-data]
    comment = Données équipe
    path = /srv/samba/data
    browseable = yes
    read only = no
    valid users = @sambagroup
    create mask = 0664
    directory mask = 0775
    force group = sambagroup

[homes]
    comment = Répertoires personnels
    browseable = no
    read only = no
    create mask = 0600
    directory mask = 0700
# Vérifier la configuration
testparm

# Gérer les utilisateurs Samba (base de données séparée de /etc/passwd)
smbpasswd -a alice          # ajouter et définir le mot de passe
smbpasswd -e alice          # activer un compte
smbpasswd -d alice          # désactiver
pdbedit -L                  # lister les utilisateurs Samba

# Statut du service
systemctl enable --now smbd nmbd
smbstatus

Accès depuis un client Linux#

# Lister les partages
smbclient -L //192.168.1.1 -U alice

# Accès interactif
smbclient //192.168.1.1/partage-data -U alice

# Montage CIFS
apt install cifs-utils
mount -t cifs //192.168.1.1/partage-data /mnt/data \
    -o username=alice,password=secret,uid=1000,gid=1000,iocharset=utf8

# Via /etc/fstab avec fichier de credentials
//192.168.1.1/partage-data  /mnt/data  cifs  credentials=/etc/samba/.creds,uid=1000  0 0

Supervision des services réseau#

systemctl status et journalctl#

# État d'un service avec les dernières lignes de log
systemctl status nginx
systemctl status bind9
systemctl status nfs-kernel-server

# Logs en temps réel
journalctl -u nginx -f

# Logs depuis le dernier démarrage
journalctl -u nginx -b

# Logs des dernières 24 heures
journalctl -u nginx --since "24 hours ago"

# Logs d'erreur uniquement
journalctl -u nginx -p err

# Filtrer par heure
journalctl -u nginx --since "2024-03-01 08:00" --until "2024-03-01 09:00"

Healthchecks#

# Vérifier qu'un service répond (HTTP)
curl -fsS --max-time 5 http://localhost/health || echo "SERVICE DOWN"

# Vérifier un port TCP
nc -zv -w 3 localhost 443 && echo "OK" || echo "DOWN"

# Script de healthcheck simple
cat > /usr/local/bin/check-nginx.sh << 'EOF'
#!/bin/bash
STATUS=$(curl -fsS -o /dev/null -w "%{http_code}" http://localhost/)
if [ "$STATUS" -ne 200 ]; then
    echo "CRITICAL: nginx répond $STATUS"
    systemctl restart nginx
    exit 2
fi
echo "OK: nginx répond 200"
exit 0
EOF
chmod +x /usr/local/bin/check-nginx.sh

Monitoring externe#

# Uptime Kuma, Nagios, Zabbix ou simple cron
# Exemple avec cron + curl
*/5 * * * * /usr/local/bin/check-nginx.sh >> /var/log/healthcheck.log 2>&1

# Prometheus Node Exporter (métriques système)
apt install prometheus-node-exporter
systemctl enable --now prometheus-node-exporter
# Métriques disponibles sur http://localhost:9100/metrics

Démonstrations Python#

Parse d’une configuration Nginx — extraction des server blocks#

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

# Configuration Nginx simulée
nginx_config = """
server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example.com;
    access_log /var/log/nginx/example.com.access.log;
    error_log /var/log/nginx/example.com.error.log;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;
    root /var/www/example.com;
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    access_log /var/log/nginx/example.com.ssl.access.log;
    error_log /var/log/nginx/example.com.ssl.error.log;
}

server {
    listen 443 ssl http2;
    server_name api.example.com;
    root /var/www/api;
    ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
    access_log /var/log/nginx/api.access.log;
}

server {
    listen 80;
    server_name staging.example.com;
    root /var/www/staging;
    access_log /var/log/nginx/staging.access.log;
}
"""

def parser_nginx(config: str) -> list[dict]:
    """Extrait les server blocks d'une configuration Nginx."""
    blocs = []
    # Trouver tous les blocs server { ... }
    pattern = re.compile(r'server\s*\{([^}]+)\}', re.DOTALL)
    for m in pattern.finditer(config):
        contenu = m.group(1)
        def get(key):
            r = re.search(rf'^\s*{key}\s+([^;]+);', contenu, re.MULTILINE)
            return r.group(1).strip() if r else ""
        def get_all(key):
            return [r.group(1).strip()
                    for r in re.finditer(rf'^\s*{key}\s+([^;]+);', contenu, re.MULTILINE)]
        listen = get("listen")
        ssl = "ssl" in listen or "ssl_certificate" in contenu
        blocs.append({
            "listen": listen,
            "server_name": get("server_name"),
            "root": get("root") or "—",
            "ssl": ssl,
            "access_log": get("access_log").split("/")[-1] if get("access_log") else "—",
            "protocole": "HTTPS" if ssl else "HTTP",
        })
    return blocs

blocs = parser_nginx(nginx_config)
df_nginx = pd.DataFrame(blocs)
print(f"Server blocks trouvés : {len(blocs)}\n")
print(df_nginx[["listen", "server_name", "protocole", "ssl"]].to_string(index=False))

# Visualisation
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Tableau des server blocks
ax = axes[0]
ax.axis("off")
tbl = ax.table(
    cellText=df_nginx[["listen", "server_name", "protocole", "root"]].values,
    colLabels=["Listen", "Server Name", "Protocole", "Root"],
    loc="center", cellLoc="center"
)
tbl.auto_set_font_size(False)
tbl.set_fontsize(8.5)
tbl.scale(1.1, 1.8)
colors_proto = {"HTTPS": "#59a14f", "HTTP": "#f28e2b"}
for i, bloc in enumerate(blocs):
    c = colors_proto[bloc["protocole"]] + "44"
    for j in range(4):
        tbl[(i+1, j)].set_facecolor(c)
for j in range(4):
    tbl[(0, j)].set_facecolor("#333333")
    tbl[(0, j)].set_text_props(color="white", fontweight="bold")
ax.set_title("Server blocks extraits", fontweight="bold", pad=12)

# Répartition HTTP vs HTTPS
ax2 = axes[1]
proto_counts = df_nginx["protocole"].value_counts()
colors_pie = [colors_proto[p] for p in proto_counts.index]
wedges, texts, autotexts = ax2.pie(
    proto_counts.values, labels=proto_counts.index,
    autopct="%1.0f%%", colors=colors_pie,
    startangle=90, pctdistance=0.7
)
for at in autotexts:
    at.set_fontweight("bold")
    at.set_fontsize(12)
ax2.set_title("Répartition HTTP / HTTPS", fontweight="bold")

plt.suptitle("Analyse de configuration Nginx", fontsize=13, fontweight="bold")
plt.show()
Server blocks trouvés : 4

       listen                 server_name protocole   ssl
           80 example.com www.example.com      HTTP False
443 ssl http2 example.com www.example.com     HTTPS  True
443 ssl http2             api.example.com     HTTPS  True
           80         staging.example.com      HTTP False
_images/12697d2e88e5a242537e042bda83892d517b32c5ea95d244e45870b1e3da52da.png

Schéma reverse proxy et load balancing#

sns.set_theme(style="white", palette="muted", font_scale=1.0)

fig, ax = plt.subplots(figsize=(14, 8))
ax.set_xlim(0, 14)
ax.set_ylim(0, 8)
ax.axis("off")

def boite(ax, x, y, w, h, titre, sous="", color="#4e79a7", fs=9):
    r = plt.Rectangle((x-w/2, y-h/2), w, h,
                       facecolor=color+"33", edgecolor=color, linewidth=2, zorder=3)
    ax.add_patch(r)
    ax.text(x, y+(0.15 if sous else 0), titre, ha="center", va="center",
            fontsize=fs, fontweight="bold", zorder=4)
    if sous:
        ax.text(x, y-0.22, sous, ha="center", va="center",
                fontsize=7.5, color="#444444", zorder=4)

def fleche(ax, x1, y1, x2, y2, label="", color="#555555", style="->"):
    ax.annotate("", xy=(x2, y2), xytext=(x1, y1),
                arrowprops=dict(arrowstyle=style, color=color, lw=1.8), zorder=2)
    if label:
        ax.text((x1+x2)/2+0.05, (y1+y2)/2+0.2, label, ha="center",
                fontsize=7.5, color=color)

# Internet
boite(ax, 7.0, 7.2, 2.0, 0.75, "Internet / Client", "", "#888888", fs=10)

# CDN/WAF
boite(ax, 7.0, 6.0, 2.4, 0.75, "CDN / WAF", "Cloudflare, Fastly…", "#76b7b2")
fleche(ax, 7.0, 6.82, 7.0, 6.38, "HTTPS :443", "#76b7b2")

# Nginx LB
boite(ax, 7.0, 4.7, 2.8, 0.9, "Nginx", "Reverse proxy + LB\n:80 :443", "#f28e2b")
fleche(ax, 7.0, 5.62, 7.0, 5.15, "HTTPS :443", "#f28e2b")

# Upstream 1
boite(ax, 3.0, 3.2, 2.4, 0.85, "Backend App #1", "127.0.0.1:8000\nGunicorn/uWSGI", "#59a14f", fs=8.5)
fleche(ax, 5.6, 4.5, 4.2, 3.5, "HTTP :8000\n(round-robin)", "#59a14f")

# Upstream 2
boite(ax, 7.0, 3.2, 2.4, 0.85, "Backend App #2", "127.0.0.1:8001\nGunicorn/uWSGI", "#59a14f", fs=8.5)
fleche(ax, 7.0, 4.25, 7.0, 3.63, "HTTP :8001", "#59a14f")

# Upstream 3
boite(ax, 11.0, 3.2, 2.4, 0.85, "Backend App #3", "127.0.0.1:8002\nGunicorn/uWSGI", "#59a14f", fs=8.5)
fleche(ax, 8.4, 4.5, 9.8, 3.5, "HTTP :8002", "#59a14f")

# Base de données
boite(ax, 5.0, 1.6, 2.4, 0.8, "Base de données", "PostgreSQL :5432", "#4e79a7", fs=8.5)
boite(ax, 9.0, 1.6, 2.4, 0.8, "Cache Redis", "Redis :6379", "#e15759", fs=8.5)

for xapp, xdb in [(3.0, 5.0), (7.0, 6.0), (11.0, 9.5)]:
    fleche(ax, xapp, 2.77, xdb, 2.0, color="#aaaaaa")

# Annotations algorithmes LB
ax.text(7.0, 4.05, "Algorithmes : round-robin | least_conn | ip_hash | weighted",
        ha="center", fontsize=7.5, color="#f28e2b",
        bbox=dict(boxstyle="round,pad=0.3", facecolor="#fff4e6",
                  edgecolor="#f28e2b", linewidth=0.8))

# Légende SSL
ax.text(0.5, 1.0, "TLS/SSL\n(chiffré)", fontsize=8, color="#4e79a7", ha="center",
        bbox=dict(boxstyle="round", facecolor="#e8f4f8", edgecolor="#4e79a7"))
ax.text(0.5, 0.3, "HTTP interne\n(non chiffré)", fontsize=8, color="#59a14f", ha="center",
        bbox=dict(boxstyle="round", facecolor="#e8f8e8", edgecolor="#59a14f"))

ax.set_title("Architecture Nginx — Reverse Proxy et Load Balancing",
             fontsize=13, fontweight="bold", pad=14)
plt.show()
_images/549bfcfd73125bd3c90937e46377534102e95445fe92262c105e1d11309d81b9.png

Enregistrements DNS — visualisation#

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.0)

# Types d'enregistrements DNS avec exemples et descriptions
enregistrements = [
    {"Type": "A",     "Famille": "Adressage", "Exemple": "example.com → 203.0.113.5",    "Description": "IPv4 address",         "RFC": "RFC 1035", "Score": 100},
    {"Type": "AAAA",  "Famille": "Adressage", "Exemple": "example.com → 2001:db8::1",    "Description": "IPv6 address",         "RFC": "RFC 3596", "Score": 90},
    {"Type": "CNAME", "Famille": "Alias",     "Exemple": "www → example.com.",            "Description": "Canonical name alias", "RFC": "RFC 1035", "Score": 85},
    {"Type": "MX",    "Famille": "Messagerie","Exemple": "@ 10 mail.example.com.",        "Description": "Mail exchanger",       "RFC": "RFC 1035", "Score": 80},
    {"Type": "NS",    "Famille": "Délégation","Exemple": "@ ns1.example.com.",            "Description": "Name server",          "RFC": "RFC 1035", "Score": 75},
    {"Type": "TXT",   "Famille": "Texte",     "Exemple": "@ \"v=spf1 mx -all\"",          "Description": "Text / SPF / DKIM",    "RFC": "RFC 1035", "Score": 70},
    {"Type": "PTR",   "Famille": "Reverse",   "Exemple": "5.113.0.203.in-addr.arpa → …", "Description": "Reverse DNS lookup",   "RFC": "RFC 1035", "Score": 60},
    {"Type": "SOA",   "Famille": "Zone",      "Exemple": "@ ns1 admin 2024030101 …",      "Description": "Start of Authority",   "RFC": "RFC 1035", "Score": 95},
    {"Type": "SRV",   "Famille": "Service",   "Exemple": "_http._tcp 10 0 80 www.",       "Description": "Service locator",      "RFC": "RFC 2782", "Score": 55},
    {"Type": "CAA",   "Famille": "Sécurité",  "Exemple": "0 issue \"letsencrypt.org\"",   "Description": "CA Authorization",    "RFC": "RFC 8659", "Score": 50},
]

df_dns = pd.DataFrame(enregistrements)

famille_colors = {
    "Adressage":  "#4e79a7",
    "Alias":      "#f28e2b",
    "Messagerie": "#59a14f",
    "Délégation": "#e15759",
    "Texte":      "#76b7b2",
    "Reverse":    "#edc948",
    "Zone":       "#b07aa1",
    "Service":    "#ff9da7",
    "Sécurité":   "#9c755f",
}

fig, axes = plt.subplots(1, 2, figsize=(16, 6))

# Tableau des enregistrements
ax = axes[0]
ax.axis("off")
tbl = ax.table(
    cellText=df_dns[["Type", "Famille", "Exemple", "RFC"]].values,
    colLabels=["Type", "Famille", "Exemple", "RFC"],
    loc="center", cellLoc="left"
)
tbl.auto_set_font_size(False)
tbl.set_fontsize(8)
tbl.scale(1.1, 1.65)
for i, row in enumerate(enregistrements):
    c = famille_colors[row["Famille"]] + "44"
    for j in range(4):
        tbl[(i+1, j)].set_facecolor(c)
    tbl[(i+1, 0)].set_text_props(fontweight="bold")
for j in range(4):
    tbl[(0, j)].set_facecolor("#333333")
    tbl[(0, j)].set_text_props(color="white", fontweight="bold")
ax.set_title("Types d'enregistrements DNS", fontweight="bold", pad=12)

# Fréquence d'utilisation
ax2 = axes[1]
colors_bar = [famille_colors[f] for f in df_dns["Famille"]]
bars = ax2.barh(df_dns["Type"][::-1], df_dns["Score"][::-1],
                color=colors_bar[::-1], edgecolor="white")
ax2.set_xlabel("Fréquence d'utilisation relative")
ax2.set_title("Utilisation des types DNS", fontweight="bold")
ax2.set_xlim(0, 115)
for bar, val in zip(bars, df_dns["Score"][::-1]):
    ax2.text(val + 1, bar.get_y() + bar.get_height()/2,
             f"{val}%", va="center", fontsize=8.5)

# Légende familles
patches = [mpatches.Patch(color=v, label=k) for k, v in famille_colors.items()]
axes[1].legend(handles=patches, loc="lower right", fontsize=7.5, ncol=2)

plt.suptitle("Enregistrements DNS — types et utilisation", fontsize=13, fontweight="bold")
plt.show()
_images/f5241e32cab1338369691d721fa0bf0c70e94b20766d26937f8b90ca45fe961e.png

Résolution DNS avec la stdlib Python#

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

domaines = [
    "python.org",
    "github.com",
    "wikipedia.org",
    "cloudflare.com",
    "debian.org",
]

print("=== Résolution DNS via socket.getaddrinfo() ===\n")
resultats = []

for domaine in domaines:
    try:
        infos = socket.getaddrinfo(domaine, None)
        ipv4 = [i[4][0] for i in infos if i[0] == socket.AF_INET]
        ipv6 = [i[4][0] for i in infos if i[0] == socket.AF_INET6]
        ipv4_uniques = list(dict.fromkeys(ipv4))
        ipv6_uniques = list(dict.fromkeys(ipv6))
        resultats.append({
            "Domaine": domaine,
            "IPv4": ipv4_uniques[0] if ipv4_uniques else "—",
            "Nb IPv4": len(ipv4_uniques),
            "IPv6": ipv6_uniques[0][:20] + "…" if ipv6_uniques else "—",
            "Nb IPv6": len(ipv6_uniques),
        })
        print(f"{domaine}")
        print(f"  IPv4 : {', '.join(ipv4_uniques) if ipv4_uniques else 'non résolu'}")
        if ipv6_uniques:
            print(f"  IPv6 : {ipv6_uniques[0]}")
        print()
    except socket.gaierror as e:
        print(f"{domaine} : erreur de résolution ({e})\n")
        resultats.append({"Domaine": domaine, "IPv4": "erreur",
                          "Nb IPv4": 0, "IPv6": "—", "Nb IPv6": 0})

df_res = pd.DataFrame(resultats)

# Résolution inverse
print("=== Résolution inverse via socket.gethostbyaddr() ===\n")
ips_test = []
for r in resultats:
    if r["IPv4"] not in ("—", "erreur"):
        ips_test.append((r["Domaine"], r["IPv4"]))

for domaine_orig, ip in ips_test[:4]:
    try:
        hostname, _, _ = socket.gethostbyaddr(ip)
        print(f"  {ip:16s}{hostname}")
    except socket.herror:
        print(f"  {ip:16s} → (pas de PTR)")

# Visualisation
fig, ax = plt.subplots(figsize=(12, 4.5))
ax.axis("off")
tbl = ax.table(
    cellText=df_res.values,
    colLabels=df_res.columns,
    loc="center", cellLoc="center"
)
tbl.auto_set_font_size(False)
tbl.set_fontsize(9.5)
tbl.scale(1.2, 2.0)
for i in range(len(df_res)):
    for j in range(len(df_res.columns)):
        tbl[(i+1, j)].set_facecolor("#e8f4f8" if i % 2 == 0 else "#ffffff")
for j in range(len(df_res.columns)):
    tbl[(0, j)].set_facecolor("#333333")
    tbl[(0, j)].set_text_props(color="white", fontweight="bold")
ax.set_title("Résolution DNS — résultats socket.getaddrinfo()",
             fontweight="bold", pad=14, fontsize=12)
plt.show()
=== Résolution DNS via socket.getaddrinfo() ===

python.org
  IPv4 : 151.101.128.223, 151.101.192.223, 151.101.0.223, 151.101.64.223
  IPv6 : 2a04:4e42:200::223

github.com
  IPv4 : 140.82.121.3

wikipedia.org
  IPv4 : 185.15.58.224
  IPv6 : 2a02:ec80:600:ed1a::1

cloudflare.com
  IPv4 : 104.16.132.229, 104.16.133.229
  IPv6 : 2606:4700::6810:84e5
debian.org
  IPv4 : 151.101.2.132, 151.101.194.132, 151.101.66.132, 151.101.130.132
  IPv6 : 2a04:4e42:600::644

=== Résolution inverse via socket.gethostbyaddr() ===

  151.101.128.223  → (pas de PTR)
  140.82.121.3     → lb-140-82-121-3-fra.github.com
  185.15.58.224    → text-lb.drmrs.wikimedia.org
  104.16.132.229   → (pas de PTR)
_images/e1d1282cd68abbefc8d8f96a3b7c26fad662fb9f5ae69fc03618c40450b41cfd.png

Résumé#

Les services réseau constituent le cœur fonctionnel d’un serveur Linux. Leur configuration rigoureuse et leur supervision continue sont indispensables en production.

Points essentiels à retenir :

  • Nginx est la solution polyvalente de référence pour HTTP, HTTPS, reverse proxy et load balancing. La directive proxy_pass et les blocs upstream suffisent à couvrir la majorité des architectures.

  • Le TLS moderne exige TLS 1.2 minimum, des ciphers AEAD (AES-GCM, ChaCha20-Poly1305) et le HSTS. Let’s Encrypt / Certbot automatise entièrement la gestion des certificats avec renouvellement systemd.

  • Bind9 est le serveur DNS faisant autorité de référence ; sa configuration en zone primaire/secondaire avec transfert AXFR reste le standard. Unbound est préférable comme résolveur récursif local avec validation DNSSEC et DNS-over-TLS.

  • NFS est simple à déployer pour les partages de fichiers intra-réseau Linux, mais ses permissions root_squash / no_root_squash sont critiques pour la sécurité. NFSv4 avec Kerberos est recommandé en environnement hétérogène.

  • Samba permet l’interopérabilité avec les clients Windows. testparm est l’outil de diagnostic indispensable ; maintenir server min protocol = SMB2 élimine les protocoles SMB1 vulnérables (EternalBlue).

  • La supervision combine systemctl status + journalctl pour le diagnostic immédiat, et des healthchecks périodiques (cron, systemd timer) pour la détection proactive. Une pile complète (Prometheus + Grafana ou Zabbix) est indispensable au-delà de quelques serveurs.

  • Le module Python socket de la stdlib permet d’interroger le DNS et d’effectuer des résolutions directes/inverses sans dépendance externe — utile pour les scripts de healthcheck et les outils d’inventaire.