Aller au contenu

Lab : préparer le serveur de Signalements

200 Pratiquer ⏱ 3 h linuxubuntubashsystemd

Prérequis

Testé avec sudo 1.9.15 systemd 255 ubuntu 24.04 , vérifié le 5 octobre 2026

Ce lab rassemble le cours Linux : premiers pas. Vous préparez une machine Ubuntu neuve pour qu'elle héberge une version réduite de Signalements, en appliquant chaque leçon, puis un script vérifie que la machine a les propriétés attendues. Il valide le niveau 200 des compétences « Shell et ligne de commande », « Système de fichiers et permissions », « Processus, services et systemd » et « Gestion des paquets ».

Comptez une demi-journée. Toutes les pièces sont dans les leçons ; essayez d'écrire chaque commande avant d'ouvrir la leçon correspondante.

Ce qu'il faut

  • Une machine Ubuntu 24.04 jetable (machine virtuelle locale ou petite instance cloud), avec un compte qui a le droit d'utiliser sudo. Le lab crée des comptes et modifie la configuration de sudo : jamais sur votre poste ni sur un serveur réel.
  • Une seconde personne fictive, dominique, que vous allez créer : elle doit pouvoir redémarrer le service et lire ses journaux, rien de plus.

L'application

Pour ne pas dépendre de Flask ni d'une base de données, le lab utilise une version minimale de l'API, écrite avec la seule bibliothèque standard de Python, qui répond à GET /sante. Enregistrez-la sous /opt/signalements/app.py :

#!/usr/bin/env python3
"""Signalements, version de laboratoire : répond à GET /sante."""
import json
import os
from http.server import BaseHTTPRequestHandler, HTTPServer


class Gestionnaire(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == "/sante":
            code, corps = 200, {"etat": "ok", "version": os.environ.get("APP_VERSION", "inconnue")}
        else:
            code, corps = 404, {"erreur": "introuvable"}
        donnees = json.dumps(corps).encode()
        self.send_response(code)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(donnees)))
        self.end_headers()
        self.wfile.write(donnees)


HTTPServer(("127.0.0.1", 8000), Gestionnaire).serve_forever()

Elle écoute sur 127.0.0.1:8000 et lit sa version dans la variable d'environnement APP_VERSION.

Les exigences

ExigenceLeçon
Le système est à jour, et les mises à jour de sécurité s'appliquent automatiquement12
Un compte système signalements existe : UID inférieur à 1000, pas de shell de connexion, pas de répertoire personnel créé8
/opt/signalements et app.py appartiennent à root et ne sont modifiables que par lui ; le service peut les lire3, 9
/etc/signalements est en 750 root:signalements, et /etc/signalements/env en 640 root:signalements ; il contient APP_VERSION=1.0.05, 7, 9
Aucun fichier modifiable par tous sous /opt/signalements ni /etc/signalements9
Une unité signalements.service lance l'application sous le compte signalements, avec EnvironmentFile= ; elle est active, activée au démarrage, et redémarre en cas d'échec7, 11
L'application répond 200 sur http://127.0.0.1:8000/sante, et n'écoute que sur l'interface locale6, 10
Le compte dominique est dans le groupe signalements-dev, pas dans sudo ; il peut lancer sudo systemctl restart signalements et rien d'autre en root ; il lit les journaux grâce au groupe systemd-journal8, 11
La configuration de sudo est valide (visudo -c)8
/srv/equipe est un dossier d'équipe en 2770, groupe signalements-dev : les fichiers créés dedans héritent du groupe9
Le journal est persistant (/var/log/journal existe)11

Pistes

  • Créez les répertoires et fixez les droits avec install -d -m ... -o ... -g ..., puis le fichier env avec install -m 0640 -o root -g signalements /dev/null et sudoedit (leçons 5 et 7).
  • Écrivez l'unité dans /etc/systemd/system/signalements.service ; ExecStart= prend un chemin absolu (/usr/bin/python3 /opt/signalements/app.py). Après chaque modification : daemon-reload, puis restart. En cas d'échec, systemctl status et journalctl -u signalements -b (leçon 11).
  • La règle sudo de dominique se dépose avec visudo -f /etc/sudoers.d/signalements-dev, dans un fichier dont le nom ne contient pas de point (leçon 8).
  • Pour tester la règle, sudo -l -U dominique, puis connectez-vous en dominique (sudo -iu dominique) et essayez sudo systemctl stop signalements : le refus est attendu.
  • Testez le redémarrage automatique : envoyez SIGKILL au processus principal (systemctl show -p MainPID --value signalements), puis observez le nouveau PID (leçon 10).

Vérifier

Enregistrez le script sous verifier-lab.sh et lancez-le avec sudo bash verifier-lab.sh : plusieurs contrôles lisent des informations réservées à root (sudo -l -U, visudo -c). Il ne modifie rien.

#!/usr/bin/env bash
# Vérifie les propriétés attendues du lab « préparer le serveur de Signalements ».
# Usage : sudo bash verifier-lab.sh
# Chaque contrôle affiche OK ou ÉCHEC ; le code de sortie est le nombre d'échecs.
set -uo pipefail
export LC_ALL=C.UTF-8
echecs=0

