Gérer ses secrets avec sops

Gérer les secrets dans vos dépôts de code est une gageure? SOPS — pour Secrets OPerationS — est sûrement l’outil qu’il vous faut. Il permet une gestion simple et efficace de secrets, seul ou en équipe.

Dans cet article, nous allons voir comment l’installer, le paramétrer et l’utiliser. Nous aborderons aussi trois cas d’usage:

  1. chiffrer des variables d’environnement que nous utiliserons dans un script;
  2. chiffrer des secrets Kubernetes pour un environnement de test;
  3. utiliser sops-nix pour chiffrer les secrets utilisés dans un flake nix.

Avec en plus un petit focus sur le fonctionnement de sops-nix.

Premiers pas avec sops

Sops est capable de chiffrer des fichiers en utilisant plusieurs types de backend:

  • age, outil moderne de chiffrement sans configuration;
  • PGP;
  • services comme GCP KMS, AWS KMS, Hashicorp Vault ect.

Nous utiliserons pour cet article des clés age.

Installation

Installer SOPS

Malheureusement il n’est pas disponible de base dans les dépôts de la plupart des distributions *nix. Si vous n’êtes pas sous Archlinux, Alpine, FreeBSD ou encore NixOS mauvaise nouvelle: il faut l’installer manuellement.

Bonne nouvelle cependant: c’est un binaire statique (merci le go) disponible directement sur le dépôt Github du projet. Dans ce cas voici comment installer la dernière version (3.11 au moment de l’écriture de cet article)

# Télécharger le binaire
curl -LO https://github.com/getsops/sops/releases/download/v3.11.0/sops-v3.11.0.linux.amd64

# Télécharger le fichier de sommes de contrôles
curl -LO https://github.com/getsops/sops/releases/download/v3.11.0/sops-v3.11.0.checksums.txt

# Vérifier le binaire téléchargé
sha256sum -c sops-v3.11.0.checksums.txt --ignore-missing
# Résultat:
# sops-v3.11.0.linux.amd64: OK

chmod +x sops-v3.11.0.linux.amd64
sudo mv sops-v3.11.0.linux.amd64 /usr/local/bin/sops

# Maintenant on fait le ménage
rm sops-v3.11.0.checksums.txt

Vous pouvez aussi l’installer seulement pour votre utilisateur:

mv sops-v3.11.0.linux.amd64 ~/.local/bin/sops

Installer age

Nous avons besoin d’installer age pour avoir accès à la commande age-keygen pour — comme son nom l’indique — générer une clé au format age. Le paquet age est disponible dans les dépôts de paquets de la plupart des distributions. Pour l’installer sur Debian (et dérivés) par exemple:

sudo apt install age

Création de la paire de clé age

Maintenant que tout est installé, passons à la pratique. La création d’une clé age est relativement simple, mais nous devons d’abord créer le répertoire dans lequel SOPS ira la chercher.

mkdir -p "${XDG_CONFIG_HOME:-"$HOME/.config"}/sops/age/"

Pour enfin générer la paire de clés:

age-keygen -o "${XDG_CONFIG_HOME:-"$HOME/.config"}/sops/age/keys.txt"
# Résultat:
# Public key: age1r2g2ety9pa7079uxu39uykj2s50s2dx37pwj9d0j8y5tersrnc3q6caux0

pour MacOS

En général la variable d’environnement $XDG_CONFIG_HOME n’existe pas, dans ce cas sops cherchera dans le répertoire $HOME/Library/Application Support/sops/age/.

mkdir -p "${XDG_CONFIG_HOME:-$HOME/Library/Application Support}/sops/age/"
age-keygen -o "${XDG_CONFIG_HOME:-$HOME/Library/Application Support}/sops/age/keys.txt"

Premier essais

Place à la pratique… Commençons par un simple fichier texte contenant un message secret:

echo "Ceci est un message secret important" > message.txt

Chiffrons le message en utilisant la partie publique de la clé que nous générée plus haut:

sops encrypt \
  --age age1r2g2ety9pa7079uxu39uykj2s50s2dx37pwj9d0j8y5tersrnc3q6caux0 \
  message.txt > message.sops

Regardons de plus près le fichier message.sops:

