Retour
Featured image of post SOPS la solution de gestion de secret DevOps ?

SOPS la solution de gestion de secret DevOps ?

Chiffrez, déchiffrer à volonté !

S’il y a bien une problématique que l’on rencontre tous les jours dans nos métiers c’est la gestion des secrets au sens large, mot de passe, tokens, clés privées. Cependant, leur gestion peut s’avérer complexe, surtout lorsque nous devons les intégrer à divers outils d’infrastructure tels que Terraform, Ansible, Kubernetes, et bien d’autres.

Sur le marché, il existe de nombreuses solutions pour relever ce défi, allant du célèbre Vault de HashiCorp (qui, tout comme Terraform, a connu des changements de licence), à AWS Secret Manager, Azure Vault, Infiniscal, et bien d’autres. Mais parmi cette multitude d’options, il en est une qui se distingue par son approche innovante : SOPS, acronyme de Secrets OPerationS.

Contrairement à la plupart des solutions qui adoptent une approche centralisée, souvent sous la forme d’un service exposant les secrets sous forme de clé-valeur, SOPS se démarque en proposant le chiffrement des valeurs directement dans des fichiers de configuration au format JSON, YAML, init, et bien d’autres encore. Une fois chiffrées, ces données peuvent être partagées et versionnées en toute sécurité via des outils tels que Git ou d’autres systèmes de gestion de versions.

Ce qui rend SOPS très intéressant c’est son origine. Elle a été directement créée par les équipes techniques de Mozilla. Ce qui lui a permis de bénéficier d’une réelle expertise de la part d’un acteur majeur de la sécurité et de la vie privée. Préparez-vous je vous propose de plonger avec moi dans la découverte de cet outil !

Fonctionnement

Le fonctionnement de SOPS est assez flexible et permet un vaste choix, que ça soit sur le format du fichier de sortie ou du gestionnaire de clé.

Une solution flexible
Une solution flexible

Le point intéressant est que Sops va uniquement chiffrer les valeurs de vos fichiers. Par exemple pour un fichier YAML ayant une ligne : db_password: my_password, uniquement la valeur de la clé sera chiffrée donc ici my_password.

Ce qui va permettre la lecture et la compréhension de la structure des fichiers sans la nécessité de le déchiffrer.

Pour gérer la configuration de ce chiffrement, SOPS va ajouter un ensemble de metadatas sous la clé sops lors du processus de chiffrement.

Mise en marche

Installation

# Download the binary
curl -LO https://github.com/getsops/sops/releases/download/v3.8.0/sops-v3.8.0.linux.amd64

# Move the binary in to your PATH
mv sops-v3.8.0.linux.amd64 /usr/local/bin/sops

# Make the binary executable
chmod +x /usr/local/bin/sops

Les manipulations de base se font assez facilement et de manière transparente. SOPS va utiliser l’éditeur par défaut de votre système (défini par la variable d’environnement: EDITOR).

Comme noté plus tôt l’intérêt de SOPS c’est aussi de supporter plusieurs bases de chiffrement. Pour ça je vais vous donner deux exemples, le premier avec PGP qui reste le grand classique, le second avec Age qui est la solution recommandée par SOPS.

Il est aussi possible de se reposer sur les KMS cloud, mais je vais laisser cette partie de côté.

Utilisation avec PGP

On commence par PGP, le classique que j’utilise déjà notamment pour chiffrer mon Pass.

Dans mon cas, pour utiliser ma clé :

# On récupère la fingerprint de notre clé
gpg --list-keys
# On export le fingerprint
export SOPS_PGP_FP="<VOTRE FINGERPRINT>"

J’aurai aussi pu donner la clé avec le paramètre --pgp : sops --pgp "<VOTRE FINGERPRINT>"

vous pouvez mettre plusieurs clés à la suite en les séparant par des virgules.

Utilisation avec AGE

Le chiffrement de fichier, simple et efficace
Le chiffrement de fichier, simple et efficace

J’ai découvert Age avec SOPS, cela regroupe un outil, un format et une bibliothèque ayant pour but de gérer du chiffrement par clé asymétrique (couple public privé). C’est une bonne alternative à PGP pour des besoins simples, en local notamment. Si vous voulez en savoir plus, le dépôt Git est publique.

