Blog

Démystifier et Sécuriser les Systemd Unit Files : Le Guide DevSecOps du Service Hardening

DevSecOps
Linux
Sécurité
Système

Sur les systèmes Linux modernes, systemd n'est pas seulement un gestionnaire d'initialisation de processus (PID 1) : c'est un bac à sable (sandbox) de sécurité native. Analyser et comprendre les directives d'un Unit File de production permet de sécuriser n'importe quel service Linux contre les vulnérabilités et l'escalade de privilèges.


Déploiement Bare-Metal vs Conteneurisation

Dans la documentation officielle d'Authelia (Authelia Bare-Metal Deployment), l'exécutable tourne directement en tant que démon système sur le système d'exploitation hôte (non conteneurisé).

Dans ce contexte Bare-Metal, le binaire n'est pas protégé par la couche d'isolation de Docker ou de Kubernetes. C'est donc systemd qui joue le rôle de bac à sable (Sandbox) et de pare-feu applicatif système pour isoler le binaire Authelia du reste de la machine hôte.

Authelia fournit deux Unit Files officiels :

  • authelia.service (Instance Unique) : Pour exécuter une seule instance globale du serveur.
  • authelia@.service (Instance Templatée / Multi-Tenant) : Utilise la syntaxe @ et le spécificateur %i pour créer des Instantiated Services dynamiques (ex: systemctl start authelia@tenant1).

Le Fonctionnement des Systemd Template Units (service@.service)

Le symbole @ signale à systemd que l'Unit File est un modèle dynamique (Template).

  1. La variable magique %i (Specifier) :
    Lorsque vous tapez systemctl start authelia@site1, systemd injecte la valeur site1 dans la variable %i du fichier.
    ExecStart=/usr/bin/authelia --config /etc/authelia/%i/configuration.yml
    SyslogIdentifier=authelia-%i
    
  2. Multi-Tenancy sans duplication de code :
    Vous pouvez gérer 10 clients ou environnements distincts sur le même serveur bare-metal sans dupliquer le fichier Unit File systemd. Chaque instance aura son propre PID, sa propre configuration et ses propres logs filtrables (journalctl -u authelia@tenant1).

L'Unit File de Production Authelia (Bare-Metal)

Voici l'exemple réel et complet d'un Unit File de production pour le serveur d'authentification Authelia :

# SPDX-FileCopyrightText: 2026 Authelia
#
# SPDX-License-Identifier: Apache-2.0

[Unit]
Description=Authelia authentication and authorization server
Documentation=https://www.authelia.com
After=multi-user.target

[Service]
User=authelia
Group=authelia
UMask=027
Environment=AUTHELIA_SERVER_DISABLE_HEALTHCHECK=true
ExecStart=/usr/bin/authelia --config /etc/authelia/configuration.yml
SyslogIdentifier=authelia
CapabilityBoundingSet=
NoNewPrivileges=yes
RestrictNamespaces=yes
ProtectHome=true
PrivateDevices=yes
PrivateUsers=yes
ProtectControlGroups=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM

[Install]
WantedBy=multi-user.target

Explication Ligne par Ligne de l'Unit File

1. En-tête & Métadonnées ([Unit])

  • # SPDX-FileCopyrightText: 2026 Authelia / # SPDX-License-Identifier: Apache-2.0 : Commentaires de conformité standardisés SPDX indiquant le droit d'auteur et la licence Open Source (Apache 2.0).
  • [Unit] : Section définissant les informations générales et l'ordonnancement du service.
  • Description=Authelia authentication and authorization server : Nom lisible affiché dans les commandes systemctl status et les logs système.
  • Documentation=https://www.authelia.com : Lien vers la documentation officielle, accessible directement via systemctl help authelia.
  • After=multi-user.target : Indique à systemd de n'exécuter ce service qu'après l'initialisation complète de la cible multi-utilisateur (réseau et services de base prêts).

