Stocker les secrets utilisés par une application
Dans une application, il est souvent nécessaire d’utiliser des secrets. Ces secrets peuvent servir à se connecter à une base de données, à accéder à une API distante, chiffrer/signer des données, etc.
2 questions se posent :
- Comment/Où stocker ces secrets ?
- Comment mettre à jour facilement un secret utilisé par plusieurs applications ?
Le moyen le plus facile de mettre à disposition ces mots de passe est de les enregistrer dans les fichiers de configuration de l’application nommés par environnement (appsettings.dev.json, appsettings.prod.json, etc.) et de pousser ensuite tous ces fichiers dans le repository git.
Cette méthode est facile à mettre en place mais présente de gros inconvénients.
Pourquoi ?
Toutes les personnes ayant accès au gestionnaire de code source pourront visualiser les mots de passes des différents environnements. Un développeur ou un testeur n’a peut être pas à connaître les secrets permettant d’accéder aux bases de données de production.
Lors du changement du mot de passe d’un des composants accédés, toutes les applications consommatrices doivent être mises à jour.
Les applications sont souvent les points d’entrée pour les attaquants. Trouver des informations sensibles permettant d’accéder à d’autres composants internes ou à un compte sur un cloud provider (AWS, GCP, etc) est très intéressant.
Cas de Uber en 2016
Uber utilisait des repositories privés GitHub pour ses applications jusqu’à ce qu’un attaquant réussisse à s’y connecter (pas de MFA activé). Le code source accessible par l’attaquant contenait des éléments pour accéder à un bucket S3 situé sur AWS dans lequel Uber stockait des informations.
En se connectant au bucket S3, l’attaquant a récupéré 57 millions de comptes de chauffeurs et de passagers ainsi que 600 000 informations sur des conducteurs américains.
Uber a conclu un accord à 100 000$ avec le pirate contre la destruction de ces informations. L’attaque n’a été rendue publique qu’un an plus tard.
Outils
Des outils existent pour détecter les secrets dans les repositories git.
gitleaks
https://github.com/zricethezav/gitleaks
gitleaks permet de scanner des repositories en cherchant des patterns de secrets connus (ex : token AWS, Facebook, etc.). De nouveaux patterns peuvent être ajoutés manuellement (source).
Pour éviter de commiter des secrets, l’analyse peut être faite sur les changements à enregistrer.
Attention gitleaks ne détecte pas 100% des secrets et peut remonter des faux positifs. Par défaut, Il ne détecte par exemple pas les connectionStrings
de base de données dans les fichiers de configuration .NET.
GitHub token scanning
GitHub scanne les repositories publics de ses utilisateurs pour détecter les formats de tokens connus (AWS, Google, etc.). Si un secret correspondant à un des fournisseurs gérés est détecté, GitHub informe ce fournisseur pour qu’il révoque le secret trouvé.
Comment stocker les secrets ?
Stocker les secrets chiffrés avec git-secret
Si stocker les secrets dans git est la seule option possible, une solution consiste à chiffrer via gpg les secrets stockés dans des fichiers avant de les enregistrer dans git.
Pour pouvoir déchiffrer les secrets, il est nécessaire que la clé publique de l’utilisateur autorisé soit ajoutée via la commande git secret tell user@email.com
.
Ci-dessous un exemple d’utilisation fourni par l’auteur :
En variable d’environnement
Prôné par The Twelve-Factor App, le stockage des secrets dans les variables d’environnement laisse le code source vierge de tout secret.
Pour déclarer la variable, 2 possibilités :
- Déclarer la variable de manière globale :
$ export MY_SECRET=MyS3cr3t
$ ./myApp.py
- Déclarer la variable uniquement pour la commande à lancer
$ MY_SECRET=MyS3cr3t ./myApp.py
Ensuite, dans l’application le code python suivant récupère la valeur de la variable MY_SECRET
:
import os
secret = os.environ.get('MY_SECRET')
print(secret)
input()
Comme indiqué ci-dessous, la variable d’environnement est bien récupérée dans l’application python et la variable MY_SECRET
est disponible pour le process de l’application.
En revanche, la variable n’est pas disponible globalement :
Attention, les variables d’environnement, même locales, sont passées aux sous-process.
Par exemple, avec le code suivant :
import os
import subprocess
secret = os.environ.get('MY_SECRET')
print(secret)
subprocess.Popen('/bin/echo $MY_SECRET', shell=True)
input()
A noter qu’en fonction des niveaux de logs configurés, les variables d’environnement peuvent être insérées dans les fichiers de logs de certains langages/frameworks.
La solution des variables d’environnement répond au problème du stockage des secrets dans le code source mais ne facilite pas la mise à jour unique des secrets des composants accédés.
Utiliser un coffre fort
Une autre solution pour stocker des secrets est d’utiliser un composant dédié attaqué par les applications en leur fournissant les secrets nécessaires à l’accès d’autres composants.
Le composant le plus connu pour remplir ce rôle est Vault développé par Hashicorp. Vault a de multiple fonctionnalités :
- Gestion de secrets statiques
- Interfaçage avec des providers de cloud pour générer des identifiants à la demande
- Interfaçage avec des composants types RabbitMQ, MySQL, MongoDB, etc. pour générer des identifiants à la demande
- Chiffrement à la demande
- PKI
- etc.
Concernant les secrets, Vault peut aussi bien gérer des secrets classiques comme MyS3cr3t
de l’exemple précédent mais aussi générer pour une application donnée et pour un temps donné un compte utilisateur pour aller récupérer des données sur sur le cloud ou dans une base de données.
Afin de récupérer ces informations, l’application doit s’authentifier auprès de Vault afin d’accéder aux secrets qui lui sont autorisés. Pour le faire, plusieurs méthodes sont disponibles, par exemple :
- certificats TLS
- username/password
- token JWT
- etc.
Exemple
Dans l’exemple ci-dessous, Hashicorp Vault est configuré pour accepter une authentification par token GitHub (pratique pour le développement) et contient un secret my-secret
dans le moteur Key/Value.
Ajout du secret
Ajout d’une policy de lecture des secrets
Pour permettre aux applications qui s’authentifient de pouvoir récupérer et lire les secrets stockés dans Vault, il est nécessaire de créer une policy (Policies -> Create ACL policy).
Cette policy nommée secret_read
permet de lire tout ce qui ce qui est accessible via le chemin /secret/*
dans Vault.
Configuration de la méthode d’authentification GitHub
Dans les méthodes d’authentification (Access -> Auth Methods), l’authentification GitHub est ajoutée en spécifiant le nom de l’organisation configurée sur GitHub.
On indique également que les tokens générés avec l’authentification GitHub auront la policy secret_read
créée précédemment.
Récupération du secret dans l’application
Pour récupérer le code dans l’application python, la librairie hvac est utilisée :
import hvac
client = hvac.Client()
login_response = client.auth.github.login(token='<GitHubToken>')
print(client.is_authenticated())
apikey = client.secrets.kv.v2.read_secret_version(path='my-secret')
print(apikey["data"]["data"]["my-secret"])
La liste des librairies permettant d’utiliser Hashicorp Vault est disponible ici.
A l’exécution, l’application affiche bien le secret stocké dans Vault :
Si le secret doit être modifié, il peut l’être directement dans Vault sans avoir à modifier l’application.
Attention, cet exemple présente un cas simpliste de l’utilisation de Vault. Sa mise en place dans un environnement de production réel est plus complexe et nécessite la compréhension de certains concepts.
Alternatives cloud
Les principaux providers de cloud fournissent également leurs solutions “as a service” de coffre-fort.
Conclusion
Les variables d’environnement sont un moyen simple de rendre accessible des secrets à des applications sans avoir à les stocker dans le code source. Cette approche présente tout de même certaines limitations surtout si le nombre d’applications, de machines et de secrets est important.
Le coffre fort est la solution la plus sérieuse pour gérer des secrets au sein des applications. La base de code des logiciels développés reste vierge et la gestion centralisée des secrets rend leurs mises à jour aisées. Les autres fonctionnalités proposées par exemple par Vault sont également à étudier et peuvent avoir une réelle plus value sur la sécurité du SI (mTLS entre les services, chiffrement as a service, etc.).