Durcir un serveur
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 («
chronydpeut 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 hauteurEn 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 :
- 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. - Une seconde session ouverte en root (
sudo -i), gardée jusqu'à la validation d'une nouvelle connexion. - La console Scaleway à portée de main (leçon 5).
- Commencer par
sig-app-2, sorti du répartiteur, puissig-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 --nowarrête et retire du démarrage. CUPS peut être réveillé par sa socket ou son unité.path, d'où les quatre unités.masklie l'unité à/dev/null: plus rien ne peut la démarrer. Utile quand le paquet doit rester.apt purgesupprime 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_configcommence parInclude /etc/ssh/sshd_config.d/*.conf, lus dans l'ordre lexical. Un fichier10-…passe avant50-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 noPermitRootLogin no: toute action d'administration part d'un compte nominatif et passe parsudo, donc est imputable (BP-028 R33). Le défaut,prohibit-password, autorise encore root par clé.PasswordAuthentication noetKbdInteractiveAuthentication no(tous deuxyespar 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 = 0kptr_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 deCAP_SYSLOG.dmesg_restrict = 1: seuls les détenteurs deCAP_SYSLOGlisent le tampon du noyau. Déjà le défaut : l'écrire documente l'intention.yama.ptrace_scope = 2:ptraceest 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 deCAP_SYS_PTRACE, donc l'administrateur viasudo. Un worker Gunicorn compromis ne peut plus lire la mémoire du maître. La valeur 3 interdit toutptraceet, selon la documentation du noyau, ne peut plus être changée avant le redémarrage.unprivileged_bpf_disabled = 1: interditbpf()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 avecO_CREATun 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épertoire | Ubuntu 24.04 | Debian 13 |
|---|---|---|
/tmp | sur le disque racine | tmpfs par défaut (tmp.mount), nosuid,nodev, 50 % de la mémoire au plus |
/var/tmp | disque racine | disque racine |
/dev/shm | tmpfs monté par systemd avec nosuid,nodev | idem |
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/falsePourquoi 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=yespose 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=yesinterdit de créer des fichiers setuid, moyen classique de persistance. - Système de fichiers.
ProtectSystem=strictmonte toute l'arborescence en lecture seule pour le service, sauf/dev,/procet/sys(protégés par les options suivantes) : Gunicorn ne peut plus modifier son code dans/opt/signalementsni rien d'autre.ProtectHome=yesrend/home,/rootet/run/userinaccessibles.PrivateTmp=yesdonne des/tmpet/var/tmpprivés, vidés à l'arrêt.PrivateDevices=yesne laisse que les pseudo-périphériques (/dev/null,/dev/urandom…).ProtectProc=invisiblecache les processus des autres utilisateurs. - Noyau et système. Les options
Protect…rendent/proc/syset/sysen lecture seule et interdisent modules, lecture du tampon du noyau, modification des cgroups, de l'horloge et du nom d'hôte.RestrictNamespacesinterdit de créer des espaces de noms, briques des conteneurs et de bien des évasions ;RestrictRealtimeinterdit l'ordonnancement temps réel ;LockPersonalityfige 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 socketAF_PACKET(capture réseau) échoue. - Appels système.
SystemCallFilter=@system-serviceinstalle 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=EPERMfait échouer un appel interdit au lieu de tuer le processus.SystemCallArchitectures=nativeinterdit 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.
| Recommandation | Décision | Raison | Compensation |
|---|---|---|---|
| R5 : mot de passe GRUB | non appliquée | pas d'accès physique ; la console passe par l'authentification Scaleway | MFA sur les comptes de la console |
R10 : kernel.modules_disabled | non appliquée | redémarrage à chaque mise à jour nécessitant un module | modules superflus bloqués par modprobe.d |
| R13 : désactiver IPv6 | non appliquée | IPv6 utilisé chez Scaleway | sysctl IPv6 durcis, filtrage IPv6 (leçon 8) |
| R45 : profils AppArmor | partielle | pas de profil pour Gunicorn | confinement 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 optionsD'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 deReadWritePaths=n'existe pas ; le journal indiqueFailed to set up mount namespacinget le chemin.status=31/SYS: processus tué par seccomp (SIGSYS), siSystemCallErrorNumber=est absent. AvecEPERM, l'application journalise plutôtPermissionError: [Errno 1] Operation not permitted.OSError: [Errno 30] Read-only file system: l'application écrit sousProtectSystem=strict. Ajoutez le répertoire àStateDirectory=ouReadWritePaths=, ne retirez pasProtectSystem.OSError: [Errno 97] Address family not supported by protocol: une bibliothèque ouvre une famille de sockets non autorisée (souventAF_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_filterqui jette le trafic du répartiteur : chaque mesure se teste d'abord sursig-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-3dé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 danssshd_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_scopestrict complique le débogage,noexeccasse des installations,modules_disabledimpose 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
securityContextde 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, montagesnoexec). - Reporté avec plan : le profil AppArmor de Gunicorn, construit en mode complain sur
sig-app-2pendant 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 -tpuissshd -T, clés seules, pas de root,AllowGroups, une session de secours ouverte. - Noyau : un fichier
sysctl.dcommenté, 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,noexecsur/tmp,/var/tmp,/dev/shm;install <module> /bin/falsepour 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, etsystemd-analyze syscall-filter @system-servicepour la liste exacte des appels système de l'ensemble sur votre version.- La documentation du noyau sur
/proc/sys/kernel,/proc/sys/fsetip-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.
Sources
- ANSSI, Recommandations de configuration d'un système GNU/Linux (ANSSI-BP-028), version 2.0, 3 octobre 2022
- Documentation du noyau Linux, /proc/sys/kernel
- Documentation du noyau Linux, /proc/sys/fs
- Documentation du noyau Linux, IP Sysctl
- Documentation du noyau Linux, Yama
- systemd, page de manuel systemd.exec(5), version 255 (source)
- systemd, code source de systemd-analyze security (v255)
- systemd, définition des ensembles d'appels système (seccomp-util.c, v255)
- systemd, fichier sysctl.d/50-default.conf (v255)
- OpenBSD, page de manuel sshd_config(5)
- man7.org, page de manuel modprobe.d(5)
- Ubuntu, paquet procps : fichiers /etc/sysctl.d livrés par défaut (noble)
- Ubuntu, paquet kmod : blacklist-rare-network.conf (noble)
- Ubuntu 24.04 LTS, notes de publication (restriction des espaces de noms utilisateur)
- Debian 13, notes de publication : problèmes à connaître (tmpfs pour /tmp, /etc/sysctl.conf)
- Debian Wiki, AppArmor/HowToUse
- AppArmor, code source de aa-status (v4.0.1)
- CISOfy, Lynis : documentation de démarrage
- Canonical, Hardening automation for CIS benchmarks now available for Ubuntu 24.04 LTS
- Red Hat, ANSSI BP-028 security recommendations updated version 2.0 (profils ComplianceAsCode)
- Scaleway, se connecter à une Instance
- Gunicorn, code source : workers/workertmp.py et util.py