Prendre en charge un serveur
Pourquoi
Camille quitte l'équipe vendredi. Pendant trois ans, elle a administré seule sig-app-1 et sig-app-2, les deux instances Ubuntu 24.04 qui servent l'API Signalements, et sig-outils, la Debian 13 qui exécute l'export nocturne pour la mairie, reçoit les journaux et pousse les sauvegardes. Elle vous laisse un document de deux pages vieux de dix-huit mois.
Ce que vous ne savez pas est inquiétant. Quelle tâche tourne à 3 heures du matin, et sous quel compte ? Quel port est ouvert sur Internet sans que personne ne s'en souvienne ? Qui, en dehors de Camille, a une clé acceptée par root ? Quel binaire a été déposé à la main en 2024 et jamais mis à jour ? Que se passera-t-il au prochain redémarrage, le premier depuis huit mois ?
Sans état des lieux, la première panne sera aussi la première fois que vous découvrez la machine. Et la première modification faite de bonne foi (un apt autoremove, la suppression d'un compte « visiblement inutile ») peut casser une dépendance que personne n'avait écrite : avant d'enlever une barrière au milieu d'un champ, on cherche pourquoi quelqu'un l'a posée.
Cette leçon donne une méthode pour prendre en charge un serveur que l'on n'a pas installé : l'examiner sans rien changer, consigner ce que l'on trouve dans une fiche de serveur, et ouvrir le journal des changements où vous noterez désormais chaque intervention. Elle constate le démarrage, le réseau ou le pare-feu sans les détailler : chaque question de l'état des lieux renvoie à la leçon du cours qui y répond. Le guide d'hygiène informatique de l'ANSSI demande d'ailleurs de tenir un inventaire exhaustif des comptes privilégiés et d'appliquer des procédures de départ : le départ de Camille est exactement ce cas.
Les concepts
Trois couches, dont une invisible
Une machine en production superpose trois couches :
- La distribution : les paquets d'Ubuntu ou de Debian et leur configuration par défaut. Publique et reproductible.
- Les ajouts traçables : paquets installés par APT, unités dans
/etc/systemd/system/, fichiers sous/etc/. Pas forcément documentés, mais les outils savent les retrouver. - Les gestes manuels : un binaire dans
/usr/local/bin, des modules installés parpiphors paquet, une ligne dans la crontab deroot. C'est cette couche qui fait les mauvaises surprises.
L'état des lieux rend les couches 2 et 3 visibles, puis les écrit.
Lecture seule d'abord
Pendant l'état des lieux, on ne modifie rien : pas de mise à jour, pas de redémarrage, pas de nettoyage. Vous ne savez pas encore ce qui dépend de quoi ; vous devez pouvoir distinguer l'état hérité de vos propres actions si un problème survient lundi ; et tant que Camille est là, chaque question peut encore lui être posée. Lecture seule ne veut pas dire sans sudo : le processus derrière un port, les crontabs des autres comptes ou le journal complet demandent des privilèges. Mais chaque commande de cette leçon se contente de lire.
Animaux, bétail et dette
L'image des animaux de compagnie et du bétail (pets vs cattle) oppose le serveur unique, configuré à la main et soigné quand il tombe malade, au serveur interchangeable, reconstruit à l'identique par un outil et remplacé quand il flanche. Les machines de Signalements sont aujourd'hui des animaux : personne ne saurait les reconstruire, puisque personne ne sait ce qu'elles contiennent. On ne peut pas décrire dans cloud-init ou Ansible ce que l'on n'a pas inventorié : l'état des lieux est le premier pas vers l'infrastructure immuable.
Chaque modification manuelle est une dette : gratuite le jour où on la fait, coûteuse ensuite, parce qu'elle crée une dérive de configuration entre la machine et toute description qu'on en a. sig-app-1 et sig-app-2 sont censées être identiques ; elles ne le sont presque sûrement pas. Cette dette se rembourse en l'écrivant, puis en la reproduisant par un outil, ou en la supprimant. Cette leçon s'occupe de la première étape.
Les questions, dans l'ordre
On commence par savoir de quoi l'on parle, puis par ce qui présente un risque immédiat (accès, ports), et l'on finit par l'historique.
| Question | Commandes principales | Approfondi dans |
|---|---|---|
| Quelle machine, quel système ? | hostnamectl, /etc/os-release, uname -r | Premiers pas, leçon 1 |
| Quelle instance ? | systemd-detect-virt, scw-metadata | Cloud, leçon 4 |
| Quelles ressources ? | lscpu, free -h, lsblk, df | Leçon 6 |
| Qu'est-ce qui tourne et écoute ? | systemctl, ss -tulpn | Leçon 8 |
| Qui a accès ? | /etc/passwd, sudoers, authorized_keys | Leçon 13 |
| Qu'a-t-on fait à la main ? | apt-mark showmanual, /usr/local, systemd-delta | Premiers pas, leçon 12 |
| Que se passe-t-il la nuit ? | systemctl list-timers, crontabs | Leçon 10 |
| Est-elle à jour et saine ? | apt list --upgradable, uptime, journalctl -p err | Leçon 4 |
En pratique
Les commandes se lancent sur sig-app-1 (Ubuntu 24.04) ; les différences avec sig-outils (Debian 13) sont signalées.
Garder la trace de sa session
Avant de vous connecter, enregistrez la session sur votre poste avec script (fourni par util-linux), pour ne rien écrire sur le serveur :
$ script -q -a ~/signalements/prise-en-charge/sig-app-1-2026-10-07.log
$ ssh sig-app-1
-q supprime les messages de début et de fin, -a ajoute au fichier au lieu de l'écraser. Un second exit après la session SSH arrête l'enregistrement ; less -R le relit avec ses couleurs. Ce fichier contiendra tout ce qui s'est affiché, secrets compris : gardez-le chiffré sur votre poste, jamais dans un dépôt.
Identité
hostnamectl interroge le service systemd-hostnamed. Sortie typique :
$ hostnamectl
Static hostname: sig-app-1
Icon name: computer-vm
Chassis: vm
Machine ID: <32 caractères hexadécimaux>
Boot ID: <32 caractères hexadécimaux>
Virtualization: kvm
Operating System: Ubuntu 24.04.3 LTS
Kernel: Linux 6.8.0-79-generic
Architecture: x86-64
Selon le firmware virtuel, des lignes Hardware Vendor, Hardware Model et Firmware Version s'ajoutent. À retenir : le nom statique (/etc/hostname), à comparer avec le nom de l'instance dans la console Scaleway ; kvm, l'hyperviseur des instances Scaleway ; le Machine ID (/etc/machine-id), identifiant de cette installation qui nomme aussi le répertoire du journal persistant, masqué ici pour une raison expliquée en section Sécurité ; le Boot ID, qui change à chaque démarrage. Sur sig-outils, on lit Debian GNU/Linux 13 (trixie) et un noyau de la forme 6.12.NN+deb13-amd64 (ou -cloud-amd64 si l'image utilise le noyau allégé de Debian pour le cloud).
Dans un script, préférez /etc/os-release (qui, d'après os-release(5), l'emporte sur /usr/lib/os-release) et uname -r. Sortie typique :
$ . /etc/os-release && echo "$PRETTY_NAME ($VERSION_CODENAME)"; uname -r
Ubuntu 24.04.3 LTS (noble)
6.8.0-79-generic
Virtualisation et instance Scaleway
systemd-detect-virt affiche la technologie de virtualisation (kvm sur une instance Scaleway) et, d'après sa page de manuel, renvoie 0 si une virtualisation est détectée, une valeur non nulle sinon (avec la sortie none). --vm (-v) et --container (-c) restreignent la recherche ; --quiet (-q) n'en garde que le code de sortie, pratique dans un test : if systemd-detect-virt -q --container; then .... On rencontre aussi qemu (émulation sans accélération), microsoft, amazon, et pour les conteneurs docker ou lxc.
Pour savoir quelle instance on a sous les doigts, interrogez le service de métadonnées de Scaleway, joignable à l'adresse lien-local 169.254.42.42 (fd00:42::42 en IPv6), décrit dans la leçon 4 du cours sur le cloud. Sur les images Scaleway, scw-metadata affiche toutes les métadonnées sous la forme CLÉ=valeur, ou une seule si on lui donne son nom ; scw-metadata-json les donne en JSON. Exemple tiré de la documentation Scaleway :
$ scw-metadata NAME
sig-app-1
Si l'outil manque, une requête HTTP suffit : curl -s 'http://169.254.42.42/conf?format=json' | jq '{name, id, commercial_type, tags}'. Notez l'identifiant de l'instance (celui que demandent le support et la CLI), la zone (fr-par-1 et fr-par-2 : les deux serveurs d'application sont volontairement dans deux zones), le type commercial et les étiquettes, souvent la seule documentation laissée côté fournisseur. Complétez depuis votre poste avec scw instance server get <identifiant> zone=fr-par-1 : volumes, instantanés, groupe de sécurité, IP publique, réseau privé.
Ressources
Sur une machine virtuelle, le matériel est ce que l'hyperviseur présente. Sortie typique :
$ lscpu | grep -E '^(CPU\(s\)|Model name|Hypervisor vendor)'
CPU(s): 4
Model name: AMD EPYC 7543 32-Core Processor
Hypervisor vendor: KVM
$ free -h
total used free shared buff/cache available
Mem: 3.8Gi 1.1Gi 1.6Gi 1.0Mi 1.3Gi 2.7Gi
Swap: 0B 0B 0B
$ lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS
NAME SIZE TYPE FSTYPE MOUNTPOINTS
sda 20G disk
├─sda1 19.9G part ext4 /
└─sda15 106M part vfat /boot/efi
Les valeurs dépendent du type d'instance et de volume. Notez le nombre de processeurs et la mémoire, sur lesquels le nombre de workers Gunicorn a été réglé ; lisez la colonne available de free (mémoire utilisable, cache récupérable compris) plutôt que free ; l'absence d'échange, courante sur les images cloud, est un choix à documenter (leçon 6). Le nom des disques (sda, vda, nvme0n1) dépend du type de volume et ne doit jamais servir d'identifiant.
Ajoutez df -hT -x tmpfs -x devtmpfs -x squashfs (remplissage, sans les systèmes en mémoire ni les images snap) et df -i (inodes). Sur sig-outils, /tmp apparaît en tmpfs : c'est le défaut depuis Debian 13 d'après ses notes de publication, alors qu'Ubuntu 24.04 garde /tmp sur le disque. Sur une instance, lspci et dmidecode n'apprennent guère plus que des périphériques virtio ; gardez-les pour une machine physique.
Ce qui tourne
Les services actifs d'abord. La sortie ressemble à ceci :
$ systemctl list-units --type=service --state=running
UNIT LOAD ACTIVE SUB DESCRIPTION
cron.service loaded active running Regular background program processing daemon
rsyslog.service loaded active running System Logging Service
signalements.service loaded active running API Signalements
ssh.service loaded active running OpenBSD Secure Shell server
systemd-resolved.service loaded active running Network Name Resolution
...
Pour chaque ligne : « est-ce que je sais pourquoi ceci tourne ? ». Ce qui n'appartient ni à la distribution ni à Signalements va dans la liste des questions pour Camille. Puis :
$ systemctl --failed
$ systemctl list-unit-files --type=service --state=enabled
Un service en échec depuis des mois est fréquent et rarement anodin (une sauvegarde qui ne tourne plus). La liste des services activés dit ce qui démarrera au prochain démarrage : un service actif mais non activé disparaîtra, un service activé mais arrêté reviendra sans prévenir (premier cours, leçon 11).
Ensuite, les ports en écoute :
$ sudo ss -tulpn
-t et -u sélectionnent TCP et UDP, -l les sockets en écoute (omises par défaut), -p le processus propriétaire (visible pour les autres comptes seulement avec sudo), -n garde adresses et ports en chiffres. La sortie ressemble à ceci :
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
udp UNCONN 0 0 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=598,fd=14))
tcp LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=598,fd=15))
tcp LISTEN 0 2048 0.0.0.0:8000 0.0.0.0:* users:(("gunicorn",pid=1043,fd=5),("gunicorn",pid=987,fd=5))
tcp LISTEN 0 4096 *:22 *:* users:(("sshd",pid=1201,fd=3),("systemd",pid=1,fd=118))- Le résolveur local
systemd-resolvedn'écoute que sur127.0.0.53: joignable depuis la machine seulement (leçon 7). - Gunicorn écoute sur
0.0.0.0:8000, toutes interfaces confondues, y compris l'interface publique si l'instance en a une, alors que le répartiteur n'a besoin que de172.16.8.11. Le groupe de sécurité ou le pare-feu peuvent bloquer, mais c'est un constat prioritaire pour la leçon 8. LeSend-Qd'une socket en écoute est la taille de sa file d'attente de connexions : 2048 est lebacklogpar défaut de Gunicorn. - SSH est tenu par
systemdetsshd: depuis Ubuntu 22.10, systemd ouvre le port (ssh.socket) et le transmet àsshd. Sur Debian 13, seulsshdapparaît.
Pour un port inconnu, systemctl status <PID> affiche l'unité qui contient le processus. Regardez aussi vers qui la machine parle : sudo ss -tnp state established doit montrer Gunicorn vers sig-db sur le port 5432 et rsyslog vers sig-outils. Une connexion sortante inexpliquée est une question pour Camille, ou un indice de compromission.
Le reste se relève sans s'y attarder : ip -br addr et ip route (leçon 7), sudo ufw status verbose ou sudo nft list ruleset (leçon 8), timedatectl (leçon 9).
Qui a accès
C'est la partie critique d'un départ : répondre à « qui peut se connecter, avec quels droits ? » par tous les chemins.
Les comptes qui ont un shell (les comptes système ont nologin ou false, voir le premier cours). Sortie typique :
$ awk -F: '$7 !~ /(nologin|false|sync)$/ {print $1, $3, $7}' /etc/passwd
root 0 /bin/bash
camille 1000 /bin/bash
deploy 1001 /bin/bash
awk -F: '$3 == 0' /etc/passwd ne doit renvoyer que root : un second UID 0 est une très mauvaise pratique ou une porte dérobée. sudo passwd -S -a donne l'état du mot de passe de chaque compte (P utilisable, L verrouillé, NP absent) ; un P sur root, verrouillé par défaut sur Ubuntu, signifie que quelqu'un lui a donné un mot de passe.
Les privilèges. getent group sudo adm liste les membres de sudo (administration complète) et d'adm (lecture des journaux, qui contiennent des données personnelles des usagers). Sur Red Hat, le groupe d'administration s'appelle wheel. Puis les règles elles-mêmes, sans commentaires :
$ sudo grep -rHvE '^\s*(#|$)' /etc/sudoers /etc/sudoers.d/
Une règle deploy ALL=(ALL) NOPASSWD: ALL signifie que quiconque détient la clé de deploy, peut-être stockée dans l'intégration continue, est root sans mot de passe. sudo -l -U deploy affiche ce qu'un compte peut faire.
Les clés SSH. Affichez les empreintes plutôt que les clés. La sortie ressemble à ceci (empreintes tronquées) :
$ for f in /root/.ssh/authorized_keys /home/*/.ssh/authorized_keys; do
> [ -f "$f" ] && { echo "== $f"; sudo ssh-keygen -lf "$f"; }
> done
== /root/.ssh/authorized_keys
256 SHA256:Xq3v... camille@portable (ED25519)
256 SHA256:9bKe... ci-deploiement (ED25519)
Le commentaire est libre et peut mentir, l'empreinte non. sudo sshd -T | grep -i authorizedkeysfile vérifie que sshd ne lit pas d'autres fichiers que ceux par défaut (.ssh/authorized_keys .ssh/authorized_keys2).
Important
Sur les images Scaleway, /root/.ssh/authorized_keys est régénéré à partir des clés SSH du projet et des étiquettes AUTHORIZED_KEY de l'instance, au démarrage ou par scw-fetch-ssh-keys --upgrade. Retirer la clé de Camille du fichier ne suffit pas : elle reviendrait au prochain redémarrage. Il faut la retirer du projet Scaleway et des étiquettes.
Le fournisseur fait donc partie de la surface d'accès : relevez aussi les membres de l'organisation Scaleway, leurs rôles IAM et les clés d'API. Quiconque peut ouvrir la console série ou le mode de secours d'une instance a un accès équivalent à root (leçon 5).
Les dernières connexions. Sur Ubuntu 24.04, last -F et lastlog fonctionnent. Sur Debian 13, les notes de publication indiquent que util-linux ne fournit plus last ni lastb, que login ne fournit plus lastlog, et que leurs remplaçants (wtmpdb avec libpam-wtmpdb, et lastlog2) ne sont pas installés par défaut. Le journal fonctionne partout : journalctl -u ssh --since "30 days ago" | grep -E 'Accepted (publickey|password)'. Un Accepted password là où vous pensiez les clés seules admises se remonte tout de suite.
Ce qui a été fait à la main
Les paquets demandés explicitement. apt-mark showmanual liste les paquets installés à la demande, y compris ceux de l'installation initiale, ce qui rend la liste longue. Sa valeur vient de la comparaison : avec sig-app-2, et avec une instance neuve créée depuis la même image. Ce qui reste est ce que Camille a ajouté.
Les paquets orphelins de dépôt. Un .deb installé avec dpkg -i, ou dont le dépôt a été retiré, n'est plus mis à jour. Dans le langage de motifs d'APT (apt-patterns(7)), ~o désigne les paquets qui n'existent plus dans aucun dépôt :
$ apt list '~o'
$ ls /etc/apt/sources.list.d/
La seconde commande montre les dépôts tiers ajoutés. Sur Ubuntu, snap list complète le tableau.
L'historique d'APT. /var/log/apt/history.log et ses archives notent chaque opération, avec la personne quand elle est passée par sudo. Un extrait typique :
$ zgrep -hE '^(Start-Date|Commandline|Requested-By)' /var/log/apt/history.log* | tail -n 30
Start-Date: 2025-03-12 14:02:41
Commandline: apt install redis-tools
Requested-By: camille (1000)
Sa profondeur dépend de la rotation par logrotate : quelques mois, rarement trois ans.
Les logiciels hors paquets. La norme FHS réserve /opt et /usr/local à ce qui échappe au gestionnaire de paquets. Pour un fichier suspect, dpkg -S cherche le paquet propriétaire ; sa réponse ressemble à ceci quand il n'y en a pas :
$ ls -la /opt /usr/local/bin /usr/local/sbin
$ dpkg -S /usr/local/bin/sig-purge
dpkg-query: no path found matching pattern /usr/local/bin/sig-purge
/opt/signalements est l'application ; tout le reste demande une explication, de même que les modules Python installés pour tout le système (/usr/local/lib/python3.*/dist-packages/) et d'éventuels conteneurs (docker ps -a).
Les unités surchargées. systemd-delta compare les unités entre /etc/, /run/ et /usr/lib/. La sortie ressemble à ceci :
$ systemd-delta --type=overridden,extended --no-pager
[EXTENDED] /usr/lib/systemd/system/rsyslog.service → /etc/systemd/system/rsyslog.service.d/override.conf
[OVERRIDDEN] /etc/systemd/system/cron.service → /usr/lib/systemd/system/cron.service
EXTENDED signale un drop-in (fragment qui complète une unité) ; OVERRIDDEN, une unité recopiée en entier dans /etc, suivie du diff avec l'original : plus fragile, car elle ne recevra plus les corrections du paquet.
La configuration récemment modifiée. sudo find /etc -type f -mtime -180 -printf '%TY-%Tm-%Td %p\n' | sort liste ce qui a changé sous /etc en six mois. Les mises à jour de paquets y figurent aussi, mais c'est une bonne base de questions. dpkg --verify, présenté à la leçon 15, repère plus précisément les fichiers de paquets modifiés.
Tâches planifiées
systemctl list-timers --all liste les minuteurs avec leurs colonnes NEXT, LEFT, LAST, PASSED, UNIT et ACTIVATES (le service déclenché). Une Ubuntu fraîche en compte une dizaine (apt-daily.timer, logrotate.timer, fstrim.timer...) ; un LAST vide ou très ancien trahit un minuteur qui ne se déclenche pas. Pour cron, plusieurs endroits :
$ cat /etc/crontab
$ ls -l /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/
$ sudo ls -l /var/spool/cron/crontabs/
$ sudo crontab -l -u camille
/var/spool/cron/crontabs/ contient la crontab personnelle de chaque compte sur Debian et Ubuntu (/var/spool/cron/ sur Red Hat). Si l'export de la mairie vit dans la crontab de camille sur sig-outils, il tourne sous son identité : verrouiller son compte arrêtera l'export. Notez-le en gras ; la leçon 10 montre comment le migrer. sudo atq liste les éventuelles tâches at.
Mises à jour et santé
Relevez l'état des mises à jour sans lancer apt update :
$ apt list --upgradable 2>/dev/null
$ ls /var/run/reboot-required 2>/dev/null
$ ls /boot/vmlinuz-*; uname -r
La liste se fonde sur les index du dernier rafraîchissement automatique. /var/run/reboot-required (premier cours, leçon 12) signale un redémarrage en attente ; sur Debian, il n'est créé que si unattended-upgrades est installé, mais un noyau dans /boot plus récent que uname -r dit la même chose partout. Sur Ubuntu, pro status indique le rattachement à Ubuntu Pro (leçon 4).
Puis l'ancienneté du démarrage. Sortie typique :
$ uptime
09:14:02 up 243 days, 18:51, 1 user, load average: 0.21, 0.18, 0.12
$ journalctl --list-boots --no-pager | tail -n 3
243 jours sans redémarrage, c'est un noyau sans aucun correctif de sécurité depuis, quelles que soient les mises à jour de paquets. Surtout, personne ne sait s'il redémarrera : une erreur dans /etc/fstab ou un noyau mal installé peut dormir depuis des mois (leçon 5). Notez-le comme risque majeur et planifiez un redémarrage contrôlé, précédé d'un instantané, plutôt que de le subir lors d'une maintenance de l'hyperviseur.
Enfin, les erreurs récentes : journalctl -p err -b affiche les messages de priorité err et plus graves depuis le démarrage ; journalctl -p warning --since "7 days ago" -o cat --output-fields=SYSLOG_IDENTIFIER | sort | uniq -c | sort -rn | head compte les avertissements de la semaine par programme. Un programme qui en produit des milliers par jour mérite une ligne dans la fiche.
La fiche de serveur
Les observations se condensent dans une fiche de serveur : un document court qui permet à quelqu'un qui ne connaît pas la machine de comprendre ce qu'elle fait et comment intervenir. Pas sur le serveur (c'est quand il ne répond plus qu'on en a besoin), pas dans un wiki que personne ne relit : dans le dépôt Git d'infrastructure, par exemple docs/serveurs/sig-app-1.md, versionnée, relue par demande de fusion, à côté du code qui la remplacera peu à peu. Un modèle :
# sig-app-1
- Rôle : API Signalements (Gunicorn), derrière le répartiteur de charge
- Environnement : production ; données personnelles : oui (journaux avec IP)
- Instance : <identifiant Scaleway>, fr-par-1, <type> ; 172.16.8.11 (pn-signalements)
- Système : Ubuntu 24.04 LTS, noyau 6.8.0-79-generic au 2026-10-07
- Services : signalements.service sur 0.0.0.0:8000 (À CORRIGER) ; ssh par socket
- Dépendances : sig-db (5432) ; sig-outils (journaux)
- Accès : root par les clés du projet Scaleway ; sudo : camille (À RETIRER) ;
deploy : NOPASSWD ALL, clé de l'intégration continue
- Tâches : minuteurs de la distribution uniquement
- Dettes : /usr/local/bin/sig-purge hors dépôt ; 243 jours sans redémarrage
- Procédures : docs/runbooks/Les dettes y sont écrites noir sur blanc : une fiche qui ne montre que ce qui va bien ne sert à rien. Les procédures sont des runbooks (fiches d'intervention pas à pas) rangés à part. La mention des données personnelles alimente le registre des traitements exigé par le RGPD et conditionne la conservation des journaux (leçon 12) et des sauvegardes (leçon 16). sig-db, que vous n'administrez pas, mérite une fiche plus courte : point d'accès, version, sauvegardes managées, détenteurs des identifiants.
Le journal des changements
La fiche décrit un état ; le journal des changements raconte comment on y est arrivé. Chaque intervention y laisse une entrée, au moment où on la fait :
## 2026-10-09 10:40 UTC, sig-app-1, Dominique
- Quoi : retrait de camille du groupe sudo (gpasswd -d camille sudo)
- Pourquoi : départ de Camille (ticket PLAT-412)
- Vérification : getent group sudo ; sudo -l -U camille
- Retour arrière : gpasswd -a camille sudoL'heure UTC se corrèle avec les journaux ; le pourquoi évite qu'on défasse le changement en le croyant inutile ; la vérification prouve l'effet ; le retour arrière est écrit avant d'en avoir besoin.
Pour /etc, etckeeper complète cette discipline : il place /etc sous Git, enregistre un commit avant et après chaque opération APT, et conserve permissions et propriétaires, que Git ignore normalement. sudo etckeeper commit "message" enregistre une modification manuelle ; sudo git -C /etc log -p montre qui a changé quoi. L'installer modifie la machine : ce sera votre premier changement après l'état des lieux, et la première entrée du journal.
Un script d'inventaire
Refaire tout cela à la main sur trois machines, puis dans six mois, mène aux oublis. Un script Bash simple, en lecture seule, produit le relevé en Markdown :
#!/usr/bin/env bash
# inventaire.sh : relevé en lecture seule d'un serveur Debian ou Ubuntu.
set -u
export LC_ALL=C
[ "$(id -u)" -eq 0 ] || { echo "À lancer avec sudo." >&2; exit 1; }
section() { printf '\n## %s\n\n' "$1"; }
run() {
printf '```console\n$ %s\n' "$1"
bash -c "$1" 2>&1
local rc=$?
[ "$rc" -ne 0 ] && printf '(code de sortie %s)\n' "$rc"
printf '```\n\n'
}
printf '# Inventaire de %s, %s\n' "$(hostname)" "$(date -u +%Y-%m-%dT%H:%MZ)"
section "Identité"
run "hostnamectl | grep -vE 'Machine ID|Boot ID'"
run "systemd-detect-virt"
section "Ressources"
run "free -h"
run "lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS"
run "df -hT -x tmpfs -x devtmpfs -x squashfs"
section "Services et ports"
run "systemctl list-units --type=service --state=running --no-pager --no-legend"
run "systemctl --failed --no-pager --no-legend"
run "systemd-delta --type=overridden,extended --no-pager"
run "ss -tulpn"
section "Accès"
run "awk -F: '\$7 !~ /(nologin|false|sync)\$/ {print \$1, \$3, \$7}' /etc/passwd"
run "getent group sudo adm"
run "grep -rHvE '^\s*(#|\$)' /etc/sudoers /etc/sudoers.d/"
run "for f in /root/.ssh/authorized_keys /home/*/.ssh/authorized_keys; do [ -f \"\$f\" ] && { echo \"== \$f\"; ssh-keygen -lf \"\$f\"; }; done"
section "Logiciels et tâches"
run "apt-mark showmanual"
run "apt list '~o' 2>/dev/null"
run "ls -la /opt /usr/local/bin"
run "systemctl list-timers --all --no-pager --no-legend"
run "ls /etc/cron.d /var/spool/cron/crontabs"
section "Santé"
run "uptime -s; uname -r; ls /var/run/reboot-required 2>/dev/null"
run "journalctl -p err -b --no-pager -q | tail -n 30"set -uarrête le script sur une variable non définie, mais passet -e: une commande absente ne doit pas interrompre tout le relevé, son code de sortie est affiché à la place.LC_ALL=Cdonne des sorties stables et comparables pardiff.runreçoit la commande sous forme de chaîne et la confie àbash -c, pour que tubes et redirections fonctionnent ; d'où les\$échappés, qui doivent arriver intacts au second shell.- Les lignes
Machine IDetBoot IDsont retirées (voir Sécurité), et rien n'est écrit sur le serveur.
Pour le lancer sans le copier, envoyez-le sur l'entrée standard de SSH :
$ for h in sig-app-1 sig-app-2 sig-outils; do
> ssh "$h" 'sudo bash -s' < inventaire.sh > "docs/inventaires/$h-$(date +%F).md"
> done
$ diff docs/inventaires/sig-app-1-2026-10-07.md docs/inventaires/sig-app-2-2026-10-07.md
bash -s lit le script sur son entrée standard, ce qui suppose un sudo sans mot de passe ; sinon, copiez-le avec scp et lancez-le avec ssh -t. Le diff des deux serveurs d'application est souvent l'instant le plus instructif : chaque différence est une dérive à expliquer. Dès qu'Ansible arrive, ses « faits » collectés sur chaque machine remplacent ce relevé (cours Ansible : les fondamentaux).
Sous le capot
hostnamectl est un client. Il interroge par D-Bus (le bus de messages entre processus) le service systemd-hostnamed, démarré à la demande, qui lit /etc/hostname, /etc/machine-info, /etc/os-release et les informations DMI exposées par le noyau sous /sys/class/dmi/id/. D'où un hostnamectl qui échoue dans un conteneur sans D-Bus, alors que cat /etc/hostname fonctionne.
Comment systemd-detect-virt sait. Pour une machine virtuelle, l'instruction CPUID du processeur expose un bit « hyperviseur présent » et une signature (KVMKVMKVM pour KVM), recoupée avec les tables DMI. Pour un conteneur, il cherche container= dans l'environnement du PID 1 et le fichier /run/systemd/container. En cas d'imbrication, la page de manuel précise que la virtualisation la plus interne l'emporte.
L'identifiant de machine. /etc/machine-id contient 32 caractères hexadécimaux (128 bits) tirés au hasard à l'installation ou au premier démarrage. machine-id(5) demande que les images destinées au clonage le livrent vide ou absent, pour que chaque instance génère le sien. Une image mal préparée produit des machines au même identifiant, ce qui embrouille le journal centralisé et le DHCP, dont l'identifiant client de systemd-networkd est dérivé.
Pourquoi ss -p demande sudo. ss obtient les sockets du noyau par netlink (le canal entre noyau et espace utilisateur), mais le noyau n'y dit pas quel processus détient chaque socket. ss parcourt donc /proc/<pid>/fd/ à la recherche de liens socket:[inode], et un utilisateur ordinaire ne lit que les descripteurs de ses propres processus. Sans sudo, la colonne Process reste vide, sans erreur.
Où APT range « manuel ». Pas dans la base de dpkg, qui l'ignore, mais dans /var/lib/apt/extended_states, où chaque paquet installé en dépendance porte Auto-Installed: 1. Un paquet installé par dpkg -i est donc vu comme manuel, ce qui est exact.
Les historiques de démarrage. uptime lit /proc/uptime, tenu par le noyau. last reboot lit /var/log/wtmp sur Ubuntu ; sur Debian 13, wtmpdb range ces enregistrements dans une base SQLite sous /var/lib/wtmpdb/, pour échapper aux horodatages 32 bits qui débordent en 2038. journalctl --list-boots repose sur le journal, indépendant de ces fichiers : la source la plus fiable tant que le journal est persistant.
Pièges courants
ss sans sudo. Colonne Process vide, aucun message : on conclut à tort que rien de connu n'écoute.
journalctl sans privilèges. Hors des groupes adm et systemd-journal, on ne voit que ses propres messages, ce que systemd 255 signale ainsi :
Hint: You are currently not seeing messages from other users and the system.
Users in groups 'adm', 'systemd-journal' can see all messages.
Pass -q to turn off this notice.Un état des lieux sans le journal système ne vaut rien : relancez avec sudo.
last: command not found sur Debian 13. Commande retirée, pas machine cassée. Si vous installez wtmpdb et libpam-wtmpdb plus tard, sachez que son README pour Debian indique que libpam-wtmpdb ignore par défaut les connexions de sshd (option skip_if=sshd), le sshd de Debian les enregistrant lui-même.
sudo: unable to resolve host sig-app-1: Name or service not known. Le nom de la machine manque dans /etc/hosts, souvent après un renommage à la main. sudo fonctionne quand même ; c'est un indice de dérive (leçon 7). Notez, ne corrigez pas encore.
no crontab for camille. Ne prouve pas qu'aucune tâche ne tourne sous ce compte : /etc/crontab et /etc/cron.d/ ont un champ utilisateur, et une unité systemd peut déclarer User=camille (grep -r 'User=camille' /etc/systemd/).
WARNING: apt does not have a stable CLI interface. Use with caution in scripts. Écrit sur la sortie d'erreur dès que la sortie n'est pas un terminal. Sans conséquence ici ; pour un vrai traitement automatique, préférez apt-get ou dpkg-query, au format stable.
Lancer apt update « pour avoir une liste à jour ». Cela efface l'information « depuis quand les index n'ont-ils pas été rafraîchis ? », qui dit si apt-daily.timer fonctionne. Relevez d'abord ls -lt /var/lib/apt/lists/ | head.
Retirer un paquet parce qu'on ignore son rôle. redis-tools alors que personne n'utilise Redis ? Un script de Camille l'appelle peut-être. Jamais pendant l'état des lieux.
Oublier la deuxième machine. sig-app-2 « est pareille ». C'est le diff qui le dira.
Sécurité
L'inventaire est sensible. Comptes, ports, versions, règles sudo : c'est ce qu'un attaquant rassemble en reconnaissance. Rangez fiches et relevés dans un dépôt privé aux accès revus, et ne les collez pas dans un service externe sans les expurger.
L'identifiant de machine est confidentiel. D'après machine-id(5), il doit être considéré comme confidentiel et ne pas être exposé dans un environnement non sûr, en particulier sur le réseau : il identifie l'installation de façon permanente. Une application qui a besoin d'un identifiant stable en dérive un par hachage à clé (sd_id128_get_machine_app_specific()).
Le départ d'une personne est un événement de sécurité. L'état des lieux produit la liste de ce qui doit changer : ses clés dans tous les authorized_keys et dans le projet Scaleway ; ses groupes sudo et adm et ses règles sudoers ; son compte Scaleway, ses rôles IAM et ses clés d'API ; les tâches qui tournent sous son identité, à migrer avant de verrouiller le compte ; tous les secrets qu'elle connaissait, à renouveler par rotation. Verrouiller (usermod -L -e 1) plutôt que supprimer dans un premier temps conserve ses fichiers et l'attribution de ses actions passées.
Chercher les signes de compromission. Second UID 0, clé que personne ne reconnaît, port sans service connu, connexion sortante inexpliquée, binaire récemment modifié dans /usr/local/bin, crontab qui télécharge et exécute un script : aucun n'est une preuve, chacun exige une explication. Si elle manque, ne « nettoyez » pas : faites un instantané du volume et traitez-le comme un incident.
Votre passage laisse des traces, et c'est bien. Chaque sudo est journalisé (journalctl _COMM=sudo) : c'est ce qui distinguera l'état hérité de vos interventions, la même traçabilité que vous exigerez des autres (leçon 15).
En production
- À l'échelle, l'inventaire est continu. Trois machines se relèvent à la main, trente non : les faits d'Ansible et le code d'infrastructure deviennent la source de vérité. Le script sert à amorcer la démarche.
- La fiche se périme dès qu'on cesse de la toucher. Mettez-la à jour dans la même demande de fusion que le changement, et relancez le relevé lors de chaque revue des accès : tout écart est un changement non documenté ou de la dérive.
- Prioriser. Vingt constats arrivent vite. D'abord les accès (une personne partie qui garde
root), puis l'exposition (un port ouvert au monde), puis la capacité à redémarrer et à restaurer, enfin le confort. Le cours suit à peu près cet ordre. - Deux machines d'un même rôle doivent être identiques. Derrière un répartiteur, une différence produit un comportement qui dépend de la machine qui reçoit la requête, le pire à diagnostiquer. Automatisez le
diff. - Reconstruire plutôt que documenter. Le but n'est pas une fiche parfaite, mais de ne plus en avoir besoin : chaque ligne « Dettes » est à faire entrer dans cloud-init ou Ansible, ou à supprimer. Le jour où
sig-app-2peut être détruite et recréée sans inquiétude, elle est devenue du bétail.
Exercices
1. Lire l'identité (niveau 100). Sur une machine inconnue, systemd-detect-virt affiche none et renvoie 1, et hostnamectl indique Chassis: server. Que concluez-vous, quelles commandes de la leçon perdent ou gagnent en intérêt, et pourquoi ne recopiez-vous pas la ligne Machine ID dans la fiche ?
Solution
Aucune virtualisation : c'est une machine physique (serveur dédié, Elastic Metal). Le service de métadonnées d'instance ne s'applique pas ; dmidecode -t system (fabricant, modèle, numéro de série pour le support) et lspci deviennent utiles. L'identifiant de machine est confidentiel selon machine-id(5) ; le nom d'hôte et le numéro de série suffisent à désigner la machine.
2. Un port inconnu (niveau 200). sudo ss -tulpn sur sig-app-2 montre une ligne absente de sig-app-1 :
tcp LISTEN 0 4096 *:9100 *:* users:(("node_exporter",pid=812,fd=3))Quelles commandes disent quel service l'a lancé, d'où vient le binaire, depuis quand il tourne et s'il est joignable de l'extérieur ? Que notez-vous ?
Solution
systemctl status 812: l'unité qui contient le processus, son fichier, son activation, sa date de démarrage ;systemctl cat <unité>: la commande lancée, donc le chemin du binaire.dpkg -S <chemin>: paquetprometheus-node-exporterde la distribution, ouno path found matching patternsi déposé à la main.ps -o lstart= -p 812: l'heure de démarrage.*:9100: toutes les interfaces, IPv4 et IPv6. À recouper avec le pare-feu de l'hôte et le groupe de sécurité Scaleway ; un exportateur de métriques ouvert au monde renseigne un attaquant.
Dans la fiche : service, origine, port, exposition, et une ligne « Dettes » puisqu'il n'existe que sur une machine. Question à poser à Camille avant vendredi.
3. Mesurer la dérive (niveau 200). Écrivez une commande, lancée depuis votre poste, qui affiche en deux colonnes les paquets installés manuellement sur une seule des deux machines d'application.
Solution
$ comm -3 <(ssh sig-app-1 apt-mark showmanual | sort) <(ssh sig-app-2 apt-mark showmanual | sort)
comm compare deux fichiers triés et affiche trois colonnes : propres au premier, propres au second, communs ; -3 supprime la troisième. <( ... ) est une substitution de processus de Bash : chaque liste est présentée à comm comme un fichier, sans rien écrire sur le disque. Sans tri, comm se plaint : comm: file 1 is not in sorted order. apt-mark n'a pas besoin de sudo. Le même principe vaut pour systemctl list-unit-files --state=enabled ou ls /usr/local/bin.
4. Le plan de départ de Camille (niveau 300). L'état des lieux a révélé : la clé de Camille dans /root/.ssh/authorized_keys des trois machines et dans le projet Scaleway ; Camille dans sudo et adm partout ; l'export de la mairie dans la crontab de camille sur sig-outils, avec le mot de passe de sig-db dans /home/camille/.pgpass ; une clé d'API Scaleway à son nom utilisée par les sauvegardes. Ordonnez le retrait des accès sans interrompre ni l'export ni les sauvegardes, avec une vérification par étape.
Solution
On migre d'abord ce qui tourne sous son identité, on retire ensuite ses accès, on renouvelle enfin les secrets.
- Identités de remplacement : le compte de service
signalementssursig-outils, sans shell (celui que reprennent les tâches de la leçon 10) ; une application IAM Scaleway limitée au bucketsig-sauvegardes, avec sa propre clé d'API. Vérifier :getent passwd signalements; la clé apparaît sous l'application, pas sous une personne. - Migrer l'export sous
signalements(idéalement en minuteur systemd, leçon 10), avec un.pgpassen0600; le lancer une fois à la main ; vérifier le fichier produit ; puis seulement retirer la ligne de la crontab decamille. - Migrer les sauvegardes vers la nouvelle clé ; vérifier qu'une sauvegarde complète apparaît dans le bucket.
- Retirer les accès humains : sa clé du projet Scaleway et des étiquettes, puis
scw-fetch-ssh-keys --upgradesur chaque machine (sinon la clé reste jusqu'au redémarrage) ;gpasswd -d camille sudo,gpasswd -d camille adm;usermod -L -e 1 camille. Vérifier : son empreinte a disparu des sorties dessh-keygen -lf;sudo -l -U camillerépondUser camille is not allowed to run sudo on sig-outils. - Côté Scaleway : supprimer son ancienne clé d'API, ses rôles IAM, son appartenance à l'organisation.
- Renouveler les secrets connus d'elle : mot de passe PostgreSQL (en coordination avec
sig-app-1etsig-app-2), clés du bucket, phrase de passe des sauvegardes (leçon 16). Vérifier que l'API, l'export et la sauvegarde suivants réussissent. - Plus tard, après quelques semaines sans dépendance découverte, supprimer le compte.
Chaque étape a son entrée au journal des changements, avec son retour arrière. Le piège évité : verrouiller le compte en premier, ce qui arrêtait l'export dès la nuit suivante.
Récapitulatif
- Prendre en charge un serveur commence par un état des lieux en lecture seule, session enregistrée par
scriptsur votre poste. - Identité et instance :
hostnamectl,/etc/os-release,uname -r,systemd-detect-virt,scw-metadataou169.254.42.42, plus la console Scaleway. - Ce qui tourne : services actifs, en échec, activés ;
sudo ss -tulpnpour relier chaque port à un processus ; connexions établies ; minuteurs et crontabs. - Qui a accès : comptes avec shell, UID 0,
sudoetadm, sudoers, empreintes desauthorized_keys, et le fournisseur (clés du projet Scaleway régénérées au démarrage, IAM, console). - Ce qui a été fait à la main :
apt-mark showmanualcomparé entre machines,apt list '~o', historique d'APT,/optet/usr/local,systemd-delta. - Santé : mises à jour, noyau en cours contre noyau installé, ancienneté du démarrage, erreurs du journal. Debian 13 n'a plus
lastnilastlogpar défaut. - Le résultat va dans une fiche de serveur versionnée, dettes comprises ; chaque intervention ultérieure dans un journal des changements (quoi, pourquoi, vérification, retour arrière) ; etckeeper versionne
/etc. - Un script d'inventaire permet de refaire le relevé et de comparer les machines ; chaque écart est une dette à écrire, reproduire ou supprimer.
Pour aller plus loin
- Les pages
hostnamectl(1),machine-id(5),systemd-detect-virt(1)etsystemd-delta(1), etapt-patterns(7)pour d'autres requêtes d'inventaire (~cpour les configurations résiduelles). - Les notes de publication de Debian 13, chapitre « Problèmes à connaître », avant de prendre en charge une machine trixie.
- Le guide d'hygiène informatique de l'ANSSI, pour l'inventaire des comptes privilégiés et les procédures de départ.
- Le cours Ansible : les fondamentaux, pour remplacer le script par les faits collectés automatiquement.
- La leçon suivante, Le démarrage, du firmware à systemd.
Sources
- man-pages, hostnamectl(1)
- man-pages, machine-id(5)
- man-pages, systemd-detect-virt(1)
- man-pages, systemd-delta(1)
- man-pages, os-release(5)
- man-pages, ss(8)
- Debian, page de manuel apt-patterns(7) (trixie)
- Debian, page de manuel apt-mark(8) (trixie)
- Debian 13, notes de publication : problèmes à connaître (last, lastlog, /tmp)
- Debian, wtmpdb : README.Debian
- Scaleway, Use cloud-init (section sur l'API de métadonnées et scw-metadata)
- Scaleway, Using tags to add Instance-specific SSH keys
- cloud-init, source de données Scaleway
- ANSSI, guide d'hygiène informatique
- etckeeper, site du projet
- Debian, paquets trixie (util-linux, apt, etckeeper)
- Ubuntu, paquets noble (util-linux, apt, etckeeper)