Vous utilisez peut-être SOPS pour gérer vos secrets dans vos dépôts Git, peut-être même déjà avec Ansible ? Mais est-ce que vous vous simplifiez vraiment la vie ? Je vais vous présenter une structure de projet et une méthode qui tend vers une logique GitOps.

Structure du projet

Avec Ansible, il y a des structures recommandées pour les fichiers et moi je vous recommande la version alternative : cela permet en spécifiant juste le dossier d’inventaire d’utiliser les bons hosts et les variables qui vont avec :

inventories/
   production/
      hosts               # inventory file for production servers
      group_vars/
         group1.yaml       # here we assign variables to particular groups
         group2.yaml
      host_vars/
         hostname1.yaml    # here we assign variables to particular systems
         hostname2.yaml

   staging/
      hosts               # inventory file for staging environment
      group_vars/
         group1.yaml       # here we assign variables to particular groups
         group2.yaml
      host_vars/
         stagehost1.yaml   # here we assign variables to particular systems
         stagehost2.yaml

library/
module_utils/
filter_plugins/

site.yaml
webservers.yaml
dbservers.yaml

roles/
    common/
    webtier/
    monitoring/
    fooapp/

La structure alternative recommandée par Ansible

Configuration pour intégrer SOPS à Ansible

Si vous voulez utiliser des fichiers de variables chiffrés avec SOPS, il va vous falloir plusieurs choses :

  • Installer de la collection communautaire Ansible, qui contient community.sops.sops :
ansible-galaxy collection install community.general
  • Activer le plugin community.sops.sops dans le fichier de configuration ansible.cfg :
# ansible.cfg
[defaults]
vars_plugins_enabled = host_group_vars,community.sops.sops
# Au passage je definis mon environnement par défaut, le dev
inventory = ./environments/dev

Un fichier .sops.yaml à la racine du projet :

# .sops.yaml
creation_rules:
  - path_regex: "environments/prod/.*.sops.yaml"
    key_groups:
      - age:
          - "agekey_user1"
  - path_regex: "environments/dev/.*.sops.yaml"
    key_groups:
      - age:
          - "agekey_user1"
          - "agekey_user2"

  - path_regex: "environments/common/.*.sops.yaml"
    key_groups:
      - age:
          - "agekey_user1"
          - "agekey_user2"
Info

Là j’ai fais le choix d’utiliser des clés age pour le chiffrement/déchiffrement, mais vous pouvez utiliser GPG ou encore un vault.

Pourquoi ne pas stocker directement les secrets dans un vault ?

Cela me permet d’avoir une seule source de vérité pour mon infra. Si je dois rollback quelque chose cela m’évite de modifier de la configuration en plusieurs endroits (c’est à peu presque du GitOps).

Si vous voulez générer une nouvelle paire de clés et qu’elle soit utilisée par SOPS, une fois age installé :

mkdir -p $HOME/.config/sops/age/
echo '# name: my new age key' >> $HOME/.config/sops/age/keys.txt
age-keygen | tee >> $HOME/.config/sops/age/keys.txt

Vous pourrez ensuite créer votre structure de dossier comme ceci :

environments/
  dev/
    hosts
    group_vars/all/
      main.yaml
      main.sops.yaml
  prod/
    hosts
    group_vars/all/
      main.yaml
      main.sops.yaml

En utilisant sops environments/dev/group_vars/all/main.sops.yaml pour les fichiers chiffrés.

Le fichier hosts est votre inventaire dans le format de votre choix (ça peut même être un inventaire dynamique)

J’ai appelé les fichiers de variables main, mais vous pouvez les nommer comme bon vous semble, faites juste attention à respecter vos règles de création définies dans .sops.yaml

Variables communes à plusieurs environnements

Dans .sops.yaml, vous avez vu la référence à un dossier common.
C’est pratique pour définir des variables partagées entre tous les environnements.

Je vais donc créer un dossier common avec dedans deux fichier, un chiffré un en clair.

mkdir -p environments/common/group_vars/all/
touch environments/common/group_vars/all/000_common.yaml
sops environments/common/group_vars/all/000_common.sops.yaml
Conseil

Le préfixe 000 garantit que ces fichiers sont lus en premier par Ansible (ordre alphanumérique : 0→9→a→z).

Ensuite, je veux que ces fichiers soient pris en compte dans mes autres environnements, je vais avoir recours à des liens symboliques !

ln -sf environments/common/group_vars/all/* environments/dev/group_vars/all/
ln -sf environments/common/group_vars/all/* environments/prod/group_vars/all/

Et voilà à quoi va ressembler la structure finale :

environments/
  common/
    group_vars/all/
      000_common.yaml
      000_common.sops.yaml
  dev/
    hosts
    group_vars/all/
      000_common.yaml --> `../../../common/group_vars/all/000_common.yaml`
      000_common.sops.yaml `../../../common/group_vars/all/000_common.sops.yaml`
      main.yaml
      main.sops.yaml
  prod/
    hosts
    group_vars/all/
      000_common.yaml --> `../../../common/group_vars/all/000_common.yaml`
      000_common.sops.yaml `../../../common/group_vars/all/000_common.sops.yaml`
      main.yaml
      main.sops.yaml

Vous pouvez ensuite utiliser le dossier au complet avec vos playbooks !

# Déployer en dev (par défaut, ansible.cfg)
ansible-playbook deploy.yml
# Déployer en production
ansible-playbook -i environments/prod deploy.yml

Conclusion

Il existe mille façons d’organiser un projet Ansible et d’utiliser SOPS. J’aime cette approche car elle est simple : un dossier = un environnement.

Libre à vous ensuite de :

  • créer plus de dossiers par groupe de machines
  • séparer par brique applicative
  • ou affiner par rôle

Bref, adaptez selon vos besoins 😗