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.

Source 1 et Source 2

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.

detect-secrets

https://github.com/Yelp/detect-secrets

Plus complet, detect-secrets permet également de scanner des repositories git et propose un système de pre-commit hook pour détecter automatiquement les secrets sur le point d'être sauvegardés dans git.

Attention gitleaks et detect-secrets ne détectent pas 100% des secrets et peuvent remonter des faux positifs. Par exemple gitleaks ne détecte pas par défaut les connectionStrings de base de données dans les fichiers de configuration .NET et detect-secrets remonte des alertes sur des classes CSS comportant le mot password.

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é.

Source

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 :

asciicast

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.

Variable d’environnement locale

En revanche, la variable n’est pas disponible globalement :

Variable d’environnement globale

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()

Variable d’environnement sous-process

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

Vault 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.

Vault policy

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 :

Application avec 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.).


Voir également