Introduction
Les unités systemd durcies réduisent la surface d'attaque des services Linux en appliquant par défaut le moindre privilège, en isolant fichiers et périphériques, en limitant l'accès réseau et en restreignant fortement les actions des processus. Ce guide présente un workflow pratique et des exemples copiables applicables à de nouveaux ou d'anciens services. Vous apprendrez à exécuter en tant qu'utilisateur sans privilèges, verrouiller l'accès au système de fichiers, restreindre les capacités et les appels système, gérer les secrets en toute sécurité, contrôler l'exposition réseau et valider les modifications avec des vérifications sûres.
Aperçu du workflow
- Profiler le service : identifier l'utilisateur sous lequel il s'exécute, les fichiers qu'il doit écrire, les ports sur lesquels il écoute et les besoins noyau particuliers (par ex.
CAP_NET_BIND_SERVICEpour les ports inférieurs à 1024, ouCAP_DAC_OVERRIDEpour les ACL du système de fichiers). - Réduire les privilèges : s'exécuter comme un utilisateur dédié, supprimer les capacités Linux et bloquer l'élévation de privilèges avec
NoNewPrivileges=yes. - Verrouiller le système de fichiers : tout rendre en lecture seule par défaut avec
ProtectSystem=strict, puis autoriser explicitement les chemins d'écriture viaStateDirectory=,CacheDirectory=,LogsDirectory=ouReadWritePaths=. - Isoler les périphériques et les espaces de noms : retirer l'accès aux périphériques de l'hôte (
PrivateDevices=yes) et aux espaces de noms non sûrs (RestrictNamespaces=yes). - Limiter les appels système et l'exécution mémoire : appliquer des filtres d'appels système (
SystemCallFilter=@system-service) et désactiver les violations W^X (MemoryDenyWriteExecute=yes). - Contraindre le réseau : tout refuser avec
IPAddressDeny=any, puis autoriser explicitement seulement les adresses et familles requises. - Gérer les secrets correctement : transmettre les secrets via
LoadCredential=et les lire depuis$CREDENTIALS_DIRECTORY, jamais comme variables d'environnement en ligne. - Vérifier et itérer : charger l'unité, lancer
systemd-analyze security, tester la fonctionnalité, examiner les journaux pour les refus et revenir proprement si nécessaire.
Exemples pratiques de durcissement
Utilisez ces extraits comme point de départ et adaptez-les à votre service.
1) Modèle de service durci de base
Ce modèle suppose un service TCP simple qui se lie à localhost sur le port 8080 et a besoin d'écrire l'état et les journaux.
# /etc/systemd/system/myapp.service
[Unit]
Description=MyApp (durci)
After=network-online.target
Wants=network-online.target
[Service]
# Commande
ExecStart=/usr/local/bin/myapp --listen 127.0.0.1:8080
# Moindre privilège
User=myapp
Group=myapp
UMask=0077
NoNewPrivileges=yes
CapabilityBoundingSet=
AmbientCapabilities=
# Protections du système de fichiers
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectControlGroups=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
# Emplacements d'écriture explicites gérés par systemd
StateDirectory=myapp
CacheDirectory=myapp
LogsDirectory=myapp
# Si vous devez autoriser des écritures ailleurs, les déclarer explicitement
# ReadWritePaths=/var/lib/myapp /var/log/myapp
# Durcissement mémoire et personnalité
MemoryDenyWriteExecute=yes
LockPersonality=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
RestrictNamespaces=yes
SystemCallArchitectures=native
# Liste d'autorisation conservatrice ; ajustez pour votre application
SystemCallFilter=@system-service
# Contrôles réseau : refuser par défaut, autoriser seulement la boucle locale
IPAddressDeny=any
IPAddressAllow=127.0.0.1
IPAddressAllow=::1
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
# Environnement et identifiants
EnvironmentFile=-/etc/myapp/myapp.env
# Fournir les secrets via les identifiants ; lire en utilisant $CREDENTIALS_DIRECTORY
# Le fichier doit appartenir à root:root, mode 0400 ou plus strict
LoadCredential=api_token:/etc/credentials/myapp_api_token
# Politique de redémarrage
Restart=on-failure
RestartSec=2s
[Install]
WantedBy=multi-user.target
Notes :
StateDirectory=,CacheDirectory=etLogsDirectory=créent des répertoires dédiés sous/var/lib/,/var/cache/et/var/log/appartenant à l'utilisateur du service avec le mode 0750. Préférez-les aux chemins manuels.ProtectSystem=strictrend/usr,/bootet/etcen lecture seule. Autorisez les écritures nécessaires avec les optionsXDirectory=ouReadWritePaths=.CapabilityBoundingSet=etAmbientCapabilities=vides suppriment toutes les capacités sauf si explicitement requises.IPAddressDeny=anycombiné avecIPAddressAllow=contraint tout le trafic réseau entrant et sortant aux adresses listées.SystemCallFilter=@system-serviceutilise un profil conservateur. Si le service casse, inspectezjournalctl -u myapp.service -bpour les refusSECCOMPet ajoutez les groupes d'appels système spécifiques dont vous avez besoin (par ex.@system-service @network-io).
2) Service sans aucun accès réseau
# /etc/systemd/system/batch-job.service
[Unit]
Description=Tâche batch nocturne (isolée)
After=local-fs.target
[Service]
ExecStart=/usr/local/bin/batch-job
User=batch
Group=batch
UMask=0077
NoNewPrivileges=yes
CapabilityBoundingSet=
AmbientCapabilities=
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
RestrictSUIDSGID=yes
MemoryDenyWriteExecute=yes
LockPersonality=yes
RestrictNamespaces=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service
# Isolation réseau stricte
PrivateNetwork=yes
RestrictAddressFamilies=AF_UNIX
IPAddressDeny=any
StateDirectory=batch-job
LogsDirectory=batch-job
Restart=on-failure
3) Transmettre les secrets en toute sécurité avec les identifiants
Évitez de placer des secrets bruts directement dans les fichiers d'unité. Utilisez plutôt LoadCredential= et lisez-les depuis le répertoire des identifiants.
# /etc/systemd/system/report.service
[Unit]
Description=Générateur de rapports
[Service]
ExecStart=/usr/local/bin/report --token-file "$CREDENTIALS_DIRECTORY/api_token"
User=report
Group=report
NoNewPrivileges=yes
CapabilityBoundingSet=
AmbientCapabilities=
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
LoadCredential=api_token:/etc/credentials/report_api_token
StateDirectory=report
LogsDirectory=report
Sur le disque :
/etc/credentials/report_api_tokendoit appartenir àroot:rootavec le mode0400.- Le service lit le secret depuis
$CREDENTIALS_DIRECTORY/api_tokenà l'exécution.
4) Vérifications et validation sûres
# Syntaxe et intégrité de l'unité
systemd-analyze verify /etc/systemd/system/myapp.service
# Charger les modifications et redémarrer proprement
systemctl daemon-reload
systemctl restart myapp.service
# Examiner les journaux pour les refus et permissions manquantes
journalctl -u myapp.service -b
# Score de sécurité et indices
systemd-analyze security myapp.service
# Confirmer la configuration effective
systemctl show -p User,Group,NoNewPrivileges,CapabilityBoundingSet myapp.service
# Vérifier l'exposition réseau
ss -tulpn | grep 8080
Si un durcissement casse la fonctionnalité, revenez progressivement : commentez le dernier changement, lancez systemctl daemon-reload, redémarrez et retestez. N'ajoutez que les permissions ou appels système minimaux requis pour restaurer le comportement correct.
Plan pilote local
Objectif : appliquer une base durcie à un service non critique et vérifier le comportement localement avant un déploiement plus large.
Périmètre
- Choisir un seul service avec des vérifications de santé claires (par exemple, un service HTTP interne sur localhost).
Plan
- Capturer le comportement actuel : quel utilisateur l'exécute, quels fichiers il écrit, quels ports il écoute, et les variables d'environnement nécessaires.
- Créer une surcharge drop-in plutôt que de modifier l'unité du fournisseur :
systemctl edit myapp.service
Ajouter : User=, Group=, NoNewPrivileges=yes, ProtectSystem=strict, PrivateTmp=yes, CapabilityBoundingSet=, IPAddressDeny=any avec autorisation de la boucle locale.
- Ajouter
StateDirectory=,LogsDirectory=et déplacer les écritures là-dedans ; supprimer les chemins d'écriture ad hoc. - Ajouter
RestrictAddressFamilies=etSystemCallFilter=@system-service; redémarrer et vérifier les journaux pour les opérations refusées. - Remplacer les secrets en ligne par
LoadCredential=et pointer l'application vers$CREDENTIALS_DIRECTORY. - Valider :
- Lancer
systemd-analyze security myapp.serviceet capturer le score avant/après. - Vérifications fonctionnelles :
curl localhost:8080, vérifier les écritures de fichiers dans/var/lib/myapp, journaux dans/var/log/myapp. ss -tulpnconfirme seulement les écouteurs attendus.
- Plan de retour arrière : conserver chaque changement comme un commit et bloc de commentaires distincts. En cas d'échec, supprimer la dernière directive, lancer
systemctl daemon-reload, redémarrer et retester.
Critères de sortie
- Le service s'exécute de manière fiable lors des opérations normales.
- Seuls les fichiers attendus sont accessibles en écriture.
- Seuls les ports prévus sont accessibles.
- Le score de sécurité s'améliore sans casser la fonctionnalité.
Conclusion
Les unités systemd durcies offrent des défenses fortes et reproductibles avec le moindre privilège, des permissions de système de fichiers explicites, des limites réseau strictes et une gestion sûre des secrets. Commencez par un petit pilote, mesurez les améliorations et itérez. Comme base durable, exécutez comme un utilisateur dédié, définissez NoNewPrivileges=yes, supprimez toutes les capacités, rendez le système de fichiers en lecture seule par défaut, autorisez seulement les chemins d'écriture requis, restreignez les familles d'adresses, appliquez IPAddressDeny/IPAddressAllow et utilisez les identifiants pour les secrets. Validez avec systemd-analyze verify et systemd-analyze security, observez le journal pour les refus, et étendez le motif à travers les services une fois stable.