Toutes les versions récentes des distributions Linux les plus populaires utilisent systemd
pour démarrer la machine et gérer les services système. Systemd
fournit plusieurs fonctionnalités pour faciliter le démarrage des services et plus sécurisé. C'est une combinaison rare, et cet article montre pourquoi il est utile de laisser systemd
gérer les ressources et le sandboxing d'un service.
Justification
Alors, pourquoi devrions-nous utiliser systemd
pour le sandboxing de sécurité ? Premièrement, on pourrait soutenir que chaque partie de cette fonctionnalité est déjà exposée via des outils existants et bien connus, qui peuvent être scriptés et combinés de manière arbitraire. Deuxièmement, en particulier dans le cas de programmes écrits en C/C++ et d'autres langages de bas niveau, les appels système appropriés peuvent être utilisés directement, réalisant une implémentation allégée soigneusement adaptée aux besoins d'un service particulier.
Il y a quatre raisons principales :
Systemd
atténue les différences entre les architectures matérielles, les versions du noyau et les configurations système.
La fonctionnalité qui assure le renforcement des services est implémentée dans la mesure du possible sur un système donné. Par exemple, un systemd
peut contenir à la fois des configurations AppArmor et SELinux. Le premier est utilisé sur les systèmes Ubuntu/Debian, le second sur Fedora/RHEL/CentoOS, et aucun pour les distributions qui n'activent aucun système MAC. L'autre côté de cette flexibilité est que ces fonctionnalités ne peuvent pas être considérées comme les uniques mécanisme de confinement (ou que ces services ne sont utilisés que sur des systèmes prenant en charge toutes les fonctionnalités requises).
Pour mettre cela en contexte, le Fedora 32 récemment publié contient près de 1 800 fichiers unitaires différents pour démarrer des services écrits en C, C++, Python, Java, Ocaml, Perl, Ruby, Lua, Tcl, Erlang et ainsi de suite - et un seul systemd
.
[ Besoin d'en savoir plus sur systemd ? Téléchargez la feuille de triche systemd pour obtenir des conseils plus utiles. ]
Quelques façons équivalentes de démarrer un service
Le plus souvent, systemd
les services sont définis via un fichier d'unité :un fichier texte au format ini qui déclare les commandes à exécuter et divers paramètres. Une fois ce fichier d'unité modifié, systemctl daemon-reload
doit être appelé pour pousser le gestionnaire à charger les nouveaux paramètres. La sortie du démon atterrit dans le journal et une commande distincte est utilisée pour l'afficher. Lors de l'exécution interactive de commandes, tout cela n'est pas très pratique. Le systemd-run
La commande indique au gestionnaire de lancer une commande au nom de l'utilisateur et constitue une excellente alternative pour une utilisation interactive. La commande à exécuter est spécifiée de la même manière que sudo
. Le premier argument positionnel et tout ce qui suit est la commande réelle, et toutes les options précédentes sont interprétées par systemd-run
lui-même. Le systemd-run
La commande a des options pour spécifier des paramètres spécifiques tels que --uid
et --gid
pour l'utilisateur et le groupe. Le -E
l'option définit une variable d'environnement, tandis qu'une option "fourre-tout" -p
accepte des paires clé=valeur arbitraires similaires au fichier d'unité.
$ systemd-run whoami
Running as unit: run-rbd26afbc67d74371a6d625db78e33acc.service
$ journalctl -u run-rbd26afbc67d74371a6d625db78e33acc.service
journalctl -u run-rbd26afbc67d74371a6d625db78e33acc.service
-- Logs begin at Thu 2020-04-23 19:31:49 CEST, end at Mon 2020-04-27 13:22:35 CEST. --
Apr 27 13:22:18 fedora systemd[1]: Started run-rbd26afbc67d74371a6d625db78e33acc.service.
Apr 27 13:22:18 fedora whoami[520662]: root
Apr 27 13:22:18 fedora systemd[1]: run-rbd26afbc67d74371a6d625db78e33acc.service: Succeeded.
systemd-run -t
connecte les flux d'entrée, de sortie et d'erreur standard de la commande au terminal appelant. C'est idéal pour exécuter des commandes de manière interactive (notez que le processus de service est toujours un enfant du gestionnaire).
$ systemd-run -t whoami
Running as unit: run-u53517.service
Press ^] three times within 1s to disconnect TTY.
root
Environnement cohérent
Une unité démarre toujours dans un environnement soigneusement défini. Lorsque nous démarrons une unité en utilisant systemctl
ou systemd-run
, la commande est toujours invoquée en tant qu'enfant du gestionnaire. L'environnement du shell n'affecte pas l'environnement dans lequel les commandes de service s'exécutent. Tous les paramètres pouvant être spécifiés dans un fichier d'unité ne sont pas pris en charge par systemd-run
, mais la plupart le sont, et tant que nous nous en tenons à ce sous-ensemble, l'invocation via un fichier d'unité et systemd-run
sont équivalents. En fait, systemd-run
crée un fichier d'unité temporaire à la volée.
Par exemple :
$ sudo systemd-run -M rawhide -t /usr/bin/grep PRETTY_NAME= /etc/os-release
Ici, sudo
parle à PAM pour permettre l'élévation des privilèges, puis exécute systemd-run
en tant que racine. Ensuite, systemd-run
établit une connexion avec une machine nommée rawhide , où il communique avec le gestionnaire du système (PID 1 dans le conteneur) via dbus. Le gestionnaire invoque grep
, qui fait son travail. Le grep
la commande imprime sur stdout, qui est connecté au pseudo-terminal à partir duquel sudo
a été invoqué.
Paramètres de sécurité
Utilisateurs et utilisateurs dynamiques
Sans plus tarder, parlons de quelques paramètres spécifiques, en commençant par les primitives les plus simples et les plus puissantes.
Tout d'abord, le mécanisme de séparation des privilèges le plus ancien, le plus basique et peut-être le plus utile :les utilisateurs. Vous pouvez définir des utilisateurs avec User=foobar
dans le [Service] section d'un fichier unité, ou systemd-run -p User=foobar
ou systemd-run --uid=foobar
. Cela peut sembler évident - et sur Android, chaque application a son propre utilisateur - mais dans le monde Linux, nous avons encore trop de services qui s'exécutent inutilement en tant que root.
Systemd
fournit un mécanisme pour créer des utilisateurs à la demande. Lorsqu'il est invoqué avec DynamicUser=yes
, un numéro d'utilisateur unique est attribué pour le service. Ce numéro se résout en un nom d'utilisateur temporaire. Cette affectation n'est pas stockée dans /etc/passwd
, mais est plutôt généré à la volée par un module NSS chaque fois que le numéro ou le nom correspondant est interrogé. Une fois le service arrêté, le numéro peut être réutilisé ultérieurement pour un autre service.
Quand un utilisateur statique régulier doit-il être utilisé pour un service, et quand est-il préférable d'utiliser un utilisateur dynamique ? Les utilisateurs dynamiques sont parfaits lorsque l'identité de l'utilisateur est éphémère et qu'aucune intégration avec d'autres services du système n'est nécessaire. Mais lorsque nous avons une politique dans la base de données pour autoriser un accès utilisateur spécifique, des répertoires partagés avec un groupe particulier ou toute autre configuration où nous voulons faire référence au nom d'utilisateur, les utilisateurs dynamiques ne sont probablement pas la meilleure option.
Monter les espaces de noms
En général, il convient de noter que systemd
est souvent uniquement une fonctionnalité d'encapsulation fournie par le noyau. Par exemple, divers paramètres limitant l'accès à l'arborescence du système de fichiers, rendant certaines parties de celle-ci en lecture seule ou inaccessibles, sont réalisés en organisant les systèmes de fichiers appropriés dans un espace de noms de montage non partagé.
Plusieurs paramètres utiles sont implémentés comme ceci. Les deux plus utiles et les plus généraux sont ProtectHome=
et ProtectSystem=
. Le premier utilise un espace de noms de montage non partagé pour créer /home
en lecture seule ou entièrement inaccessible. La seconde concerne la protection de /usr
, /boot
, et /etc
.
Un troisième paramètre également utile mais très spécifique est PrivateTmp=
. Il utilise des espaces de noms de montage pour rendre un répertoire privé visible en tant que /tmp
et /var/tmp
pour le service. Les fichiers temporaires du service sont cachés aux autres utilisateurs pour éviter tout problème dû à des collisions de noms de fichiers ou à de mauvaises autorisations.
La vue du système de fichiers peut être gérée au niveau des répertoires individuels via InaccessiblePaths=
, ReadOnlyPaths=
, ReadWritePaths=
, BindPaths=
, et ReadOnlyBindPaths=
. Les deux premiers paramètres fournissent tout ou seulement un accès en écriture à des parties d'une hiérarchie de système de fichiers. Le troisième concerne la restauration de l'accès, ce qui est utile lorsque nous voulons donner un accès complet uniquement à un répertoire spécifique situé au plus profond de la hiérarchie. Les deux derniers permettent de déplacer des répertoires ou, plus précisément, de les lier en privé à un emplacement différent.
Retour au sujet de DynamicUser=yes
, ces utilisateurs transitoires ne sont possibles que lorsque le service n'est pas autorisé à créer des fichiers permanents sur le disque. Si ces fichiers étaient visibles par d'autres utilisateurs, ils seraient affichés comme n'ayant pas de propriétaire, ou pire, ils pourraient être consultés par le nouvel utilisateur transitoire avec le même numéro, ce qui entraînerait une fuite d'informations ou une élévation involontaire des privilèges. Systemd
utilise des espaces de noms de montage pour rendre la majeure partie de l'arborescence du système de fichiers non inscriptible pour le service. Pour permettre un stockage permanent, un répertoire privé est monté dans l'arborescence du système de fichiers visible par le service.
Notez que ces protections sont indépendantes du mécanisme de contrôle d'accès aux fichiers de base utilisant la propriété des fichiers et le masque d'autorisation. Si un système de fichiers est monté en lecture seule, même les utilisateurs qui pourraient modifier des fichiers spécifiques en fonction des autorisations standard ne peuvent pas le faire tant que le système de fichiers n'est pas remonté en lecture-écriture. Cela fournit une protection contre les erreurs dans la gestion des fichiers (après tout, il n'est pas rare que les utilisateurs définissent parfois le mauvais masque d'autorisation) et constitue une couche d'une stratégie de défense en profondeur.
Les ressources implémentées à l'aide d'espaces de noms de montage sont généralement très efficaces car l'implémentation du noyau est efficace. Les frais généraux lors de leur configuration sont également généralement négligeables.
Création automatique de répertoires pour un service
Une fonctionnalité relativement nouvelle que systemd
fournit des services est la gestion automatique des annuaires. Différents chemins du système de fichiers ont des caractéristiques de stockage et des utilisations prévues différentes, mais ils appartiennent à quelques catégories standard. Le FHS spécifie que /etc
est pour les fichiers de configuration, /var/cache
est pour le stockage non permanent, /var/lib/
est pour le stockage semi-permanent, /var/log
pour les logs, et /run
pour les fichiers volatiles. Un service a souvent besoin d'un sous-répertoire dans chacun de ces emplacements. Systemd
configure cela automatiquement, comme contrôlé par le ConfigurationDirectory=
, CacheDirectory=
, StateDirectory=
, LogsDirectory=
, et RuntimeDirectory=
réglages. L'utilisateur est propriétaire de ces répertoires. Le répertoire d'exécution est supprimé par le gestionnaire lorsque le service s'arrête. L'idée générale est de lier l'existence de ces actifs de système de fichiers à la durée de vie du service. Ils n'ont pas besoin d'être créés au préalable et ils sont nettoyés de manière appropriée après l'arrêt du service.
$ sudo systemd-run -t -p User=user -p CacheDirectory=foo -p StateDirectory=foo -p RuntimeDirectory=foo -p PrivateTmp=yes ls -ld /run/foo /var/cache/foo /var/lib/foo /etc/foo /tmp/
Running as unit: run-u45882.service
Press ^] three times within 1s to disconnect TTY.
drwxr-xr-x 2 user user 40 Apr 26 08:21 /run/foo ← automatically created and removed
drwxr-xr-x 2 user user 4096 Apr 26 08:20 /var/cache/foo ← automatically created
drwxr-xr-x. 2 user user 4096 Nov 13 21:50 /var/lib/foo ← automatically created
drwxr-xr-x. 2 root root 4096 Nov 13 21:50 /etc/foo ← automatically created, but not owned by the user, since the service (usually) shall not modify its own configuration
drwxrwxrwt 2 root root 40 Apr 26 08:21 /tmp/ ← "sticky bit" is set, but this directory is not the one everyone else sees
Bien sûr, ces sept emplacements (en comptant PrivateTmp=
comme deux) ne couvrent pas les besoins de tous les services, mais ils devraient être suffisants pour la plupart des situations. Pour les autres cas, configuration manuelle ou une configuration appropriée dans tmpfiles.d
est toujours une option.
La gestion automatique des répertoires se marie bien avec le DynamicUser=
paramètres et utilisateurs créés automatiquement, en fournissant un service qui s'exécute en tant qu'utilisateur distinct et n'est pas autorisé à modifier la majeure partie de l'arborescence du système de fichiers (même si les autorisations d'accès aux fichiers le permettent). Le service peut toujours accéder à certains répertoires et y stocker des données, sans autre configuration que la configuration du fichier d'unité.
Par exemple, un service Web Python peut être exécuté en tant que :
$ systemd-run -p DynamicUser=yes -p ProtectHome=yes -p StateDirectory=webserver --working-directory=/srv/www/content python3 -m http.server 8000
soit via le fichier unité équivalent :
[Service]
DynamicUser=yes
ProtectHome=yes
StateDirectory=webserver
WorkingDirectory=/srv/www/content
ExecStart=python3 -m http.server 8000
Nous nous assurons que le service fonctionne en tant qu'utilisateur transitoire sans possibilité de modifier le système de fichiers ou d'avoir accès aux données de l'utilisateur.
Les paramètres décrits ici peuvent être considérés comme "de haut niveau". Même si la mise en œuvre peut être délicate, les concepts eux-mêmes sont faciles à comprendre et l'effet sur le service est clair. Il existe un grand nombre d'autres paramètres pour supprimer diverses autorisations et fonctionnalités, verrouiller les protocoles réseau et les réglages du noyau, et même désactiver les appels système individuels. Ceux-ci sortent du cadre de ce court article. Reportez-vous à la documentation de référence détaillée.
Mettre tout cela à profit
Lorsque nous avons une bonne compréhension de ce que fait le service et de ses besoins, nous pouvons déterminer quels privilèges sont requis et ce que nous pouvons en retirer. Les candidats évidents s'exécutent en tant qu'utilisateur non privilégié et limitent l'accès aux données de l'utilisateur sous /home
. Plus nous autorisons systemd
pour configurer les choses pour nous (par exemple, en utilisant StateDirectory=
et amis), plus il est probable que le service puisse fonctionner avec succès en tant qu'utilisateur non privilégié. Souvent, le service a besoin d'accéder à un sous-répertoire spécifique, et nous pouvons y parvenir en utilisant ReadWritePaths=
et paramètres similaires.
L'ajout de mesures de sécurité de manière automatique est impossible. Sans une bonne compréhension de ce dont le service a besoin dans différents scénarios de configuration et pour différentes opérations, nous ne pouvons pas définir un bac à sable utile. Cela signifie que le sandboxing des services est mieux fait par leurs auteurs ou mainteneurs.
Évaluation et statu quo
Le nombre de paramètres possibles est important et de nouveaux sont ajoutés à chaque version de systemd
. Suivre cela est difficile. Systemd
fournit un outil pour évaluer l'utilisation des directives de sandboxing dans le fichier d'unité. Les résultats doivent être considérés comme des indices - après tout, comme mentionné ci-dessus, la création automatique d'une politique de sécurité est difficile, et toute évaluation ne fait que compter ce qui est utilisé et ce qui ne l'est pas, sans aucune compréhension approfondie de ce qui compte pour un service donné.
$ systemd-analyze security systemd-resolved.service
NAME DESCRIPTION EXPOSURE
...
✓ User=/DynamicUser= Service runs under a static non-root user identity
✗ DeviceAllow= Service has a device ACL with some special devices 0.1
✓ PrivateDevices= Service has no access to hardware devices
✓ PrivateMounts= Service cannot install system mounts
PrivateTmp= Service runs in special boot phase, option does not apply
✗ PrivateUsers= Service has access to other users 0.2
ProtectHome= Service runs in special boot phase, option does not apply
✓ ProtectKernelLogs= Service cannot read from or write to the kernel log ring buffer
✓ ProtectKernelModules= Service cannot load or read kernel modules
✓ ProtectKernelTunables= Service cannot alter kernel tunables (/proc/sys, …)
ProtectSystem= Service runs in special boot phase, option does not apply
✓ SupplementaryGroups= Service has no supplementary groups
...
→ Overall exposure level for systemd-resolved.service: 2.1 OK 🙂
$ systemd-analyze security httpd.service
NAME DESCRIPTION EXPOSURE
...
✗ User=/DynamicUser= Service runs as root user 0.4
✗ DeviceAllow= Service has no device ACL 0.2
✗ PrivateDevices= Service potentially has access to hardware devices 0.2
✓ PrivateMounts= Service cannot install system mounts
✓ PrivateTmp= Service has no access to other software's temporary files
✗ PrivateUsers= Service has access to other users 0.2
✗ ProtectHome= Service has full access to home directories 0.2
✗ ProtectKernelLogs= Service may read from or write to the kernel log ring buffer 0.2
✗ ProtectKernelModules= Service may load or read kernel modules 0.2
✗ ProtectKernelTunables= Service may alter kernel tunables 0.2
✗ ProtectSystem= Service has full access to the OS file hierarchy 0.2
SupplementaryGroups= Service runs as root, option does not matter
...
→ Overall exposure level for httpd.service: 9.2 UNSAFE 😨
Encore une fois, cela ne signifie pas que le service n'est pas sécurisé, mais qu'il n'utilise pas le systemd
primitives de sécurité.
Au niveau de l'ensemble de la distribution :
$ systemd-analyze security '*'
Nous constatons que la plupart des services obtiennent un score très élevé (c'est-à-dire mauvais). Nous ne pouvons pas rassembler de telles statistiques sur les différents services internes, mais il semble raisonnable de supposer qu'ils sont similaires. Il y a certainement beaucoup de fruits à portée de main, et l'application d'un sandboxing relativement simple rendrait nos systèmes plus sûrs.
Conclusion
Laisser systemd
gérer les services et le sandboxing peuvent être un excellent moyen d'ajouter une couche de sécurité à vos serveurs Linux. Envisagez de tester les configurations ci-dessus pour voir ce qui pourrait bénéficier à votre organisation.
Dans cet article, nous avons soigneusement évité toute mention de réseautage. En effet, le deuxième volet va parler de l'activation des sockets et du sandboxing des services utilisant le réseau.
[ N'oubliez pas de consulter la feuille de triche systemd pour des conseils plus utiles. ]