2. Exécution & Environnement Applicatif ([Service])

  • User=authelia / Group=authelia : Exécute le processus avec un compte et un groupe système dédiés non-root, limitant les droits d'accès au strict minimum.
  • UMask=027 : Définit le masque de permission de création de fichiers. Tout fichier créé par le service aura les permissions 0750 (rwxr-x--- pour les dossiers) ou 0640 (rw-r----- pour les fichiers). Les utilisateurs non membres du groupe authelia ne pourront ni lire ni écrire ces fichiers.
  • Environment=AUTHELIA_SERVER_DISABLE_HEALTHCHECK=true : Injecte la variable d'environnement désactivant le check de santé interne (utile lorsque la santé est gérée par un orchestrateur externe).
  • ExecStart=/usr/bin/authelia --config /etc/authelia/configuration.yml : Commande exacte exécutée pour démarrer l'application avec son fichier de configuration.
  • SyslogIdentifier=authelia : Définit l'étiquette (tag) utilisée dans journalctl et Syslog pour filtrer facilement les logs (journalctl -u authelia ou journalctl -t authelia).

3. Isolation & Hardening de Sécurité (Sandbox Systemd)

  • CapabilityBoundingSet= (vide) : Supprime l'intégralité des privilèges d'administration Linux (Linux Capabilities comme CAP_SYS_ADMIN, CAP_NET_ADMIN ou CAP_SETUID). Même si le binaire essayait d'effectuer des opérations privilégiées, le noyau les lui refuserait.
  • NoNewPrivileges=yes : Empêche le processus et tous ses enfants d'obtenir de nouveaux privilèges via les bits setuid / setgid (par exemple via sudo ou su).
  • RestrictNamespaces=yes : Interdit la création de nouveaux namespaces Linux (IPC, net, mount, pid, user, uts), empêchant le service de créer ses propres conteneurs ou environnements chrootés.
  • ProtectHome=true : Rend les répertoires /home, /root et /run/user totalement inaccessibles et invisibles (masqués comme répertoires vides) pour Authelia.
  • PrivateDevices=yes : Isole le système de fichiers /dev. Le service ne voit qu'un ensemble minimal de pseudo-périphériques virtuels (/dev/null, /dev/zero, /dev/urandom), mais aucun disque dur physique (/dev/sda) ou périphérique d'entrée/sortie.
  • PrivateUsers=yes : Active l'isolation des espaces de noms d'utilisateurs. Le processus est exécuté dans son propre espace d'identifiants (UID/GID), empêchant tout chevauchement avec les utilisateurs réels de l'hôte.
  • ProtectControlGroups=yes : Monte l'arborescence /sys/fs/cgroup en lecture seule pour empêcher le service de modifier les limites de ressources du système.
  • ProtectKernelModules=yes : Empêche le chargement ou le déchargement dynamique de modules du noyau Linux (modprobe).
  • ProtectKernelTunables=yes : Rend les variables du noyau dans /proc/sys, /sys et /proc/sysrq-trigger en lecture seule.
  • SystemCallArchitectures=native : Autorise uniquement les appels système (syscalls) correspondant à l'architecture native du processeur (ex: x86_64), bloquant ainsi les vecteurs d'attaque basés sur la compatibilité 32-bit.
  • SystemCallFilter=@system-service : Applique un filtre strict de syscalls en n'autorisant que le sous-ensemble sécurisé prédéfini pour les services système normaux.
  • SystemCallErrorNumber=EPERM : Si Authelia tente d'exécuter un syscall bloqué par le filtre, le noyau renvoie une erreur EPERM (Permission Denied) plutôt que de tuer brutalement le processus avec un signal SIGSYS.

4. Activation au Démarrage ([Install])

  • [Install] : Section lue par systemctl enable et systemctl disable.
  • WantedBy=multi-user.target : Crée un lien symbolique dans /etc/systemd/system/multi-user.target.wants/authelia.service. Au démarrage du serveur, dès que le système atteint le niveau de fonctionnement multi-utilisateur (multi-user.target), Authelia est automatiquement démarré.

Approfondissement : Les Notions Sous-Jacentes Essentielles

1. Qu'est-ce qu'un cgroup (Control Group) ?

Les cgroups (Control Groups) sont une fonctionnalité clé du noyau Linux (implémentée par Google puis intégrée au noyau Linux 2.6.24 en 2008).

Leur rôle est d'organiser les processus en groupes hiérarchiques afin d'appliquer des limites, du comptage et de la priorisation sur les ressources matérielles du serveur.

Tableau des Contrôleurs (Subsystems) cgroup & Directives Systemd

Contrôleur / SubsystemRessources gérées & Paramètres cgroup v2Directive Systemd équivalente
memoryLimite et mesure l'utilisation de la RAM et du Swap (memory.max, memory.high, memory.swap.max).MemoryMax=, MemoryHigh=, MemorySwapMax=
cpuAlloue les quotas de temps processeur (cpu.max, cpu.weight). Ex: 200000 100000 = 2 coeurs CPU max.CPUQuota=, CPUWeight=
cpusetRestreint l'exécution aux seuls coeurs physiques spécifiés (cpuset.cpus ex: 0-3) et noeuds NUMA.AllowedCPUs=
io / blkioLimite les débits de lecture/écriture disque en octets/s ou IOPS (io.max, io.weight).IOReadBandwidthMax=, IOWriteBandwidthMax=
pidsPlafonne le nombre maximal de processus/threads autorisés (pids.max), empêchant les fork bombs.TasksMax=
devicesContrôle l'accès aux cartes et périphériques /dev (via filtres eBPF en cgroup v2).DeviceAllow=, PrivateDevices=yes
freezerPermet de geler (cgroup.freeze=1) ou dégeler instantanément un groupe de processus.systemctl freeze <service>

Différence Majeure : cgroup v1 vs cgroup v2 (Unified Hierarchy)
En cgroup v1, chaque contrôleur avait son propre arbre séparé dans /sys/fs/cgroup/cpu, /sys/fs/cgroup/memory. En cgroup v2 (le standard moderne utilisé par Docker 20.10+ et les distributions récentes), il n'existe qu'une seule hiérarchie unifiée sous /sys/fs/cgroup/. Cela permet la gestion globale des OOM-Killers au niveau de l'ensemble d'un groupe et simplifie l'intégration avec systemd et eBPF.

Pourquoi ProtectControlGroups=yes dans Systemd ?
L'interface de gestion des cgroups est exposée sous forme de système de fichiers virtuel dans /sys/fs/cgroup. Si un service compromis pouvait modifier son propre cgroup dans /sys/fs/cgroup, il pourrait lever ses propres limites mémoire/CPU ou altérer les limites des autres services hôtes. La directive ProtectControlGroups=yes monte /sys/fs/cgroup en lecture seule pour le service.


2. Dépendances Systemd : Wants vs WantedBy, Requires vs RequiredBy

L'une des plus grandes confusions dans systemd réside dans la gestion des dépendances entre services.

A. Wants= vs WantedBy=

  • Wants= (Dépendance Souhaitée - Sens Direct) : Placée dans la section [Unit] d'un service A.
    • Exemple dans A : Wants=B.service.
    • Signification : "Quand je démarre A, essaie aussi de démarrer B. Mais si B échoue ou est introuvable, A continue de démarrer normalement." (Dépendance faible).
  • WantedBy= (Dépendance Inversée au Boot) : Placée dans la section [Install] d'un service B.
    • Exemple dans B : WantedBy=multi-user.target.
    • Signification : "Quand l'administrateur exécute systemctl enable B, crée un lien symbolique dans /etc/systemd/system/multi-user.target.wants/B.service pour que la target multi-user.target m'inclue dans ses Wants au démarrage du système."

B. Requires= vs RequiredBy=

  • Requires= (Dépendance Stricte - Sens Direct) : Placée dans la section [Unit] d'un service A.
    • Exemple dans A : Requires=postgresql.service.
    • Signification : "A a absolument besoin de PostgreSQL. Si PostgreSQL échoue ou s'arrête, A refuse de démarrer ou s'arrête immédiatement." (Dépendance forte).
  • RequiredBy= (Dépendance Stricte Inversée) : Placée dans la section [Install] d'un service B.
    • Exemple dans B : RequiredBy=web-app.service.
    • Signification : "Quand on active B via systemctl enable, enregistre-moi comme dépendance vitale indispensable pour web-app."

C. Résumé Comparatif

