GNU/Linux >> Tutoriels Linux >  >> Linux

Utiliser les fonctionnalités de systemd pour sécuriser les services

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 :

1. La sécurité est difficile. Une implémentation centralisée dans le gestionnaire de services signifie qu'un service qui en tire parti peut être considérablement simplifié. Sans aucun doute, cette implémentation centralisée est complexe, mais en raison de sa large utilisation, elle est bien testée. Si l'on considère qu'il est réutilisé sur des milliers de services, le global la complexité du système est réduite.

2. Les primitives de sécurité varient d'un système à l'autre. 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).

3. La sécurité nécessite une manipulation de bas niveau du système. Les fonctionnalités fournies par le gestionnaire de services sont indépendantes du langage d'implémentation du service, il est donc facile d'écrire un service dans un langage de haut niveau, par exemple, shell ou Python ou tout ce qui est pratique, tout en le verrouillant.

4. La sécurité nécessite des privilèges. C'est un paradoxe, mais il faut des privilèges pour enlever des privilèges. Par exemple, nous devons souvent être root pour configurer un espace de noms de montage personnalisé afin de limiter une vue du système de fichiers. Comme autre exemple, un démon HTTP est souvent démarré en tant que root uniquement pour pouvoir ouvrir un port à faible numéro et les ports à faible numéro sont restreints au nom de la sécurité. Le gestionnaire de services doit de toute façon s'exécuter avec les privilèges les plus élevés, mais les services ne le devraient pas, et la configuration de renforcement est souvent la seule raison d'exiger des privilèges plus élevés. Tout bogue dans la mise en œuvre du service dans cette phase peut être dangereux. En déchargeant la configuration sur le gestionnaire de services, les services peuvent démarrer sans cette phase précoce de privilèges élevés.

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. ]


Linux
  1. Comment utiliser la commande Systemctl pour gérer les services Systemd

  2. Comment créer un service Systemd sous Linux

  3. Vérifier les services en cours d'exécution sous Linux

  4. Comment arrêter le service systemd

  5. Utilisation de la variable dans le chemin de commande pour ExecStart dans le service systemd

Commandes Systemctl pour gérer le service Systemd

Gestion des cgroups avec systemd

Comment configurer l'exécution automatique d'un script Python à l'aide de Systemd

Services réseau

Manière correcte d'utiliser Ubuntu systemctl pour contrôler Systemd

systemd - Donner à mon service plusieurs arguments