
Un master Salt peut exécuter n'importe quelle commande en root sur tout le
parc. C'est l'actif le plus sensible de votre infrastructure, et il n'est pas
sécurisé par défaut. Ce module le durcit concrètement : restreindre le bus,
cadrer les droits avec publisher_acl et eauth, exposer une API
contrôlée pour la CI, et connaître les limites réelles du durcissement des
minions. Public visé : avancés exploitant Salt sur un vrai parc.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Restreindre l'exposition réseau du bus Salt.
- Cadrer les droits avec
publisher_aclet eauth. - Exposer une API contrôlée avec
salt-api. - Comprendre les limites du durcissement des minions.
Prérequis
Section intitulée « Prérequis »- Maîtriser l'ensemble du parcours Salt, en particulier les commandes distantes et les bonnes pratiques.
Le master tourne sous l'utilisateur salt
Section intitulée « Le master tourne sous l'utilisateur salt »Commençons par le fait qui explique la plupart des surprises en production : le
paquet Salt fait tourner le salt-master sous l'utilisateur salt, pas sous
root (user: salt dans /etc/salt/master). C'est une bonne nouvelle pour
la sécurité : une compromission du service ne donne pas immédiatement root sur la
machine.
Mais cela change tout le reste. Le master ne peut lire que ce qui appartient à
salt ou lui est accessible. Trois échecs classiques en découlent, avec le même
remède :
| Ce que vous voulez faire | Ce qui échoue si vous l'oubliez | Remède |
|---|---|---|
| Chiffrer les pillars en GPG | Pillar render error, trousseau illisible | chown -R salt:salt /etc/salt/gpgkeys |
| Servir les states par GitFS | detected dubious ownership, GitFS vide | chown -R salt:salt sur le dépôt |
| Authentifier par PAM (eauth) | HTTP 401, /etc/shadow illisible | usermod -aG shadow salt |
Gardez ce réflexe : quand quelque chose ne marche pas côté master, demandez-vous
si l'utilisateur salt a le droit de le lire.
Restreindre le bus Salt
Section intitulée « Restreindre le bus Salt »Le master écoute sur deux ports : le 4505 (bus de publication, il diffuse les ordres) et le 4506 (canal de retour, les minions y renvoient résultats et fichiers). Regardez sur quelle interface ils écoutent par défaut :
sudo ss -tlnp | grep -E '4505|4506'LISTEN 0 1000 0.0.0.0:4506 0.0.0.0:*LISTEN 0 1000 0.0.0.0:4505 0.0.0.0:*0.0.0.0 : le bus écoute sur toutes les interfaces. Sur un serveur ayant
une patte publique, votre master est joignable depuis Internet. C'est le premier
point à corriger, au pare-feu : n'autorisez 4505 et 4506 que depuis les
sous-réseaux des minions, et faites passer l'administration par un bastion
ou un VPN.
Cadrer les droits avec publisher_acl
Section intitulée « Cadrer les droits avec publisher_acl »Par défaut, quiconque peut publier une commande depuis le master le fait avec les
pleins pouvoirs. Le publisher_acl change cela : il déclare, pour un
utilisateur système du master, quelles fonctions il peut appeler et sur
quelles cibles.
Ici, l'utilisateur deploy ne peut qu'appliquer des states et interroger des
services, uniquement sur les minions web :
publisher_acl: deploy: - 'web*': - state.apply - service.statusLe résultat est net. La commande autorisée passe :
sudo -u deploy salt 'web1' service.status nginxweb1: TrueMais le shell arbitraire est refusé, tout comme la cible hors périmètre :
sudo -u deploy salt 'web1' cmd.run 'id'sudo -u deploy salt 'db1' state.apply motdAuthorization error occurred.C'est le moindre privilège rendu concret : un opérateur ou une CI n'obtient plus root sur tout le parc, mais exactement les verbes dont ils ont besoin, sur les cibles qui les concernent.
Exposer une API contrôlée avec salt-api
Section intitulée « Exposer une API contrôlée avec salt-api »Une CI ne devrait pas se connecter en SSH au master pour lancer des commandes. Le
bon canal est salt-api, qui expose Salt en HTTP, adossé à l'eauth
(authentification externe) pour rattacher les droits à un annuaire (PAM,
LDAP) plutôt qu'à un compte partagé.
La configuration déclare l'API et les permissions par utilisateur, avec la
même logique que publisher_acl :
rest_cherrypy: port: 8000 disable_ssl: true # lab uniquement : en production, TLS obligatoire
external_auth: pam: deploy: - 'web*': - state.apply - service.status
netapi_enable_clients: - localObtenir un jeton, puis exécuter
Section intitulée « Obtenir un jeton, puis exécuter »L'authentification renvoie un jeton à durée de vie limitée, et la réponse rappelle explicitement le périmètre accordé :
curl -sS http://localhost:8000/login -H 'Accept: application/json' \ -d username=deploy -d password='...' -d eauth=pam{ "return": [{ "token": "e5da811b046216188afa74af4dbde03d838a213e", "user": "deploy", "eauth": "pam", "perms": [{"web*": ["state.apply", "service.status"]}] }]}Muni de ce jeton, la CI exécute une commande dans son périmètre :
curl -sS http://localhost:8000/ -H 'Accept: application/json' \ -H "X-Auth-Token: $TOKEN" \ -d client=local -d tgt='web1' -d fun=service.status -d arg=nginx{"return": [{"web1": true}]}Hors de son périmètre, l'API refuse, que ce soit la fonction ou la cible qui dépasse :
cmd.run sur web1 : HTTP 401service.status sur db1 : HTTP 401service.status sur web1 (permis) : HTTP 200Vous obtenez ainsi une intégration CI/CD propre : un jeton, un périmètre, une trace, et aucun accès shell au master.
Les limites du durcissement des minions
Section intitulée « Les limites du durcissement des minions »On lit souvent que disable_modules permet de retirer le module cmd
d'un minion sensible, pour empêcher toute commande arbitraire. La réalité est plus
nuancée, et mieux vaut la connaître avant de s'appuyer dessus.
L'option fonctionne pour retirer un module. En désactivant disk sur un
minion, la fonction devient bien indisponible :
disable_modules: - diskdb1: 'disk' __virtual__ returned FalseMais avec cmd, le module résiste : cmd.run continue de répondre, car
le système de states dépend de cmd en interne et Salt le maintient
disponible. Autrement dit, disable_modules ne vous protège pas du shell
arbitraire.
Pour les machines les plus critiques, la vraie réponse reste le mode masterless
: le minion applique ses states en local avec salt-call --local, sans jamais
se connecter à un master. Vous supprimez la dépendance au bus et la surface
d'attaque associée, au prix d'une distribution des states à organiser
autrement (Git, image, artefact).
Haute disponibilité et passage à l'échelle
Section intitulée « Haute disponibilité et passage à l'échelle »Un master unique est un point de défaillance. Salt permet de faire pointer les minions vers plusieurs masters : chacun accepte les clés, et le parc reste pilotable si l'un tombe. Les masters doivent rester synchronisés (mêmes states, pillars et clés), ce que GitFS simplifie beaucoup puisque les states viennent d'un dépôt commun.
Au-delà de quelques milliers de minions, un master sature. Le syndic répond à ce besoin : un master intermédiaire relaie les ordres d'un master supérieur vers ses propres minions, formant une hiérarchie qui segmente le parc par zone ou par datacenter.
Faire tourner les clés
Section intitulée « Faire tourner les clés »La rotation limite l'impact d'une fuite de matériel cryptographique. Côté
minion, on régénère la paire, puis on retire l'ancienne clé du master
(salt-key -d) avant d'accepter la nouvelle (salt-key -a) :
salt-call saltutil.regen_keysCôté master, la manœuvre est bien plus lourde : arrêter le service, retirer
master.pem et master.pub du répertoire pki, redémarrer (une nouvelle paire
est générée), puis rétablir la confiance de tous les minions.
Dépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Solution |
|---|---|---|
Authorization error occurred. | La fonction ou la cible dépasse le publisher_acl | Comportement attendu : élargir l'ACL seulement si le besoin est légitime |
Minion did not return. [No response] alors que le job a réussi | L'utilisateur n'est pas dans le groupe salt | usermod -aG salt <utilisateur> |
HTTP 401 sur /login de salt-api | Le master (utilisateur salt) ne peut pas lire /etc/shadow | usermod -aG shadow salt |
400 Client disabled: 'local' | Les clients de l'API sont désactivés par défaut | Déclarer netapi_enable_clients: [local] |
cmd.run répond malgré disable_modules | cmd n'est pas retirable ainsi | Durcir au master (publisher_acl), pas au minion |
À retenir
Section intitulée « À retenir »- Le salt-master tourne sous l'utilisateur
salt, pas root : c'est la cause numéro un des échecs de trousseau GPG, de dépôt GitFS et d'authentification PAM. - Le bus 4505/4506 écoute sur toutes les interfaces par défaut : le filtrer au pare-feu est le premier durcissement.
publisher_aclcadre qui exécute quoi, sur quelles cibles, et refuse le reste avecAuthorization error occurred.- L'utilisateur doit appartenir au groupe
salt, sinon sa commande s'exécute mais il ne reçoit pas le retour. salt-apiet l'eauth donnent à la CI un jeton cadré, sans accès shell au master. Les clients sont désactivés par défaut.disable_modulesne bloque pascmd: le durcissement contre le shell arbitraire se fait au master, pas au minion.- Le multi-master et le syndic traitent la disponibilité et l'échelle, une fois la sécurité en place.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Durcissement des serveurs Linux : le socle système sur lequel repose la sécurité du master.
- Gérer ses secrets : les principes de protection des données sensibles manipulées par Salt.