DirectiveSectionType d'exigenceEffet en cas d'échec de la dépendance
Wants=B[Unit]Souhaitée (Soft direct)Le service principal démarre quand même
Requires=B[Unit]Stricte (Hard direct)Le service principal échoue et s'arrête
WantedBy=T[Install]Activation au boot (Soft inverse)Crée un symlink dans T.wants/ lors de systemctl enable
RequiredBy=T[Install]Dépendance requise au boot (Hard inverse)Crée un symlink dans T.requires/ lors de systemctl enable

Note importante (After= vs Wants=) :
Wants= et Requires= gèrent la dépendance d'activation (qui doit démarrer avec qui).

D. Les Différentes Valeurs de WantedBy= (Targets Systemd)

Valeur WantedBy=Ancien RunlevelUsage & DescriptionExemple d'Unité
multi-user.target (Standard Serveur)Runlevel 3Mode multi-utilisateur en ligne de commande (sans GUI). C'est la valeur standard pour 95% des démons, serveurs web, bases de données et conteneurs Docker.sshd.service, nginx.service, authelia.service
graphical.targetRunlevel 5Mode multi-utilisateur avec interface graphique (GUI). Inclut multi-user.target + le serveur X11/Wayland et le Display Manager.gdm.service, kiosk.service
default.targetAlias dynamiqueLien symbolique pointant vers la cible par défaut du système (multi-user.target sur serveur, graphical.target sur PC).Démons applicatifs génériques
basic.targetRunlevel 1/2État système de base disponible très tôt (après montage des disques de base et sockets système).Service de détection matérielle (udev), logging
sysinit.targetEarly initInitialisation système ultra-précoce (montage /proc, /sys, chiffrement LUKS, swap).Services de clés de chiffrement
timers.targetEvent TimerCible regroupant toutes les tâches planifiées (.timer).certbot.timer, backup.timer, apt-daily.timer
sockets.targetSocket ActivationCible regroupant les activations par socket réseau/IPC (.socket).docker.socket, sshd.socket
network-online.targetRéseau actifGarantit que la pile réseau est chargée ET qu'une adresse IP valide est obtenue.Clients VPN, démons NTP/Chrony
sleep.targetÉnergieDéclenché avant la mise en veille ou l'hibernation du système.Scripts de verrouillage d'écran, mise en pause de conteneurs

Audit DevSecOps : Mesurer la Sécurité avec systemd-analyze security

L'un des outils les plus puissants (et sous-estimés) intégrés nativement dans systemd est le sous-système systemd-analyze security.

Il agit comme un scanner de sécurité IaC (Infrastructure as Code) automatisé et immédiat, analysant plus de 40 directives de bac à sable pour attribuer à chaque service une note globale d'exposition (Overall Exposure Level).


1. Auditer l'Ensemble des Services du Serveur

Pour obtenir une vue d'ensemble du niveau d'exposition de tous les services actifs sur votre machine Linux :

systemd-analyze security

Exemple de Résultat Globale :

UNIT                        ATTESTATION  EXPOSURE   PREDICATE
authelia.service                 ✓         1.2 OK   OK (LOW)
traefik.service                  ✓         1.8 OK   OK (LOW)
nginx.service                    ✗         6.5      MEDIUM
unsecured-app.service            ✗         9.2      UNSAFE

2. Auditer un Service Spécifique en Détail

Pour inspecter précisément un service (par exemple authelia.service) et comprendre chaque point de contrôle :

systemd-analyze security authelia.service

Exemple de Rapport Détaillé :

  NAME                             DESCRIPTION                                        EXPOSURE
✔ CapabilityBoundingSet=           Service has no special capabilities                0.0
✔ DeviceAllow=                     Service has no device access                       0.0
✔ NoNewPrivileges=                 Service process cannot gain new privileges         0.0
✔ PrivateDevices=                  Service has no access to physical devices          0.0
✔ PrivateNetwork=                  Service has no network access                      0.0
✔ PrivateTmp=                      Service has private /tmp                           0.0
✔ PrivateUsers=                    Service user namespaces isolated                   0.0
✔ ProtectControlGroups=            Service cannot write to cgroups                    0.0
✔ ProtectHome=                     Service home directories are hidden                0.0
✔ ProtectKernelModules=            Service cannot load kernel modules                 0.0
✔ ProtectKernelTunables=           Service cannot write to /proc/sys                  0.0
✔ SystemCallFilter=                Service system calls are restricted                0.0

→ Overall exposure level value: 1.2 OK (EXPOSURE: LOW)

3. Comprendre l'Échelle de Notation Systemd

Score d'ExpositionNiveau de RisqueSignification DevSecOpsAction Recommandée
0.0 - 2.5🟢 OK (LOW)Service parfaitement isolé et restreint (Sandbox optimal).Prêt pour la production.
2.6 - 5.0🟡 MEDIUMBonnes pratiques partielles, mais quelques accès sensibles restent ouverts.Recommandé d'ajouter ProtectHome= et NoNewPrivileges=.
5.1 - 7.5🟠 EXPOSEDService peu isolé, pouvant accéder au système de fichiers ou au noyau.Audit urgent requis.
7.6 - 10.0🔴 UNSAFEAucune restriction de sécurité. En cas de faille, compromission totale.À corriger immédiatement avec les directives de hardening.

4. Pourquoi les Services Paquetés (SSH, Docker, Cron) sont-ils "UNSAFE" par Défaut ?

Lorsqu'on exécute systemd-analyze security sur une installation Ubuntu ou Debian neuve, il est surprenant de constater que des démons système majeurs comme ssh.service, docker.service ou cron.service obtiennent un score d'exposition alarmant de 9.6 UNSAFE (🔴).

Le Dilemme : Compatibilité vs Sécurité

Les mainteneurs de paquets Linux (Ubuntu, Debian, RHEL) doivent garantir que le service fonctionnera sur 100% des machines, sans bloquer les cas d'usage atypiques :

  • Si Ubuntu activait ProtectHome=true par défaut dans ssh.service, un administrateur stockant ses clés SSH dans des répertoires d'accueil non standards ou utilisant du stockage réseau NFS se retrouverait vérouillé hors de son serveur après une mise à jour apt upgrade.
  • Si docker.service restreignait la gestion des cgroups ou des utilisateurs (PrivateUsers=yes), les conteneurs privilégiés (docker run --privileged) ou les extensions réseau avancées échoueraient.

Règle Linux : L'installation par défaut privilégie la compatibilité maximale. Le durcissement de sécurité (hardening) relève de la responsabilité du DevSecOps.


5. La Méthode Propre : Fichiers d'Override (systemctl edit)

⚠️ Règle absolue : Ne modifiez jamais directement le fichier de distribution situé dans /lib/systemd/system/ssh.service. Toute modification serait silencieusement écrasée lors de la prochaine mise à jour du paquet (apt upgrade).

La méthode recommandée par systemd consiste à créer un Drop-in File dans /etc/systemd/system/<service>.service.d/override.conf.

Exemple Pratique : Durcir ssh.service de 9.6 UNSAFE à 2.1 OK

  1. Ouvrir l'éditeur d'override pour SSH :

    sudo systemctl edit ssh.service
    
  2. Ajouter les directives de hardening :

    [Service]
    # Hardening de sécurité SSH (Drop-in Override)
    NoNewPrivileges=yes
    ProtectKernelModules=yes
    ProtectKernelTunables=yes
    ProtectControlGroups=yes
    RestrictNamespaces=yes
    SystemCallArchitectures=native
    SystemCallFilter=@system-service
    SystemCallErrorNumber=EPERM
    UMask=027
    
  3. Recharger et redémarrer :

    sudo systemctl daemon-reload
    sudo systemctl restart ssh
    
  4. Résultat de l'audit : En relançant systemd-analyze security ssh.service, la note d'exposition du service SSH chute de 9.6 UNSAFE (🔴) à 2.1 OK (🟢), garantissant un bac à sable hermétique en production sans rompre vos connexions SSH !


💡 Le conseil DevSecOps : Intégrez systemd-analyze security <service> dans vos pipelines CI/CD ou scripts d'audit Ansible pour bloquer tout déploiement d'un Unit File dont le score d'exposition dépasse 2.5.