Isoler les écritures d’un service Linux et vérifier les refus

Isoler les écritures d’un service Linux et vérifier les refus

Isoler les écritures d’un service Linux et vérifier les refus

Lorsque plusieurs services applicatifs partagent un même serveur dédié, un programme n’a généralement besoin d’écrire que dans son répertoire d’état. Pourtant, des droits trop larges liés à la propriété des répertoires peuvent lui permettre de modifier les fichiers déployés. Créer un compte propre au service ne constitue qu’une première limite. Pour réduire le périmètre des écritures accidentelles, il faut aussi présenter au service un système de fichiers en lecture seule, en ne conservant que les chemins d’écriture nécessaires. La validation ne doit pas s’arrêter à « le service démarre » : elle doit également démontrer que les emplacements interdits sont réellement inaccessibles en écriture.

1. Recenser les écritures avant de configurer le bac à sable

Cet exemple utilise Ubuntu Server 24.04 LTS et systemd pour gérer des services système. Les opérations nécessitent des droits d’administration et Python 3. Tous les chemins correspondent à des répertoires de test indépendants ; aucune unité applicative existante n’est modifiée. Les identifiants de connexion des serveurs dédiés AmdMetal sont consultables dans la console après le paiement. Sous Linux, la connexion s’effectue par SSH.

Commencez par distinguer le code, l’état persistant, les fichiers d’exécution et les fichiers temporaires. Le code doit être mis à jour par le compte de déploiement ; le compte du service ne doit pouvoir que le lire. Le répertoire d’état contient les données à conserver après un redémarrage. Les fichiers d’exécution, tels que les sockets et les fichiers d’identifiant de processus, ainsi que les fichiers temporaires, doivent être recensés séparément. Si les journaux sont envoyés sur la sortie standard, le système de journalisation peut les recueillir : il n’est pas nécessaire d’ouvrir tout un répertoire de journaux en écriture.

Le réglage central est ProtectSystem=strict : dans l’espace de noms de montage du service, il rend la hiérarchie du système de fichiers accessible en lecture seule, avec certaines exceptions, notamment pour des systèmes de fichiers d’API. ReadWritePaths maintient ensuite la possibilité d’écrire dans les répertoires désignés. Ce mécanisme ne modifie pas les montages globaux de l’hôte et ne constitue pas une solution complète d’isolation des processus.

La lecture seule ne garantit pas la confidentialité. Les fichiers lisibles ne sont pas automatiquement masqués, et l’accès au réseau n’est pas automatiquement restreint. De même, une liste de chemins autorisés en écriture ne remplace pas les permissions des répertoires.

2. Établir une référence permettant d’identifier la cause des refus

Le test utilise deux répertoires : l’un doit rester accessible en écriture ; l’autre doit autoriser l’écriture au niveau des permissions ordinaires, mais être bloqué par le bac à sable. Cette distinction évite de conclure à tort que l’isolation fonctionne alors que le compte n’avait déjà pas les droits nécessaires. Vérifiez d’abord que le nom de compte et les chemins ci-dessous ne sont utilisés par aucun service existant. Il s’agit d’éléments créés pour le test, et non du compte de connexion par défaut du serveur.

sudo useradd --system --no-create-home \
  --shell /usr/sbin/nologin sandbox-check

sudo install -d -o root -g root -m 0755 \
  /srv/sandbox-check

sudo install -d -o sandbox-check -g sandbox-check -m 0750 \
  /srv/sandbox-check/blocked \
  /var/lib/sandbox-check

sudo runuser -u sandbox-check -- /usr/bin/python3 -c \
'import tempfile, os; f, p = tempfile.mkstemp(dir="/srv/sandbox-check/blocked"); os.close(f); os.unlink(p)'

