Utiliser Azure Key Vault pour sécuriser les secrets d’une Azure Function

Intro : rappel de l’article précédent
Dans l’article précédent, nous avons montré un exemple d’utilisation d’une Azure Function pour la création d’un Pass Wallet pour iOS. A la fin de l’article, nous avions expliqué pourquoi le code proposé n’était pas idéal en termes de sécurité, surtout pour passer en production.
Le but de ce nouvel article est de proposer une solution afin de remédier à ce problème, en nous appuyant notamment sur le service Azure Key Vault, pour stocker les certificats et secrets en toute sécurité. Le code de base reste le même (voir ici), mais nous allons apporter quelques modifications à celui-ci pour y intégrer Azure Key Vault.
Attention spoiler : le code source est disponible ici.
Rappel sur Azure Key Vault
Azure Key Vault est un service mis à disposition par Microsoft pour gérer de manière sécurisée les secrets, clés et certificats d’une application, afin que ceux-ci ne soient ni dans les valeurs de configuration ni dans le contrôleur de code source. Ainsi, il permet de stocker des mots de passe, des chaînes de connexions, des données sensibles en toute sécurité.
Mise en place des ressources
Pour configurer notre Azure Key Vault, plutôt que de passer par le portail Azure, nous choisissons la ligne de commande Azure CLI.
La première étape est de s’authentifier avec cette ligne de commande :
az login
Une url ainsi qu’un code sont alors affichés, ils permettent de s’authentifier via un navigateur.
De retour sur l’invite de commande / terminal, il faut sélectionner une souscription.
La commande suivante permet de voir l’ensemble de nos souscriptions.
az account list
C’est l’id de la souscription qui permet de choisir sur quelle souscription on se place :
az account set --subscription xxxxxxx-xxxx-xxxx-xxxx-xxxxxxx
Il faut ensuite créer la ressource Azure Key Vault, via cette commande :
az keyvault create --name 'MyKeyVault' --resource-group 'MyResourceGroup' --location 'West Europe'
L’import du certificat, ici le pfx utilisé dans l’article précédent, s’effectue ainsi :
az keyvault certificate import --vault-name 'MyKeyVault' -n 'com-test-exemple-walletpass' -f 'certificate.pfx' --password 'password'
Où le com-test-exemple-walletpass correspond au nom de l’identifiant du pass (qui a été choisi dans le précédent article). Les points n’étant pas acceptés, des tirets les remplacent.
Cette ligne permet d’importer le certificat qui permet de signer le pass wallet ainsi que sa chaîne de certification normalement contenus dans le pfx.
Trois entrées sont ainsi créées dans le Key Vault :
- Le certificat
- La clé du certificat
- Le secret, une chaîne en base 64 qui ici représente le .pfx, et qui sera utilisé dans le code pour la signature
Les ressources sont prêtes à être exploitées par l’Azure Function, il est temps de passer côté code C#.
Utiliser Key Vault dans le code de l’Azure Function
Pour utiliser Azure Key Vault dans le code, nous allons ajouter les paquets NuGet Microsoft.Azure.KeyVault et Microsoft.Azure.Services.AppAuthentication à la solution existante.
Pour continuer à séparer le code des valeurs de configuration, nous mettons à jour le fichier local.settings.json en enlevant les chemins vers les certificats ainsi que le mot de passe, et en ajoutant une valeur pour l’url vers le Key Vault.
La partie du code à modifier ensuite se trouve dans la méthode CreateSignatureData, qui devient en passant asynchrone.
private static async Task CreateSignatureDataAsync() { var azureServiceTokenProvider = new AzureServiceTokenProvider(); var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback)); var secret = await keyVaultClient.GetSecretAsync(ConfigurationManager.AppSettings["KeyvaultUri"], ConfigurationManager.AppSettings["PassTypeIdentifier"].Replace(".", "-")); var collection = new X509Certificate2Collection(); collection.Import(Convert.FromBase64String(secret.Value), null, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet); var authorityCertificate = collection.Find(X509FindType.FindBySubjectName, "Apple Worldwide Developer Relations Certification Authority", false)[0]; var signingCertificate = collection.Find(X509FindType.FindBySubjectName, $"Pass Type ID: {ConfigurationManager.AppSettings["PassTypeIdentifier"]}", false)[0]; var teamIdentifier = Regex.Match(signingCertificate.Subject, @"OU=(?\w+),").Groups["teamId"].Value; return new SignatureData { SigningCertificate = signingCertificate, Authority = authorityCertificate, TeamIdentifier = teamIdentifier, PassTypeIdentifier = ConfigurationManager.AppSettings["PassTypeIdentifier"] }; }
Le AzureServiceTokenProvider se base sur l’identité courante (l’utilisateur dans le cas d’un debug sur Visual Studio) pour obtenir un jeton d’authentification.
Le provider est ensuite utilisé pour créer un KeyVaultClient qui permettra de récupérer la valeur du secret.
Ce dernier est utilisé pour peupler une collection de certificats, parmi lesquels nous allons chercher l’autorité de certification et le certificat pour signer.
Ce code fonctionne très bien en debug ou en local du moment que l’utilisateur a accès au secret du Key Vault, mais il reste quelques manipulations à effectuer pour que tout se passe bien une fois déployé dans le cloud.
Ces étapes sont extraites d’un article de Microsoft sur l’utilisation d’Azure Key Vault dans une Azure Function, disponible ici.
Déployer l’Azure Function
Configurer l’application Azure Function pour qu’elle puisse interagir avec le Key Vault se fait en deux étapes via des lignes de commande.
Tout d’abord, créer une “Managed Service Identity”, c’est-à-dire un compte de service qui sera utilisé par l’Azure Function pour accéder au Key Vault. C’est cette identité qui servira à l’AzureServiceTokenProvider, comme indiqué plus haut.
az functionapp identity assign --name 'walletpassgenerator' --resource-group 'MyResourceGroup' --role Reader
Cette commande affiche en retour un json de l’objet créé, avec notamment la propriété principalId qui va être utilisée dans la commande suivante.
Ensuite, donner à ce compte de service les droits pour lire les Secrets du Key Vault
az keyvault set-policy --name 'MyKeyVault' --object-id xxxxx-xxxx-xxxx-xxxx-xxxxxxx --secret-permissions get
Ici, ce compte de service (identifié par son principalId et spécifié dans le paramètre –object-id) se voit uniquement accorder le droit de lire la valeur des secrets pour ce Key Vault.
De la même façon que nous avons mis à jour le fichier local.settings.json dans le code de l’Azure Function, il faut modifier les Applications Settings de l’Azure Function, depuis le portail Azure.
Désormais, la Function fonctionne en utilisant le compte de service et se sert du Key Vault pour récupérer le certificat afin de signer notre pkpass. Nous n’avons donc plus de valeurs sensibles déployées avec l’application ou dans la configuration.
Le code source est disponible ici.
Conclusion
Dans cet article, nous avons vu comment améliorer la sécurité de notre Azure Function en retirant mots de passe et certificats du contrôleur de code source et des valeurs de configuration.
Cela a été rendu possible par l’utilisation de Azure Key Vault, pour stocker ces données sensibles, ainsi que de Managed Service Identity, qui permet d’identifier via un compte de service les ressources telles que les Web Apps, les Functions Apps et les machines virtuelles.
Cette approche permet donc de faire du DevOps de manière sécurisée.
Article co-écrit avec Mathilde Roussel.