Aller au contenu

Durcir un serveur

200 Compagnon ⏱ 1 h 30 linuxubuntudebiansystemdsecurite

À la fin, vous saurez

  • Situer une mesure de durcissement dans un référentiel (niveaux du guide ANSSI BP-028, CIS Benchmarks) et justifier le niveau visé
  • Mesurer l'état d'un serveur avec Lynis et interpréter ses avertissements et suggestions avec discernement
  • Réduire la surface d'attaque en identifiant et en désactivant les services, paquets et modules inutiles
  • Restreindre le serveur SSH par un fichier drop-in validé avec sshd -t et sshd -T, sans perdre l'accès
  • Appliquer et expliquer les paramètres sysctl de durcissement, et vérifier leur valeur effective
  • Confiner un service avec les options de bac à sable de systemd et mesurer le gain avec systemd-analyze security
  • Tenir un registre des écarts qui documente les mesures non appliquées et leur raison

Prérequis

Testé avec apparmor 4.0 debian 13 lynis 3.0.9 openssh 9.6p1 systemd 255 ubuntu 24.04 , vérifié le 7 octobre 2026

Pourquoi

La mairie qui utilise Signalements renouvelle son contrat d'hébergement. Le cahier des charges contient une ligne que Camille n'avait jamais eu à traiter : « Le prestataire décrit les mesures de durcissement appliquées aux serveurs, en référence au guide ANSSI-BP-028, et justifie les écarts. » Vous faites le tour de sig-app-1 : SSH accepte encore les mots de passe, un service d'impression installé par erreur écoute sur le réseau, Gunicorn tourne sous un compte dédié mais peut lire presque tout le système de fichiers, et personne ne sait quels paramètres du noyau ont été modifiés.

Rien de tout cela n'est une faille au sens strict. C'est de la marge offerte à l'attaquant. Le jour où une dépendance Python de Signalements aura une vulnérabilité d'exécution de code, la question ne sera plus « peut-on entrer ? » mais « que peut-on faire une fois entré ? ». Sur une machine non durcie : lire /etc/signalements/env et ses identifiants de base de données, déposer un binaire dans /tmp et l'exécuter, charger un module noyau vulnérable en ouvrant une simple socket, explorer le réseau privé.

Durcir (hardening) un système, c'est retirer cette marge : supprimer ce qui ne sert pas, restreindre ce qui sert, cloisonner pour qu'une compromission reste locale. Cette leçon applique une méthode à sig-app-1 : choisir un référentiel, mesurer, agir couche par couche, mesurer à nouveau, et écrire ce que l'on n'a pas fait et pourquoi. Le pare-feu a sa propre leçon (leçon 8), la traçabilité aussi (leçon 15).

Les concepts

Trois principes

Le guide de l'ANSSI pose trois principes avant toute recommandation technique, et toutes les mesures de cette leçon en découlent :

  • Minimisation : ce qui n'est pas installé ne peut pas être exploité, ni oublié lors des mises à jour.
  • Moindre privilège : chaque processus ne dispose que des droits nécessaires. Gunicorn n'a besoin ni d'écrire dans /usr, ni de charger un module, ni de changer l'heure.
  • Défense en profondeur : plusieurs barrières indépendantes. Le groupe de sécurité Scaleway filtre, le pare-feu de l'hôte filtre à nouveau, SSH n'accepte que des clés, le service est confiné.

On ne cherche pas une machine invulnérable : on cherche une machine où chaque étape d'une attaque coûte plus cher et laisse plus de traces.

Un référentiel plutôt qu'une liste trouvée en ligne

Les listes de « commandes pour sécuriser Linux » mélangent mesures utiles et superstitions, et ne permettent pas de justifier un choix auprès d'un client. On s'appuie sur un référentiel.

Le guide ANSSI-BP-028, Recommandations de configuration d'un système GNU/Linux, version 2.0 du 3 octobre 2022, contient 80 recommandations (R1 à R80), chacune associée à un niveau cumulatif :

NiveauÀ appliquer
Minimal (M)systématiquement, sur tout système
Intermédiaire (I)dès que possible, sur la plupart des systèmes
Renforcé (R)sur les systèmes à fort besoin de sécurité, ou hébergeant plusieurs applications à isoler
Élevé (E)seulement si l'équipe a les compétences et le temps de maintenir la mesure

Le guide insiste : une mesure renforcée mal entretenue est contre-productive, et un noyau très durci mais rarement mis à jour est moins sûr qu'un noyau standard corrigé chaque mois. Pour Signalements, qui traite des données personnelles pour une collectivité, le niveau intermédiaire est une cible raisonnable, complété par quelques mesures renforcées peu coûteuses comme le confinement du service.

Les CIS Benchmarks sont l'autre grande famille : un document par distribution et version, avec un profil Level 1 (peu gênant pour l'exploitation) et Level 2 (plus strict). Très prescriptifs, ils s'automatisent bien ; sur Ubuntu, l'outil USG (Ubuntu Security Guide, réservé à Ubuntu Pro) les applique et les vérifie (sudo usg audit cis_level1_server). L'ANSSI explique le pourquoi, le CIS donne le comment ; face à un client public français, citer le BP-028 est attendu.

Mesurer

  • Lynis (paquet lynis) parcourt la machine en lecture seule, exécute quelques centaines de tests et produit avertissements, suggestions et un « indice de durcissement » sur 100. Il ne modifie rien et ne vérifie pas un référentiel précis : il signale ce qui mérite examen.
  • OpenSCAP (oscap) évalue la machine contre un profil formel. Le projet ComplianceAsCode publie notamment des profils ANSSI BP-028 par niveau (anssi_bp28_minimal à anssi_bp28_high). C'est l'outil des audits de conformité ; on le cite pour mémoire.

Important

L'indice de Lynis n'est pas un objectif. Une machine à 90 peut être mal protégée (un secret dans un fichier lisible par tous n'est pas testé), une machine à 70 peut être bien protégée parce que ses écarts sont justifiés. Lisez chaque suggestion comme une question : « cette mesure a-t-elle un sens ici ? »

Contrôle d'accès discrétionnaire et obligatoire

Les permissions Unix (premiers pas, leçon 9) sont un contrôle discrétionnaire : un processus agit avec toute l'autorité de son utilisateur. Un contrôle obligatoire (MAC, Mandatory Access Control) ajoute une politique fixée par l'administrateur, que les processus ne peuvent pas lever. Il passe par les Linux Security Modules (LSM), des points de contrôle du noyau :

  • AppArmor, actif par défaut sur Ubuntu et sur Debian depuis la version 10, rattache à un programme un profil fondé sur les chemins (« chronyd peut lire /etc/chrony/** »). Simple à lire et à écrire.
  • SELinux, actif par défaut sur la famille Red Hat, attribue une étiquette à chaque fichier, processus et port, et la politique décrit ce que chaque type de processus peut faire sur chaque type d'objet. Plus fin et plus exigeant.

Les couches

   ┌────────────────────────┐  groupe de sécurité Scaleway, pare-feu (leçon 8)
   │  services exposés      │  inventaire, retrait de l'inutile, SSH restreint
   ├────────────────────────┤
   │  comptes et sudo       │  premiers pas leçon 8, leçon 13
   ├────────────────────────┤
   │  service confiné       │  bac à sable systemd, AppArmor
   ├────────────────────────┤
   │  systèmes de fichiers  │  nodev, nosuid, noexec
   ├────────────────────────┤
   │  noyau                 │  sysctl, modules, mises à jour
   └────────────────────────┘
   traçabilité (leçon 15) et journaux hors machine (leçon 12) sur toute la hauteur

En pratique

Avant de toucher quoi que ce soit

Le durcissement modifie SSH, le réseau et le noyau : c'est le chantier où l'on se coupe le plus facilement l'accès. Quatre précautions :

  1. Un instantané du volume dans la console Scaleway, ou scw block snapshot create volume-id=<id> name=sig-app-1-avant-durcissement zone=fr-par-1.
  2. Une seconde session ouverte en root (sudo -i), gardée jusqu'à la validation d'une nouvelle connexion.
  3. La console Scaleway à portée de main (leçon 5).
  4. Commencer par sig-app-2, sorti du répartiteur, puis sig-app-1, en notant chaque changement dans la fiche du serveur (leçon 1).

Mesurer l'état initial avec Lynis

$ sudo apt install lynis
$ sudo lynis audit system --quick

audit system lance les tests locaux ; --quick supprime les pauses entre sections ; sudo permet de lire les fichiers protégés. Lynis écrit /var/log/lynis.log (le détail) et /var/log/lynis-report.dat (les résultats en clé=valeur, pratiques à comparer). La fin du rapport ressemble à ceci :

  Suggestions (38):
  ----------------------------
  * Set a password on GRUB boot loader to prevent altering boot configuration (e.g. boot in single user mode without password) [BOOT-5122]
      https://cisofy.com/lynis/controls/BOOT-5122/

  * Consider hardening SSH configuration [SSH-7408]
    - Details  : PasswordAuthentication (set YES to NO)
      https://cisofy.com/lynis/controls/SSH-7408/

  * One or more sysctl values differ from the scan profile and could be tweaked [KRNL-6000]
      https://cisofy.com/lynis/controls/KRNL-6000/
...
  Hardening index : 61 [############        ]

Les valeurs dépendent de la machine. Chaque ligne porte un identifiant (SSH-7408) : sudo lynis show details SSH-7408 en donne le détail, et un écart justifié se masque par skip-test=BOOT-5122 dans /etc/lynis/custom.prf. Conservez ce rapport initial. Ubuntu 24.04 empaquette Lynis 3.0.9, Debian 13 la 3.1.4 ; Lynis signale l'ancienneté de la première, sans conséquence ici.

Réduire les services exposés

$ sudo ss -tulpn

-t/-u : TCP et UDP ; -l : sockets en écoute ; -p : processus propriétaire (d'où sudo) ; -n : sans résolution de noms. Sortie typique :

Netid State  Recv-Q Send-Q   Local Address:Port  Peer Address:Port Process
udp   UNCONN 0      0           127.0.0.54:53         0.0.0.0:*     users:(("systemd-resolve",pid=612,fd=16))
udp   UNCONN 0      0            127.0.0.1:323        0.0.0.0:*     users:(("chronyd",pid=701,fd=5))
udp   UNCONN 0      0              0.0.0.0:631        0.0.0.0:*     users:(("cups-browsed",pid=903,fd=7))
tcp   LISTEN 0      4096         127.0.0.1:631        0.0.0.0:*     users:(("cupsd",pid=887,fd=7))
tcp   LISTEN 0      4096           0.0.0.0:22         0.0.0.0:*     users:(("systemd",pid=1,fd=245))
tcp   LISTEN 0      2048       172.16.8.11:8000       0.0.0.0:*     users:(("gunicorn",pid=1204,fd=5))

Les sockets sur 127.0.0.x ne sont joignables que localement. Gunicorn écoute sur l'adresse privée, comme prévu. Le port 22 appartient à systemd : sur Ubuntu 24.04, SSH est activé par socket (ssh.socket). Restent cupsd et surtout cups-browsed, à l'écoute en UDP sur toutes les interfaces : l'impression n'a rien à faire sur une API, et cups-browsed a connu en 2024 des vulnérabilités exploitables à distance par ce port.

Regardez aussi ce qui démarre sans écouter : systemctl list-unit-files --type=service --state=enabled. Pour chaque ligne : « qui en a besoin ? ». Selon l'image et son histoire, les candidats habituels sont CUPS, snapd si aucun snap n'est utilisé, multipathd sans stockage multichemin. Avant de retirer, apt-cache rdepends --installed <paquet> montre ce qui en dépend. Trois niveaux d'action :

$ sudo systemctl disable --now cups-browsed.service cups.service cups.socket cups.path
$ sudo systemctl mask cups-browsed.service
$ sudo apt purge cups cups-browsed && sudo apt autoremove --purge
  • disable --now arrête et retire du démarrage. CUPS peut être réveillé par sa socket ou son unité .path, d'où les quatre unités.
  • mask lie l'unité à /dev/null : plus rien ne peut la démarrer. Utile quand le paquet doit rester.
  • apt purge supprime paquet et configuration : la vraie minimisation (BP-028 R58 et R62, niveau minimal), le code n'est plus sur le disque.

Relancez ss -tulpn : chaque service restant qui écoute hors de 127.0.0.1 doit figurer, justifié, sur la fiche du serveur.

Restreindre SSH

La configuration complète (certificats, bastion, MFA) relève du cours SSH ; voici le minimum. Deux règles de sshd_config(5) gouvernent tout :

  • La première valeur lue l'emporte : « for each keyword, the first obtained value will be used ».
  • Sur Ubuntu 24.04 et Debian 13, sshd_config commence par Include /etc/ssh/sshd_config.d/*.conf, lus dans l'ordre lexical. Un fichier 10-… passe avant 50-cloud-init.conf, et ses valeurs gagnent.

On ne modifie donc pas le fichier principal (le paquet le met à jour) : on dépose un fichier qui se trie en premier. Particularité Scaleway : ses images ouvrent la session en root, avec les clés du projet dans /root/.ssh/authorized_keys. Avant d'interdire root, les comptes nominatifs des administrateurs doivent exister avec leurs clés et leurs droits sudo (premiers pas, leçon 8), dans un groupe dédié :

$ sudo groupadd --system acces-ssh
$ sudo usermod -aG acces-ssh alice

Puis /etc/ssh/sshd_config.d/10-durcissement.conf, avec sudoedit :

# Durcissement SSH de sig-app-1, voir fiche serveur
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
AllowGroups acces-ssh
MaxAuthTries 3
LoginGraceTime 30
AllowAgentForwarding no
AllowTcpForwarding no
X11Forwarding no
Banner /etc/issue.net
DebianBanner no
  • PermitRootLogin no : toute action d'administration part d'un compte nominatif et passe par sudo, donc est imputable (BP-028 R33). Le défaut, prohibit-password, autorise encore root par clé.
  • PasswordAuthentication no et KbdInteractiveAuthentication no (tous deux yes par défaut) : clés seules. La seconde ferme la voie « clavier interactif » qui, par PAM, redemande un mot de passe (leçon 13).
  • AllowGroups acces-ssh : liste blanche ; un compte créé par un paquet ou oublié ne peut pas se connecter, même avec une clé.
  • MaxAuthTries 3 (6 par défaut), LoginGraceTime 30 (120 s par défaut) : moins d'essais, moins de connexions à demi ouvertes.
  • Les redirections font d'un serveur compromis un tremplin vers le réseau privé ou vers l'agent SSH de l'administrateur ; si quelqu'un en a besoin, ouvrez-les pour lui seul dans un bloc Match User.
  • Banner : la bannière légale, affichée avant l'authentification (plus bas). DebianBanner no, option propre aux paquets Debian et Ubuntu, retire le suffixe de distribution de la version annoncée par le protocole.

Validez, puis vérifiez la configuration effective. Sortie attendue :

$ sudo sshd -t
$ sudo sshd -T | grep -Ei '^(permitrootlogin|passwordauthentication|allowgroups)'
permitrootlogin no
passwordauthentication no
allowgroups acces-ssh
$ sudo systemctl reload ssh

sshd -t vérifie la syntaxe et reste muet si tout va bien. sshd -T affiche la configuration telle que le démon la comprend, inclusions et valeurs par défaut comprises : la seule preuve que votre fichier n'est pas contredit par un autre lu avant lui. Le service s'appelle ssh sur Debian et Ubuntu, sshd sur Red Hat ; reload ne coupe pas les sessions. Ouvrez une nouvelle connexion depuis un autre terminal, vérifiez qu'une connexion root est refusée, et seulement ensuite fermez la session de secours.

Les paramètres du noyau

La leçon 3 a présenté sysctl et /etc/sysctl.d/. Voici les paramètres de durcissement, en partant de ce que les distributions font déjà.

Ce qui est déjà en place. Sur Ubuntu 24.04, procps dépose dans /etc/sysctl.d/ des fichiers 10-*.conf : kernel.kptr_restrict = 1, kernel.yama.ptrace_scope = 1, rp_filter = 2 pour all et default, kernel.sysrq = 176 ; /usr/lib/sysctl.d/99-protect-links.conf règle les fs.protected_*. Les noyaux Ubuntu et Debian sont compilés avec CONFIG_SECURITY_DMESG_RESTRICT (kernel.dmesg_restrict vaut 1) et BPF_UNPRIV_DEFAULT_OFF (kernel.unprivileged_bpf_disabled vaut 2). Sur Debian 13, les réglages de systemd sont dans le paquet linux-sysctl-defaults (/usr/lib/sysctl.d/50-default.conf), et les notes de publication précisent que systemd-sysctl ne lit plus /etc/sysctl.conf : seuls comptent les répertoires sysctl.d.

Écrivez /etc/sysctl.d/60-durcissement.conf, lu après les fichiers 10-* et 50-* :

# Durcissement noyau de sig-app-1, d'après ANSSI-BP-028 R9, R11, R12, R14.
# Écarts documentés dans la fiche serveur.
kernel.kptr_restrict = 2
kernel.dmesg_restrict = 1
kernel.yama.ptrace_scope = 2
kernel.unprivileged_bpf_disabled = 1
net.core.bpf_jit_harden = 2
kernel.sysrq = 0

fs.suid_dumpable = 0
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
fs.protected_fifos = 2
fs.protected_regular = 2

# Le motif * couvre all, default et chaque interface (voir Sous le capot).
net.ipv4.conf.*.rp_filter = 1
net.ipv4.conf.*.accept_redirects = 0
net.ipv4.conf.*.secure_redirects = 0
net.ipv4.conf.*.send_redirects = 0
net.ipv4.conf.*.accept_source_route = 0
net.ipv4.conf.*.log_martians = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.tcp_syncookies = 1
net.ipv6.conf.*.accept_redirects = 0
net.ipv6.conf.*.accept_source_route = 0
  • kptr_restrict = 2 : les adresses du noyau (/proc/kallsyms…) s'affichent en zéros, même pour root. Une exploitation du noyau commence souvent par localiser ses structures. La valeur 1 d'Ubuntu les montre encore aux détenteurs de CAP_SYSLOG.
  • dmesg_restrict = 1 : seuls les détenteurs de CAP_SYSLOG lisent le tampon du noyau. Déjà le défaut : l'écrire documente l'intention.
  • yama.ptrace_scope = 2 : ptrace est l'appel système qui permet d'observer et de modifier un autre processus (strace, gdb). À 0, tout processus peut s'attacher à ceux du même utilisateur ; à 1 (Ubuntu), seulement à ses descendants ; à 2, seuls les détenteurs de CAP_SYS_PTRACE, donc l'administrateur via sudo. Un worker Gunicorn compromis ne peut plus lire la mémoire du maître. La valeur 3 interdit tout ptrace et, selon la documentation du noyau, ne peut plus être changée avant le redémarrage.
  • unprivileged_bpf_disabled = 1 : interdit bpf() aux non-privilégiés ; le sous-système eBPF a fourni de nombreuses élévations de privilèges. À 2 (défaut), l'administrateur peut revenir en arrière à chaud ; à 1, c'est définitif jusqu'au redémarrage. Le BP-028 recommande 1.
  • bpf_jit_harden = 2 : le compilateur à la volée de BPF masque ses constantes, ce qui gêne le JIT spraying, pour un léger coût.
  • sysrq = 0 : coupe les combinaisons SysRq (redémarrage forcé, arrêt de tous les processus depuis la console). Sur une instance cloud, seule la console série Scaleway y donne accès : choix discutable, bon candidat au registre des écarts.
  • suid_dumpable = 0 : un programme setuid qui plante ne laisse pas de core contenant des données privilégiées.
  • protected_* : ferment les attaques dans les répertoires partagés à bit sticky comme /tmp. On ne suit plus un lien symbolique déposé par un autre, on ne crée plus de lien physique vers le fichier d'un autre, on n'ouvre plus avec O_CREAT un tube ou un fichier existant appartenant à un autre (la valeur 2 étend ce dernier point aux répertoires inscriptibles par le groupe). C'est la parade aux pièges posés à l'avance sur le nom d'un fichier temporaire prévisible.
  • rp_filter = 1 : filtrage par chemin inverse strict (RFC 3704) ; un paquet n'est accepté que s'il arrive par l'interface par laquelle la machine répondrait à sa source, ce qui écarte les adresses usurpées. Ubuntu choisit le mode lâche (2) ; voir les pièges.
  • accept_redirects, secure_redirects, send_redirects : ignorer les ICMP redirect (un voisin malveillant pourrait détourner le trafic) et ne pas en émettre (la machine ne route pas).
  • accept_source_route = 0 : refuser les paquets qui imposent leur route ; défaut d'un hôte, écrit pour mémoire.
  • tcp_syncookies = 1 : sous une inondation de demandes de connexion (SYN flood), répondre par des cookies au lieu de mémoriser chaque demande. Défaut du noyau.
  • log_martians = 1 : journalise les paquets aux adresses impossibles ; utile, mais surveillez le volume.

Le BP-028 propose aussi de désactiver IPv6 inutilisé (R13). Chez Scaleway, où les instances ont une adresse IPv6, ne le faites pas sans vérifier ; s'il reste actif, il se durcit comme IPv4. Appliquez et vérifiez. Sortie typique :

$ sudo sysctl --system
* Applying /etc/sysctl.d/10-console-messages.conf ...
...
* Applying /etc/sysctl.d/60-durcissement.conf ...
$ sysctl kernel.kptr_restrict kernel.yama.ptrace_scope
kernel.kptr_restrict = 2
kernel.yama.ptrace_scope = 2
$ sysctl -a --pattern 'net.ipv4.conf.*.rp_filter'

procps 4.0.4 (Ubuntu 24.04 et Debian 13) comprend les motifs * comme systemd. Au démarrage, c'est systemd-sysctl.service qui applique les fichiers : sudo systemctl restart systemd-sysctl rejoue ce chemin. La dernière commande doit afficher 1 pour all, default et chaque interface.

Les options de montage

Un attaquant qui exécute du code sous signalements cherche où écrire et exécuter son outillage : /tmp, /var/tmp, /dev/shm. Les options nodev (fichiers de périphérique ignorés), nosuid (bits setuid ignorés) et noexec (execve() refusé) y réduisent les possibilités ; le BP-028 les recommande (R28, intermédiaire). Point de départ :

RépertoireUbuntu 24.04Debian 13
/tmpsur le disque racinetmpfs par défaut (tmp.mount), nosuid,nodev, 50 % de la mémoire au plus
/var/tmpdisque racinedisque racine
/dev/shmtmpfs monté par systemd avec nosuid,nodevidem

Aucune n'ajoute noexec. Sur sig-app-1, dans /etc/fstab (leçon 6) :

tmpfs   /tmp      tmpfs  defaults,size=1G,mode=1777,nosuid,nodev,noexec  0 0
/tmp    /var/tmp  none   bind  0 0
tmpfs   /dev/shm  tmpfs  defaults,nosuid,nodev,noexec  0 0

/tmp passe en mémoire, borné à 1 Gio, avec les droits et le bit sticky attendus (mode=1777). /var/tmp devient un montage lié sur /tmp : simple, mais il perd sa persistance après redémarrage ; l'alternative est un petit volume dédié. Sur Debian 13, ne déclarez pas /tmp dans fstab : sudo systemctl edit tmp.mount, puis Options=mode=1777,strictatime,nosuid,nodev,noexec,size=50%%,nr_inodes=1m dans [Mount]. Vérifiez avec sudo findmnt --verify, puis appliquez au prochain redémarrage planifié (monter un tmpfs sur /tmp à chaud masquerait les fichiers ouverts des services), et contrôlez : findmnt -no TARGET,OPTIONS /tmp /var/tmp /dev/shm.

Warning

noexec bloque ./outil, pas python3 /tmp/script.py : c'est l'interpréteur, situé dans /usr, qui est exécuté. Il casse les attaques automatisées naïves, il ne remplace pas le confinement du service.

Les modules superflus

Le noyau charge des modules à la demande : ouvrir une socket d'une famille exotique suffit à charger le module correspondant, avec ses vulnérabilités. DCCP, SCTP, RDS et TIPC en ont tous eu d'exploitables ainsi. Ubuntu livre /etc/modprobe.d/blacklist-rare-network.conf, qui coupe quelques familles anciennes (ax25, x25, rds…), mais ni DCCP ni SCTP. Créez /etc/modprobe.d/durcissement.conf :

install dccp /bin/false
install sctp /bin/false
install rds /bin/false
install tipc /bin/false
install cramfs /bin/false
install hfsplus /bin/false
install udf /bin/false
install usb-storage /bin/false
install firewire-core /bin/false

Pourquoi install … /bin/false plutôt que blacklist : selon modprobe.d(5), blacklist ignore seulement les alias internes du module ; il n'empêche ni modprobe dccp explicite, ni le chargement comme dépendance. install remplace le chargement par une commande, ici toujours en échec. Vérifiez sans rien charger ; sortie attendue :

$ modprobe -n -v dccp
install /bin/false

Un module déjà chargé le reste (sudo modprobe -r dccp ou redémarrage). Le BP-028 va plus loin avec kernel.modules_disabled = 1 (R10, renforcé) : plus aucun chargement, même par root, jusqu'au redémarrage. Efficace, mais toute mise à jour exigeant un nouveau module impose un redémarrage : un écart raisonnable pour Signalements.

AppArmor

$ sudo aa-status

La sortie ressemble à ceci (nombres et profils varient selon les paquets) :

apparmor module is loaded.
36 profiles are loaded.
28 profiles are in enforce mode.
   /usr/sbin/chronyd
   rsyslogd
   ...
8 profiles are in complain mode.
0 profiles are in prompt mode.
0 profiles are in kill mode.
0 profiles are in unconfined mode.
2 processes have profiles defined.
2 processes are in enforce mode.
   /usr/sbin/chronyd (701)
   /usr/sbin/rsyslogd (655) rsyslogd
...

Deux modes : enforce (accès non prévus refusés et journalisés) et complain (autorisés mais journalisés, pour mettre au point). Les profils sont dans /etc/apparmor.d/ ; apparmor-utils fournit aa-enforce et aa-complain, et sudo apparmor_parser -r <fichier> recharge un profil. Un refus laisse dans le journal du noyau une ligne contenant apparmor="DENIED", l'opération et le chemin : journalctl -k -g 'apparmor="DENIED"'.

Constat : Gunicorn n'a pas de profil. AppArmor ne protège que les programmes qui en ont un ; les autres sont unconfined. Écrire un profil pour une application Python est faisable (aa-genprof observe en mode complain et propose des règles, aa-logprof les complète), mais le profil s'attache à l'interpréteur de l'environnement virtuel et doit suivre chaque nouvelle dépendance : un travail de niveau renforcé, avec les développeurs. Le confinement systemd ci-dessous apporte l'essentiel du bénéfice pour bien moins d'effort.

Ubuntu 24.04 ajoute par AppArmor une restriction des espaces de noms utilisateur non privilégiés (kernel.apparmor_restrict_unprivileged_userns = 1) : un programme sans profil peut en créer mais n'y obtient aucune capacité, ce qui ferme une source fréquente d'élévations de privilèges. Sur Red Hat, getenforce et sestatus tiennent le rôle d'aa-status, les refus se lisent avec ausearch -m AVC, et passer SELinux en permissive « pour que ça marche » équivaut à désactiver AppArmor : jamais en production.

Confiner le service Signalements

C'est la mesure qui protège le plus directement les données. Mesurez d'abord :

$ systemd-analyze security signalements.service

La sortie, un tableau trié par impact suivi d'une note de 0 (confinement maximal) à 10, ressemble à ceci :

  NAME                    DESCRIPTION                                                EXPOSURE
✗ PrivateNetwork=         Service has access to the host's network                        0.5
✓ User=/DynamicUser=      Service runs under a static non-root user identity
✗ ProtectSystem=          Service has full access to the OS file hierarchy                0.2
✗ NoNewPrivileges=        Service processes may acquire new privileges                    0.2
✗ PrivateTmp=             Service has access to other software's temporary files          0.2
...
→ Overall exposure level for signalements.service: 9.2 UNSAFE 😨

Les seuils sont fixés dans le code de systemd-analyze : 10 « DANGEROUS », 9 et plus « UNSAFE », 7,5 « EXPOSED », 5 « MEDIUM », 1 « OK », en dessous « SAFE », et 0 « PERFECT ». Un service sans option de confinement dépasse presque toujours 9, même sous un compte non privilégié. Ajoutez un drop-in /etc/systemd/system/signalements.service.d/durcissement.conf plutôt que de modifier l'unité :

[Service]
NoNewPrivileges=yes
CapabilityBoundingSet=
AmbientCapabilities=
RestrictSUIDSGID=yes

ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectProc=invisible
UMask=0027

ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
LockPersonality=yes
RestrictRealtime=yes
RestrictNamespaces=yes

RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources
SystemCallErrorNumber=EPERM
  • Privilèges. NoNewPrivileges=yes pose le drapeau no_new_privs : aucun gain de privilèges par un exécutable setuid (sudo, su) ou des capacités de fichier. CapabilityBoundingSet= vide retire toutes les capacités de l'ensemble limite. RestrictSUIDSGID=yes interdit de créer des fichiers setuid, moyen classique de persistance.
  • Système de fichiers. ProtectSystem=strict monte toute l'arborescence en lecture seule pour le service, sauf /dev, /proc et /sys (protégés par les options suivantes) : Gunicorn ne peut plus modifier son code dans /opt/signalements ni rien d'autre. ProtectHome=yes rend /home, /root et /run/user inaccessibles. PrivateTmp=yes donne des /tmp et /var/tmp privés, vidés à l'arrêt. PrivateDevices=yes ne laisse que les pseudo-périphériques (/dev/null, /dev/urandom…). ProtectProc=invisible cache les processus des autres utilisateurs.
  • Noyau et système. Les options Protect… rendent /proc/sys et /sys en lecture seule et interdisent modules, lecture du tampon du noyau, modification des cgroups, de l'horloge et du nom d'hôte. RestrictNamespaces interdit de créer des espaces de noms, briques des conteneurs et de bien des évasions ; RestrictRealtime interdit l'ordonnancement temps réel ; LockPersonality fige le mode d'exécution.
  • Réseau. IPv4 et IPv6 pour servir et joindre sig-db, Unix pour le journal et la résolution de noms ; une socket AF_PACKET (capture réseau) échoue.
  • Appels système. SystemCallFilter=@system-service installe un filtre seccomp en liste blanche ; la documentation de systemd présente cet ensemble comme « the recommended starting point for allow-listing system calls for system services ». La ligne préfixée par ~ en retire les appels système privilégiés (changement d'identité, chroot, montage, horloge, modules) et ceux de @resources (changer ses limites ou sa priorité). SystemCallErrorNumber=EPERM fait échouer un appel interdit au lieu de tuer le processus. SystemCallArchitectures=native interdit l'ABI 32 bits, qui permettrait de contourner le filtre.

Retirer les appels de changement d'identité est sans risque ici : le code de Gunicorn n'appelle setuid() ou setgid() que si l'identité demandée diffère de l'identité courante, ce qui n'est pas le cas avec User=signalements. Si l'application écrit localement (pièces jointes, cache), ajoutez StateDirectory=signalements, qui crée /var/lib/signalements inscriptible pour le service seul, ou ReadWritePaths= vers un répertoire existant.

$ sudo systemd-analyze verify /etc/systemd/system/signalements.service
$ sudo systemctl daemon-reload
$ sudo systemctl restart signalements
$ curl -s http://172.16.8.11:8000/sante
$ journalctl -u signalements -p warning --since "-10min"
$ systemd-analyze security signalements.service | tail -1

La note descend typiquement vers 2 ou 3 : il reste surtout l'accès au réseau de l'hôte, inévitable pour une API. Passez les tests fonctionnels (création d'un signalement, envoi de courriel) sur sig-app-2 avant sig-app-1.

La bannière légale

Affichée avant l'authentification, elle rappelle que l'accès est réservé et journalisé. Elle ne conditionne pas l'infraction d'accès frauduleux (article 323-1 du Code pénal), mais prive un intrus de l'argument de l'ignorance et contribue à l'information des personnes sur la journalisation. Dans /etc/issue.net :

Système d'information de Lyneko. Accès réservé aux personnes autorisées.
Les connexions et les actions sont journalisées.

Ni distribution, ni version, ni nom de client : sur Debian, /etc/issue.net contient par défaut le nom et la version du système.

Mesurer à nouveau, documenter les écarts

Relancez Lynis et comparez au rapport initial. Puis tenez, dans la fiche du serveur, le registre des écarts : chaque recommandation non appliquée, sa raison, sa compensation. C'est exactement ce que demande la mairie.

RecommandationDécisionRaisonCompensation
R5 : mot de passe GRUBnon appliquéepas d'accès physique ; la console passe par l'authentification ScalewayMFA sur les comptes de la console
R10 : kernel.modules_disablednon appliquéeredémarrage à chaque mise à jour nécessitant un modulemodules superflus bloqués par modprobe.d
R13 : désactiver IPv6non appliquéeIPv6 utilisé chez Scalewaysysctl IPv6 durcis, filtrage IPv6 (leçon 8)
R45 : profils AppArmorpartiellepas de profil pour Gunicornconfinement systemd, note d'exposition suivie

Sous le capot

L'ordre des fichiers sysctl. Chaque paramètre est un fichier sous /proc/sys ; l'écrire appelle un gestionnaire du noyau qui valide la valeur. systemd-sysctl lit /etc/sysctl.d/, /run/sysctl.d/, /usr/local/lib/sysctl.d/ et /usr/lib/sysctl.d/, triés par nom de fichier tous répertoires confondus (un fichier de /etc masque son homonyme de /usr/lib). Pour une même clé, la dernière affectation gagne : d'où le préfixe 60-.

Pourquoi le motif net.ipv4.conf.*. Les paramètres réseau existent pour all, pour default (copié dans chaque interface à sa création) et pour chaque interface. La documentation du noyau précise leur combinaison : pour rp_filter, la valeur effective est le maximum de conf/all et conf/<interface> ; pour accept_redirects ou send_redirects, le réglage est actif si l'un ou l'autre l'active. Écrire seulement all ne suffit donc pas : une interface créée avant votre réglage, ou réglée à 2 par la distribution (le 50-default.conf de systemd contient net.ipv4.conf.*.rp_filter = 2), garde sa valeur, et le maximum vous laisse en mode lâche. Le motif * couvre toutes les entrées de /proc/sys/net/ipv4/conf/, all et default compris, et une règle udev de systemd relance systemd-sysctl --prefix=/net/ipv4/conf/<interface> à chaque nouvelle interface. Une clé explicite (net.ipv4.conf.ens2.rp_filter = 2) l'emporte sur le motif : c'est ainsi qu'on fait une exception.

Le lancement d'un service confiné. Entre le fork() et l'execve() de Gunicorn, le processus intermédiaire de systemd crée un espace de noms de montage privé, y remonte l'arborescence en lecture seule par des montages liés, y monte des tmpfs privés pour /tmp et un /dev réduit ; il change d'identité ; il vide l'ensemble limite de capacités (prctl(PR_CAPBSET_DROP)) ; il pose no_new_privs (prctl(PR_SET_NO_NEW_PRIVS)) ; il installe enfin le filtre seccomp, un petit programme BPF que le noyau exécute à chaque appel système du processus et de ses descendants. Rien ne se défait de l'intérieur : un filtre seccomp ne se retire pas, no_new_privs ne se lève plus. Pour le constater, sortie typique :

$ pid=$(systemctl show -p MainPID --value signalements)
$ grep -E '^(NoNewPrivs|Seccomp|CapBnd)' /proc/$pid/status
NoNewPrivs:	1
Seccomp:	2
CapBnd:	0000000000000000
$ sudo findmnt -N $pid -no OPTIONS /usr

Seccomp: 2 signifie « mode filtre », CapBnd à zéro « aucune capacité possible », et findmnt -N montre les montages vus depuis l'espace de noms du service, où /usr porte l'option ro.

noexec et AppArmor. Lors d'un execve(), le noyau refuse avec EACCES un fichier situé sur un montage noexec ; le contrôle porte sur le fichier exécuté, d'où la limite des interpréteurs. AppArmor s'insère dans les points de contrôle LSM (ouverture, exécution, socket, capacité) et compare chaque opération à la politique compilée par apparmor_parser. Sa décision s'ajoute aux permissions Unix : il ne peut que restreindre, jamais accorder.

Pièges courants

Le drop-in SSH semble ignoré. Vous écrivez PasswordAuthentication no dans 99-durcissement.conf, et les mots de passe passent encore : un fichier lu avant (50-cloud-init.conf sur certaines images) dit yes, et la première valeur gagne. Diagnostic : sudo sshd -T | grep passwordauthentication, puis grep -r PasswordAuthentication /etc/ssh/. Correction : renommer en 10-….

Une faute de frappe. sudo sshd -t répond, au format du code d'OpenSSH :

/etc/ssh/sshd_config.d/10-durcissement.conf line 3: Bad configuration option: PasswordAuthenticaton
/etc/ssh/sshd_config.d/10-durcissement.conf: terminating, 1 bad configuration options

D'où la règle : sshd -t toujours avant reload.

Plus personne ne peut se connecter. Causes classiques : AllowGroups alors que vous n'êtes pas dans le groupe, ou PermitRootLogin no sur une instance Scaleway où seul root avait une clé. Le journal le dit (journalctl -u ssh -n 20) : User alice from 203.0.113.7 not allowed because none of user's groups are listed in AllowGroups. Sans session de secours : console Scaleway ou mode rescue (leçon 5).

Changer le port SSH sur Ubuntu 24.04 n'a aucun effet. Avec l'activation par socket, un générateur systemd traduit Port et ListenAddress en configuration de ssh.socket : il faut sudo systemctl daemon-reload puis sudo systemctl restart ssh.socket. Debian 13 n'active pas SSH par socket par défaut.

Le mode strict de rp_filter coupe des flux. Une instance avec une IP publique et une interface sur le réseau privé peut recevoir par l'une et répondre par l'autre (routage asymétrique, par exemple après l'ajout d'une route vers un autre réseau privé). En mode strict, ces paquets sont jetés sans message, sauf avec log_martians : cherchez martian source dans journalctl -k. Si l'asymétrie est voulue, revenez à 2 et inscrivez l'écart.

Le service confiné ne démarre plus. Les codes de sortie de systemd.exec(5) orientent :

  • status=226/NAMESPACE : l'espace de noms de montage n'a pas pu être préparé, presque toujours parce qu'un chemin de ReadWritePaths= n'existe pas ; le journal indique Failed to set up mount namespacing et le chemin.
  • status=31/SYS : processus tué par seccomp (SIGSYS), si SystemCallErrorNumber= est absent. Avec EPERM, l'application journalise plutôt PermissionError: [Errno 1] Operation not permitted.
  • OSError: [Errno 30] Read-only file system : l'application écrit sous ProtectSystem=strict. Ajoutez le répertoire à StateDirectory= ou ReadWritePaths=, ne retirez pas ProtectSystem.
  • OSError: [Errno 97] Address family not supported by protocol : une bibliothèque ouvre une famille de sockets non autorisée (souvent AF_NETLINK, pour lister les interfaces). Ajoutez-la si le besoin est légitime.

Pour isoler l'option fautive, commentez le drop-in par moitiés, ou testez à part : sudo systemd-run --pty -p ProtectSystem=strict -p User=signalements -p WorkingDirectory=/opt/signalements /opt/signalements/venv/bin/python -c 'import app'.

Un commentaire en fin de ligne dans une unité. ProtectSystem=strict # lecture seule n'est pas compris comme un commentaire : la valeur entière est jugée invalide et l'option ignorée avec un simple avertissement. Les commentaires d'unité vont sur leur propre ligne.

Un réglage irréversible en plein incident. ptrace_scope = 3, modules_disabled = 1, unprivileged_bpf_disabled = 1 ne se défont qu'au redémarrage ; avec la première, ni strace, ni gdb, ni py-spy ne marchent, même en root.

noexec casse une installation. Certains installateurs et constructions de paquets Python décompressent puis exécutent dans /tmp, avec un simple Permission denied. Pointez TMPDIR vers un répertoire dédié le temps de l'opération plutôt que de retirer noexec ; le guide de l'ANSSI signale lui-même ce type de contrainte pour dpkg.

Sécurité

Toute la leçon traite de sécurité ; cette section dit ce que le durcissement ne fait pas.

  • Il ne corrige pas l'application. Une injection SQL dans Signalements lit la base avec les droits légitimes de l'application ; aucun filtre seccomp ne l'en empêche. Les droits du compte PostgreSQL de l'application sont une autre couche, côté sig-db.
  • Il ne remplace pas les mises à jour. Un noyau vulnérable reste exploitable ; les sysctl réduisent la surface, ils ne la suppriment pas. Les mises à jour automatiques (premiers pas, leçon 12) et les redémarrages planifiés (leçon 4) restent la première mesure (R61, niveau minimal).
  • Root défait tout. Devenu root, un attaquant retire chaque mesure. D'où la maîtrise de qui devient root (leçon 13) et l'envoi des traces hors de la machine en temps réel (leçon 15).
  • Le registre des écarts est sensible : il décrit les faiblesses assumées de la machine. Il se range avec la documentation interne, jamais dans un dépôt public.
  • La disponibilité fait partie de la sécurité. Une règle SSH qui enferme l'équipe dehors un soir d'incident, un rp_filter qui jette le trafic du répartiteur : chaque mesure se teste d'abord sur sig-app-2.

En production

  • Automatiser, sinon la dérive gagne. Les fichiers de cette leçon (sshd_config.d, sysctl.d, modprobe.d, drop-in, fstab) se déposent par un outil de gestion de configuration (cours Ansible : les fondamentaux) et se versionnent, ce qui rend la dérive de configuration visible.
  • Durcir l'image. Appliqués dans une image dorée, ces réglages font naître sig-app-3 déjà durcie, et l'on reconstruit plutôt que de corriger à la main (infrastructure immuable).
  • Mesurer en continu. Lynis lancé par un minuteur (leçon 10) avec --cronjob, rapport comparé au précédent, détecte la régression : un paquet qui réactive un service, une ligne ajoutée dans sshd_config. systemd-analyze security --threshold= échoue au-delà d'une note et peut garder un pipeline de déploiement.
  • Arbitrer par le coût d'exploitation. ptrace_scope strict complique le débogage, noexec casse des installations, modules_disabled impose des redémarrages, un profil AppArmor suit chaque évolution du code. Mieux vaut le niveau intermédiaire tenu sur tout le parc qu'un niveau élevé sur une machine que personne n'ose toucher.
  • Les mêmes idées sur Kapsule. Le securityContext de Kubernetes (allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, capabilities: drop: [ALL], seccompProfile: RuntimeDefault) transpose les options systemd de cette leçon, appliquées par le même noyau.
  • Les clients publics. Une collectivité ou un établissement de santé peut exiger un hébergement SecNumCloud ou une conformité au BP-028 à un niveau donné. Un registre des écarts à jour permet de répondre en une heure plutôt qu'en une semaine.

Exercices

1. Lire une suggestion (niveau 100). Lynis affiche Consider hardening SSH configuration [SSH-7408] avec le détail MaxAuthTries (set 6 to 3). Que signifie cette ligne, comment en savoir plus, et devez-vous la suivre ?

Solution

Le test SSH-7408 compare des réglages de sshd à des valeurs recommandées : MaxAuthTries vaut 6 (défaut) et Lynis suggère 3. sudo lynis show details SSH-7408 et /var/log/lynis.log donnent le détail. C'est une suggestion à évaluer : ici elle ne coûte rien (authentification par clé) et réduit le bruit des tentatives automatisées, on l'applique dans 10-durcissement.conf. Si on la refusait, on l'inscrirait au registre des écarts ; skip-test=SSH-7408 dans /etc/lynis/custom.prf masquerait alors aussi les autres contrôles SSH de ce test, donc avec prudence.

2. Une valeur qui ne change pas (niveau 200). Sur sig-app-2, un collègue a écrit net.ipv4.conf.all.rp_filter = 1 dans /etc/sysctl.d/60-durcissement.conf. Après sudo sysctl --system, sysctl net.ipv4.conf.all.rp_filter renvoie 1, mais un autre collègue affirme que la machine reste en mode lâche. Qui a raison, et comment corriger ?

Solution

Le second a probablement raison : la valeur effective de rp_filter est le maximum de conf/all et conf/<interface>. sysctl -a --pattern 'net.ipv4.conf.*.rp_filter' montre chaque interface ; si l'interface publique ou privée affiche 2 (fichier de la distribution, ou default hérité avant le réglage), le mode y est lâche. Correction : remplacer la ligne all par le motif net.ipv4.conf.*.rp_filter = 1, relancer sudo sysctl --system, revérifier. La règle udev de systemd réappliquera le motif aux interfaces ajoutées plus tard.

3. Confiner l'export nocturne (niveau 200). Sur sig-outils, signalements-export.service, déclenché par un minuteur, tourne sous signalements, écrit dans /srv/donnees/exports/, se connecte à sig-db et dépose le fichier sur Object Storage par HTTPS. Quelles différences apportez-vous au drop-in de Signalements, et comment validez-vous ?

Solution

Le même drop-in convient (privilèges, système de fichiers, noyau, RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6, filtre @system-service moins @privileged @resources), avec une seule exception à la lecture seule : ReadWritePaths=/srv/donnees/exports. Le répertoire doit exister, sinon le service échoue en 226/NAMESPACE. Validation : systemd-analyze verify sur l'unité, sudo systemctl daemon-reload, lancement manuel hors horaire par sudo systemctl start signalements-export.service (de type oneshot, la commande attend la fin), puis journalctl -u signalements-export -n 50 sans erreur, fichier présent dans /srv/donnees/exports/ et dans le bucket, et systemd-analyze security signalements-export.service avant et après pour le registre.

4. Arbitrer (niveau 300). L'équipe sécurité d'un client demande d'appliquer sur sig-app-1 et sig-app-2 tout le niveau élevé du BP-028, dont kernel.modules_disabled = 1, kernel.yama.ptrace_scope = 3, la désactivation d'IPv6 et un profil AppArmor en enforce pour Gunicorn. Rédigez votre réponse.

Solution

Partir du guide lui-même : le niveau élevé ne s'applique que si l'équipe a les compétences et le temps d'en assurer le maintien, sous peine de dégrader la sécurité.

  • Appliqué : tout le niveau intermédiaire et les mesures renforcées peu coûteuses (confinement systemd, modules bloqués par install … /bin/false, montages noexec).
  • Reporté avec plan : le profil AppArmor de Gunicorn, construit en mode complain sur sig-app-2 pendant plusieurs semaines de trafic réel, intégré au processus de livraison (chaque dépendance peut exiger une règle), puis basculé en enforce. Échéance et responsable notés.
  • Refusé avec compensation : modules_disabled (redémarrages non planifiés ; compensé par le blocage ciblé et les redémarrages coordonnés de la leçon 4) ; ptrace_scope = 3 (plus aucun diagnostic, même par root ; compensé par la valeur 2) ; IPv6 (utilisé par l'infrastructure ; compensé par les sysctl et le filtrage IPv6).

Chaque point va au registre avec une date de revue. L'important est de montrer que chaque décision pèse le gain de sécurité contre le coût d'exploitation et le risque pour la disponibilité.

Récapitulatif

  • Durcir, c'est appliquer minimisation, moindre privilège et défense en profondeur pour réduire ce qu'un attaquant peut faire une fois entré.
  • S'appuyer sur un référentiel : ANSSI-BP-028 v2.0 (minimal, intermédiaire, renforcé, élevé), CIS Benchmarks pour le détail par distribution. Viser l'intermédiaire, ajouter le renforcé là où il coûte peu.
  • Mesurer avant et après avec Lynis, lu comme une liste de questions.
  • Retirer l'inutile : ss -tulpn, disable --now, mask, apt purge.
  • SSH : un drop-in lu en premier, sshd -t puis sshd -T, clés seules, pas de root, AllowGroups, une session de secours ouverte.
  • Noyau : un fichier sysctl.d commenté, connaître les valeurs de la distribution, utiliser le motif * pour les paramètres par interface, se méfier des réglages irréversibles.
  • nodev,nosuid,noexec sur /tmp, /var/tmp, /dev/shm ; install <module> /bin/false pour les modules superflus.
  • AppArmor ne protège que les programmes qui ont un profil ; le confinement systemd apporte beaucoup pour peu d'effort, mesuré par systemd-analyze security.
  • Tenir un registre des écarts : décision, raison, compensation.

Pour aller plus loin

  • Le guide ANSSI-BP-028 v2.0, en particulier les chapitres 3 (principes) et 7 (services), et les recommandations de l'ANSSI sur OpenSSH et sur le cloisonnement système.
  • systemd.exec(5), section Sandboxing, et systemd-analyze syscall-filter @system-service pour la liste exacte des appels système de l'ensemble sur votre version.
  • La documentation du noyau sur /proc/sys/kernel, /proc/sys/fs et ip-sysctl, à relire avant d'écrire chaque paramètre.
  • Les cours SSH et systemd en profondeur, pour les certificats, le bastion, les credentials et le confinement avancé.
  • La leçon suivante, Tracer et contrôler l'intégrité : auditd et AIDE.
+20 XP Carte du ciel →Mon cosmonaute →

Sources