La dernière commande doit se terminer avec succès : elle crée un fichier temporaire au nom aléatoire dans le répertoire destiné à être interdit, puis le supprime immédiatement. En cas d’échec, vérifiez d’abord les droits de traversée des répertoires, les ACL et les propriétés du montage sous-jacent. Sans cette vérification, les échecs suivants ne peuvent pas être attribués au bac à sable.

Le script déployé doit appartenir à l’administrateur afin que le compte du service ne puisse pas modifier la logique de contrôle. Les répertoires de test sont volontairement attribués à un propriétaire disposant du droit d’écriture, uniquement pour démontrer l’origine de la restriction. En production, le compte du service doit toujours être privé du droit d’écriture dans les répertoires de code : cette exception de test ne doit pas être reproduite.

3. Configurer les chemins autorisés et une sonde indépendante

Enregistrez le script suivant dans /srv/sandbox-check/probe.py, attribuez-le à l’administrateur et définissez ses permissions sur 0644. L’écriture dans le chemin autorisé doit réussir. Celle dans le chemin interdit doit renvoyer une erreur de système de fichiers en lecture seule ; toute autre erreur constitue un échec de validation.

import errno
import os
import tempfile

def create_and_remove(directory):
    fd, path = tempfile.mkstemp(prefix="probe-", dir=directory)
    try:
        with os.fdopen(fd, "w") as stream:
            stream.write("sandbox-check\n")
            stream.flush()
            os.fsync(stream.fileno())
    finally:
        os.unlink(path)

create_and_remove("/var/lib/sandbox-check")

try:
    create_and_remove("/srv/sandbox-check/blocked")
except OSError as exc:
    if exc.errno != errno.EROFS:
        raise
else:
    raise RuntimeError("Write boundary was not enforced")

print("Allowed write passed; blocked write returned EROFS")

Les noms de fichiers aléatoires évitent d’écraser des données existantes. L’appel à fsync sert à vérifier la chaîne d’appels d’écriture ; il ne constitue ni un test de performances ni un test de persistance en cas de coupure de courant. La sortie de la sonde indique le résultat de la validation et ne contient ni identifiants ni données applicatives.

Créez /etc/systemd/system/sandbox-check.service :

[Unit]
Description=Filesystem sandbox verification

[Service]
Type=oneshot
User=sandbox-check
Group=sandbox-check
ExecStart=/usr/bin/python3 /srv/sandbox-check/probe.py
ProtectSystem=strict
ReadWritePaths=/var/lib/sandbox-check
PrivateTmp=true
NoNewPrivileges=true

PrivateTmp=true fournit au service une vue isolée des répertoires temporaires. « Un seul répertoire persistant autorisé en écriture » ne signifie donc pas que le processus ne dispose que d’un seul emplacement accessible en écriture. NoNewPrivileges=true empêche l’acquisition de nouveaux privilèges par l’exécution de programmes, mais ne révoque pas les droits d’accès aux fichiers déjà accordés.

4. Valider à la fois les écritures autorisées et les refus

Vérifiez d’abord l’unité, puis rechargez les définitions et lancez le service :

sudo systemd-analyze verify /etc/systemd/system/sandbox-check.service
sudo systemctl daemon-reload
sudo systemctl start sandbox-check.service

sudo systemctl show sandbox-check.service \
  -p Result -p ExecMainStatus -p ProtectSystem -p ReadWritePaths

sudo journalctl -u sandbox-check.service -b --no-pager -n 30

Les résultats attendus sont Result=success et ExecMainStatus=0, avec le message de réussite de la sonde dans les journaux. Il est normal que ce service à exécution unique ne soit plus indiqué comme étant en cours d’exécution une fois terminé : aucune exécution continue ni aucun maintien à l’état actif n’est configuré ici.

Consulter les valeurs de configuration ne suffit pas à prouver que la limite est effective. Les preuves doivent réunir trois résultats : une écriture réussie avec le même compte hors du bac à sable, une écriture réussie dans le répertoire autorisé à l’intérieur du bac à sable, et une écriture refusée avec EROFS dans le répertoire interdit. Une erreur EACCES prouve seulement que les permissions ordinaires ou une autre politique de sécurité refusent l’accès ; elle ne valide pas ce contrôle.

