
Chiffrement et déchiffrement
Article Wikipedia : https://fr.wikipedia.org/wiki/Chiffrement
Journaux liées à cette note :
Workflow de gestion des secrets d'un projet basé sur Age et des clés ssh
De 2018 à 2023, j'utilisais l'outil "pass" (https://www.passwordstore.org/) couplé avec GNU Privacy Guard pour chiffrer et déchiffrer les secrets dans mes projets professionnels.
Bien que cette méthode fonctionnait, elle s'avérait laborieuse.
La procédure documentant l'installation, la création des clés et la configuration de gnupg était l'étape posant le plus de difficultés d'onboarding des développeur sur ces projets.
Au feeling, je dirais que trois développeurs sur quatre bloquaient à cette étape.
En parallèle à cette méthode, j'ai essayé d'utiliser la cli de Bitwarden, mais l'expérience n'a pas été très concluante, principalement à cause de problèmes de latence lors de son exécution.
Aujourd'hui, j'ai exploré une nouvelle méthode basée sur Age et son support natif des clés ssh.
Voici le repository Git du résultat de cette exploration : age-secret-skeleton
.
Le résultat est très positif et je pense avoir trouvé ma nouvelle méthode de chiffrement des secrets dans mes projets 🙂.
Pour en savoir plus, vous pouvez à la fois suivre le README.me du repository et continuer la lecture de cette note.
Détail des fichiers du repository
.
├── .envrc
├── .gitignore
├── .mise.toml
├── README.md
├── scripts
│ ├── decrypt_secrets.sh
│ └── encrypt_secrets.sh
├── .secret
├── .secret.age
├── .secret.skel
└── ssh-keys
└── stephane-klein.pub
/.secret
: fichier qui contient les secrets en clair. Ce fichier est ignoré par.gitignore
afin qu'il ne soit pas présent dans le repository git/.secret.age
: fichier qui contient le contenu du fichier.secret
chiffré avec Age. Ce fichier est ajouté au repository git./ssh-keys/
: ce dossier contient les clés publiques ssh des personnes qui ont accès au contenu de.secret.age
/scripts/decrypt_secrets.sh
: permet de déchiffrer avec Age le contenu de.secret.age
et écrit le résultat en clair dans.secret
/scripts/encrypt_secrets.sh
: permet de chiffrer avec Age le contenu de.secret
vers.secret.age
. Ce script doit être exécuté quand le contenu de.secret
est modifié ou quand une nouvelle clé est ajoutée dans/ssh-keys/
/.envrc
: Dans ce skeleton, ce fichier charge uniquement les variables d'environnement présentes dans.secret
mais dans un vrai projet, il permet de charger automatiquement des variables d'environnement qui ne sont pas des secrets./.secret.skel
: ce fichier contient l'exemple de contenu du fichier.secret
, sans les secrets. Par exemple :
# You can either request these secrets from Stéphane Klein (contact@stephane-klein.info)
# or, if your public ssh key is present in `./ssh-keys/` use the ./scripts/decrypt_secrets.sh command
# which will automatically decrypt the .secret.age file to .secret
export POSTGRES_PASSWORD="..."
export SCW_SECRET_KEY="..."
Ce fichier n'est pas nécessaire au bon fonctionnement du workflow mais je le trouve utile pour :
- documenter le contenu de
.secret
aux personnes qui ont juste accès au dépôt Git - permettre de review les modifications de
.secret.skel
dans des Merge Request.
Utilisation des clés ssh
L'un des avantages majeurs de Age par rapport à pass + gnupg est son support natif des clés SSH pour chiffrer et déchiffrer les secrets.
Ainsi, je n'ai plus besoin de demander aux développeurs deux types de clés (SSH et gnupg) : une seule clé SSH suffit.
De plus, il me semble qu'un grand nombre de développeurs possèdent déjà une clé SSH, alors que je pense que gnupg reste une technologie bien moins répandue.
Détail d'implémentation des scripts
Vvoici le contenu de /scripts/encrypt_secrets.sh
:
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/../"
# Prepare recipient arguments for age
recipient_args=()
for pubkey in ./ssh-keys/*.pub; do
if [ -f "$pubkey" ]; then
recipient_args+=("-R" "$pubkey")
fi
done
# Execute age with all public keys
age "${recipient_args[@]}" -o .secret.age .secret
La boucle permet de passer toutes les clés ssh du dossier /ssh-keys/*
en paramètre d' Age.
Voici le contenu de /scripts/decrypt_secrets.sh
:
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/../"
# Prepare identity arguments for age
identity_args=()
for key in ~/.ssh/id_*; do
if [ -f "$key" ] && ! [[ "$key" == *.pub ]]; then
identity_args+=("-i" "$key")
fi
done
# Execute age with all identity files
age -d "${identity_args[@]}" -o .secret .secret.age
cat << EOF
Secret decrypted in .secret
Don't forget to run the command:
$ source .envrc
EOF
La boucle permet de passer en paramètre toutes les clés privées ssh du dossier ~/.ssh/
en espérant en trouver une qui correspond à une clé publique du dossier /ssh-keys/
.
Plusieurs niveaux de sécurisation
Si je veux cloisonner les secrets en limitant leur accès à des groupes d'utilisateurs distincts, je peux utiliser des secrets différents selon l'environnement.
Par exemple :
.
├── production
│ ├── .secret.age
│ └── ssh-keys
└── sandbox
├── .secret.age
└── ssh-keys
Cette structure me permet de donner à certains utilisateurs accès aux secrets de l'environnement sandbox
, sans leur donner accès à ceux de production
.
Aller plus loin avec par exemple Vault ?
Je pense qu'il est possible d'aller plus loin en matière de sécurité avec des solutions comme Vault, mais trouve que la méthode basée sur Age reste plus simple à déployer dans une petite équipe.