Petite exemple avec la création d’une clé :

# Création d'une clé
age-keygen -o private.txt
# On export le fichier contenant la clé privée :
export SOPS_AGE_KEY_FILE="~/private.txt"
# On peut aussi exporter directement la clé :
export SOPS_AGE_KEY="<AGE_PRIVTE_KEY>"

Tout comme PGP, j’aurai aussi pu utiliser directement le paramèrte en CLI --age <VOTRE CLÉ PUBLIC>

Manipulation de base des fichiers

Pour modifier des fichiers, il suffit de faire la commande sops <FILENAME>, un fichier sera créé si aucuns n’existe, sinon il sera ouvert avec votre éditeur favori.

On constate que si on essaye de consulter le fichier sans SOPS, avec cat par exemple, celui-ci est bien chiffré :

❯ cat demo.json
{
    	"hello": "ENC[AES256_GCM,data:CNT6xm/V+fNiMPNVkYob,iv:A97AN48RnPKwIWw1phDCsz5173SfwhOTYS7ruv/g
    	"example_key": "ENC[AES256_GCM,data:FkvHEbCzUurxjBRtEg==,iv:4KpxUeKXxsWM6ovFfK3KzBaLt7Lhe+cRYg
    	"example_array": [
            	"ENC[AES256_GCM,data:brsfGZb5RTbd6LptRNg=,iv:cVP3FAhfHZrJ0wyRFXkvxVsOpDnzJJJaGhDbTXu2J
            	"ENC[AES256_GCM,data:pgS9SPpFhuHEdxc=,iv:kGNeXctjpuOGwIrUqPKygSNDZQ3tn1ezJ/jA/LGL0Kg=,
    	],
    	"example_number": "ENC[AES256_GCM,data:2hF1/gxuNa7L,iv:APQXF55YNOf2b1I7WqUo2FkJjKBdzoRf0/Ddjic
    	"example_booleans": [
            	"ENC[AES256_GCM,data:V6++nQ==,iv:87GrwookidDyM4gpffbf3FhcX0wWE7ArxMVt3COUS9A=,tag:br37
            	"ENC[AES256_GCM,data:65PVZDA=,iv:GS9nzbsALrAQuC0RYkfIWdmkdJIjnQsLu2wLIgrswsY=,tag:VqxC
    	],
    	"sops": {
            	"kms": null,
            	"gcp_kms": null,
            	"azure_kv": null,
            	"hc_vault": null,
            	"age": [
                    	{
                            	"recipient": "age1w07kwz4ff5f3lfsaah9q3qpqpt9fwjadj6f37hfkej7y2tjm55zs
                            	"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2UFdYbkpDcnpaek5jUC9N\nb1pIb296anFtK25QNmV5Yk1rTTlHR3pZWVVvCkdRMjAwZWNLT2xpd1R1c3ZDSDgv\nUE03SEpEMng0Q1JnMnI0NFRYQkwzdFkKLS0tIDA0YjZ2d09veHcydWFiZGVRRGdp\na3ByM1Rocm90VjNBL2lxMEhsTlJCc00KbjavVxgs7JeOvFwRDWupePggMZ3NEr8e\nZvQihxsNYTxdD95QtJQfns+bcGXmCSnGq66Rv/P+LWfhrEgL4KN35Q==\n-----END AGE ENCRYPTED FILE-----\n"
                    	}
            	],
            	"lastmodified": "2023-10-03T19:40:39Z",
            	"mac": "ENC[AES256_GCM,data:txriRB+7w56xm7DnU9TrADNm6NDdhw41MTYfiqaFmDbyv0AN0DQ80zU46fD6dFJu5yP1q0Qd9sh+V2FdVoNZLmHKE5fi1t2OowtoL3mmGrEE3mqCvJuESI2yIjCgfY42kkj5HH7Lk+jSnATNz+jzfxNuZss39G1bPGZwrzmvlJ8=,iv:5DBu91Nadq+fEAEgjmkl+yqSKHbuFUwipi2Jrls7jBQ=,tag:77LwtA96Tw7PfceiB2ltqA==,type:str]",
            	"pgp": null,
            	"unencrypted_suffix": "_unencrypted",
            	"version": "3.8.0"
    	}
}%

Vous pouvez remarquez que le fichier contient aussi les metadatas nécessaire à SOPS.

Pour afficher le contenu du fichier déchiffré je peux utiliser la commande sops -d <FILENAME> qui va permettre d’afficher le contenu déchiffré sans ouvrir l’éditeur :

❯ sops -d demo.json
{
    	"hello": "Fichier de demo",
    	"example_key": "example_value",
    	"example_array": [
            	"Vous ne pouvez",
            	"pas me lire"
    	],
    	"example_number": 1773.1773,
    	"example_booleans": [
            	true,
            	false
    	]
}

On peut même directement extraire des valeurs précises : sops -d --extract '["env"]["prod"]["db"]["password"]'. Dans notre cas :

❯ sops -d --extract '["hello"]' demo.json
Fichier de demo%

Configuration

Imaginons la situation suivante qui est plutôt courante. On souhaite regrouper dans un dossier plusieurs fichiers de configuration pour les différents environements. On va vouloir les chiffrer avec des clés différentes en fonction des environnements. Jongler entre les clés peut très rapidement devenir fatiguant. Ça tombe bien, vous pouvez configurer celles-ci avec un simple fichier de configuration YAML .sops.yaml.

Si aucun fichier de configuration n’est présent dans le répertoire courant, SOPS va de manière récursive chercher dans le répertoire parent.

Ce fichier va permettre de configurer en fonction de RegEx sur les chemins, différentes clés qui seront automatiquement configurées.

WARNING: si vous utilisez un fichier de configuration, les options de sélection de clés en cli annuleront les règles présentes dans le fichier.

Exemple de fichier .sops.yaml :

creation_rules:
	- path_regex: demo\.json$
  	age: 'age15lxggyhzkvzpxrczkf4alnuhejj8xdje602ay8zm3msy2djjuufsqsrt2g,age1w07kwz4ff5f3lfsaah9q3qpqpt9fwjadj6f37hfkej7y2tjm55zs0hugwg' #Clés séparées par une virgule

Lle fichier demo.json pourra être déchiffré automatiquement avec SOPS à condition d’avoir la partie privée d’une des deux clés publiques Age.

Ce qui veut aussi dire que si demain vous souhaitez ajouter ou supprimer un accès, vous devez supprimer la clé de la liste. Néanmoins une fois le fichier .sops.yaml modifié, celui-ci ne va pas mettre à jour les fichiers qui sont impactés. Il vous faudra utiliser la commande sops updatekeys <fichier_chiffré> pour mettre à jour les clés.

❯ sops updatekeys demo.json
2023/10/29 17:45:29 Syncing keys for file /home/thomas/Documents/sops-demo/base-exemple/demo.json
The following changes will be made to the file's groups:
Group 1
	age1w07kwz4ff5f3lfsaah9q3qpqpt9fwjadj6f37hfkej7y2tjm55zs0hugwg
+++ age15lxggyhzkvzpxrczkf4alnuhejj8xdje602ay8zm3msy2djjuufsqsrt2g
Is this okay? (y/n):y
2023/10/29 17:45:41 File /home/thomas/Documents/sops-demo/base-exemple/demo.json synced with new key

Il est aussi possible de donner plusieurs clés en argument à la création en les séparant par des virgules. Exemple : sops –age=, demo.json

Dans la vie des SREs

Bon, éditer les fichiers, les afficher c’est bien beau, mais on veut surtout que nos outils et systèmes puissent consulter les secrets.

Je vais en majorité utiliser Age pour ces exemples, mais comme vous l’aurez deviné ils sont adaptables avec les autres protocoles supportés par SOPS.

Usage dans la CI/CD (Gitlab CI)

Souvent le premier vrai cas d’usage dans lequel on a besoin de récupérer des secrets c’est la CI/CD. Ici je vais vous montrer ce que ça donne sous Gitlab.

À savoir que pour l’instant, SOPS n’est pas nativement supporté dans Gitlab, il va donc falloir procéder de manière plus manuelle.

Il faut avant tout créer une image Docker dans laquelle on va exécuter notre job. Personnellement, je suis resté sur du basique :

FROM debian:testing

