ajoute le portail Laravel : connexion prof/élève, session unique, gestion de classe, salle d'attente
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
aa31b6112f
commit
30e14b1c54
191 changed files with 25324 additions and 0 deletions
227
portail/public/js/contenus-lot-8.js
Normal file
227
portail/public/js/contenus-lot-8.js
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
// Cours complets · lot 8 : processus et systemd, disques LVM RAID, SSH avancé, messagerie
|
||||
(function () {
|
||||
var reg = function (c) {
|
||||
(window.CONTENUS_PRO = window.CONTENUS_PRO || []).unshift(c.cours);
|
||||
(window.CONTENUS_PLUS = window.CONTENUS_PLUS || {})[c.cours.id] = c.plus;
|
||||
(window.CONTENUS_EXEMPLES = window.CONTENUS_EXEMPLES || {})[c.cours.id] = c.exemples;
|
||||
(window.CONTENUS_SCHEMAS = window.CONTENUS_SCHEMAS || {})[c.cours.id] = { termes: c.termes, figs: [] };
|
||||
};
|
||||
|
||||
reg({
|
||||
cours: { id: 'systemd', match: /Processus|systemd|journalctl|Planification|cron|timers|Journaux : rsyslog|logrotate|Démarrage : BIOS|GRUB|service en échec|Ordonnancement des processus/i,
|
||||
titre: 'Processus, services systemd, planification et journaux', duree: '',
|
||||
niveau: 'BTS SIO SISR · BTS CIEL IR · BUT R&T · TSSR · LPIC-1',
|
||||
prerequis: ['Le terminal Linux', 'Droits Unix'],
|
||||
competences: ['BTS SIO SISR · Bloc 2 · Exploiter les serveurs', 'TSSR · Exploiter des serveurs Linux', 'LPIC-1 · Topics 101 à 108'],
|
||||
intro: 'Un serveur, c’est d’abord un ensemble de processus qui tournent en permanence : serveur web, base de données, agent de supervision, tâches de sauvegarde. Savoir les observer, les arrêter proprement, les faire démarrer au bon moment et lire leurs journaux est le quotidien d’un administrateur. Ce module va du processus au service systemd, en passant par les signaux, la planification et la gestion des journaux, jusqu’au dépannage d’un service qui refuse de démarrer.',
|
||||
cm: [
|
||||
{ t: 'Processus, PID et arborescence', d: '', html: 'Chaque programme en cours d’exécution est un <b>processus</b>, identifié par un <b>PID</b> et rattaché à son parent (<b>PPID</b>). Le premier processus, PID 1, est <b>systemd</b> sur les distributions actuelles ; tous les autres en descendent. Un processus a un propriétaire, un état (R en exécution, S en attente, D en attente d’entrée-sortie non interruptible, Z zombie, T arrêté), une priorité et des ressources consommées. <code>ps aux</code> et <code>ps -ef --forest</code> les listent, <code>top</code> et <code>htop</code> les suivent en direct, <code>pgrep</code> et <code>pidof</code> retrouvent un PID par nom.' },
|
||||
{ t: 'Signaux et priorités', d: '', html: 'On communique avec un processus par des <b>signaux</b>. <b>SIGTERM</b> (15), envoyé par défaut par <code>kill</code>, demande poliment l’arrêt : le programme peut terminer ses écritures. <b>SIGKILL</b> (9) le tue immédiatement, sans lui laisser le choix : à réserver aux processus bloqués, car il peut laisser des fichiers incohérents. <b>SIGHUP</b> (1) demande souvent de relire la configuration. <b>SIGSTOP</b> et <b>SIGCONT</b> suspendent et relancent.<br><br>La <b>priorité</b> d’ordonnancement se règle par la valeur <b>nice</b>, de −20 (très prioritaire) à 19 (très peu) : <code>nice -n 10 commande</code> au lancement, <code>renice</code> ensuite. Une compression de sauvegarde lancée en nice 19 ne ralentit pas le service web. <code>ionice</code> fait de même pour les entrées-sorties.' },
|
||||
{ t: 'systemd et les unités', d: '', html: 'systemd pilote le démarrage et les services à l’aide d’<b>unités</b> : <code>.service</code> (un démon), <code>.timer</code> (une planification), <code>.socket</code>, <code>.mount</code>, <code>.target</code> (un groupe d’unités, équivalent des anciens niveaux d’exécution : multi-user.target, graphical.target). Les unités fournies par les paquets sont dans <code>/lib/systemd/system</code> ; les unités et surcharges de l’administrateur dans <code>/etc/systemd/system</code>, qui a priorité.<br><br><code>systemctl start|stop|restart|reload</code> agit maintenant ; <code>enable|disable</code> agit au démarrage ; <code>status</code> montre l’état, le PID principal, le cgroup et les dernières lignes de journal. Pour modifier une unité fournie par un paquet, on ne l’édite pas : <code>systemctl edit service</code> crée un fichier de surcharge qui survit aux mises à jour.', exemple: '# /etc/systemd/system/resa.service\n[Unit]\nDescription=Application de réservation\nAfter=network-online.target postgresql.service\nWants=network-online.target\n\n[Service]\nUser=resa\nWorkingDirectory=/srv/resa\nExecStart=/srv/resa/venv/bin/gunicorn -b 127.0.0.1:8000 app:app\nRestart=on-failure\nRestartSec=5\nMemoryMax=512M\nNoNewPrivileges=yes\nProtectSystem=strict\nReadWritePaths=/srv/resa/data\n\n[Install]\nWantedBy=multi-user.target' },
|
||||
{ t: 'Planifier : cron et timers', d: '', html: '<b>cron</b> exécute des commandes selon cinq champs (minute, heure, jour du mois, mois, jour de la semaine) : <code>30 2 * * 1-5 /usr/local/bin/sauvegarde</code> tourne à 2 h 30 du lundi au vendredi. Les tâches système se placent dans <code>/etc/cron.d/</code>, celles d’un utilisateur avec <code>crontab -e</code>. Pièges classiques : un environnement minimal (PATH réduit), l’absence de sortie visible, et le fuseau horaire.<br><br>Les <b>timers systemd</b> sont l’alternative moderne : journalisation automatique dans journald, rattrapage des exécutions manquées quand la machine était éteinte (<code>Persistent=true</code>), délai aléatoire pour éviter que cent serveurs lancent la même tâche à la même seconde, et dépendances entre unités. <code>systemctl list-timers</code> affiche les prochaines exécutions. <code>at</code> planifie une exécution unique.' },
|
||||
{ t: 'Journaux : journald, rsyslog, logrotate', d: '', html: '<b>journald</b> collecte les journaux du noyau, des services et du démarrage, avec des métadonnées (unité, PID, priorité). <code>journalctl -u nginx --since "1 hour ago"</code>, <code>-p err</code> pour les erreurs, <code>-b -1</code> pour le démarrage précédent, <code>-f</code> pour suivre. La persistance après redémarrage demande le dossier <code>/var/log/journal</code> (Storage=persistent). <b>rsyslog</b> écrit les journaux texte classiques dans /var/log et peut les envoyer vers un serveur central. <b>logrotate</b> fait tourner les fichiers (compression, nombre de fichiers conservés) pour qu’ils ne remplissent pas le disque ; journald se limite avec SystemMaxUse.' },
|
||||
{ t: 'Le démarrage', d: '', html: 'Au démarrage, le micrologiciel (<b>UEFI</b>, ou BIOS sur les anciennes machines) charge le chargeur d’amorçage <b>GRUB</b>, qui charge le noyau et l’<b>initramfs</b>, petit système de fichiers temporaire contenant les pilotes nécessaires pour monter la vraie racine. Le noyau lance systemd, qui atteint la cible par défaut. <code>systemd-analyze blame</code> classe les unités par durée de démarrage, <code>systemd-analyze critical-chain</code> montre la chaîne qui retarde le démarrage. En cas de panne, on démarre en <b>mode secours</b> (rescue.target) en ajoutant <code>systemd.unit=rescue.target</code> à la ligne du noyau dans GRUB.' }
|
||||
],
|
||||
synthese: ['PID, PPID, états ; ps, top, pgrep', 'SIGTERM avant SIGKILL ; nice et ionice pour les tâches lourdes', 'Unités systemd : start/enable, status, surcharge avec systemctl edit', 'Durcissement d’un service : User, NoNewPrivileges, ProtectSystem, MemoryMax', 'cron ou timers systemd (Persistent, journalisation)', 'journalctl -u -p -b ; logrotate et SystemMaxUse', 'UEFI → GRUB → noyau + initramfs → systemd ; systemd-analyze, rescue.target'],
|
||||
qcm: [
|
||||
['Quel signal envoyer en premier pour arrêter un processus ?', ['SIGKILL', 'SIGTERM', 'SIGSTOP', 'SIGHUP'], 1, 'Il laisse le programme terminer proprement.'],
|
||||
['Différence entre systemctl start et enable ?', ['Aucune', 'start agit maintenant, enable au démarrage', 'enable démarre immédiatement', 'start est obsolète'], 1, 'On fait souvent enable --now.'],
|
||||
['Comment modifier une unité fournie par un paquet ?', ['Éditer /lib/systemd/system', 'systemctl edit, qui crée une surcharge', 'Recompiler', 'Impossible'], 1, 'La surcharge survit aux mises à jour.'],
|
||||
['Que signifie 30 2 * * 1-5 dans cron ?', ['2 h 30 tous les jours', '2 h 30 du lundi au vendredi', 'Toutes les 30 minutes', 'Le 2 de chaque mois'], 1, 'Minute, heure, jour, mois, jour de semaine.'],
|
||||
['Quel avantage d’un timer avec Persistent=true ?', ['Plus rapide', 'Rattrape l’exécution manquée si la machine était éteinte', 'Chiffre la tâche', 'Aucun'], 1, 'Pratique pour les postes et VM.'],
|
||||
['Quelle commande montre les erreurs du démarrage précédent ?', ['dmesg', 'journalctl -b -1 -p err', 'systemctl status', 'cat /var/log/boot'], 1, '-b -1 = démarrage précédent.'],
|
||||
['Un processus en état Z est :', ['en exécution', 'zombie, terminé mais non récupéré par son parent', 'suspendu', 'prioritaire'], 1, 'Il ne consomme presque rien, le parent doit le récupérer.']
|
||||
],
|
||||
td: [
|
||||
{ n: 'Échauffement', c: 'Trouver les trois processus qui consomment le plus de mémoire, puis baisser la priorité du plus gros sans l’arrêter.', r: 'ps aux --sort=-%mem | head -4 · sudo renice 10 -p <PID> (et ionice -c3 -p <PID>)' },
|
||||
{ n: 'Application', c: 'Écrire l’unité systemd d’une application Python qui doit tourner sous l’utilisateur resa, redémarrer en cas d’échec, être limitée à 512 Mo et démarrer après PostgreSQL.', r: 'Voir l’exemple du cours : User=resa, After=postgresql.service, Restart=on-failure, MemoryMax=512M, WantedBy=multi-user.target · systemctl daemon-reload && systemctl enable --now resa' },
|
||||
{ n: 'Application', c: 'Remplacer la tâche cron « 0 3 * * * /usr/local/bin/sauvegarde » par un timer systemd qui rattrape les exécutions manquées.', r: 'sauvegarde.service (Type=oneshot, ExecStart=…) · sauvegarde.timer : [Timer] OnCalendar=*-*-* 03:00 · Persistent=true · RandomizedDelaySec=10m · [Install] WantedBy=timers.target · systemctl enable --now sauvegarde.timer' },
|
||||
{ n: 'Approfondissement', c: 'Le démarrage d’un serveur prend 2 minutes. Identifier l’unité responsable.', r: 'systemd-analyze · systemd-analyze blame | head · systemd-analyze critical-chain → souvent un montage réseau ou un service qui attend network-online, à corriger (nofail, _netdev, dépendances).' }
|
||||
],
|
||||
tp: { type: 'Lab VM', url: 'apercu-labs.html', desc: 'Exploitation d’un serveur Debian.', etapes: ['Observer et signaler des processus', 'Écrire et durcir une unité de service', 'Remplacer un cron par un timer', 'Lire les journaux par unité et priorité', 'Analyser le temps de démarrage'] },
|
||||
plusLoin: [['Documentation systemd (man systemd.service, systemd.timer)', 'https://www.freedesktop.org/software/systemd/man/latest/'], ['Debian Administrator’s Handbook (fr)', 'https://debian-handbook.info/browse/fr-FR/stable/'], ['LPI · objectifs LPIC-1', 'https://www.lpi.org/fr/our-certifications/lpic-1-overview/']] },
|
||||
plus: { cas: { titre: '', texte: '' },
|
||||
resolu: { enonce: 'Après une modification de configuration, le service de l’application de réservation ne démarre plus et le site renvoie une erreur 502.', etapes: [
|
||||
['Lire l’état', 'systemctl status resa\n● resa.service - Application de réservation\n Active: failed (Result: exit-code) since …\n Process: 2231 ExecStart=… (code=exited, status=1/FAILURE)\n→ Le processus principal s’est terminé avec le code 1.'],
|
||||
['Lire le journal de l’unité', 'journalctl -u resa -n 20 --no-pager\nPermissionError: [Errno 13] Permission denied: \'/srv/resa/data/app.db\'\n→ L’application ne peut plus écrire dans son dossier de données.'],
|
||||
['Chercher le changement', 'systemctl cat resa | grep -E "Protect|ReadWrite"\nProtectSystem=strict\n→ La surcharge de durcissement ajoutée hier rend tout le système en lecture seule, et ReadWritePaths a été oublié.'],
|
||||
['Corriger', 'systemctl edit resa\n[Service]\nReadWritePaths=/srv/resa/data\nsystemctl restart resa && systemctl is-active resa\nactive\n→ Le durcissement est conservé, avec une exception limitée au dossier de données.'],
|
||||
['Vérifier et prévenir', 'curl -sI http://127.0.0.1:8000/health | head -1 → HTTP/1.1 200 OK\n→ Procédure : toute modification d’unité est testée en préproduction, et systemd-analyze security resa mesure le niveau de durcissement.']
|
||||
] },
|
||||
erreurs: ['kill -9 par réflexe', 'Éditer directement les unités de /lib/systemd/system', 'Oublier daemon-reload après une modification', 'Tâches cron qui dépendent du PATH de l’utilisateur', 'Journaux sans rotation qui remplissent le disque', 'Démarrer un service sans enable et le perdre au redémarrage'],
|
||||
memo: [['Commande', 'Usage'], ['ps -ef --forest', 'arborescence des processus'], ['pgrep -a nom', 'retrouver un PID'], ['kill -TERM PID · kill -KILL PID', 'arrêter proprement, puis de force'], ['renice 10 -p PID', 'baisser la priorité'], ['systemctl status|start|enable --now', 'piloter un service'], ['systemctl edit unité', 'surcharger une unité'], ['systemctl list-timers', 'planifications'], ['journalctl -u unité -p err --since today', 'erreurs d’un service'], ['journalctl -b -1', 'démarrage précédent'], ['systemd-analyze blame', 'durées de démarrage'], ['systemd-analyze security unité', 'niveau de durcissement']],
|
||||
examen: { titre: 'Sujet type · 20 points', q: ['Expliquer SIGTERM, SIGKILL et SIGHUP. (3 pts)', 'Écrire une unité de service durcie. (6 pts)', 'Convertir une tâche cron en timer. (4 pts)', 'Diagnostiquer un service en échec à partir de sorties fournies. (4 pts)', 'Décrire la séquence de démarrage. (3 pts)'], r: ['arrêt poli, forcé, relecture', 'User, Restart, MemoryMax, Protect…', 'service oneshot + timer Persistent', 'status puis journalctl -u', 'UEFI, GRUB, noyau, initramfs, systemd'] },
|
||||
prof: { deroule: [['Ouverture', 'Tuer un serveur de base de données en SIGKILL et constater la récupération'], ['Cours', 'Processus et signaux'], ['Pratique', 'Unité systemd'], ['Cours', 'Planification et journaux'], ['Incident', 'Service en échec'], ['Clôture', 'Quiz flash']], diff: 'Élèves en difficulté : unité à compléter. Élèves avancés : durcissement systemd, analyse du démarrage.', eval: 'Lab validé, sujet type.' } },
|
||||
exemples: [
|
||||
['Arborescence des processus', 'Qui a lancé ce processus ?', 'ps -ef --forest | grep -B2 gunicorn | head -4', 'root 1 0 … /sbin/init\nresa 842 1 … gunicorn: master [app:app]\nresa 901 842 … gunicorn: worker [app:app]', 'Le maître gunicorn est lancé par systemd (PID 1) et crée ses workers.'],
|
||||
['État d’un service', 'Vérification rapide.', 'systemctl status nginx --no-pager | head -5', '● nginx.service - A high performance web server\n Loaded: loaded (/lib/systemd/system/nginx.service; enabled)\n Active: active (running) since Thu 2026-10-01 07:12:03 CEST\n Main PID: 612 (nginx)', 'Actif et activé au démarrage.'],
|
||||
['Erreurs du jour', 'Point du matin.', 'journalctl -p err --since today --no-pager | tail -3', 'oct. 01 03:00:12 srv sauvegarde.sh[4410]: restic: repository is already locked\noct. 01 06:41:55 srv kernel: EXT4-fs warning (device sdb1): …', 'Une sauvegarde bloquée par un verrou restant et un avertissement disque à surveiller.'],
|
||||
['Prochaines exécutions', 'Contrôle des planifications.', 'systemctl list-timers --no-pager | head -4', 'NEXT LEFT UNIT\nThu 2026-10-01 03:00:00 CEST 7h left sauvegarde.timer\nThu 2026-10-01 06:34:00 CEST 10h left apt-daily-upgrade.timer', 'La sauvegarde et les mises à jour automatiques sont planifiées.'],
|
||||
['Temps de démarrage', 'Serveur long à démarrer.', 'systemd-analyze blame | head -3', '1min 30.112s systemd-networkd-wait-online.service\n 4.211s postgresql@16-main.service', 'L’attente du réseau dure 90 secondes : une interface configurée mais non câblée. On la marque comme facultative.'],
|
||||
['Niveau de durcissement', 'Audit d’un service.', 'systemd-analyze security resa | tail -1', '→ Overall exposure level for resa.service: 3.9 OK', 'Le score est passé de 9,6 (très exposé) à 3,9 après ajout des options de protection.']
|
||||
],
|
||||
termes: ['processus', 'PID', 'PPID', 'SIGTERM', 'SIGKILL', 'SIGHUP', 'nice', 'renice', 'ionice', 'systemd', 'unité', 'systemctl', 'enable', 'daemon-reload', 'target', 'timer', 'cron', 'crontab', 'journalctl', 'journald', 'rsyslog', 'logrotate', 'UEFI', 'GRUB', 'initramfs', 'rescue.target', 'systemd-analyze']
|
||||
});
|
||||
|
||||
reg({
|
||||
cours: { id: 'disques', match: /Disques|partitions|fstab|LVM|RAID|mdadm|systèmes de fichiers|disque plein/i,
|
||||
titre: 'Stockage : partitions, systèmes de fichiers, LVM et RAID logiciel', duree: '',
|
||||
niveau: 'BTS SIO SISR · BTS CIEL IR · BUT R&T · TSSR · LPIC-1',
|
||||
prerequis: ['Le terminal Linux', 'Processus et systemd'],
|
||||
competences: ['BTS SIO SISR · Bloc 2 · Administrer le stockage', 'TSSR · Exploiter des serveurs', 'LPIC-1 · Topic 104'],
|
||||
intro: '« Disque plein » est l’une des causes les plus fréquentes de panne en production : la base de données s’arrête, les journaux ne s’écrivent plus, les utilisateurs ne peuvent plus enregistrer. Et un disque qui meurt sans redondance, c’est un serveur à reconstruire. Ce module présente la chaîne complète du stockage sous Linux : disques et partitions, systèmes de fichiers et montage, LVM pour agrandir à chaud, RAID logiciel pour survivre à une panne, et la méthode pour libérer de l’espace en urgence.',
|
||||
cm: [
|
||||
{ t: 'Disques et partitions', d: '', html: 'Linux nomme les disques <code>/dev/sda</code>, <code>/dev/sdb</code> (SATA, SCSI, USB, disques virtuels) ou <code>/dev/nvme0n1</code> (NVMe) ; les partitions ajoutent un numéro (<code>sda1</code>, <code>nvme0n1p1</code>). La table de partitions <b>GPT</b> remplace l’ancienne table MBR : plus de quatre partitions primaires, disques de plus de 2 To, et une partition système EFI pour démarrer en UEFI. <code>lsblk</code> affiche l’arborescence disques, partitions et points de montage ; <code>fdisk</code>, <code>gdisk</code> ou <code>parted</code> modifient les partitions.' },
|
||||
{ t: 'Systèmes de fichiers et montage', d: '', html: 'Un <b>système de fichiers</b> organise les données sur une partition : <b>ext4</b>, robuste et universel ; <b>XFS</b>, performant sur de gros volumes, agrandissable mais non réductible ; <b>Btrfs</b> et <b>ZFS</b>, avec instantanés, sommes de contrôle et compression. On le crée avec <code>mkfs.ext4</code>, on le <b>monte</b> dans l’arborescence avec <code>mount</code>, et on rend le montage permanent dans <code>/etc/fstab</code>, en désignant la partition par son <b>UUID</b> (stable) plutôt que par /dev/sdb1 (qui peut changer).', exemple: '$ sudo mkfs.ext4 -L donnees /dev/sdb1\n$ sudo blkid /dev/sdb1\n/dev/sdb1: LABEL="donnees" UUID="3f9c…" TYPE="ext4"\n# /etc/fstab\nUUID=3f9c… /srv/donnees ext4 defaults,noatime 0 2\n$ sudo mount -a && findmnt /srv/donnees', attention: 'Une erreur dans /etc/fstab peut empêcher le démarrage. On teste toujours avec mount -a (ou findmnt --verify) avant de redémarrer, et on ajoute nofail pour un disque non indispensable au démarrage.' },
|
||||
{ t: 'Espace et inodes', d: '', html: '<code>df -h</code> affiche l’espace par système de fichiers, <code>du -sh</code> la taille d’un dossier. Un système de fichiers peut aussi être plein en <b>inodes</b> (une structure par fichier) alors qu’il reste de l’espace : des millions de petits fichiers de session ou de cache en sont la cause habituelle, visible avec <code>df -i</code>. Un fichier supprimé mais encore ouvert par un processus continue d’occuper l’espace jusqu’à la fermeture : <code>lsof +L1</code> les révèle. ext4 réserve 5 % de l’espace à root, ce qui permet encore d’intervenir sur un disque « plein ».' },
|
||||
{ t: 'LVM : des volumes souples', d: '', html: 'Le <b>LVM</b> (Logical Volume Manager) ajoute une couche d’abstraction : des <b>volumes physiques</b> (PV, disques ou partitions) sont regroupés en un <b>groupe de volumes</b> (VG), dans lequel on découpe des <b>volumes logiques</b> (LV) qui accueillent les systèmes de fichiers. On peut agrandir un LV à chaud, ajouter un disque au VG, déplacer des données d’un disque à un autre ou prendre des <b>instantanés</b>. C’est la configuration par défaut de nombreuses installations serveur.', exemple: '$ sudo pvcreate /dev/sdc\n$ sudo vgextend vg_data /dev/sdc\n$ sudo lvextend -r -L +200G /dev/vg_data/lv_partage\n Size of logical volume vg_data/lv_partage changed from 500.00 GiB to 700.00 GiB.\n resize2fs … The filesystem on /dev/mapper/vg_data-lv_partage is now 183500800 blocks long.' },
|
||||
{ t: 'RAID logiciel', d: '', html: 'Le <b>RAID</b> combine plusieurs disques pour la performance ou la tolérance aux pannes. <b>RAID 0</b> répartit les données : rapide, mais la perte d’un disque perd tout. <b>RAID 1</b> duplique : on survit à la perte d’un disque sur deux. <b>RAID 5</b> répartit avec une parité : on perd un disque de capacité et on survit à une panne, mais la reconstruction de gros disques est longue et risquée. <b>RAID 6</b> supporte deux pannes. <b>RAID 10</b> combine miroir et répartition : performant et robuste, au prix de la moitié de la capacité.<br><br>Sous Linux, <b>mdadm</b> gère le RAID logiciel ; <code>/proc/mdstat</code> en montre l’état. On surveille les disques avec <b>SMART</b> (<code>smartctl</code>) pour les remplacer avant la panne. Rappel : le RAID n’est pas une sauvegarde.', table: [['Niveau', 'Disques min.', 'Capacité utile', 'Pannes tolérées'], ['RAID 0', '2', '100 %', '0'], ['RAID 1', '2', '50 %', '1'], ['RAID 5', '3', '(n−1)/n', '1'], ['RAID 6', '4', '(n−2)/n', '2'], ['RAID 10', '4', '50 %', '1 par miroir']] },
|
||||
{ t: 'Remplacer un disque en panne', d: '', html: 'Quand un disque d’un RAID 1 ou 5 tombe, l’ensemble passe en mode <b>dégradé</b> : il fonctionne, mais une seconde panne serait fatale. La procédure : identifier le disque (numéro de série avec <code>smartctl -i</code>, pour ne pas retirer le mauvais), le marquer défaillant et le retirer de l’ensemble, le remplacer physiquement, recréer la même table de partitions, l’ajouter à l’ensemble, puis suivre la reconstruction. Sur un serveur qui démarre sur ce RAID, on réinstalle aussi le chargeur d’amorçage sur le nouveau disque.' }
|
||||
],
|
||||
synthese: ['/dev/sdX, /dev/nvme0n1 ; GPT ; lsblk', 'ext4, XFS, Btrfs, ZFS ; fstab par UUID, tester avec mount -a', 'df -h, du -sh, df -i pour les inodes, lsof +L1 pour les fichiers supprimés ouverts', 'LVM : PV → VG → LV, lvextend -r à chaud', 'RAID 0, 1, 5, 6, 10 ; mdadm, /proc/mdstat, SMART', 'Disque en panne : identifier par numéro de série, retirer, remplacer, reconstruire'],
|
||||
qcm: [
|
||||
['Pourquoi utiliser l’UUID dans fstab ?', ['Plus court', 'Il reste stable même si l’ordre des disques change', 'Obligatoire', 'Plus rapide'], 1, 'sdb peut devenir sdc.'],
|
||||
['df -h indique 60 % mais impossible de créer un fichier. Cause probable ?', ['Disque en panne', 'Inodes épuisés', 'Droits', 'RAID dégradé'], 1, 'df -i.'],
|
||||
['Quelle commande agrandit un LV et son système de fichiers ?', ['lvextend -r', 'pvcreate', 'mkfs', 'resize2fs seul'], 0, '-r redimensionne aussi le système de fichiers.'],
|
||||
['Combien de pannes tolère un RAID 5 ?', ['0', '1', '2', 'Autant que de disques'], 1, 'Une parité.'],
|
||||
['Quel RAID ne tolère aucune panne ?', ['RAID 0', 'RAID 1', 'RAID 5', 'RAID 10'], 0, 'Répartition seule.'],
|
||||
['Espace non libéré après suppression d’un gros journal :', ['bug', 'un processus garde le fichier ouvert', 'RAID', 'LVM'], 1, 'lsof +L1 puis redémarrer le service.']
|
||||
],
|
||||
td: [
|
||||
{ n: 'Application', c: 'Un nouveau disque /dev/sdb doit devenir /srv/partage en ext4, monté au démarrage. Écrire toutes les commandes.', r: 'parted /dev/sdb mklabel gpt mkpart primary ext4 0% 100% · mkfs.ext4 -L partage /dev/sdb1 · mkdir /srv/partage · blkid · ligne UUID=… /srv/partage ext4 defaults 0 2 dans fstab · mount -a · findmnt /srv/partage' },
|
||||
{ n: 'Application', c: 'Calculer la capacité utile de 6 disques de 4 To en RAID 5, RAID 6 et RAID 10.', r: 'RAID 5 : 20 To · RAID 6 : 16 To · RAID 10 : 12 To' },
|
||||
{ n: 'Approfondissement', c: 'Le volume /srv/partage (LVM) est plein. Proposer deux solutions sans interruption de service.', r: 'Espace libre dans le VG : lvextend -r -L +100G. Sinon ajouter un disque : pvcreate, vgextend, puis lvextend -r. Dans les deux cas, chercher aussi la cause de la croissance.' }
|
||||
],
|
||||
tp: { type: 'Lab VM', url: 'apercu-labs.html', desc: 'VM avec quatre disques virtuels.', etapes: ['Partitionner et formater un disque', 'Monter par UUID dans fstab', 'Créer un VG et un LV, puis l’agrandir à chaud', 'Créer un RAID 1 avec mdadm', 'Simuler une panne et reconstruire', 'Trouver ce qui remplit un disque'] },
|
||||
plusLoin: [['Debian Wiki · LVM', 'https://wiki.debian.org/LVM'], ['Linux RAID wiki', 'https://raid.wiki.kernel.org/'], ['man fstab · man mdadm', 'https://man7.org/linux/man-pages/']] },
|
||||
plus: { cas: { titre: '', texte: '' },
|
||||
resolu: { enonce: 'La base de données de l’ENT s’est arrêtée à 11 h avec « No space left on device ». Rétablir le service en urgence, puis traiter la cause.', etapes: [
|
||||
['Identifier le système de fichiers plein', 'df -h | awk \'$5+0 > 90\'\n/dev/mapper/vg0-var 40G 40G 0 100% /var\n→ /var est plein.'],
|
||||
['Trouver ce qui occupe l’espace', 'du -xh --max-depth=2 /var | sort -h | tail -3\n2.1G /var/lib/postgresql\n34G /var/log/ent\n→ Les journaux de l’application occupent 34 Go.'],
|
||||
['Libérer sans casser', 'ls -lh /var/log/ent | tail -2\n-rw-r----- 1 ent ent 33G oct. 1 11:02 debug.log\n: > /var/log/ent/debug.log\n→ On vide le fichier au lieu de le supprimer : l’application le garde ouvert, une suppression ne libérerait rien.'],
|
||||
['Redémarrer et vérifier', 'systemctl start postgresql && df -h /var\n/dev/mapper/vg0-var 40G 6.8G 31G 18% /var\n→ La base redémarre.'],
|
||||
['Traiter la cause', '→ Le niveau de journalisation était resté en debug après un dépannage. Retour au niveau info, règle logrotate quotidienne avec compression et 14 jours de conservation, alerte de supervision à 85 % et prédiction de remplissage. /var agrandi de 20 Go dans le VG par sécurité.']
|
||||
] },
|
||||
erreurs: ['Utiliser /dev/sdb1 dans fstab', 'Redémarrer sans tester fstab', 'Supprimer un journal ouvert au lieu de le vider', 'Retirer le mauvais disque d’un RAID', 'Considérer le RAID comme une sauvegarde', 'Oublier la supervision SMART'],
|
||||
memo: [['Commande', 'Usage'], ['lsblk -f', 'disques, partitions, systèmes de fichiers'], ['blkid', 'UUID et types'], ['df -h · df -i', 'espace et inodes'], ['du -xh --max-depth=1 / | sort -h', 'trouver les gros dossiers'], ['lsof +L1', 'fichiers supprimés encore ouverts'], ['mount -a · findmnt --verify', 'tester fstab'], ['pvs · vgs · lvs', 'état LVM'], ['lvextend -r -L +50G …', 'agrandir à chaud'], ['cat /proc/mdstat · mdadm --detail /dev/md0', 'état du RAID'], ['smartctl -a /dev/sda', 'santé d’un disque']],
|
||||
examen: { titre: 'Sujet type · 20 points', q: ['Préparer et monter un nouveau disque. (5 pts)', 'Expliquer LVM et agrandir un volume. (4 pts)', 'Comparer les niveaux de RAID et calculer des capacités. (5 pts)', 'Procédure de remplacement d’un disque en panne. (3 pts)', 'Libérer de l’espace sur un disque plein. (3 pts)'], r: ['parted, mkfs, fstab UUID', 'PV/VG/LV, lvextend -r', 'tableau du cours', 'identifier, fail, remove, add', 'du, lsof, vider plutôt que supprimer'] },
|
||||
prof: { deroule: [['Ouverture', 'Remplir un disque de VM et observer la base qui s’arrête'], ['Cours', 'Partitions, systèmes de fichiers, fstab'], ['Pratique', 'Montage et LVM'], ['Cours', 'RAID'], ['Incident', 'Disque plein'], ['Clôture', 'Quiz flash']], diff: 'Élèves en difficulté : commandes guidées. Élèves avancés : ZFS, instantanés LVM.', eval: 'Lab validé, sujet type.' } },
|
||||
exemples: [
|
||||
['Voir la structure des disques', 'Inventaire.', 'lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT', 'NAME SIZE TYPE FSTYPE MOUNTPOINT\nsda 40G disk\n├─sda1 512M part vfat /boot/efi\n└─sda2 39.5G part LVM2_member\n ├─vg0-root 15G lvm ext4 /\n └─vg0-var 20G lvm ext4 /var\nsdb 500G disk', 'Le second disque sdb n’est pas encore utilisé.'],
|
||||
['Inodes épuisés', 'Impossible de créer un fichier.', 'df -i /var', 'Filesystem Inodes IUsed IFree IUse% Mounted on\n/dev/vg0/var 1310720 1310720 0 100% /var', 'Des millions de fichiers de session PHP : on les purge et on corrige leur nettoyage automatique.'],
|
||||
['Espace non libéré', 'Fichier supprimé, espace toujours occupé.', 'lsof +L1 | head -2', 'COMMAND PID USER FD SIZE/OFF NLINK NAME\njava 1204 ent 5w 34359738368 0 /var/log/ent/debug.log (deleted)', 'Le processus 1204 écrit encore dans le fichier supprimé ; un redémarrage du service libère les 32 Go.'],
|
||||
['État LVM', 'Reste-t-il de la place ?', 'vgs', 'VG #PV #LV VSize VFree\nvg0 1 3 39.50g 4.50g', '4,5 Go libres dans le groupe : suffisant pour un petit agrandissement.'],
|
||||
['RAID dégradé', 'Alerte de supervision.', 'cat /proc/mdstat', 'md0 : active raid1 sdb1[0] sdc1[1](F)\n 976630464 blocks super 1.2 [2/1] [U_]', '[U_] : un seul disque actif, sdc est marqué défaillant (F). Remplacement urgent.'],
|
||||
['Santé d’un disque', 'Maintenance préventive.', 'smartctl -A /dev/sdb | grep -E "Reallocated|Pending"', ' 5 Reallocated_Sector_Ct 0x0033 098 098 010 Pre-fail Always - 312\n197 Current_Pending_Sector 0x0012 100 100 000 Old_age Always - 16', 'Des secteurs réalloués et en attente : le disque se dégrade, on le remplace avant la panne.']
|
||||
],
|
||||
termes: ['partition', 'GPT', 'lsblk', 'ext4', 'XFS', 'Btrfs', 'ZFS', 'mkfs', 'mount', 'fstab', 'UUID', 'df', 'du', 'inode', 'lsof', 'LVM', 'PV', 'VG', 'LV', 'lvextend', 'RAID', 'mdadm', '/proc/mdstat', 'SMART', 'smartctl', 'dégradé']
|
||||
});
|
||||
|
||||
reg({
|
||||
cours: { id: 'sshav', match: /SSH avancé|tunnels|rebonds|sshd_config|Durcissement Linux|fail2ban|force brute|CIS|Lynis/i,
|
||||
titre: 'SSH avancé et durcissement : clés, tunnels, rebonds, fail2ban', duree: '',
|
||||
niveau: 'BTS SIO SISR · BTS CIEL IR · BUT R&T Cyber · TSSR / AIS',
|
||||
prerequis: ['Cryptographie appliquée', 'Pare-feu'],
|
||||
competences: ['BTS SIO · Bloc 3 · Sécuriser les accès', 'BUT R&T Cyber · Protéger', 'AIS · Sécuriser l’administration'],
|
||||
intro: 'SSH est la porte d’entrée de tous les serveurs Linux, et donc la première cible des attaquants : un serveur exposé reçoit des milliers de tentatives de connexion par jour. Au-delà du simple `ssh utilisateur@serveur`, l’administrateur doit savoir organiser ses clés, rebondir par un bastion, créer des tunnels, durcir la configuration et bloquer la force brute. Ce module couvre ces pratiques, jusqu’à l’audit de durcissement d’un serveur.',
|
||||
cm: [
|
||||
{ t: 'Authentification par clé et agent', d: '', html: 'La clé <b>Ed25519</b> est le choix actuel : courte, rapide, sûre. La clé privée est protégée par une <b>phrase de passe</b> ; l’<b>agent SSH</b> la garde déverrouillée en mémoire pendant la session pour éviter de la retaper. La clé publique est déposée dans <code>~/.ssh/authorized_keys</code> sur le serveur, avec éventuellement des <b>restrictions</b> : <code>from="10.62.99.0/28"</code>, <code>command="…"</code>, <code>no-port-forwarding</code>. Lors de la première connexion, on vérifie l’<b>empreinte</b> du serveur ; un changement ultérieur (« REMOTE HOST IDENTIFICATION HAS CHANGED ») signale une réinstallation ou une attaque, jamais à ignorer.' },
|
||||
{ t: 'Le fichier ~/.ssh/config', d: '', html: 'Le fichier de configuration client évite de retaper les options et documente l’accès au parc : alias, utilisateur, port, clé, rebond.', exemple: 'Host bastion\n HostName bastion.lycee-exemple.fr\n User admin\n IdentityFile ~/.ssh/id_ed25519\nHost srv-*\n User admin\n ProxyJump bastion\nHost srv-ent\n HostName 10.62.40.20\nHost srv-db\n HostName 10.62.40.31\n\n$ ssh srv-db # passe automatiquement par le bastion' },
|
||||
{ t: 'Bastion et rebond', d: '', html: 'Plutôt que d’exposer chaque serveur, on n’expose qu’un <b>bastion</b> durci et journalisé. Les administrateurs s’y connectent, puis rebondissent vers les serveurs internes avec <code>ssh -J bastion srv-db</code> ou la directive <b>ProxyJump</b>. La clé privée reste sur le poste de l’administrateur : contrairement au transfert d’agent (<code>-A</code>), ProxyJump ne l’expose pas sur le bastion. Les serveurs internes n’acceptent SSH que depuis l’adresse du bastion.' },
|
||||
{ t: 'Tunnels', d: '', html: 'SSH transporte d’autres flux dans sa connexion chiffrée. La <b>redirection locale</b> (<code>-L 5433:10.62.40.31:5432</code>) rend accessible sur le poste, au port 5433, la base de données interne, sans l’exposer. La <b>redirection distante</b> (<code>-R</code>) fait l’inverse : un port du serveur renvoie vers le poste. La <b>redirection dynamique</b> (<code>-D 1080</code>) crée un proxy SOCKS. Ces tunnels sont précieux pour l’administration, et dangereux s’ils permettent de contourner le pare-feu : on les désactive pour les comptes qui n’en ont pas besoin.' },
|
||||
{ t: 'Durcir sshd_config', d: '', html: 'Les réglages essentiels du serveur : <code>PermitRootLogin no</code>, <code>PasswordAuthentication no</code> et <code>KbdInteractiveAuthentication no</code> (clés uniquement), <code>AllowGroups admins</code>, <code>MaxAuthTries 3</code>, <code>AllowTcpForwarding no</code> sauf besoin, <code>X11Forwarding no</code>, des algorithmes modernes, et un bandeau d’avertissement légal. On place ses réglages dans <code>/etc/ssh/sshd_config.d/</code>, on valide avec <code>sshd -t</code> et on garde une session ouverte pendant le rechargement.', exemple: '# /etc/ssh/sshd_config.d/10-durcissement.conf\nPermitRootLogin no\nPasswordAuthentication no\nKbdInteractiveAuthentication no\nAllowGroups admins\nMaxAuthTries 3\nLoginGraceTime 30\nAllowTcpForwarding no\nX11Forwarding no\n\n$ sudo sshd -t && sudo systemctl reload ssh' },
|
||||
{ t: 'fail2ban et audit de durcissement', d: '', html: '<b>fail2ban</b> lit les journaux, repère les échecs répétés d’authentification et bannit temporairement l’adresse par une règle de pare-feu. Il réduit le bruit et ralentit la force brute, sans remplacer l’authentification par clé. Pour l’ensemble du serveur, on s’appuie sur des référentiels de durcissement : guide de l’ANSSI pour GNU/Linux, benchmarks CIS, et des outils d’audit comme <b>Lynis</b>, qui produit un score et une liste de recommandations (mises à jour automatiques de sécurité, droits des fichiers sensibles, services inutiles, journalisation).' }
|
||||
],
|
||||
synthese: ['Clé Ed25519 avec phrase de passe et agent ; vérifier les empreintes', 'authorized_keys avec restrictions from et command', '~/.ssh/config : alias, ProxyJump', 'Bastion unique ; ProxyJump plutôt que transfert d’agent', 'Tunnels -L, -R, -D ; les désactiver sans besoin', 'sshd_config : pas de root, pas de mot de passe, AllowGroups ; sshd -t', 'fail2ban, Lynis, guides ANSSI et CIS'],
|
||||
qcm: [
|
||||
['Quel type de clé choisir aujourd’hui ?', ['DSA', 'RSA 1024', 'Ed25519', 'Aucune'], 2, 'Moderne et sûre.'],
|
||||
['Pourquoi préférer ProxyJump au transfert d’agent ?', ['Plus rapide', 'La clé n’est pas exposée sur le bastion', 'Obligatoire', 'Pas de différence'], 1, 'Le bastion ne peut pas utiliser votre agent.'],
|
||||
['Que fait ssh -L 5433:10.62.40.31:5432 bastion ?', ['Expose la base sur Internet', 'Rend la base accessible sur le port 5433 du poste local via le tunnel', 'Change le port de la base', 'Rien'], 1, 'Redirection locale.'],
|
||||
['Quelle commande valide sshd_config ?', ['sshd -t', 'ssh -t', 'systemctl check ssh', 'sshd --dry'], 0, 'Avant chaque reload.'],
|
||||
['Que fait fail2ban ?', ['Chiffre SSH', 'Bannit temporairement les adresses après échecs répétés', 'Remplace les clés', 'Change le port'], 1, 'Lecture des journaux.'],
|
||||
['Message « REMOTE HOST IDENTIFICATION HAS CHANGED » :', ['à ignorer', 'vérifier : réinstallation ou attaque', 'mettre à jour ssh', 'redémarrer'], 1, 'Jamais ignoré sans vérification.']
|
||||
],
|
||||
td: [
|
||||
{ n: 'Application', c: 'Écrire le ~/.ssh/config d’un technicien qui accède à trois serveurs internes par un bastion.', r: 'Host bastion (HostName, User, IdentityFile) · Host srv-* ProxyJump bastion · un bloc Host par serveur avec HostName.' },
|
||||
{ n: 'Application', c: 'Accéder à l’interface web d’administration d’un switch (10.62.99.2:443) depuis chez soi sans l’exposer.', r: 'ssh -L 8443:10.62.99.2:443 bastion puis https://localhost:8443 (vérifier le certificat du switch).' },
|
||||
{ n: 'Approfondissement', c: 'Durcir un serveur exposé et prouver l’amélioration.', r: 'Mesure initiale lynis audit system (score) · sshd_config durci · fail2ban · unattended-upgrades · pare-feu en liste blanche · suppression des services inutiles · nouvelle mesure Lynis et comparaison.' }
|
||||
],
|
||||
tp: { type: 'Lab VM', url: 'apercu-labs.html', desc: 'Bastion, deux serveurs internes et un poste.', etapes: ['Générer une clé et la déployer', 'Configurer ProxyJump', 'Créer un tunnel vers une base interne', 'Durcir sshd_config', 'Installer fail2ban et observer un bannissement', 'Mesurer avec Lynis'] },
|
||||
plusLoin: [['ANSSI · Recommandations pour un usage sécurisé d’OpenSSH', 'https://cyber.gouv.fr/publications/openssh-secure-use-recommendations'], ['ANSSI · Recommandations de sécurité relatives à un système GNU/Linux', 'https://cyber.gouv.fr/publications/recommandations-de-securite-relatives-un-systeme-gnulinux'], ['Lynis', 'https://cisofy.com/lynis/']] },
|
||||
plus: { cas: { titre: '', texte: '' },
|
||||
resolu: { enonce: 'Après l’application d’une configuration SSH durcie, plus personne ne peut se connecter au serveur de fichiers, y compris les administrateurs.', etapes: [
|
||||
['Accéder autrement', '→ Connexion par la console de l’hyperviseur (accès hors bande).'],
|
||||
['Lire les journaux', 'journalctl -u ssh -n 5\nUser admin from 10.62.99.5 not allowed because none of user\'s groups are listed in AllowGroups\n→ La directive AllowGroups admins refuse les administrateurs.'],
|
||||
['Vérifier les groupes', 'id admin\nuid=1000(admin) gid=1000(admin) groupes=1000(admin),27(sudo)\n→ Le groupe admins n’existe pas sur ce serveur ; les administrateurs sont dans sudo.'],
|
||||
['Corriger', 'groupadd admins && usermod -aG admins admin\nsshd -t && systemctl reload ssh\n→ Connexion de test depuis une seconde session : acceptée.'],
|
||||
['Prévenir', '→ Procédure : toute modification de SSH se fait avec une session ouverte et une console hors bande disponible, et se teste d’abord sur une VM de préproduction. La configuration est déployée par Ansible avec un test de connexion après application.']
|
||||
] },
|
||||
erreurs: ['Laisser l’authentification par mot de passe sur un serveur exposé', 'Clé privée sans phrase de passe partagée entre plusieurs personnes', 'Transfert d’agent vers un serveur non maîtrisé', 'Ignorer un changement d’empreinte', 'Recharger sshd sans session de secours', 'Exposer tous les serveurs au lieu d’un bastion'],
|
||||
memo: [['Commande', 'Usage'], ['ssh-keygen -t ed25519 -C "nom"', 'créer une clé'], ['ssh-copy-id -i cle.pub hôte', 'déposer la clé'], ['ssh-add · ssh-add -l', 'agent : ajouter, lister'], ['ssh -J bastion hôte', 'rebond'], ['ssh -L port:hôte:port bastion', 'redirection locale'], ['ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub', 'empreinte du serveur'], ['sshd -t · sshd -T', 'valider · afficher la configuration effective'], ['fail2ban-client status sshd', 'adresses bannies'], ['lynis audit system', 'audit de durcissement']],
|
||||
examen: { titre: 'Sujet type · 20 points', q: ['Mettre en place l’authentification par clé avec restrictions. (4 pts)', 'Écrire un ~/.ssh/config avec bastion. (4 pts)', 'Expliquer et utiliser un tunnel local. (4 pts)', 'Durcir sshd_config et justifier chaque directive. (5 pts)', 'Rôle et limites de fail2ban. (3 pts)'], r: ['ssh-keygen, authorized_keys from=', 'ProxyJump', '-L', 'root, mot de passe, groupes', 'ralentit sans remplacer les clés'] },
|
||||
prof: { deroule: [['Ouverture', 'journalctl -u ssh d’un serveur exposé : les tentatives'], ['Cours', 'Clés, config, bastion'], ['Pratique', 'Rebond et tunnel'], ['Cours', 'Durcissement'], ['Incident', 'Verrouillage après durcissement'], ['Clôture', 'Quiz flash']], diff: 'Élèves en difficulté : configurations fournies. Élèves avancés : certificats SSH, clés matérielles FIDO2.', eval: 'Lab validé, score Lynis amélioré, sujet type.' } },
|
||||
exemples: [
|
||||
['Tentatives sur un serveur exposé', 'Pourquoi durcir.', 'journalctl -u ssh --since today | grep -c "Invalid user"', '4127', 'Plus de 4 000 tentatives avec des noms d’utilisateurs inventés en une journée.'],
|
||||
['Vérifier une empreinte', 'Première connexion.', 'ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub (sur le serveur, par la console)', '256 SHA256:q1Vt…fX8 root@srv-ent (ED25519)', 'On compare avec l’empreinte affichée par le client avant d’accepter.'],
|
||||
['Configuration effective', 'Une directive est-elle appliquée ?', 'sudo sshd -T | grep -E "^(permitrootlogin|passwordauthentication|allowgroups)"', 'permitrootlogin no\npasswordauthentication no\nallowgroups admins', 'sshd -T affiche la configuration réellement retenue après lecture de tous les fichiers.'],
|
||||
['Rebond par le bastion', 'Accès à un serveur interne.', 'ssh -v srv-db 2>&1 | grep -E "Connecting to|Authenticated"', 'debug1: Connecting to bastion.lycee-exemple.fr port 22.\nAuthenticated to 10.62.40.31 (via proxy) using "publickey".', 'La connexion passe par le bastion et s’authentifie par clé sur la cible.'],
|
||||
['Adresses bannies', 'Contrôle de fail2ban.', 'sudo fail2ban-client status sshd', 'Status for the jail: sshd\n|- Currently failed: 3\n`- Actions\n |- Currently banned: 27\n `- Banned IP list: 185.220.101.4 45.143.220.9 …', '27 adresses bannies temporairement.'],
|
||||
['Audit de durcissement', 'Avant et après.', 'sudo lynis audit system --quick | grep "Hardening index"', 'Hardening index : 58 [########### ]\n(après corrections) Hardening index : 79 [################ ]', 'Le score passe de 58 à 79 ; les recommandations restantes sont listées dans /var/log/lynis-report.dat.']
|
||||
],
|
||||
termes: ['SSH', 'Ed25519', 'clé publique', 'clé privée', 'phrase de passe', 'ssh-agent', 'authorized_keys', 'empreinte', '~/.ssh/config', 'bastion', 'ProxyJump', 'tunnel', '-L', '-R', '-D', 'sshd_config', 'PermitRootLogin', 'PasswordAuthentication', 'AllowGroups', 'sshd -t', 'fail2ban', 'Lynis', 'CIS']
|
||||
});
|
||||
|
||||
reg({
|
||||
cours: { id: 'messagerie', match: /Messagerie|Postfix|Dovecot|SPF|DKIM|DMARC|SMTP/i,
|
||||
titre: 'Messagerie : SMTP, Postfix, Dovecot, SPF, DKIM et DMARC', duree: '',
|
||||
niveau: 'BTS SIO SISR · BUT R&T · TSSR / AIS',
|
||||
prerequis: ['DNS : enregistrements MX et TXT', 'Certificats TLS'],
|
||||
competences: ['BTS SIO SISR · Bloc 2 · Installer un service de messagerie', 'BUT R&T · Administrer les services'],
|
||||
intro: 'Le courriel reste le principal canal de communication officiel d’un établissement avec les familles, et le premier vecteur d’attaque (hameçonnage, usurpation). Envoyer un courriel qui arrive vraiment, et pas dans les indésirables, suppose de maîtriser SMTP, la réputation de l’expéditeur et les mécanismes d’authentification du domaine. Ce module décrit l’acheminement d’un message, la configuration de Postfix et Dovecot, et surtout SPF, DKIM et DMARC, désormais exigés par les grands fournisseurs de messagerie.',
|
||||
cm: [
|
||||
{ t: 'Le trajet d’un courriel', d: '', html: 'Le client de messagerie (MUA) remet le message à un serveur de soumission (port <b>587</b> avec authentification et STARTTLS, ou 465 en TLS direct). Le serveur d’envoi (MTA) cherche l’enregistrement <b>MX</b> du domaine destinataire et lui transmet le message en <b>SMTP</b> sur le port <b>25</b>. Le serveur de destination le dépose dans la boîte, que l’utilisateur consulte en <b>IMAP</b> (993 en TLS). Chaque serveur traversé ajoute un en-tête <code>Received:</code> : lus de bas en haut, ils retracent le parcours du message.' },
|
||||
{ t: 'Postfix et Dovecot', d: '', html: '<b>Postfix</b> est le MTA de référence sous Linux : il reçoit, met en file d’attente et achemine. Sa configuration principale est <code>/etc/postfix/main.cf</code> (nom d’hôte, domaines acceptés, réseaux autorisés à relayer, TLS) et <code>master.cf</code> (services, dont la soumission sur 587). <b>Dovecot</b> fournit l’accès IMAP aux boîtes et l’authentification que Postfix utilise pour la soumission (SASL). Le point le plus critique est de ne <b>jamais être un relais ouvert</b> : seuls les utilisateurs authentifiés ou les réseaux internes peuvent envoyer vers l’extérieur.', exemple: '# /etc/postfix/main.cf (extrait)\nmyhostname = mx1.lycee-exemple.fr\nmydestination = $myhostname, localhost\nvirtual_mailbox_domains = lycee-exemple.fr\nmynetworks = 127.0.0.0/8 [::1]/128\nsmtpd_tls_cert_file = /etc/letsencrypt/live/mx1.lycee-exemple.fr/fullchain.pem\nsmtpd_tls_key_file = /etc/letsencrypt/live/mx1.lycee-exemple.fr/privkey.pem\nsmtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination' },
|
||||
{ t: 'SPF', d: '', html: 'SMTP n’authentifie pas l’expéditeur : n’importe qui peut écrire « From: proviseur@lycee-exemple.fr ». <b>SPF</b> publie dans un enregistrement TXT la liste des serveurs autorisés à envoyer pour le domaine : <code>v=spf1 mx include:_spf.prestataire.fr -all</code>. Le serveur destinataire vérifie que l’adresse IP de l’émetteur y figure. <code>-all</code> demande de rejeter le reste, <code>~all</code> de le marquer suspect. SPF vérifie l’enveloppe (MAIL FROM), pas l’adresse visible par l’utilisateur, et il est cassé par les transferts automatiques.' },
|
||||
{ t: 'DKIM', d: '', html: '<b>DKIM</b> signe cryptographiquement chaque message sortant : le serveur calcule une signature sur certains en-têtes et sur le corps avec une clé privée, et publie la clé publique dans le DNS sous un <b>sélecteur</b> (<code>2026._domainkey.lycee-exemple.fr</code>). Le destinataire vérifie la signature : le message vient bien d’un serveur autorisé par le domaine et n’a pas été modifié en route. Sous Linux, OpenDKIM ou rspamd signent pour Postfix.' },
|
||||
{ t: 'DMARC', d: '', html: '<b>DMARC</b> relie SPF et DKIM à l’adresse visible par l’utilisateur (From) : il exige qu’au moins l’un des deux réussisse <b>et</b> soit aligné sur le domaine du From. L’enregistrement <code>_dmarc.lycee-exemple.fr</code> indique la politique à appliquer en cas d’échec (<code>p=none</code> pour observer, <code>quarantine</code>, puis <code>reject</code>) et une adresse de réception des <b>rapports</b> (rua), qui listent tous les serveurs envoyant au nom du domaine. On déploie progressivement : none, analyse des rapports pour découvrir les services oubliés (ENT, logiciel de vie scolaire, prestataire de newsletters), puis quarantine et reject. Les grands fournisseurs de messagerie exigent désormais SPF, DKIM et DMARC pour les expéditeurs en volume.', exemple: '_dmarc.lycee-exemple.fr. TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@lycee-exemple.fr; adkim=s; aspf=r"' },
|
||||
{ t: 'Délivrabilité et sécurité', d: '', html: 'Au-delà de SPF, DKIM et DMARC, la délivrabilité dépend d’une <b>résolution inverse</b> (PTR) cohérente avec le nom du serveur, d’une adresse IP absente des listes de blocage, de TLS entre serveurs (MTA-STS pour l’imposer), et de volumes d’envoi réguliers. Côté réception, un filtre (rspamd, SpamAssassin) note les messages, et un antivirus analyse les pièces jointes. La sensibilisation des utilisateurs à l’hameçonnage reste indispensable : DMARC empêche l’usurpation exacte du domaine, pas les domaines ressemblants.' }
|
||||
],
|
||||
synthese: ['MUA → soumission 587 authentifiée → MX → SMTP 25 → IMAP 993', 'Postfix achemine, Dovecot sert les boîtes ; jamais de relais ouvert', 'SPF : serveurs autorisés (TXT) ; vérifie l’enveloppe', 'DKIM : signature, clé publique sous un sélecteur', 'DMARC : alignement sur le From, politique none → quarantine → reject, rapports', 'PTR, listes de blocage, TLS, filtrage et sensibilisation'],
|
||||
qcm: [
|
||||
['Sur quel port un client soumet-il un message avec authentification ?', ['25', '587', '110', '143'], 1, '25 sert entre serveurs.'],
|
||||
['Qu’est-ce qu’un relais ouvert ?', ['Un serveur rapide', 'Un serveur qui accepte d’acheminer pour n’importe qui', 'Un serveur IMAP', 'Un MX secondaire'], 1, 'Exploité par les spammeurs.'],
|
||||
['Que publie SPF ?', ['Une signature', 'Les serveurs autorisés à envoyer pour le domaine', 'La politique de rejet', 'Le certificat'], 1, 'Enregistrement TXT.'],
|
||||
['Où se trouve la clé publique DKIM ?', ['Dans le message', 'Dans le DNS, sous sélecteur._domainkey', 'Sur le poste', 'Dans le MX'], 1, 'Le destinataire la récupère.'],
|
||||
['Que vérifie DMARC en plus de SPF et DKIM ?', ['La taille', 'L’alignement avec l’adresse From visible', 'Le contenu', 'L’antivirus'], 1, 'C’est ce que voit l’utilisateur.'],
|
||||
['Première étape du déploiement DMARC ?', ['p=reject', 'p=none et analyse des rapports', 'Supprimer SPF', 'Changer de MX'], 1, 'Découvrir tous les émetteurs légitimes.']
|
||||
],
|
||||
td: [
|
||||
{ n: 'Application', c: 'Le lycée envoie depuis son serveur mx1 et via un prestataire de lettres d’information (include:_spf.newsletter.example). Écrire SPF, et les enregistrements DKIM et DMARC de départ.', r: 'TXT @ "v=spf1 mx include:_spf.newsletter.example -all" · TXT 2026._domainkey "v=DKIM1; k=ed25519; p=<clé>" (ou rsa) · TXT _dmarc "v=DMARC1; p=none; rua=mailto:dmarc@lycee-exemple.fr"' },
|
||||
{ n: 'Application', c: 'Lire les en-têtes Authentication-Results d’un message reçu et dire s’il est authentique.', r: 'spf=pass, dkim=pass header.d=lycee-exemple.fr, dmarc=pass → authentique. dmarc=fail avec From du lycée → usurpation probable.' },
|
||||
{ n: 'Approfondissement', c: 'Proposer le calendrier de passage de DMARC à p=reject sans bloquer d’envois légitimes.', r: 'Mois 1 : p=none, collecte des rapports · mois 2 : correction des émetteurs non alignés (ENT, vie scolaire) · mois 3 : p=quarantine; pct=25 puis 100 · mois 4 : p=reject, surveillance continue des rapports.' }
|
||||
],
|
||||
tp: { type: 'Lab VM', url: 'apercu-labs.html', desc: 'Postfix, Dovecot et OpenDKIM avec un DNS de lab.', etapes: ['Installer Postfix et Dovecot', 'Configurer la soumission authentifiée', 'Vérifier qu’il n’y a pas de relais ouvert', 'Publier SPF', 'Signer avec DKIM', 'Lire les en-têtes d’authentification'] },
|
||||
plusLoin: [['ANSSI · Recommandations de sécurité pour la mise en œuvre d’un système de messagerie', 'https://cyber.gouv.fr/'], ['Documentation Postfix', 'https://www.postfix.org/documentation.html'], ['RFC 7489 · DMARC', 'https://www.rfc-editor.org/rfc/rfc7489'], ['Cybermalveillance.gouv.fr · hameçonnage', 'https://www.cybermalveillance.gouv.fr/']] },
|
||||
plus: { cas: { titre: '', texte: '' },
|
||||
resolu: { enonce: 'Les parents signalent que les convocations envoyées par le logiciel de vie scolaire arrivent dans leurs courriers indésirables, alors que les messages des enseignants arrivent normalement.', etapes: [
|
||||
['Lire les en-têtes d’un message reçu', 'Authentication-Results: mx.fournisseur.example;\n spf=fail (ip 198.51.100.77 not permitted) smtp.mailfrom=lycee-exemple.fr;\n dkim=none; dmarc=fail (p=quarantine) header.from=lycee-exemple.fr\n→ Le logiciel envoie depuis les serveurs de son éditeur, non déclarés dans SPF, et sans signature DKIM.'],
|
||||
['Vérifier le SPF', 'dig +short TXT lycee-exemple.fr | grep spf\n"v=spf1 mx -all"\n→ Seul le serveur du lycée est autorisé.'],
|
||||
['Corriger SPF', 'Ajout de l’include fourni par l’éditeur :\n"v=spf1 mx include:_spf.editeur-vie-scolaire.example -all"\n→ Les serveurs de l’éditeur deviennent autorisés.'],
|
||||
['Ajouter DKIM', '→ Activation de la signature DKIM dans l’interface de l’éditeur avec le sélecteur vs2026 ; publication de vs2026._domainkey.lycee-exemple.fr.'],
|
||||
['Vérifier', 'Nouveau message test : spf=pass, dkim=pass header.d=lycee-exemple.fr, dmarc=pass\n→ Les convocations arrivent en boîte de réception. Les rapports DMARC sont désormais analysés chaque semaine pour repérer tout nouvel émetteur.']
|
||||
] },
|
||||
erreurs: ['Relais ouvert', 'Oublier des émetteurs légitimes dans SPF', 'Plus de 10 résolutions DNS dans SPF (erreur permerror)', 'Passer DMARC directement à reject', 'Pas de PTR cohérent', 'Ignorer les rapports DMARC'],
|
||||
memo: [['Commande', 'Usage'], ['dig +short MX domaine', 'serveurs de messagerie'], ['dig +short TXT domaine', 'SPF'], ['dig +short TXT sel._domainkey.domaine', 'clé DKIM'], ['dig +short TXT _dmarc.domaine', 'politique DMARC'], ['postconf -n', 'configuration Postfix non par défaut'], ['postqueue -p', 'file d’attente'], ['postsuper -d ALL deferred', 'purger les messages en échec'], ['tail -f /var/log/mail.log', 'journal de messagerie'], ['swaks --to x --server mx1 --port 587 --auth', 'tester l’envoi'], ['openssl s_client -starttls smtp -connect mx1:25', 'tester TLS']],
|
||||
examen: { titre: 'Sujet type · 20 points', q: ['Décrire le trajet d’un courriel avec les ports. (4 pts)', 'Configurer Postfix contre le relais ouvert. (4 pts)', 'Écrire SPF, DKIM et DMARC pour un cas donné. (6 pts)', 'Analyser des en-têtes Authentication-Results. (3 pts)', 'Proposer un plan de déploiement DMARC. (3 pts)'], r: ['587, MX, 25, 993', 'smtpd_relay_restrictions', 'TXT', 'pass/fail et alignement', 'none → quarantine → reject'] },
|
||||
prof: { deroule: [['Ouverture', 'Envoyer un faux courriel au nom du proviseur en lab'], ['Cours', 'SMTP, Postfix, Dovecot'], ['Pratique', 'Lab messagerie'], ['Cours', 'SPF, DKIM, DMARC'], ['Incident', 'Convocations en indésirables'], ['Clôture', 'Quiz flash']], diff: 'Élèves en difficulté : enregistrements à compléter. Élèves avancés : MTA-STS, rspamd.', eval: 'Message signé et authentifié, sujet type.' } },
|
||||
exemples: [
|
||||
['Serveurs de messagerie d’un domaine', 'Diagnostic de réception.', 'dig +short MX lycee-exemple.fr', '10 mx1.lycee-exemple.fr.\n20 mx2.lycee-exemple.fr.', 'mx1 est essayé en premier, mx2 en secours.'],
|
||||
['Tester le relais ouvert', 'Audit.', 'swaks --to test@exemple.org --from x@exemple.org --server mx1.lycee-exemple.fr', '<** 554 5.7.1 <test@exemple.org>: Relay access denied', 'Le serveur refuse de relayer pour un inconnu : correct.'],
|
||||
['Lire la file d’attente', 'Messages bloqués.', 'postqueue -p | tail -3', 'A1B2C3D4E5 2048 Thu Oct 1 08:12:03 vie-scolaire@lycee-exemple.fr\n(host mx.fournisseur.example said: 421 4.7.0 Try again later, IP temporarily deferred)', 'Le fournisseur retarde nos envois : réputation ou authentification à vérifier.'],
|
||||
['Vérifier DKIM', 'Signature publiée ?', 'dig +short TXT 2026._domainkey.lycee-exemple.fr', '"v=DKIM1; k=rsa; p=MIIBIjANBgkqh…"', 'La clé publique est en place.'],
|
||||
['Lire DMARC', 'Politique actuelle.', 'dig +short TXT _dmarc.lycee-exemple.fr', '"v=DMARC1; p=none; rua=mailto:dmarc@lycee-exemple.fr"', 'Mode observation : aucun message usurpé n’est encore bloqué.'],
|
||||
['Suivre un envoi', 'Un parent n’a rien reçu.', 'grep "to=<parent@fournisseur.example>" /var/log/mail.log | tail -1', 'postfix/smtp[3310]: 7F2E…: to=<parent@fournisseur.example>, relay=mx.fournisseur.example[…]:25, status=sent (250 2.0.0 OK)', 'Le message a été accepté par le serveur du destinataire : il faut chercher chez lui (indésirables, filtre).']
|
||||
],
|
||||
termes: ['SMTP', 'MX', 'MTA', 'MUA', 'IMAP', '587', '25', '993', 'Postfix', 'Dovecot', 'relais ouvert', 'SASL', 'STARTTLS', 'SPF', 'DKIM', 'sélecteur', 'DMARC', 'alignement', 'p=reject', 'PTR', 'MTA-STS', 'rspamd', 'hameçonnage']
|
||||
});
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue