Aller au contenu
Infrastructure as Code medium

Chef : gérer les secrets avec des data bags chiffrés

11 min de lecture

Logo Chef

Un mot de passe écrit dans un cookbook part en clair dans Git. Les data bags de Chef sortent la donnée du code, mais ne la protègent pas pour autant : un data bag ordinaire est lisible par tous. Ce module chiffre vraiment les secrets avec les data bags chiffrés (AES-256-GCM), les fait déchiffrer par le nœud au moment de la convergence, et empêche leur fuite dans les journaux. Public visé : intermédiaires ayant déjà déployé un cookbook sur une vraie machine.

  • Comprendre pourquoi un data bag clair ne suffit pas.
  • Chiffrer un data bag avec une clé partagée.
  • Consommer un secret dans une recette sans le journaliser.
  • Savoir quand passer à chef-vault.
  • Savoir écrire une recette et converger un nœud avec knife-zero.

Un data bag est un conteneur de données JSON, rangé hors des cookbooks. C'est déjà un progrès : le mot de passe ne traîne plus au milieu du code. Mais un data bag ordinaire reste un fichier JSON en clair. Versionné, il expose le secret à quiconque a accès au dépôt ; sur le poste, il est lisible par tout compte qui passe par là.

Il faut donc une couche de chiffrement. Chef la fournit nativement avec les data bags chiffrés : les valeurs sont chiffrées avec une clé secrète, et seuls ceux qui possèdent cette clé peuvent les relire.

Tout repose sur un secret partagé. On le génère une fois, avec suffisamment d'entropie, et on le protège comme l'actif le plus sensible du dépôt :

Fenêtre de terminal
openssl rand -base64 512 | tr -d '\r\n' > .chef/encrypted_data_bag_secret
chmod 600 .chef/encrypted_data_bag_secret

Il faut aussi indiquer à Chef où vivent les data bags, dans .chef/config.rb :

data_bag_path File.join(File.dirname(__FILE__), '..', 'data_bags')

On part d'un fichier JSON en clair, rangé hors du dépôt (par exemple dans un répertoire temporaire), qui décrit le secret :

{
"id": "db",
"user": "appuser",
"password": "S3cr3t-MySQL-2026"
}

La commande knife data bag from file le chiffre avec la clé et l'enregistre dans le dépôt :

Fenêtre de terminal
knife data bag create passwords -z
knife data bag from file passwords /tmp/chef-secrets/db.json \
--secret-file .chef/encrypted_data_bag_secret -z
Updated data_bag_item[passwords::db]

Le fichier écrit dans data_bags/passwords/db.json ne contient plus rien de lisible : chaque valeur devient un bloc chiffré en AES-256-GCM.

{
"id": "db",
"user": {
"encrypted_data": "Noie9gXmnlHnHRtQuHb8Jf+rsoCh7lhvMWQ=\n",
"iv": "g3JpE0SdXX9Y25V5\n",
"auth_tag": "KO+XqEqzb5xS4Z0KC74jTA==\n",
"version": 3,
"cipher": "aes-256-gcm"
},
"password": {
"encrypted_data": "ToL5cv6xpC5Be9XZTASzeFqcJ7qD3z9UNz296h6EfLlHaiV+\n",
"iv": "B7EpMnqRtmCL3iXp\n",
...
}
}

C'est ce fichier-là que vous versionnez, sans risque.

Le test décisif tient en deux commandes. Sans la clé, knife n'affiche que le chiffré :

Fenêtre de terminal
knife data bag show passwords db -z
id: db
password:
auth_tag: JKtJ+M1T/49AgbCNizX6WA==
cipher: aes-256-gcm
encrypted_data: ToL5cv6xpC5Be9XZTASzeFqcJ7qD3z9UNz296h6EfLlHaiV+

Avec la clé, le secret réapparaît :

Fenêtre de terminal
knife data bag show passwords db --secret-file .chef/encrypted_data_bag_secret -z
id: db
password: S3cr3t-MySQL-2026
user: appuser

Le nœud doit posséder la clé pour déchiffrer pendant la convergence. Par convention, elle se pose dans /etc/chef/encrypted_data_bag_secret, en 0600 et appartenant à root :

Fenêtre de terminal
sudo install -o root -g root -m 0600 secret /etc/chef/encrypted_data_bag_secret

En pratique, on ne fait pas ce geste à la main : knife bootstrap accepte l'option --secret-file, qui installe la clé sur le nœud au moment de son enrôlement. La clé arrive donc avec la machine, une fois pour toutes.

La recette charge la clé depuis le nœud, déchiffre le data bag, puis écrit la configuration applicative. Deux détails comptent : le mode 0600 du fichier produit, et surtout l'attribut sensitive true.

cookbooks/appsecrets/recipes/default.rb
secret = Chef::EncryptedDataBagItem.load_secret(
'/etc/chef/encrypted_data_bag_secret'
)
creds = Chef::EncryptedDataBagItem.load('passwords', 'db', secret)
directory '/etc/appdb' do
owner 'root'
group 'root'
mode '0750'
end
file '/etc/appdb/database.yml' do
owner 'root'
group 'root'
mode '0600'
sensitive true
content <<~CONF
production:
adapter: mysql2
username: #{creds['user']}
password: #{creds['password']}
CONF
end

On ajoute la recette au run-list du nœud, et on converge :

Fenêtre de terminal
knife zero converge 'name:web1'
Recipe: appsecrets::default
* directory[/etc/appdb] action create
- create new directory /etc/appdb
- change mode from '' to '0750'
* file[/etc/appdb/database.yml] action create
- create new file /etc/appdb/database.yml
- suppressed sensitive resource
- change mode from '' to '0600'
Infra Phase complete, 4/13 resources updated in 04 seconds

La ligne suppressed sensitive resource est la preuve que sensitive true fait son travail : Chef a bien écrit le fichier, mais n'a rien montré de son contenu.

Le résultat sur la machine est exactement celui attendu, avec des permissions strictes :

Fenêtre de terminal
sudo stat -c '%a %U:%G' /etc/appdb/database.yml
sudo cat /etc/appdb/database.yml
600 root:root
production:
adapter: mysql2
username: appuser
password: S3cr3t-MySQL-2026

Une seconde convergence ne doit rien changer : le nœud est déjà conforme.

* directory[/etc/appdb] action create (up to date)
* file[/etc/appdb/database.yml] action create (up to date)

« up to date » sur les deux ressources : le secret est en place, Chef le constate et n'écrit rien. La chaîne est complète : chiffré dans le dépôt, déchiffré sur le nœud, jamais journalisé, fichier en 0600.

Ce modèle a un défaut structurel qu'il faut regarder en face. La clé est partagée : tous les nœuds qui la possèdent peuvent déchiffrer tous les data bags, y compris ceux qui ne les concernent pas. Un serveur web compromis donne accès aux secrets de la base de données.

chef-vault répond exactement à cela : il chiffre chaque secret pour les clés publiques des nœuds autorisés, individuellement. Un serveur ne peut alors déchiffrer que ce qui lui a été destiné, et l'ajout d'un nœud se fait sans redistribuer une clé maîtresse.

Les échecs de déchiffrement ont presque toujours la même origine : le nœud n'a pas la bonne clé. Voici comment les distinguer.

SymptômeCause probableSolution
Cannot find a secret fileLe nœud n'a pas la cléPoser /etc/chef/encrypted_data_bag_secret (ou bootstrapper avec --secret-file)
DecryptionFailure à la convergenceLa clé du nœud diffère de celle du chiffrementRedéployer la même clé sur le nœud
Le data bag s'affiche en clairCréé sans --secret-fileLe recréer avec la clé : il n'a jamais été chiffré
Le mot de passe apparaît dans la sortiesensitive true oubliéL'ajouter, puis changer le secret exposé
id visible malgré le chiffrementComportement normalNe jamais mettre d'information sensible dans un identifiant
  1. Un data bag ordinaire est en clair : il sort la donnée du code, il ne la protège pas.
  2. Un data bag chiffré protège les valeurs en AES-256-GCM ; il est donc versionnable sans risque.
  3. Seules les valeurs sont chiffrées : l'id et les noms de champs restent lisibles.
  4. La clé encrypted_data_bag_secret ne va jamais dans Git et se distribue aux nœuds via knife bootstrap --secret-file.
  5. sensitive true empêche Chef d'afficher le secret dans la sortie de convergence, donc dans les logs et la CI.
  6. La clé partagée est la limite du modèle : chef-vault chiffre par nœud, mais exige un serveur Chef.

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