Tester aussi les chemins réellement utilisés en production

Une fois la sonde validée, préparez une configuration de durcissement distincte pour le service réel. Exécutez un test applicatif couvrant le téléversement, la génération du cache, l’enregistrement de l’état et le nettoyage des fichiers temporaires. Si l’application met à jour ses données en créant un fichier temporaire puis en le renommant, le répertoire de destination doit lui aussi être accessible en écriture. Une exception limitée à un seul fichier ne suffit souvent pas.

Vérifiez également la destination réelle des liens symboliques. Un lien qui semble se trouver dans le répertoire d’état peut finalement pointer vers un autre chemin en lecture seule. Il ne faut pas masquer ce problème en élargissant les droits sur tout le répertoire parent.

5. Diagnostiquer, revenir en arrière et consigner la validation

Si le service échoue avant le lancement du programme, consultez d’abord les journaux pour déterminer si la création de l’espace de noms de montage a échoué, si un chemin est absent ou si la configuration comporte une faute de frappe. Si l’échec survient après le lancement, examinez les emplacements concernés par les opérations sur les fichiers : le programme écrit-il discrètement du cache dans le répertoire de code ? Dépend-il de fichiers temporaires partagés ? A-t-il besoin d’un répertoire d’exécution supplémentaire ?

Si le service de production nécessite un répertoire d’exécution, utilisez RuntimeDirectory pour en confier le cycle de vie à systemd, puis testez les accès réels. N’ajoutez pas intégralement /var ou /srv à la liste des chemins autorisés : accordez les accès un par un, selon les besoins d’écriture confirmés.

Après une modification, daemon-reload recharge uniquement les définitions des unités ; il n’applique pas le nouveau bac à sable aux processus déjà en cours d’exécution. Le service de production doit faire l’objet d’un redémarrage contrôlé pour créer un nouvel environnement de processus, puis les tests d’autorisation et de refus doivent être répétés.

Pour retirer le dispositif de test, arrêtez l’unité, supprimez le fichier d’unité créé à cette occasion, puis rechargez les définitions. Nettoyez séparément les répertoires de données et le compte de test, après avoir vérifié qu’aucun processus ne les utilise. N’exécutez pas de commande générique de suppression récursive visant des répertoires applicatifs. Pour un service de production, restaurez la configuration conservée avant les modifications, puis redémarrez et vérifiez le fonctionnement.

Consignez enfin la configuration effective de l’unité, le compte du service, la liste des chemins autorisés en écriture, le code de sortie de la sonde et les résultats des tests applicatifs réels. L’objectif n’est pas d’obtenir un meilleur score de sécurité, mais de vérifier que chaque écriture nécessaire réussit et que chaque écriture censée être interdite produit un refus explicable et reproductible.

Questions fréquentes

ReadWritePaths accorde-t-il des droits d’écriture supplémentaires ?

Non. Il réserve des chemins accessibles en écriture dans le système de fichiers rendu accessible en lecture seule, sans modifier propriétaire, permissions ou ACL. Le compte du service doit aussi disposer des droits ordinaires.

ProtectSystem=strict empêche-t-il la lecture des secrets ?

Non. Cette directive limite surtout les écritures. Elle ne masque pas automatiquement les fichiers lisibles et ne bloque pas le réseau. Les permissions des secrets, le masquage des chemins et les restrictions réseau restent à configurer séparément.

Serveur dédié AMD EPYC 7543P

À partir de $458.7/mois

Configurer un serveur dédié AmdMetal Consulter les tarifs de location AmdMetal

Vérification détaillée de la configuration du serveur dédié

AMD EPYC 7543P dédié en exclusivité ; le modèle et les options sont calculés ensemble sur la page de configuration.