Aller au contenu
Infrastructure as Code medium

Les pillars Salt : données sensibles et configuration ciblée

8 min de lecture

Coder un mot de passe en dur dans un state est une faute. Les pillars sont la réponse de Salt : des données confidentielles, servies par le master, ciblées par minion, que les states consomment sans les contenir. Ce module apprend à externaliser toute la configuration de plusieurs applications. Public visé : intermédiaires ayant écrit des states avec Jinja.

  • Structurer un arbre de pillars.
  • Cibler des données par minion avec le pillar top file.
  • Séparer données publiques et données sensibles.
  • Externaliser la configuration de plusieurs applications.
  • Savoir écrire des states et utiliser Jinja.

La distinction est fondamentale. Les grains sont des faits collectés côté minion (système, CPU, rôle) : le minion les connaît, ils ne sont pas secrets. Les pillars sont des données définies côté master et poussées vers les minions autorisés : c'est l'endroit des secrets et de la configuration par environnement. Un minion ne voit que les pillars qui lui sont assignés.

Les pillars vivent dans un arbre séparé des states (/srv/pillar par défaut), avec leur propre top file qui décide quel minion reçoit quelles données, avec le même ciblage que partout dans Salt.

/srv/pillar/top.sls
base:
'role:web':
- match: grain
- web
'db1':
- db

Le top file des pillars ne sert qu'à deux choses : inclure des fichiers pillar et cibler les minions qui les reçoivent. On n'y déclare jamais de variable : les données vivent dans les fichiers inclus (ici web.sls et db.sls), pas dans le top.sls.

Un fichier pillar porte le nom de son state : un pillar apache.sls alimente les states apache, un pillar web.sls les states du rôle web. Cette correspondance de nommage rend l'arbre lisible.

/srv/pillar/web.sls
paquets_web:
- nginx
- certbot
app:
admin_email: ops@exemple.fr

pillar.items liste les pillars d'un minion. Notez que Salt masque les valeurs en sortie de terminal, un garde-fou pour ne pas exposer un secret dans un historique de commandes ou un log :

Fenêtre de terminal
salt web1 pillar.items
web1:
----------
app:
----------
admin_email:
**********
paquets_web:
- **********
- **********

L'isolation est la propriété clé : un minion ne voit que ses pillars. Le secret de base de données, ciblé par identifiant db1, est servi à db1...

Fenêtre de terminal
salt db1 pillar.get db:password
db1:
**********

...mais reste invisible pour web1, qui n'y a pas droit :

Fenêtre de terminal
salt web1 pillar.get db:password
web1:

Le masquage protège le terminal, mais la valeur réelle circule bien jusqu'au minion. Dans un state ou un template, on lit un pillar avec la syntaxe mapping salt['pillar.get'](...), entre crochets, jamais la forme pointée salt.pillar.get(...). Le second argument fournit une valeur de repli qui évite un rendu en échec quand la clé est absente.

# écrit la valeur du pillar dans un fichier de configuration
/etc/ops-contact.conf:
file.managed:
- mode: "0644"
- contents: |
admin_email={{ salt['pillar.get']('app:admin_email', 'root@localhost') }}

À l'application, le fichier déployé contient la vraie valeur, preuve que la donnée a traversé le master jusqu'au minion :

Fenêtre de terminal
salt web1 state.apply webconf
salt web1 cmd.run 'cat /etc/ops-contact.conf'
web1:
admin_email=ops@exemple.fr

Un second passage ne change rien, file.managed étant idempotent :

Comment: File /etc/ops-contact.conf is in the correct state

Les secrets vivent exclusivement en pillar : jamais dans un grain, jamais dans un state versionné. Un grain est modifiable côté minion, donc non fiable pour une donnée sensible ; un state poussé dans Git expose le secret à quiconque lit le dépôt. Le pillar reste le seul emplacement qui limite la diffusion aux minions autorisés.

Cibler par identifiant ne suffit pourtant pas à protéger la valeur au repos. Un pillar reste stocké en clair sur le master tant que vous ne le chiffrez pas : le ciblage contrôle qui reçoit la donnée, pas comment elle est protégée sur le disque du master ou dans Git.

Les pillars se déclinent par environnement (base, dev, prod) pour servir des valeurs différentes selon le contexte : une base de données de test en dev, la vraie en prod, avec le même code de states. Chaque environnement a sa section dans le top file, ce qui évite de dupliquer les states pour changer une poignée de valeurs.

SymptômeCause probableSolution
Un minion ne reçoit pas son pillarCache non rafraîchisalt '<minion>' saltutil.refresh_pillar
pillar.get renvoie vide dans un stateClé absente ou mauvais cheminFournir un repli : pillar.get('x', defaut)
Un minion voit un pillar qui ne le concerne pasCiblage trop large dans le top fileRestreindre le match, cibler les secrets par identifiant
  1. Les grains sont des faits du minion ; les pillars sont des données du master ciblées par minion.
  2. Le pillar top file décide qui reçoit quoi, par inclusion et ciblage.
  3. Salt masque les valeurs de pillar en sortie CLI : un garde-fou, la donnée circule quand même jusqu'au minion.
  4. pillar.get en syntaxe mapping avec une valeur de repli évite les états indéfinis.
  5. Un pillar clair n'est pas chiffré : les vrais secrets passent par GPG ou Vault.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn