Durcir un service systemd avec les options de sandbox
Apprenez à isoler un service systemd avec des directives simples : accès fichiers, privilèges, réseau et vérifications sans interruption.
Pourquoi le sandboxing systemd devient un réflexe d’administration
Un service Linux n’a pas besoin d’être compromis pour représenter un risque. Une erreur de configuration, une dépendance vulnérable ou une mauvaise manipulation de données peuvent suffire à donner au processus un accès excessif au système. Lorsqu’un démon s’exécute avec des droits étendus, peut écrire partout dans l’arborescence ou ouvrir librement des connexions réseau, les conséquences d’un incident sont mécaniquement plus importantes.
Systemd intègre des mécanismes de sandboxing qui permettent de limiter ce qu’un service peut voir, modifier ou utiliser. Ces mécanismes s’appliquent directement dans l’unité du service, généralement un fichier se terminant par .service. Ils reposent notamment sur les espaces de noms du noyau Linux, les capacités, les restrictions d’appels système et les contrôles de droits du processus.
L’objectif n’est pas de transformer chaque service en prison hermétique au prix d’une indisponibilité. En administration, un durcissement utile est avant tout un compromis explicite : le processus conserve les accès indispensables à sa mission, mais perd les droits qui ne lui servent pas.
Cette approche est particulièrement pertinente pour les services exposés, tels qu’un serveur web, une API interne, un collecteur de métriques, un agent d’automatisation ou un traitement périodique lancé par systemd. Elle est aussi intéressante pour les services maison : un script Python, PHP, Node.js ou shell lancé comme unité systemd ne devrait pas obtenir davantage de privilèges que nécessaire par défaut.
Le sandboxing ne remplace pas les mises à jour, la gestion des secrets, les sauvegardes, les permissions Linux ou le contrôle des accès SSH. Il ajoute une couche de défense. Si un compte de service est compromis, les limites définies par systemd peuvent empêcher ou compliquer l’accès à des fichiers sensibles, au réseau ou à certaines interfaces du noyau.
Le bon point de départ n’est pas « quelles directives copier ? », mais « de quels fichiers, privilèges, sockets et destinations réseau ce service a-t-il réellement besoin ? »
Commencer par observer le service et préserver sa configuration d’origine
Avant toute modification, il faut identifier l’unité réellement exécutée et son contexte. Un nom de service apparent peut correspondre à une unité fournie par une distribution, à une unité générée ou à un fichier surchargé localement. La commande systemctl cat nom-du-service.service affiche le contenu de l’unité et des éventuels fichiers de surcharge appliqués.
Il est également utile de consulter l’utilisateur du processus, ses journaux et son comportement courant. Les commandes systemctl status nom-du-service.service et journalctl -u nom-du-service.service donnent une première vision des erreurs, des chemins manipulés ou des dépendances réseau. Pour apprendre à filtrer ces informations, consultez aussi le guide consacré à journalctl sous Linux.
La pratique la plus sûre consiste à ne pas modifier directement le fichier fourni dans /usr/lib/systemd/system ou /lib/systemd/system. Une mise à jour du paquet pourrait écraser les changements. Préférez une surcharge, créée avec la commande systemctl edit nom-du-service.service. Systemd écrit alors un fichier de type override.conf dans /etc/systemd/system/nom-du-service.service.d/.
Une surcharge minimale peut d’abord contenir une seule directive. Après chaque étape, rechargez la configuration avec systemctl daemon-reload, puis redémarrez le service dans une fenêtre de maintenance adaptée avec systemctl restart nom-du-service.service. Vérifiez ensuite son état et ses journaux. Pour un service sensible, testez aussi la fonction métier : requête HTTP, traitement d’un fichier, connexion à une base de données, envoi d’un message ou exécution de la tâche attendue.
Systemd fournit un outil d’audit pratique : systemd-analyze security nom-du-service.service. Il examine les paramètres de sécurité de l’unité et signale des protections absentes. Son résultat est un indicateur, pas une politique universelle. Un score plus strict n’est pas utile si l’unité ne démarre plus ou si elle empêche une fonction nécessaire. La documentation de systemd-analyze détaille les analyses proposées.
Exécuter le processus avec l’utilisateur et les privilèges justes
Le premier levier de durcissement reste le compte Unix utilisé par le service. Lorsqu’un démon n’a pas besoin de privilèges d’administration, l’exécuter sous root augmente inutilement son périmètre d’action. Les directives User= et Group= de la section [Service] permettent de choisir un compte dédié existant.
Un compte de service dédié doit disposer des droits Unix nécessaires sur les seuls répertoires qu’il exploite. Par exemple, une application qui lit sa configuration dans /etc/monapp/, écrit ses données dans /var/lib/monapp/ et dépose des journaux dans le journal systemd n’a pas besoin de pouvoir écrire dans /etc ni dans le répertoire personnel d’un administrateur.
Pour certains services simples, DynamicUser=yes est une option intéressante. Systemd attribue alors au service un utilisateur dynamique, sans compte permanent utilisable pour une connexion classique. Cette approche convient surtout aux applications pensées pour utiliser les répertoires gérés par systemd, via StateDirectory=, CacheDirectory=, LogsDirectory= ou RuntimeDirectory=. Il faut toutefois vérifier la compatibilité avec les chemins, les propriétaires de fichiers et les services externes qui attendraient un UID fixe.
La directive NoNewPrivileges=yes empêche le service et ses descendants d’obtenir de nouveaux privilèges par les mécanismes setuid et setgid, ainsi que par les file capabilities lors d’un execve. C’est souvent une protection pertinente pour un processus qui n’a aucune raison de changer d’identité ou de devenir plus privilégié pendant son exécution.
Les capacités Linux permettent de découper une partie des privilèges traditionnellement associés à root. Avec CapabilityBoundingSet=, systemd limite les capacités qu’un processus peut acquérir. Un service ne nécessitant aucune capacité particulière peut recevoir une liste vide. En revanche, il ne faut pas retirer des capacités au hasard : un processus qui écoute sur certains ports, gère des interfaces réseau ou manipule des opérations d’administration peut en dépendre.
La documentation officielle de systemd.exec décrit précisément l’effet et les interactions de ces directives. C’est une lecture indispensable avant d’appliquer des paramètres de privilèges à un service de production.
Réduire l’accès aux fichiers et aux interfaces du système
Le système de fichiers est souvent la première surface à restreindre. Un service applicatif n’a généralement pas besoin d’écrire dans les binaires système, dans la configuration globale ou dans les répertoires personnels. Systemd propose plusieurs directives complémentaires pour imposer cette séparation.
ProtectSystem=full rend en lecture seule une partie importante de l’arborescence système, notamment /usr, /boot et /etc. Le niveau ProtectSystem=strict est plus restrictif et rend l’arborescence entière inaccessible en écriture, à l’exception des emplacements explicitement rouverts. Ce dernier est très efficace, mais il exige de connaître les chemins d’écriture réels du service.
Avec ReadWritePaths=, on autorise explicitement certains chemins à rester modifiables lorsqu’une protection du système de fichiers est active. Une application peut ainsi conserver l’écriture sur son répertoire de données, par exemple /var/lib/monapp, sans récupérer la possibilité de modifier le reste du système. À l’inverse, ReadOnlyPaths= et InaccessiblePaths= permettent de rendre certains chemins seulement lisibles ou totalement indisponibles dans l’espace de montage du service.
ProtectHome=yes masque les répertoires personnels, ainsi que les répertoires associés à root, au processus. C’est un bon défaut pour un démon qui n’a rien à faire dans /home ou /root. Les variantes read-only et tmpfs répondent à des besoins différents, mais doivent être choisies selon le comportement observé.
PrivateTmp=yes fournit au service des répertoires temporaires privés. Un fichier créé dans son /tmp ne devient pas automatiquement visible aux autres services, et réciproquement. Cette directive évite aussi certaines interactions involontaires via des fichiers temporaires partagés. Elle est généralement simple à adopter, sauf si le service doit échanger explicitement des fichiers temporaires avec un autre processus.
D’autres protections visent des interfaces sensibles du système :
- PrivateDevices=yes limite l’accès aux périphériques et expose une vue minimale de /dev ;
- ProtectKernelTunables=yes protège notamment des zones liées aux réglages du noyau ;
- ProtectKernelModules=yes empêche l’accès aux mécanismes de gestion des modules du noyau ;
- ProtectControlGroups=yes réduit l’accès à la hiérarchie des groupes de contrôle ;
- ProtectKernelLogs=yes limite l’accès aux journaux du noyau.
Ces options sont particulièrement adaptées aux services applicatifs ordinaires. Elles ne conviennent pas automatiquement à un outil de supervision bas niveau, à un gestionnaire de conteneurs ou à un service d’administration système dont la mission porte précisément sur le noyau, les périphériques ou les cgroups.
Encadrer le réseau sans casser les flux utiles
Les restrictions réseau demandent une méthode plus prudente que les protections de fichiers. Un service peut avoir besoin de joindre une API, un serveur SMTP, une base de données, un résolveur DNS, un proxy ou un socket local. Bloquer le réseau trop tôt est une cause fréquente de panne discrète : le processus démarre, mais certaines requêtes échouent plus tard.
PrivateNetwork=yes donne au service un espace de noms réseau privé. Dans ce contexte, il ne dispose pas des interfaces réseau de l’hôte telles qu’elles sont normalement visibles. C’est très utile pour un service qui ne doit avoir aucune communication réseau, mais ce n’est pas un réglage à appliquer à un serveur web, un agent de sauvegarde distant ou une application qui dépend d’une base externe.
Lorsque le service doit communiquer, RestrictAddressFamilies= permet de limiter les familles d’adresses et de sockets qu’il peut utiliser. Une application strictement TCP et UDP sur IPv4 et IPv6 n’a pas nécessairement besoin des autres familles prises en charge par Linux. Cette directive doit néanmoins être testée : certains composants utilisent des sockets Unix locaux, et une restriction trop étroite peut les bloquer.
Systemd propose aussi IPAddressAllow= et IPAddressDeny= pour définir des règles sur les adresses IP accessibles au service. Ces réglages sont utiles lorsqu’un service doit communiquer avec un nombre limité de réseaux ou d’hôtes. Ils ne remplacent pas une politique de pare-feu générale : leur objectif est de réduire les destinations accessibles par une unité donnée.
Avant de restreindre le réseau, établissez une liste concrète : ports d’écoute, socket Unix éventuel, noms de domaine résolus, adresses de bases de données, relais SMTP, proxy et endpoints de supervision. Une capture ponctuelle avec des outils adaptés, l’examen de la configuration applicative et les journaux sont plus fiables qu’une supposition.
Un service qui n’a besoin que d’un socket Unix local n’a pas le même profil qu’un processus qui expose HTTPS et contacte plusieurs services SaaS. Le sandboxing doit décrire cette différence, plutôt que lui imposer une règle identique.
Ajouter des restrictions avancées avec discernement
Une fois les droits, les fichiers et le réseau maîtrisés, d’autres directives peuvent réduire la surface d’attaque. Elles sont utiles, mais certaines sont plus susceptibles de révéler des dépendances techniques cachées.
RestrictSUIDSGID=yes interdit la création de fichiers et répertoires avec les bits setuid et setgid. Un service web, un worker applicatif ou un collecteur de données n’a en général aucune raison légitime de créer ce type de fichier.
LockPersonality=yes empêche le processus de modifier sa personnalité d’exécution. RestrictRealtime=yes bloque l’utilisation de l’ordonnancement temps réel. Ces protections sont souvent adaptées aux services classiques, mais un logiciel spécialisé dans l’audio, l’industrie ou le temps réel peut avoir des contraintes différentes.
MemoryDenyWriteExecute=yes interdit les zones mémoire à la fois inscriptibles et exécutables. Cette mesure peut gêner des environnements utilisant une compilation à la volée ou certaines techniques d’exécution dynamique. Il faut donc la valider sur la charge réelle, et pas seulement sur un démarrage à vide.
SystemCallFilter= filtre les appels système qu’un processus peut effectuer. Le potentiel de réduction est important, mais la configuration demande une bonne compréhension du programme, de ses bibliothèques et de son environnement. Une liste trop restrictive peut provoquer des erreurs difficiles à diagnostiquer. Pour un premier chantier de durcissement, les restrictions de compte, de système de fichiers et de privilèges produisent souvent un meilleur rapport entre sécurité et risque opérationnel.
Construire une surcharge progressive et lisible
Voici un exemple volontairement générique de surcharge pour un service applicatif non privilégié. Il ne doit pas être copié sans validation : les chemins, les accès réseau et les privilèges nécessaires dépendent toujours de l’application.
[Service]
NoNewPrivileges=yes
PrivateTmp=yes
ProtectHome=yes
ProtectSystem=strict
ReadWritePaths=/var/lib/monapp /var/cache/monapp
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictSUIDSGID=yes
LockPersonality=yes
Dans cet exemple, ProtectSystem=strict constitue le changement le plus structurant. Les répertoires listés dans ReadWritePaths= deviennent des exceptions explicites. Si le service doit écrire ailleurs, les journaux ou les traces de l’application aideront à identifier le chemin manquant. Il est préférable d’ajouter une exception ciblée plutôt que d’abandonner immédiatement toute restriction.
Pour un service fourni par un paquet, commencez avec une surcharge contenant seulement PrivateTmp=yes et NoNewPrivileges=yes, si le comportement du logiciel le permet. Ajoutez ensuite les protections de fichiers, puis les restrictions plus spécifiques. Ce découpage facilite le diagnostic : lorsqu’une régression apparaît, la dernière directive ajoutée est un suspect évident.
Les unités peuvent déjà inclure des protections définies par l’éditeur du logiciel ou par la distribution. Une surcharge ne doit donc pas supposer un état vierge. Consultez toujours la sortie de systemctl cat avant de compléter ou de modifier les paramètres existants.
Tester, surveiller et annuler sans interruption inutile
Un durcissement sérieux se valide comme une modification de production. Sur un environnement de préproduction, redémarrez le service, exercez ses parcours fonctionnels et observez les logs pendant une durée représentative. Les tâches différées, les rotations de fichiers, les sauvegardes ou les renouvellements de certificats peuvent échouer bien après le démarrage initial.
Après chaque modification, vérifiez au minimum que l’unité est active avec systemctl is-active nom-du-service.service, puis examinez les messages récents avec journalctl -u nom-du-service.service. La présence d’erreurs de permission, de fichiers introuvables, d’échecs de socket ou de refus réseau indique souvent une directive à ajuster.
La commande systemd-analyze security permet de constater l’évolution des protections déclarées. Utilisez-la pour repérer des pistes, non comme un objectif chiffré autonome. Un démon volontairement connecté au réseau ou chargé de gérer des ressources système restera moins isolé qu’un worker sans privilèges, et c’est normal.
En cas de problème, la surcharge est réversible. La commande systemctl revert nom-du-service.service supprime les surcharges locales créées dans /etc et rétablit les fichiers fournis par le paquet. Avant une intervention, conservez néanmoins le contenu de votre surcharge dans votre gestion de configuration, un dépôt Git ou une documentation d’exploitation.
Pour les modifications sur un service critique, prévoyez une procédure de retour arrière, un créneau adapté et des vérifications applicatives précises. Systemd aide à isoler les services, mais il ne peut pas deviner les dépendances métier de votre infrastructure.
Conclusion : faire du moindre privilège une propriété du service
Le sandboxing systemd permet de renforcer un service Linux avec des mécanismes déjà présents sur la machine : exécution sous un compte adapté, système de fichiers en lecture seule, répertoires d’écriture explicitement autorisés, accès aux périphériques réduit, privilèges bornés et réseau encadré.
La démarche la plus efficace reste progressive. Observez d’abord le fonctionnement réel, créez une surcharge lisible, ajoutez une protection à la fois et contrôlez le résultat avec les journaux, les tests métier et systemd-analyze security. Commencez par un service non critique : cette première unité durcie vous donnera une méthode réutilisable pour le reste du parc.