Gérer ses secrets avec Mozilla SOPS

En travaillant sur de l'Infrastructure as Code (IaC) ou encore K8S il vous est certainement arrivé de manipuler des tokens, des mot de passes, des certificats et plein d’autres données sensibles qu’il vaudrait mieux ne pas partager publiquement. Nous allons voir dans cet article qu’il est possible de gérer les secrets d’une application avec un VCS comme git au même titre que du code source sans s’exposer à des risques de sécurité. Cette solution est SOPS de Mozilla. Elle a l’avantage d’être une solution transparente et universelle car elle s'intègre bien avec les différents clouds publics.

Présentation de l’outil

SOPS est un éditeur de fichier chiffré supportant les formats YAML, JSON, ENV et INI ainsi que les fichiers binaires. Le chiffrement se fait aussi bien avec AWS KMS, GCP KMS, Azure Key Vault ou une clé PGP. Ces méthodes de chiffrement peuvent être combinées sur un seul et même fichier. Sa particularité est qu’il chiffre uniquement les valeurs et non les clés du fichier, ce qui signifie que dans tous les cas nous avons accès à l’architecture du fichier.

Démonstration en utilisant une clé PGP

Pour commencer voici un exemple simple de l’utilisation de SOPS avec une clé PGP fraîchement générée. Notre but ici va être de créer un fichier yaml que nous allons chiffrer avec SOPS à l’aide de PGP. Le but est de comprendre son utilisation mais aussi d’avoir une idée plus précise de comment ce chiffrement fonctionne.

Génération de la clé PGP :

> gpg --generate-key
gpg (GnuPG) 2.2.19; Copyright (C) 2019 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Remarque : Utilisez « gpg --full-generate-key » pour une fenêtre de dialogue de génération de clef complète.

GnuPG doit construire une identité pour identifier la clef.

Nom réel : sops_key
Adresse électronique : afougerouse@ippon.fr
Vous avez sélectionné cette identité :
    « sops_key <afougerouse@ippon.fr> »

 [ … ]

pub   rsa2048 2020-05-04 [SC] [expire : 2022-05-04]
      59C8C737858631D5832043E83F59D0BA0AA089E7
uid                      sops_key <afougerouse@ippon.fr>
sub   rsa2048 2020-05-04 [E] [expire : 2022-05-04]

‌‌‌‌‌‌‌À l’aide de l’empreinte de la clé "59C8C737858631D5832043E83F59D0BA0AA089E7" nous allons pouvoir créer notre premier fichier avec SOPS. Pour information, une empreinte de clé PGP n’est pas la clé elle-même, c’est un identifiant unique qui permet d’authentifier que la clé est bien détenue par la personne se réclamant détenteur de ladite clé.

> sops -p 59C8C737858631D5832043E83F59D0BA0AA089E7 demo_pgp.yaml

À noter que SOPS utilise Vim pour l’édition des fichiers par défaut si la variable d'environnement $EDITOR n’est affectée à aucune valeur.

Une fois que SOPS ouvre notre éditeur nous allons pouvoir remplir notre fichier. Voici l’exemple que j’ai utilisé :

key: value
service:
    db:
        password: my-secret-password

À la fermeture du fichier SOPS se chargera de le chiffrer. Si nous l’ouvrons simplement sans utiliser SOPS nous obtenons ceci :

> cat demo_pgp.yaml
key: ENC[AES256_GCM,data:OyFGrsA=,iv:Pe61hnynI1KCgNLEZB2+BeUQDxkEjPzMdt80P96lrKM=,tag:8Z4idajd/ciH4nQgqCUAkA==,type:str]
service:
    db:
        password: ENC[AES256_GCM,data:L2PJssrCqWJUVQhYMgLXJH7u,iv:5ILlqLaKg5KFjT9Ods1Kvtss9LqlFHXw11bCADAnHZw=,tag:UMQdRLZJ4KeIG92IKfJa8g==,type:str]
sops:
    kms: []
    gcp_kms: []
    azure_kv: []
    lastmodified: '2020-05-04T12:55:23Z'
    mac: ENC[AES256_GCM,data:kDnzC7v8OFcIIRneBZh1ou0NehoGdAS5ddJRDyww9bAdcKwgnEG5TC8cmUaDrz4kt68TJ0C3EvGhrvzfXbpl/eLdfzTjEWev+wdeOBFDJNMHMsWL3ayGZJAcb4z2uo0Rg1qJOVPDwAzkw1bRfMr20/ldm/5kI1nFfZoS8T9Hz1I=,iv:Nkj5qf+ghUT8EqIOyEFINT7JdfLfVOSJdFSf8BbzIk8=,tag:LfecshluJHqw/uGoSSj2ZQ==,type:str]
    pgp:
    -   created_at: '2020-05-04T12:49:01Z'
        enc: |
                -----BEGIN PGP MESSAGE-----
            hQEMA1+85P4Q6IY2AQgAuhwZUpvWxKsryuY1MdcAz3Ig/U5QEM7eTOnmNH/0qAgb
            JydQT4NOzZm3GNppJ7vT10OKSpHNiRattCm4muBWR8U7xhuDhh6R179D7bWRiuRL
            s2asB4cmjsYFCNvqd64nCbbepiw57e7AgtstySGcsqjZAZt9OIzLr26VoGBeENJt
            154AGq+Fbjm8iGWOjLQIP0wlH8kCMnn5VIMLTZaraJcBdZEqEQDfOhmMnd86rPkt
            w6KEt1+I0Rc9eJ7rBCmm/lMHz6LkB9j5vZ34/jpAXvv7646yNMkUSNBEFjyD35gP
            HUENhruKiP4W15fcyHGiPOnKCsfLleey2IOdW0hrgdJcASGK2ksU+w3BgqxK9Rha
            Ct5hPF7SOcIxk7niKLq4ELs5V/GHKoQQVDaMmdmANDBLmToXNk3qcvmMHdUk1kIC
            nm+my5XT/KdEomydiiZwZiyaAx5PX7rQt2jdYTQ=
            =hjjv
                -----END PGP MESSAGE-----
        fp: 59C8C737858631D5832043E83F59D0BA0AA089E7
    unencrypted_suffix: _unencrypted
    version: 3.5.0

‌‌‌Tout d’abord nous constatons bien, comme mentionné plus haut, qu’uniquement les valeurs sont chiffrées et non les clés. L’avantage de ce fonctionnement est que dans une utilisation avec git l’ensemble des contributeurs peuvent voir les changements affectés aux fichiers sans avoir à les déchiffrer avec SOPS à chaque fois.

Pour ce qui est de nos valeurs, nous pouvons remarquer qu’à la place nous avons une enveloppe qui contient :

  • le nom de l’algorithme utilisé, AES256_GCM
  • un champs data qui est la valeur précédemment entrée, mais chiffrée
  • les champs iv (initial vector) et tag utiles au chiffrement/déchiffrement
  • le type de la donnée, string dans notre cas

Ensuite, nous trouvons sous la clé SOPS un ensemble de métadonnées. Les valeurs kms, gcp_kms et azure_kv sont vides car nous n’utilisons ici aucune clé provenant d’un cloud public. Le mac sert à garantir l’intégrité du fichier, il sera donc mis à jour à chaque modification apportée à notre fichier. Enfin, la clé publique à la fin du fichier sert à chiffrer les valeurs de notre fichier yaml. Pour les déchiffrer SOPS utilisera la clé privé associée à celle-ci.

Maintenant que vous avez bien saisi son fonctionnement de base nous allons pouvoir utiliser SOPS dans un cas plus concret en utilisant AWS.

Démonstration de son utilisation avec AWS

Afin de pouvoir faire une présentation de l’utilisation de SOPS avec AWS, j’ai au préalable :

  • généré une application monolithique JHispter utilisant une base de données MySQL
  • configuré des identifiants AWS sur mon poste
  • créé une instance EC2 sur AWS avec docker, docker compose et SOPS
  • créé une clé AWS KMS

Si vous voulez plus de détails vous pouvez visiter ce dépôt, vous trouverez notamment l’application JHispter, le script Terraform qui m’a permis de générer tout le nécessaire sur AWS et un Dockerfile créant une image Docker de l’application.

Notre but ici va être de chiffrer le fichier docker-compose définissant deux services. Un pour l’application, un pour la base de données. Les deux injectent des variables d'environnement contenant des données sensibles que nous voulons chiffrer.

Voici le docker-compose en claire, il se trouve dans le répertoire /src/main/docker/.

version: "3"
services:
    sops_demo-app:
        image: sops-demo
        environment:
        - _JAVA_OPTIONS=-Xmx512m -Xms256m
        - SPRING_PROFILES_ACTIVE=prod,swagger
        - MANAGEMENT_METRICS_EXPORT_PROMETHEUS_ENABLED=true
        - SPRING_DATASOURCE_URL=jdbc:mysql://sops_demo-mysql:3306/sops_demo?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=utf8&createDatabaseIfNotExist=true
        - SPRING_DATASOURCE_PASSWORD=a-very-secure-password
        ports:
        - 8080:8080
        depends_on:
        - sops_demo-mysql
    sops_demo-mysql:
        image: mysql:8.0.19
        environment:
        - MYSQL_ROOT_PASSWORD=a-very-secure-password
        - MYSQL_DATABASE=sops_demo
        ports:
        - 3306:3306
        command: mysqld --lower_case_table_names=1 --skip-ssl --character_set_server=utf8mb4
            --explicit_defaults_for_timestamp
~  

‌Pour le chiffrer nous n’allons pas passer une clé PGP en argument à SOPS, comme précédemment. Nous allons plutôt créer un fichier .sops.yamlà la racine du projet, celui-ci contiendra l’empreinte de la clé PGP que nous avons utilisée plus haut dans l’article et un identifiant ARN d’une clé AWS KMS.

Ce qui donne :

creation_rules:
        - kms: 'arn:aws:kms:eu-west-3:236323065396:key/8a467ae6-bd8c-46b2-9702-c48d648e82db'
          pgp: '59C8C737858631D5832043E83F59D0BA0AA089E7'

‌‌Pour appliquer le chiffrement à un fichier déjà existant il suffit d’utiliser l’option -ede SOPS. Elle permet de générer sur la sortie standarde le fichier chiffré, sortie que nous allons rediriger dans un fichier app.yml.

> sops -e app_not_encrypt.yml > app.yml
> cat app.yml

version: ENC[AES256_GCM,data:/Q==,iv:khsUUonbwtXEPEsoGpQxdL6KODCgOcY23bZsvC2aiIA=,tag:/eXOf/rYCqTB8pAd3Tj6pA==,type:str]
services:
    sops_demo-app:
        image: ENC[AES256_GCM,data:+EWMTFtRlpYY,iv:7dMqCzV9bUIDpA+shmkH3vqk6umnJZPTo0xmougHaqM=,tag:4f78r12klL+qXO/tHJoF4w==,type:str]
        environment:
        - ENC[AES256_GCM,data:3MQCTvl3soJ6pdKPGMDGsJcCWsUGkUnEZC5VuQk5wg==,iv:YIEWXK3NEZtT+35DNeqCQkCCTxT8BPvl+hDwh0k8qbk=,tag:0DZQY/pr0eglbWBUHLelNQ==,type:str]
        - ENC[AES256_GCM,data:Ncfq/qaNvaHgnMe3yBCYQ80DbHBGj8lCv+rbhkAf2hHPN/c=,iv:ap6NUHzmACwDYqHs4Vkn+xBv0AIjBG6NucHKKFZHa50=,tag:2y8KH4mkunMFO0Pcv6d7/w==,type:str]

[ … ]

sops:
    kms:
    -   arn: arn:aws:kms:eu-west-3:236323065396:key/8a467ae6-bd8c-46b2-9702-c48d648e82db
        created_at: '2020-05-06T09:35:14Z'
        enc: AQICAHijBlzZqCPpmXHUcK+zANSXooCkWvXha3R2gF+wJjfnHAGfk09toWpQS+fsO8gPjKfMAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMu+IfaLetmCxQbzi8AgEQgDsiNdNsMWoHa4zQiu3IC3kLgV8SfjX9hFp77R6hDFITaRbnj9dlB/v+/6uciWLY73ax73L98aEuvNzhmQ==
        aws_profile: ""
    gcp_kms: []
    azure_kv: []
    lastmodified: '2020-05-06T09:35:14Z'
    mac: ENC[AES256_GCM,data:yvSVucCVktAii3JJM2EGjGO+uLUpSeorcdVZyQT/Hu8O9nqkPqy4PtOPcxqV2IpVgnHsNcLJ2ajGyPnA3ihwKrunhTkDct0RZIJVlzsJkbUviJxHFGLm8RXf4HrP+My1bkUnHmF+J1WNt+x/W/tTV4JaxCqOYchh9TOreIQhdjQ=,iv:zjCm9A/Sk9j3OPoI9DBAO8I4V4ap5bYZlXmRfAt2Aqw=,tag:xMu1NYbuQpDCAceGcdmFJA==,type:str]
    pgp:
    -   created_at: '2020-05-06T09:35:14Z'
        enc: |
            -----BEGIN PGP MESSAGE-----            hQEMA1+85P4Q6IY2AQf/U28jJdkNwn+CgLgLDshKBqVMXgJaeVuQVWf0QX7cjQRx
/PRUQ+puq/52qURaeFXtjWGcqq5kx0kcdVR54Be9HyQaiN8pSGriDQx7oBltbZNj
yRyd4wX2dQz/TnLjyuO+kGCFd97zdAWEGGwPPoMZ15Bs2kPx2vzkQ9htK26umoEy
kDaKUaza9AHgYe/tkG6QZa7JPVIjZJMmYgenXlBTj5BEL2Zp8VFECoWCAbWAYGde
mdg/wWp945j2654lBnCfS4GCy4ZuhRJCqx8G8WZo9Y1Pp7vQrNiGLWXQE2bM/edo
YhYLMUC8bVuCapqcn5gnqmUHYQ0Dji9sD1tdTmlEidJcAWLqJaRjejkMGxSYpJOp
mEeFBE9NT1Q20yGLiYfpk6Z31mWfeBOL2T5GbJpXU9qNonI9ydADDG6vW2Lg1Ixq
oMe8+1bWG6IHZOVDAj9PEIN2rNrZdV5FdjdnHB8=
=IgGi
            -----END PGP MESSAGE-----
        fp: 59C8C737858631D5832043E83F59D0BA0AA089E7
    unencrypted_suffix: _unencrypted
    version: 3.5.0

Comme pour tout à l’heure nous pouvons voir qu’uniquement les valeurs sont chiffrées. Tout va bien ! Concernant les métadonnées cette fois-ci nous avons, en plus d’avoir des données sur le chiffrement PGP, des informations sur le chiffrement via AWS KMS. Ce fichier pourra donc aussi bien être déchiffré par notre clé PGP que par la clé KMS d’Amazon.

Afin de transférer tout le projet sur mon instance EC2 j’ai créé une archive tar pour me faciliter la vie. Une fois décompressée et l’image Docker créée c’est le moment de passer à l'instant de vérité.

> scp  -i "private_key" demo_sops.tar.gz ec2-user@ec2-15-188-207-142.eu-west-3.compute.amazonaws.com:/home/ec2-user/

# Sur l’EC2
> docker build -t sops-demo .

‌Pour faire exécuter le fichier déchiffré, SOPS possède plusieurs fonctionnalités. Ici nous allons utiliser la fonction exec-file. Cette fonction va créer une liste FIFO en mémoire sur laquelle notre fichier déchiffré se trouvera. Le but étant que notre fichier soit utilisable une seule et unique fois et ne soit jamais écrit sur le disque en clair.

Couplé à la commande docker-compose nous obtenons ceci :

> sops exec-file app.yml 'docker-compose -f {} up'

‌Après un long moment d’attente, si nous nous connectons à l’adresse publique de notre instance EC2 l’application JHipster tourne parfaitement !‌

Quelques fonctionnalités utiles

Je ne vais pas détailler toutes les fonctionnalités de SOPS mais je vais vous faire part de certaines qui ont retenu mon attention.

Pour plus de sécurité SOPS propose un sytème de rotation de clés qui va permettre de renouveler la clé de chiffrement. Pour l’utiliser rien de plus simple, il suffit de faire comme ceci : > sops -r file.yml

Néanmoins la rotation n’est effectuée qu’une seule fois, c’est à vous de mettre en place une solution de rotation récurrente.

Il est également possible de définir dans le fichier .sops.yaml quels fichiers seront chiffrés avec quelles clés et lesquelles seront chiffrées avec d’autres clés. Ceci permet d’avoir une séparation des privilèges au niveau de l’équipe en permettant à certains membres de déchiffrer des fichiers que d’autres ne pourront pas déchiffrer.

Voici un exemple :

creation_rules:
        - path_regex: \.dev\.yaml$
          kms: 'arn:aws:kms:eu-west-3:XX:key/XX'
          pgp: '59C8C7378XX'

        - path_regex: \.prod\.yaml$
          kms: 'arn:aws:kms:eu-west-2:XXY:key/XX'
          gcp_kms: projects/mygcproject/locations/global/keyRings/mykeyring/cryptoKeys/key
          pgp: 'E83F59DXXXXXBA'

Dans le premier cas les fichiers *.dev.yaml seront affectés, dans le deuxième cas ce seront les fichiers *.prod.yaml.

D’autres fonctionnalités permettent d’établir des règles d’envoi de fichiers sur des buckets S3 en les chiffrant, ou encore de stocker facilement sur HashiCorp Vault les données clés-valeurs.

Conclusion

Comme nous pouvons le constater une fois configuré, SOPS est un outil assez transparent à l’utilisation. Le chiffrement/déchiffrement s'effectue rapidement. Il peut à mon avis très bien s’intégrer dans une chaîne DevOps afin d’augmenter drastiquement la sécurité sans pour autant être vu comme une contrainte.

Source : https://github.com/mozilla/sops