controle() {   # controle "libellé" commande...
  local libelle=$1; shift
  if "$@" > /dev/null 2>&1; then
    echo "OK     $libelle"
  else
    echo "ÉCHEC  $libelle"
    echecs=$((echecs + 1))
  fi
}

droits() {   # droits chemin "mode propriétaire:groupe"
  [ "$(stat -c '%a %U:%G' "$1")" = "$2" ]
}

[ "$(id -u)" -eq 0 ] || { echo "Lancez ce script avec sudo." >&2; exit 1; }

controle "aucune mise à jour de sécurité en attente" \
  bash -c '! apt-get -s upgrade | grep -q "^Inst .*-security"'
controle "les mises à jour automatiques sont activées" \
  bash -c 'eval "$(apt-config shell V APT::Periodic::Unattended-Upgrade)"; [ "${V:-0}" = 1 ]'

controle "le compte système signalements existe (UID < 1000, sans shell)" \
  bash -c 'getent passwd signalements | awk -F: '\''$3 < 1000 && $7 ~ /(nologin|false)$/ {ok=1} END {exit !ok}'\'''
controle "aucun répertoire personnel n'a été créé pour signalements" \
  bash -c 'h=$(getent passwd signalements | cut -d: -f6); [ ! -d "$h" ] || [ "$h" = /nonexistent ]'

controle "/opt/signalements : 755 root:root" droits /opt/signalements "755 root:root"
controle "/opt/signalements/app.py : 644 root:root" droits /opt/signalements/app.py "644 root:root"
controle "/etc/signalements : 750 root:signalements" droits /etc/signalements "750 root:signalements"
controle "/etc/signalements/env : 640 root:signalements" droits /etc/signalements/env "640 root:signalements"
controle "env définit APP_VERSION=1.0.0" grep -qx 'APP_VERSION=1.0.0' /etc/signalements/env
controle "aucun fichier modifiable par tous" \
  bash -c '[ -z "$(find /opt/signalements /etc/signalements -perm -o+w)" ]'

controle "signalements.service est actif" systemctl is-active --quiet signalements
controle "signalements.service est activé au démarrage" systemctl is-enabled --quiet signalements
controle "le service tourne sous le compte signalements" \
  bash -c '[ "$(ps -o user= -p "$(systemctl show -p MainPID --value signalements)")" = signalements ]'
controle "le service lit /etc/signalements/env" \
  bash -c 'systemctl show -p EnvironmentFiles --value signalements | grep -q "^/etc/signalements/env "'
controle "le service redémarre en cas d'échec" \
  bash -c 'r=$(systemctl show -p Restart --value signalements); [ "$r" = on-failure ] || [ "$r" = always ]'
controle "GET /sante répond 200 avec la bonne version" \
  bash -c 'curl -fsS http://127.0.0.1:8000/sante | grep -q "\"version\": \"1.0.0\""'
controle "le port 8000 n'écoute que sur 127.0.0.1" \
  bash -c '[ "$(ss -Hltn "sport = :8000" | awk "{print \$4}" | sort -u)" = "127.0.0.1:8000" ]'

controle "dominique est dans signalements-dev" \
  bash -c 'id -nG dominique | tr " " "\n" | grep -qx signalements-dev'
controle "dominique n'est pas dans sudo" \
  bash -c '! id -nG dominique | tr " " "\n" | grep -qx sudo'
controle "dominique lit les journaux (groupe systemd-journal)" \
  bash -c 'id -nG dominique | tr " " "\n" | grep -qx systemd-journal'
controle "dominique peut redémarrer le service" \
  bash -c 'sudo -l -U dominique | grep -q "/usr/bin/systemctl restart signalements"'
controle "dominique n'a aucun droit sudo général" \
  bash -c '! sudo -l -U dominique | grep -qE "\(ALL( : ALL)?\) (NOPASSWD: )?ALL$"'
controle "la configuration de sudo est valide" visudo -c -q

controle "/srv/equipe : 2770 root:signalements-dev" droits /srv/equipe "2770 root:signalements-dev"
controle "le journal est persistant" test -d /var/log/journal

echo
echo "$echecs échec(s)"
exit "$echecs"

Quelques contrôles demandent une explication :

  • Les mises à jour en attente sont lues avec apt-get -s upgrade, qui simule la mise à jour sans rien installer ; une ligne Inst venue d'une poche -security signale un correctif de sécurité non appliqué. Lancez apt update avant le script, sinon il compare avec des listes anciennes.
  • La lecture de la configuration APT passe par apt-config shell, qui affiche une affectation de variable prête à être évaluée par le shell : c'est la façon prévue de lire une valeur fusionnée de tous les fichiers de /etc/apt/apt.conf.d/.
  • Les droits de sudo se lisent avec sudo -l -U, réservé à root : c'est ce que sudo accorderait réellement à cette personne, toutes règles combinées.
  • ss -Hltn "sport = :8000" liste les sockets en écoute sur le port 8000, sans en-tête ; une seule adresse, 127.0.0.1:8000, est attendue.
  • Le propriétaire de /srv/equipe importe peu, tant que ce n'est pas un compte de personne ; le script attend root, et c'est le groupe qui donne l'accès.

Démonter

Supprimez la machine. Si vous l'avez créée chez un fournisseur de cloud, vérifiez qu'il ne reste ni volume ni adresse IP (voir Les coûts, le contrat et la sortie).

Plan du cours