{
  "data": "ENC[AES256_GCM,data:KF+0/XiB2GJL...,type:str]",
  "sops": {
    "kms": null,
    "gcp_kms": null,
    "azure_kv": null,
    "hc_vault": null,
    "age": [
    {
    "recipient": "age1r2g2ety9pa7079uxu39uykj2s50s2dx37pwj9d0j8y5tersrnc3q6caux0",
    "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi..."
    }
    ],
    "lastmodified": "2026-02-05T21:49:00Z",
    "mac": "ENC[AES256_GCM,data:3Y+0vjNtO...,type:str]",
    "pgp": null,
    "unencrypted_suffix": "_unencrypted",
    "version": "3.9.2"
  }
}

Comme vous pouvez le voir, c’est un fichier JSON. Les données chiffrées sont dans la clé data et les méta-données utiles à sops dans sops C’est dans sops.age que se trouvent les éléments relatifs à la clé age utilisée.

Remarquez le chemin sops.age: la clé enc contient le clé symétrique utilisée pour chiffrer le message. Cette clé est alors chiffrée avec la clé publique age utilisée.

Il est possible d’utiliser plusieurs clé age pour chiffrer un contenu. sops.age[] contiendra alors autant d’éléments que de clés publiques utilisées. Nous verrons ce cas de figure un peu plus loin.

sops.mac contient la somme de contrôle qui permet à sops de vérifier l’intégrité de l’ensemble du fichier.

Pour déchiffrer le message, rien de plus simple:

sops decrypt message.sops
# Résultat
# Ceci est un message secret important

Pas besoin de lui fournir la clé à utiliser: nous avons vu que les éléments contenu dans le chemin message.sops donnent déjà cette information. Sops est capable de trouver la clé privée correspondante dans le fichier ~/.config/sops/age/keys.txt (Linux). Si la clé ne se trouve pas à cet endroit, il est toujours possible de définir le chemin à utiliser via la variable d’environnement SOPS_AGE_KEY_FILE

Pour éditer un secret sans avoir à le déchiffrer / chiffrer il est possible d’utiliser la commande:

sops edit message.sops

Sops va déchiffrer le secret, l’ouvrir dans votre éditeur de texte par défaut1 et le chiffrer à nouveau une fois l’éditeur fermé.

Chiffrer un fichier structuré

Prenons maintenant un fichier yaml: example.yaml.

services:
  database:
    user: my-user
    password: myComplexP4ss#ord

Chiffrons-le:

sops encrypt \
  --age age1r2g2ety9pa7079uxu39uykj2s50s2dx37pwj9d0j8y5tersrnc3q6caux0 \
  example.yaml > example.sops.yaml

Et observons le fichier créé:

services:
  database:
    user: ENC[AES256_GCM,data:1KFhzewxGw==,iv:NJHlv...,type:str]
    password: ENC[AES256_GCM,data:Qs1wqVqCkL6,iv:GY...,type:str]
sops:
  kms: []
  gcp_kms: []
  azure_kv: []
  hc_vault: []
  age:
    - recipient: age1r2g2ety9pa7079uxu39uykj2s50s2dx37pwj9d0j8y5tersrnc3q6caux0
      enc: |
        -----BEGIN AGE ENCRYPTED FILE-----
        ...
        -----END AGE ENCRYPTED FILE-----
  lastmodified: "2026-02-05T22:15:58Z"
  mac: ENC[AES256_GCM,data:cP6FiaXKGdHBdVZulQ...,type:str]
  pgp: []
  unencrypted_suffix: _unencrypted
  version: 3.9.2

Vous remarquez tout de suite que le contenu des clés user et password sont chiffrés. Cependant ni le chemin (service.database) ni les nom des clés sont chiffrés. C’est le fonctionnement normal de sops, notamment pour faciliter la lecture des diffs avec git tout en protégeant les valeurs. On retrouve toujours les méta-données utiles sous le chemin sops.

Utiliser sops pour chiffrer un fichier .env

Lors du lancement d’un script, il est parfois nécessaire de lui passer des paramètres sensible comme des identifiants ou des clés API. La meilleure option pour le faire reste alors les variables d’environnement, les valeurs sensibles ne se retrouvent pas dans votre historique par exemple.

Penons un simple script Bash suivant:

#!/usr/bin/env bash

MY_USER=${MY_USER:-"none"}
MY_PASSWORD=${MY_PASSWORD:-"none"}

main() {
  printf "Secret -> user: '%s' / password: '%s'\n" "$MY_USER" "$MY_PASSWORD"
}

main

Il est possible de définir les variables MY_USER et MY_PASSWORD lors du lancement du script comme par exemple:

MY_USER=ephase MY_PASSWORD=secret ./example.sh
# Résultat:
# Secret -> user: 'ephase' / password: 'secret'

Mais pourquoi ne pas utiliser un fichier .env chiffré avec sops? Parce que les secrets se retrouveraient alors dans le même dépôt que le code. Dans ce cas, utilisons sops pour chiffrer les variables.

configurer sops

Comme nous l’avons vu, il est nécessaire de fournir une clé age lors de l’appel de la commande sops avec l’option --age. Mais il est aussi possible de configurer SOPS par l’intermédiaire d’un fichier .sops.yaml.

Lors de son appel, la commande sops cherche ce fichier dans le dossier courant et tous ses parents jusqu’à en trouver un. En règle générale, il se trouve à la racine du dépôt git du projet en cours.

Voici la configuration que nous allons utiliser:

creation_rules:
  - path_regex: .*\.env
    key_groups:
      - age:
          - age1r2g2ety9pa7079uxu39uykj2s50s2dx37pwj9d0j8y5tersrnc3q6caux0

creation_rules permet de définir un ensemble de règles à appliquer pour le chiffrement des secrets. Chaque règle contenue dans creation_rules permet de définir un ensemble de configuration à appliquer en fonction d’un chemin.

  • L’expression régulière définie dans path_regex donne les fichiers ciblés par la règle, ici les fichiers se terminant par .env.
  • key_groups défini les groupe de clés à utiliser pour chiffrer / déchiffrer le contenu. Nous avons ici un groupe utilisant la clé age créée précédemment.

Créer chiffrer, utiliser un fichier dotenv

Commençons par créer un fichier .test.env avec le contenu suivant:

MY_USER=ephase
MY_PASSWORD=MySup4rS3cRetP4Ss

Maintenant chiffrons notre secret mais cette fois utilisons l’option -i (--in-place) de sops permettant de chiffrer le fichier directement au lieu de l’afficher sur la sortie standard.

sops encrypt --in-place .test.env

Et comme nous avons configuré sops, pas besoin de fournir la clé à la commande.

Maintenant comment utiliser notre fichier dotenv? Tout simplement avec la commande sops exec-env:

sops exec-env .test.env ./example.sh
# Résultat
# Secret -> user: 'ephase' / password: 'MySup4rS3cRetP4Ss'

SOPS a donc déchiffré le secret et injecté les variables dans l’environnement passé au script example.sh. Notre script a donc pu en afficher le contenu.

Quel usage?

J’utilise principalement sops exec-enc pour chiffrer des clés API / identifiants utilisées par des scripts. Il est alors plus simple de gérer les secrets d’un projet donné.

Je l’utilise aussi Avec OpenTofu pour protéger la phrase de passe utilisée pour chiffrer les fichiers states.

Utiliser SOPS avec Kustomize pour chiffrer des secrets Kubernetes

Laisser les secrets Kubernetes dans un dépôt git n’est pas possible, il seraient exposés. Nous avons vu que SOPS permet de les chiffrer, mais il deviennent alors inutilisable tel quel. C’est ici de KSOPS rentre en jeu.

Le but ici est de créer un message chiffré dans un secrets Kubernetes qui sera utilisé dans un pod. Nous utiliserons Kubernetes in docker, Kubectl, kustomize et KSOPS.

Dans cet exemple le secret sera déchiffré localement par ksops. Mais il est aussi possible de l’intégrer à ArgoCD pour que le déchiffrement se fasse directement dans le cluster, mais on sort du cadre de l’exemple proposé ici.

Installation

Kind, Kubectl et Kustomize sont disponibles dans les dépôts de la plupart des distributions. Pour Debian et distributions dérivées, l’installation se fait simplement:

sudo apt install kind kubectl kustomize

Docker (ou un autre moteur de conteneurisation) doit être installé pour que Kind fonctionne, installez-le suivant les recommandation de votre distribution.

KSOPS n’est pas disponible dans les dépôts de la quasi-totalité des distributions (mis à part NixOS). Il faut donc télécharger le binaire compilé depuis le dépôt Github et l’installer