RUN apt-get update && apt-get install -y gnupg age curl

RUN curl -qsL https://github.com/getsops/sops/releases/download/v3.8.1/sops-v3.8.1.linux.amd64 -o /opt/sops && chmod +x /opt/sops

Si vous souhaitez tester directement, l’image est publique : Docker hub

On peut ensuite utiliser l’image dans notre CI/CD pour déchiffrer not mots de passe.

On commence par créer une paire de clé pour la CI/CD : age-keygen -o key-gitlab.txt

Attention : Il est possible de partager les clés mais cela est largement déconseillé pour des questions de sécurité. Il faut aussi faire attention à ne pas commit les fichiers Age qui contiennent la clé publique ET privée.

Dans notre cas on va chiffrer un fichier yaml par environnement qui va contenir nos variables sensibles : sops --age=<Gitlab-age-pub-key>,<my-age-pub-key> env.dev.encrypted.yml, je répète l’opération pour l’environnement de staging. J’aurais aussi pu passer par un fichier : .sops.yaml.

Je vais ici, faire un fichier .gitlab-ci.yml de CI simple afin de montrer le principe et de tester

image: damyr/sops-ci

test_dev:
  before_script:
	sops -d env.dev.encrypted.yml > env.yml
  script:
	cat env.yml

test_staging:
  before_script:
	sops -d env.staging.encrypted.yml > env.yml
  script:
	cat env.yml

C’est simple, mais ça vous laisse imaginer ce qu’il est possible de faire ensuite avec ces environnements.

WARNING: Je déconseille par contre de stocker ce fichier en clair dans des artefacts de CI/CD, en cas d’accès au runner ils pourraient être trouvés déchiffrésr.

Il me reste plus qu’à ajouter la clé privée (SOPS_AGE_KEY) en variable protégée de CI/CD.

Une démo simple, des possibilités infinies
Une démo simple, des possibilités infinies

Usage avec Terraform

Terraform est l’un des outils qui nécessite le plus d’accéder à des secrets et d’en créer de nouveaux, ce qui souligne l’importance cruciale de l’intégration de SOPS. Pour simplifier l’usage avec Terraform, un provider est disponible, testons le !

provider "sops" {
  // age dans notre cas, les autres implémentations fonctionnes
  age = {
	key = "~/Documents/sops-demo/terraform/keys-tf.txt" // A mettre si vous ne souhaitez pas passer par les variables d'environnement standard SOPS
  }
}

Si on souhaite lire les datas de notre fichier vars.json, on va pouvoir directement utiliser un datasource sops_file :

data "sops_file" "id" {
  source_file = "vars.json"
}

output "database-user" {
  value = data.sops_file.id.data["user"]
  sensitive = true
}

output "database-password" {
  value = data.sops_file.id.data["password"]
  sensitive = true
}

Je vais utiliser json qui est plus simple de manipulation dans ce cas d’usage et évite de devoir passer par un cast intermédiaire.