# Téléchargement de ksops et du fichier sommes de contrôles
curl -LO https://github.com/viaduct-ai/kustomize-sops/releases/download/v4.4.0/ksops_4.4.0_Linux_x86_64.tar.gz
curl -LO https://github.com/viaduct-ai/kustomize-sops/releases/download/v4.4.0/checksums.txt

# Vérification du fichier téléchargé
sha256sum -c checksums.txt --ignore-missing
# Résultat:
# ksops_4.4.0_Linux_x86_64.tar.gz: OK

# Création du répertoire pour accueillir le plugin
mkdir -p ~/.config/kustomize/plugin/viaduct.ai/v1/ksops/

# et installation du plugin
sudo tar -C /usr/local/bin -xf ksops_4.4.0_Linux_x86_64.tar.gz ksops

# Maintenant on fait le ménage
rm ksops_4.4.0_Linux_x86_64.tar.gz checksums.txt

Avec un devshell nix

Pour les afficionados de Nix, voici un devshell adapté avec les dépendances (télécharger le fichier):

let
  nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-unstable";
  pkgs = import nixpkgs {
    config = {};
    overlays = [];
  };
in
  pkgs.mkShellNoCC {
    packages = with pkgs; [
      age
      sops
      ssh-to-age
      kind
      kubectl
      kustomize
    ];
  }

Que nous pouvez lancer avec la commande nix-shell ou en utilisant direnv

Configuration de sops

Reprenons le fichier .sops.yaml pour y ajouter la configuration que nous allons utiliser pour les fichiers YAML.

creation_rules:
  - path_regex: .*\.env
    key_groups:
      - age:
          - age1r2g2ety9pa7079uxu39uykj2s50s2dx37pwj9d0j8y5tersrnc3q6caux0
  - path_regex: .*\.sops\.yaml
    encrypted_regex: ^(data|stringData)$
    key_groups:
      - age:
          - age1r2g2ety9pa7079uxu39uykj2s50s2dx37pwj9d0j8y5tersrnc3q6caux0

encrypted_regex permet de définit les clés dont le conrenu sera chiffré dans nos fichiers YAML, ici seulement le contenu de data ou stringData.

Création du secret

Mettons en place notre secret Kubernetes dans le fichier secret-page.secret.sops.yaml:

apiVersion: v1
kind: Secret
metadata:
    name: secret-page
stringData:
    index.html: |
        <html>
        <head>
        <title>Secret Page!</title>
        </head>
        <body>
        <h1>Secret Page</h2>
        <p>This is a top secret page</p>
        </body>
        </html>

Nous utiliserons ce secret comme page HTML index.html dans un pod Nginx. Il sera facile de contrôler que tout fonctionne comme attendu. Maintenant chiffrons-le en utilisant SOPS:

sops encrypt -i secret-page.secret.sops.yaml

Le secret ne peut plus être utilisé comme tel par l’API Kubernetes…

Mise en place de la kustomization

Pour utiliser notre secret, mettons en place un pod Nginx et le service associé2. Voici le manifeste nginx.deployment.yaml (télécharger le fichier):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: nginx
  replicas: 1
  template:
    metadata:
      labels:
        app.kubernetes.io/name: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.29.4-alpine
          ports:
            - containerPort: 80
          volumeMounts:
            - name: secret-file
              mountPath: /usr/share/nginx/html
              readOnly: true
      volumes:
        - name: secret-file
          secret:
            secretName: secret-page
            items:
              - key: index.html
                path: index.html

Le secret est utilisé comme un volume et sera monté dans le conteneur nginx comme un fichier. Voici maintenant le service associé nginx.service.yaml (télécharger le fichier):

apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  selector:
    app.kubernetes.io/name: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

Utiliser kustomize-sops

Nous devons maintenant faire passer notre secrets dans la moulinette KSOPS qui le déchiffrera. C’est ce que défini notre manifest secrets-generator.yaml dont voici le contenu (télécharger le fichier):

apiVersion: viaduct.ai/v1
kind: ksops
metadata:
  name: nginx-secrets
  annotations:
    config.kubernetes.io/function: |
      exec:
        path: ksops
files:
  - ./secret-page.secret.sops.yaml

Nous avons un seul secret dans notre example, mais il est tout à fait possible de mettre plusieurs éléments dans la liste files.

La Kustomization

Ajoutons la glue à tous ces éléments dans le fichier kustomization.yaml (télécharger le fichier):

namespace: default
generators:
  - ./secrets-generator.yaml
resources:
  - ./nginx.deployment.yaml
  - ./nginx.service.yaml

En plus des resources, nous ajoutons une section generators qui permet à Kustomize de générer dynamiquement des ressources Kubernetes. C’est dans cette section que nous ajoutons notre manifest KSOPS.

Mise en place du cluster

Notre Kustomization est prête, il reste plus qu’à tester dans un cluster local. Commençons par le créer:

kind create cluster --name=sops-test

Si vous avez déjà des clusters définis dans votre configuration Kubernetes il est préférable de s’assurer que le bon cluster est choisi:

kubectl config use-context kind-sops-test

Nous pouvons maintenant appliquer la kustomization que nous venons de créer:

kustomize build --enable-alpha-plugins --enable-exec . | kubectl apply -f -
# Résultat:
# Secret/secret-page created
# Service/nginx created
# Deployment.apps/nginx-deployment created

Vérifier que ça fonctionne

Il n’y a d’Ingress ni même de gateway de défini dans notre cluster local, mais un forward de port nous permet de vérifier que notre pod Nginx renvoie bien notre page secrète:

kubectl port-forward services/nginx 8080:80

Il suffit ensuite d’ouvrir votre navigateur favori adresse http://localhost:8080. Sinon dans une seconde fenêtre de terminal, lancer curl fait aussi l’affaire:

curl localhost:8080
# Résultat:
# <html>
# <head>
# <title>Secret Page!</title>
# </head>
# <body>
# <h1>Secret Page</h2>
# <p>This is a top secret page</p>
# </body>
# </html>

Quel usage? KSOPS, kind et ArgoCD

Cet exemple n’est rien de plus qu’une illustration de l’usage de KSOPS avec Kubernetes. J’utilise KSOPS principalement dans le cadre professionnel.

  • Pour la mise d’environnements locaux de développement avec kind pour — par exemple — chiffrer les clés API nécessaire pour tester des services externes;
  • Conjointement avec ArgoCD pour le déploiement des secrets utilisés dans les différentes applications, voir la documentation de KSOPS.

Utiliser sops-nix

La gestion quotidienne de machines avec NixOS — serveurs ou postes utilisateurs — nécessite souvent de gérer des secrets (clés, mots de passe services / utilisateurs, etc.). Mais comment le faire sans les compromettre?

sops-nix stocke les secrets chiffrés dans le nix-store (à froid) et se charge de les déchiffrer. Chaque secret déchiffré est stockés en mémoire sous la forme d’un fichier accessibles dans le dossier /run/secrets. Il est possible d’utiliser les clés SSH pour générer à la volée des clés age, il est possible d’utiliser par exemple les clés SSH de l’hôte.

Dernière partie de cet article, nous allons utiliser sops-nix afin de chiffrer les secrets de notre configuration NixOS. Nous utiliserons une machine virtuelle QEMU que nous créerons avec nixos-rebuild à partir d’un flake pour tester que tout fonctionne comme attendu.

En plus de sops et age, vous aurez besoin d’installer le paquet ssh-to-age pour cette partie. Si besoin voici un fichier shell.nix qui vous permettra d’installer les dépendances nécessaires:

let
  nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-unstable";
  pkgs = import nixpkgs {
    config = {};
    overlays = [];
  };
in
  pkgs.mkShellNoCC {
    packages = with pkgs; [
      age
      sops
      ssh-to-age
    ];
  }

Une fois dans le dossier contenant ce fichier, la commande nix-shell vous permettra d’activer ce shell.

Mise en place du flake

Commençons par créer le fichier flake.nix avec le contenu suivant:

{
  description = "Test sops-nix using a nix VM";
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    sops-nix.url = "github:Mic92/sops-nix";
    sops-nix.inputs.nixpkgs.follows = "nixpkgs";
  };
  outputs = {
    self,
    nixpkgs,
    sops-nix,
  }: {
    nixosConfigurations = {
      "sops-nix-test" = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [
          ./configuration.nix
          sops-nix.nixosModules.sops
        ];
      };
    };
  };
}

Remarquez l’input mic92/sops-nix qui rend disponible les modules sops-nix pour NixOS et Home-Manager. Dans notre exemple nous utiliserons seulement celui pour NixOS que nous ajoutons dans la définition de notre nixosConfiguration.

Passons maintenant à la configuration de notre machine, créez un fichier configuration.nix avec le contenu suivant:

{config, ...}: {
  system.stateVersion = "26.05";
  services.sshd.enable = true;
  networking.hostName = "sops-nix-test";
}

nixos-rebuild couplé la commande build-vm nous permet de créer la machine virtuelle partir de notre flake:

nixos-rebuild --flake .#sops-nix-test build-vm

Nous sommes maintenant prêt pour le lancement de la VM avec les options QEMU nous pemettant de nous connecter en SSH via le port local 2222:

QEMU_NET_OPTS="hostfwd=tcp::2222-:22" ./result/bin/run-sops-nix-test-vm

Configurer sops-nix

Depuis autre terminal, récupérez la clé age publique dérivée de la partie publique de clé SSH de l’hôte nouvellement créé avec la commande suivante:

ssh-keyscan -p 2222 -t ed25519 127.0.0.1 | ssh-to-age
# Résultat:
# age16y5yrgkuwzpumjyv8hqdgmkg5vx3v4fks7ce2qwcnfn03g593gyqt5kpel

Nous avons maintenant la clé age publique de notre machine virtuelle dérivée de sa clé SSH ED25519. Vous pouvez éteindre la VM (menu machine puis PowerDown dans la fenêtre QEMU). Rajoutons-la dans les clés autorisées à déchiffrer les secrets dans la configuration de sops (.sops.yaml):

creation_rules:
  - path_regex: secrets.yaml
    key_groups:
      - age:
          - age1r2g2ety9pa7079uxu39uykj2s50s2dx37pwj9d0j8y5tersrnc3q6caux0
          - age16y5yrgkuwzpumjyv8hqdgmkg5vx3v4fks7ce2qwcnfn03g593gyqt5kpel

Notre fichier de configuration .sops.yaml doit contenir deux clés publique:

  • celle que nous avons créée au début de cet article
  • et celle de notre machine virtuelle.

Mettre en place un service

Pour illustrer le paramétrage d’un service avec sops-nix, nous allons installer Redis protégé par un mot de passe.

Dans le fichiersecrets.yaml ajoutons une entrée pour le mot de passe:

redisPassword: Sup3rPassWord

Chiffrons le avec avec SOPS:

sops encrypt -i secrets.yaml

Modifions le fichier configuration.nix pour ajouter les configurations de sops-nix et de Redis.

{config, ...}: {
  system.stateVersion = "26.05";
  services.sshd.enable = true;
  networking.hostName = "sops-nix-test";

  sops.defaultSopsFile = ./secrets.yaml;
  sops.age.sshKeyPaths = ["/etc/ssh/ssh_host_ed25519_key"];
  sops.secrets."redisPassword" = {};

  services.redis = {
    servers."" = {
      enable = true;
      requirePassFile = config.sops.secrets.redisPassword.path;
      bind = null;
      openFirewall = true;
    };
  };
}
  • sops.defaultSopsFile nous donne le fichier contenant les secrets.
  • sops.age.sshKeyPath défini le chemin vers la clé SSH à utiliser. Ici la partie privée de la clé hôte au format ED25519.
  • Enfin sops.secrets."redisPassword" rend disponible le secret redisPassword dans notre flake. Le bloc {} qui suit permet de définir des options relatives au secret comme par exemple les droits sur le fichier, son propriétaire ou encore les services à relancer si le secret change.

Pour le service Redis, la mise en place du mot de passe passe par l’option requirePassFile. Cette variable contient maintenant le chemin vers le secrets.

Reconstruisons notre machine virtuelle et relançons-la:

# Reconstruire la machine virtuelle
nixos-rebuild --flake .#sops-nix-test build-vm

# lancer la machine
QEMU_NET_OPTS="hostfwd=tcp::6379-:6379"  ./result/bin/run-sops-nix-test-vm

Testons l’ajout d’une clé dans la base de donnée depuis un autre terminal:

nix run nixpkgs#redis -- SET "mykey" "This is the way"
# Résultat:
# (error) NOAUTH Authentication required.

Opération refusée, maintenant avec un mot de passe:

nix run nixpkgs#redis -- -a "Sup3rPassWord" --no-auth-warning SET "mykey" "This is the way"
# Résultat:
# OK

Notre service Redis a bien pris en compte le mot de passe chiffré dans notre secret et ainsi ajouté la clé demandée. Vous vous demandez peut-être comment tout ça fonctionne? Nous y reviendrons un peu plus tard, mais prenons un autre exemple.

Définir le mot de passe utilisateur

Autre exemple d’utilisation de nix-sops: définir le mot de passe d’un utilisateur. Il nous faut ajouter un mot de passe dans le fichier secrets.yaml. Pour commencer, nous devons créer un condensat (hash) de notre mot de passe

printf 'MyUs3rP4ss' | mkpasswd -s
# Résultats
# $y$j9T$U/L3qNKoZe7bCnjTjmLCl1$inSOljmq.y7y8znstj9wKBgy0msfW/jWIgDEfj3agb6

Maintenant éditons notre fichier secrets.yaml:

sops edit secrets.yaml

Et ajoutons une clé hashedPassword contenant le condensat donné par la commande précédente:

redisPassword: Sup3rPassWord
hashedPassword: $y$j9T$U/L3qNKoZe7bCnjTjmLCl1$inSOljmq.y7y8znstj9wKBgy0msfW/jWIgDEfj3agb6

Il nous reste qu’à définir notre utilisateur dans le fichier configuration.nix:

{config, ...}: {
  system.stateVersion = "26.05";
  services.sshd.enable = true;
  networking.hostName = "sops-nix-test";

  sops.defaultSopsFile = ./secrets.yaml;
  sops.age.sshKeyPaths = ["/etc/ssh/ssh_host_ed25519_key"];
  sops.secrets."redisPassword" = {};
  sops.secrets."hashedPassword" = {
    neededForUsers = true;
  };

  services.redis = {
    servers."" = {
      enable = true;
      requirePassFile = config.sops.secrets.redisPassword.path;
      bind = null;
      openFirewall = true;
    };
  };

  users.users."user" = {
    hashedPasswordFile = config.sops.secrets.hashedPassword.path;
    isNormalUser = true;
    extraGroups = ["wheel"];
  };
}

L’option neededForUsers permet à sops-nix de déchiffrer le secret avant que NixOS crée le compte utilisateur.

Il faut reconstruire notre machine virtuelle et la lancer:

nixos-rebuild --flake .#sops-nix-test build-vm

# pour permettre l'accès à la machine en SSH, il faut rediriger le port 22 vers
# le 2222 de notre hôte
QEMU_NET_OPTS="hostfwd=tcp::2222-:22,hostfwd=tcp::6379-:6379"  ./result/bin/run-sops-nix-test-vm

Pour tester, nous allons nous connecter à notre machine virtuelle en utilisant SSH depuis un autre terminal et notre Sup3rPassWord:

ssh user@127.0.0.1 -p 2222 -o NoHostAuthenticationForLocalhost=yes
# Résultat:
# (user@127.0.0.1) Password:
# [user@sops-nix-test:~]$

Fonctionnement de sops-nix

Maintenant que nous somme connecté à la machine virtuelle, nous pouvons en apprendre plus sur le fonctionnement de nix-sos. Pour commence, essayons d’accéder au secret Redis:

ls -l /run/secrets/
# Résultat:
# ls: cannot open directory '/run/secrets/': Permission denied

Impossible d’y accéder en tant qu’utilsateur normal. Cependant, l’utilisateur user fait parti du groupe wheel, nous pouvons donc utiliser sudo pour lister et afficher le secrets:

sudo ls -l /run/secrets/
# Résultat:
# [sudo] password for user:
# Total 4
# -r-------- 1 root root 13 Feb 10 10:19 redisPassword

sudo cat /run/secrets/redisPassword
# Résultat:
# Sup3rPassWord

Le mot de passe de notre usilisateur se trouve dans un dossier différent:

cat /run/secrets-for-users/hashedPassword
# Résultat:
# $y$j9T$U/L3qNKoZe7bCnjTjmLCl1$inSOljmq.y7y8znstj9wKBgy0msfW/jWIgDEfj3agb6

Comment sont déchiffrés les secrets

Au démarrage de NixOS, Le script d’activation de l’environnement /run/current-system/activate se charge de lancer la phase 2 du boot. C’est ici que sops-nix installe les appels à sops-install-secrets chargé du déchiffrement et de la configuration des secrets.

Première partie intéressante du script, la mise en place des secrets pour les mots de passe utilisateurs.