À noter que la documentation, parle d’une ressource pour écrire des secrets (https://registry.terraform.io/providers/lokkersp/sops/latest/docs/resources/file), mais celle-ci n’est pas implémentée dans le provider.

Aussi le provider peut rapidement montrer des limites sur des structures un peu complexes notamment en yaml. À suivre l’évolution de celui-ci.

Je préfère mettre mes secrets sous forme d’un yaml et le déchiffrer avec la commande SOPS en amont. Néanmoins, il faut bien faire attention à ne pas commit le fichier déchiffré.

Utilisation avec Kubernetes (FluxCD)

S’il y a bien une plateforme dont j’aime particulièrement les apports ces dernières années c’est bien Kubernetes. Parmi ces nouveautés il y a une approche que j’apprécie tout particulièrement, c’est le GitOps avec FluxCD. Pour résumer, vous allez décrire l’ensemble des ressources de votre cluster sous forme de fichier YAML dans un repos Git et FluxCD va s’occuper de synchroniser et réconcilier l’ensemble en permanence.

Le workflow GitOps
Le workflow GitOps

Certains d’entre vous doivent déjà faire le lien avec le sujet de l’article. Comment faire pour push des secrets sur Git à destination de FluxCD pour les déployer sur Kubernetes tout en assurant le chiffrement ?

Et ça tombe bien, FluxCD supporte SOPS ! Et on va voir comment ça se passe.

Je commence avec une arborescence FluxCD assez classique, proche de la structure Monorepo décrite dans la documentation officielle.

Pour rester simple et efficace, je vais m’imposer une convention. Tous les fichiers chiffrés seront dans une arborescence à part pour éviter les blagues et je vais me limiter aux champs : data et stringData. Je vais définir ça dans le fameux fichier .sopos.yaml à la racine de mon dépôt FluxCD.

creation_rules:
	- path_regex: ^secrets/.*\.(yaml|yml)$
   	   encrypted_regex: '^(data|stringData)$'
  	age: '<ma-clé-pub>,<clé-pub-de-fluxcd>'

On va ensuite pouvoir faire notre premier secret. Commençons par un secret bien classique dans le monde k8s: le secret d’authentification au Docker registry.

On le créer notre secret : kubectl create secret docker-registry regcred --docker-server=registry.damyr.fr --docker-username=damyr --docker-password=password@demo@fakepassword --docker-email=demo.damyr.fr --dry-run=client -o yaml > secrets/staging/regcred.yaml

Je le chiffre ensuite : sops --encrypt --in-place secrets/staging/regcred.yaml

Fluxcd doit déjà être fonctionnel sur le cluster à partir de là, je vous laisse consulter la documentation officielle si vous ne l’avez jamais installé.

Je crée ensuite mon secret qui va contenir la clé de FluxCD : kubectl create secret generic sops-age --namespace=flux-system --from-file=age.agekey=kube.key. Celui-ci est placé dans le même namespace de FluxCD, lui seul est censé déchiffrer les fichiers.

On crée ensuite l’appel au dossier avec une Kustomization :

apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
  name: secrets
  namespace: flux-system
spec:
  interval: 5m0s
  path: ../secrets/staging
  prune: true
  sourceRef:
	kind: GitRepository
	name: flux-system
  decryption:
	provider: sops
	secretRef:
  	name: sops-age

Je push ensuite la kustomization sur mon dépôt Git. Une fois que FluxCD a bien fait la réconciliation, notre secret apparait :

❯ kubectl get secret
NAME  	TYPE                         	DATA   AGE
regcred   kubernetes.io/dockerconfigjson   1  	58s

Qu’en penser ?

La gestion des secrets est plus que jamais une problématique centrale dans nos métiers, notamment avec l’automatisation qui a mis en exergue les besoins de ce côté.

C’est un domaine où je n’ai pas vu de grande diversité, la majorité utilisant la solution fournie par leurs provider cloud ou la solution Vault d’Hasicorp qui est très populaire. Une partie utilise aussi Git-crypt avec les désavantages que ça apporte.

Face à une problématique aussi cruciale, adopter une approche agnostique semble être une option judicieuse. C’est là que SOPS se distingue en proposant une solution à la fois simple et entièrement décentralisée, qui se rapproche davantage de Git-crypt que de Vault d’Hasicorp, tout en offrant des fonctionnalités essentielles telles que le chiffrement des clés, la gestion des fichiers de configuration, le chiffrement intelligent et partiel des fichiers, la rotation des clés, et bien plus encore. Dans de nombreux cas, SOPS s’avère être une solution adaptée, qui ne demande ni infrastructure supplémentaire ni abonnement.

L’un de ses principaux avantages réside dans sa simplicité d’utilisation. Comme nous l’avons constaté dans les exemples concrets de notre quotidien, l’intégration de certaines solutions n’est pas toujours parfaite ni native. Cependant, la simplicité de SOPS permet de l’intégrer en amont de manière transparente pour faciliter le pré-déchiffrement.

Cependant, il est important de noter que SOPS présente également des limites. Son approche décentralisée signifie qu’il offre moins de contrôle sur l’accès aux secrets, car chaque détenteur de la clé peut les consulter sans restriction, sans qu’un système centralisé ne puisse enregistrer ces accès. De plus, SOPS ne propose pas de solution de rotation automatique des secrets, ce qui constitue encore une lacune à combler.

Head photo by Philip Swinburn on Unsplash

Généré avec Hugo
Thème Stack conçu par Jimmy