#### Activation script snippet setupSecretsForUsers:
_localstatus=0
[ -e /run/current-system ] || echo setting up secret
(
  export HOME='/var/empty'
  export PATH=''
  export SOPS_GPG_EXEC='/nix/store/hafxwi09djh8aknfm7lkii9qdnslv4hn-gnupg-2.4.8/bin/gpg'
  /nix/store/fzzj17d3czyxkq5c3bq677qx1yamkhra-sops-install-secrets-0.0.1/bin/sops-install-secrets -ignore-passwd /nix/store/1p2zyqdrl9hxb0rfbcjglk29bm405y3x-manifest-for-users.json
)

Dans cet exemple, configuration passée au programme sops-install-secrets se trouve dans un fichier /nix/store/1p2zyqdrl9hxb0rfbcjglk29bm405y3x-manifest-for-users.json stocké dans le store nix. Ce fichier est créé lors de la construction de l’environnement NixOS avec nixos-rebuild build. On y trouve les chemins vers les fichiers YAML contenant nos secrets ainsi que les options associées.

Juste en dessous se trouve l’appel au script Perl chargé de créer utilisateurs et groupes

#### Activation script snippet users:
_localstatus=0
install -m 0700 -d /root
install -m 0755 -d /home

/nix/store/cv4kpqppwn43gkf1zm36s4q3y4ashps2-perl-5.42.0-env/bin/perl \
-w /nix/store/dyx8qsmgnsz2csfzwn723gbd2jvp0j7g-update-users-groups.pl /nix/store/jxnmyw841lbcvm7vm7ir07phkbpwjcv3-users-groups.json

Un peu plus bas dans le script nous retrouvons la partie relative au déchiffrement des secrets “standards”.

### Activation script snippet setupSecrets:
_localstatus=0
[ -e /run/current-system ] || echo setting up secrets...
(
  export HOME='/var/empty'
  export PATH=''
  export SOPS_GPG_EXEC='/nix/store/hafxwi09djh8aknfm7lkii9qdnslv4hn-gnupg-2.4.8/bin/gpg'
  /nix/store/fzzj17d3czyxkq5c3bq677qx1yamkhra-sops-install-secrets-0.0.1/bin/sops-install-secrets /nix/store/g6vj1x5m8h37vrb1wpla2wi731i23qdl-manifest.json
)

Là encore un fichier de configuration au format JSON est passé en paramètre de la commande sops-install-secrets.

Avec Home-manager

Si vous utilisez Home-Manager, c’est un service utilisateur qui se charge de déchiffrer les secrets toujours en utilisant sops-install-secrets. Sous Linux, systemctl permet de vérifier le service:

systemctl status --user sops-nix.service
# Résultat:
# ○ sops-nix.service - sops-nix activation
#      Loaded: loaded (/home/user/.config/systemd/user/sops-nix.service; enabled; preset: ignored)
#      Active: inactive (dead) since Fri 2026-02-13 20:38:12 CET; 3h 36min ago
#  Invocation: cdb51bbc91ac459daac695d810123f9a
#     Process: 1716 ExecStart=/nix/store/5nahw837w7f4as2zhs6yj9mh0yqd3x8i-sops-nix-user (code=exited, status=0/SUCCESS)
#    Main PID: 1716 (code=exited, status=0/SUCCESS)
#    Mem peak: 48.6M
#         CPU: 38ms
#
# Feb 13 20:38:12 hostname systemd[1701]: Starting sops-nix activation...
# Feb 13 20:38:12 hostname systemd[1701]: Finished sops-nix activation.

Quel usage?

Pour le moment, il me sert à provisionner les mots de passe pour mes comptes mails et Webdav. Je l’utilise donc exclusivement avec Home-Manager.

En conclusion

J’espère vous avoir démontré par ces trois example que SOPS est un outil incontournable pour gérer simplement les secrets dans vos dépôts de code. Malgré un article assez conséquent, nous avons juste survolé les différentes possibilités offertes par SOPS. Je vous invite donc à parcourir sa documentation mais aussi celle des différents projets présentés.

Cet article a été écrit suite à une demande d’Heuzef, merci d’ailleurs pour les nombreuses relectures et les innombrables crash tests des exemple donnés. Merci aussi Gwendal et Yishan pour les conseils et corrections.


  1. défini par la variable d’environnement $EDITOR 

  2. Le service n’est pas strictement nécessaire…