Pour cet article, j’ai eu envie d’aborder la problématique de l’accès aux secrets d’une KeyVault via une Azure Function.

Je voulais répondre, par cet article, à toutes les questions que je me posais lorsque j’ai débuté les azure functions et les keyvaults mais que je n’ai malheureusement pas trouvé.

Je voulais évoquer le pourquoi, le comment, le comment ça marche et les conséquences de tout cela dans un vrai environnement de production exigeant de hautes performances.

 

 

Keyvault pour une meilleure protection des données

 

Une keyvault est la ressource Azure parfaite pour stocker des secrets et des certificats. Ils sont cryptés à froid dans des machines spécialisées. Mais surtout, la définition des stratégies d’accès est intuitive, rapide tout en restant puissante et sécurisante.

Il y a un point particulièrement intéressant, en effet, les administrateurs n’ont pas accès aux secrets de la keyvault par défaut. Il faut qu’ils obtiennent un droit spécifique et la création / modification de ces droits sont auditables.

On peut même définir des alertes pour tout changement sur ces droits. C’est ce dernier élément qui m’a convaincu personnellement.
Il est impossible pour un “pirate” de voler les secrets sans laisser des traces immédiates, même s’il venait à avoir un accès administrateur. En se rappelant que bon nombre des actes de piratage proviennent des membres de notre équipe (ou ex membres). Que ce soit de la malveillance, des méconnaissances ou des maladresses de ceux ci, toutes les précautions sont les bienvenues.

Au moindre doute, on réinitialise les accès de la Keyvault et les valeurs des secrets, un ou deux redémarrages et c’est tout.

 

 

Azure Functions et les Keyvault

 

De nombreux articles similaires existent également un peu partout sur internet. La documentation Azure Function de Microsoft présente de nombreux tutoriels. D’ailleurs, un très bon article Cellenza existe déjà à ce sujet : Utiliser Azure Key Vault pour sécuriser un certificat dans une Azure Function.

N’ayant pas trouvé les réponses à mes problématiques de Secrets, voilà pourquoi j’ai décidé d’axer mon article sur ce sujet.

 

 

Problématiques posées par ce type d’implémentation

 

Tout semble fonctionner correctement dans les différents tutoriaux, alors pourquoi écrire un article supplémentaire ?

Dans presque tous les tutoriels présents sur internet, la connexion vers la keyvault est déclarée dans une méthode exécutée à chaque appel de l’Azure Function.

J’aimerai évoquer 5 problématiques résultants de ce type d’implémentation pour les Secrets. D’ailleurs, les 3 premiers problèmes ne sont pas limitées aux Azure Functions mais à toute utilisation de secrets.

 

1. Limitation d’une Keyvault

Créer une connexion à la keyvault à chaque appel de la fonction a des impacts sur la keyvault.

En effet, il faut indiquer que la limite de transactions pour une KeyVault est de 2 000 requêtes toutes les 10 secondes ! Soit 200 / seconde. Et 200 requêtes par secondes en moyenne, ce n’est vraiment pas beaucoup.

💡 Pour en savoir plus à ce sujet, je vous invite à consulter la documentation Microsoft qui présente les limites du service Azure Keyvault.

 

2. Limitation des connexions TCP

Pour les mêmes raisons, la connexion vers la KeyVault, si elle est déclarée à chaque appel, va coûter une connexion TCP (pour Transmission Control Protocal).

  • Si votre azure fonction est crée en mode “Consumption Plan” vous serez peu affectés car les instances mutualisées sont assez puissantes et leurs limites sont importantes.
  • Si, par contre, votre azure function est hébergée sur une App Service Plan vous êtes limités en nombre de connexions TCP.

Voici des exemples de limitations selon le type de plans courant :

  • 1,920 connexions tcp maximum par instance B1/S1/P1
  • 3,968 connexions tcp maximum par instance B2/S2/P2
  • 8,064 connexions tcp maximum par instance B3/S3/P3

En sachant qu’il faut ensuite prendre en compte que les connexions vers les ressources (Storage, Bases de données, api, …) prennent également des connexions TCP supplémentaires.

Si vous dépassez la limite de connexions TCP, voici le message fatidique que l’on n’aime pas voir en production :

“An operation on a socket could not be performed becausethe system lacked sufficient buffer space or because a queue was full”

💡 Pour en savoir plus sur les connexions TCP, je vous invite à lire l’article “Deep Dive into TCP Connections in App Service Diagnostics” du blog App Service.

 

 

3. Performance d’une KeyVault

Il faut savoir que chaque appel pour récupérer un secret de la keyvault prend du temps. 30 millisecondes pour les appels les plus rapides et jusqu’à 600 millisecondes pour les plus lents.

Multipliez ce délai par le nombre de secrets dont vous avez besoin, cela peut donc dépasser la seconde rapidement.

Cette latence vers la KeyVault est impossible à améliorer. Elle dépend de la charge des ressources Azure, de la charge réseau, …

Cela peut vous paraître très peu, mais pour d’autres, dont je fais partie, c’est au contraire un temps d’attente énorme lorsque l’on veut créer des APIs (pour Applications Programming Interface) de hautes performances.

 

 

4. Initialisation de certaines ressources

Certaines ressources doivent être initialisées avant que la startup de l’Azure Function ne soit lancée. Malheureusement, on ne peut pas modifier cette initialisation sans procéder à des centaines de manipulations ou implémenter énormément de code. Ce sont par exemple les triggers : abonnement à un event hub ou à des évènements cosmos db, …

 

Exemple de connexion vers un trigger EventHub

[FunctionName(“EventHubTriggerCSharp”)]`
public static void Run([EventHubTrigger(“samples-workitems”, Connection =
“EventHubConnectionAppSetting”)] EventData[] events, ILogger log)`
{
foreach (EventData eventData in events)`
{
// Do your things

}
}

Dans l’exemple ci-dessus, la simple déclaration EventHubTrigger(…) permet de nous faciliter énormément le travail. Il suffit d’indiquer le nom de la chaîne de connexion de notre configuration et le nom du container. Il ne nous reste qu’à écrire le code à exécuter pour les messages, et c’est tout, c’est ce que l’on appelle du low code.

Si nous voulions définir la connexion contenant le token dans un secret KeyVault, nous devrions implémenter entièrement une classe EventProcessorHost pour gérer les messages de l’event Hub, les connexions, la parallélisation, … C’est long, fastidieux et source d’erreur.

 

Le secret oublié : APPINSIGHTS_INSTRUMENTATIONKEY

 

C’est le secret que tout le monde oublie et qui mériterait d’être dans une keyvault.

L’APPINSIGHTS_INSTRUMENTATIONKEY est le token qui lie votre Azure Function à un puit de log Azure Application Insights. Il est enregistré dans la configuration de l’Azure Function.

On pourrait considérer que ce token n’est pas un secret. Effectivement, dans beaucoup de cas, on retrouve en clair les instrumentations key. C’est le cas dans les applications web, car le code source ne peux pas rester secret dans une page web.
Ils ne permettent pas de lire les logs ou les modifier mais seulement de les créer, ils semblent donc être inoffensifs.

Alors pourquoi vouloir le protéger ? Et bien parce que dans certains autres cas, il est indispensable de sécuriser le token application insights. Par exemple, si vous utilisez les métriques d’app Insights pour allouer / désallouer des ressources supplémentaires. Toutes personnes malveillantes ayant accès au token pourraintt causer des coûts d’exploitation importants en générant de faux logs.

Un autre exemple plus parlant peut être. Pensez au temps perdu à rechercher des causes d’erreurs, qui n’en sont pas, parce que n’importe qui se log dans votre app insights.

Toute ressemblance à une situation déjà vécue dans une de mes missions ne serait pas tout à fait fortuite. Des développeurs loguant dans le puit de logs de la production sans en avoir connaissance parce qu’ils ont recopié les valeurs de la configuration de production sans chercher à comprendre.

 

 

5. Esprit Low Code

Résoudre les problématiques 1 à 4 est possible avec du code sans utiliser l’astuce décrite dans cet article.

Nous pourrions par exemple définir des connexions statiques en singleton et en utilisant un système de cache pour résoudre les problèmes 1, 2 et 3. Mais cela nous demanderait des centaines de lignes de code, une expertise importante et de très nombreux tests à chaque livraison. C’est long.

Nous pourrions bidouiller le startup pour changer l’instrumentation key ou redéfinir la connexion des event hub ou autres triggers pour les problèmes n°4. Cependant, ici, le risque d’erreurs est immense. La documentation de ces paramètres est souvent inexistante ou partielle. Qui plus est, ce genre de contournement ne fonctionne bien souvent que jusqu’à la prochaine mise à jour. Et dans le cloud, les mises à jour sont constantes.

Par dessus tout, ces implémentations de code c’est du temps, de l’expertise et un risque élevé d’erreurs. Tout l’inverse de l’esprit Low Code.
Si vous aussi vous avez choisi les Azure functions pour leur facilité, difficile de justifier ces développements à votre client ou votre responsable.

Gardez votre temps pour résoudre vos problématiques métier et pas les tracasseries techniques.
Il existe une solution magique à ces problèmes.

Le mot magique est @Microsoft.KeyVault(SecretUri=…)

 

Depuis novembre 2018 (en bêta) puis fin 2019 pour la release générale, il existe un mot clé utilisable dans les valeurs de la configuration des Azure Functions. Mais très peu d’articles en parlent et c’est dommage.

@Microsoft.KeyVault(SecretUri= <L'Url vers votre secret> )

Voici un exemple :

@Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/mysecret/ec96f02080254f109c51a1f14cdb1931)

On peut l’écrire également:

@Microsoft.KeyVault(VaultName=myvault;SecretName=mysecret;SecretVersion=ec96f02080254f109c51a1f14cdb1931)

 

Tutoriel en deux étapes

 

Ne partez pas tout de suite, il y a plein de chouettes explications après le tutoriel. Promis,  juré !

Pour ma démonstration, je vais protéger mon Application Insights Instrumentation Key mais vous pouvez reproduire la même manipulation pour n’importe quelle autre valeur de votre configuration.

 

 

Etape 1 : activer une System Assign Identity et donner des droits

Si vous possédez déjà une System Assigned Identity donnant accès à une keyvault sur votre fonction vous pouvez passez à l’étape suivante.

Cette étape n’est à effectuer qu’une seule fois.

Si non, voici comment faire en utilisant le portail. Retrouvez comment le faire en ligne de code dans cet article : Utiliser Azure Key Vault pour sécuriser les secrets d’une Azure Function.

  1. Ouvrez votre Azure Function puis cliquez sur Platform Features
    Ouvrez votre Azure Function puis cliquez sur Platform Features
  2. Cliquez sur Identity
    Cliquez sur Identity
  3. Puis activez si ce n’est déjà fait le Status à On puis enregistrez. Enregistrez et passons au keyvault.A la fin de l’article toutes les explications sur ce que nous venons de faire.
    capture Identity
  4. Ouvrez dans le portail azure toujours le keyvault dans lequel vous stockerez les secrets puis cliquez sur Access Policies puis ajouter une Access Policy
    capture ouverture de la keyvault

Note : Vous pouvez remarquer dans la copie d’écran ci-dessus qu’un utilisateur avec mon nom est déjà présent et possède les permissions pour les Clés, les Secrets et les certificats. Cela me donnera la possibilité, dans l’étape 2, de pouvoir créer et modifier les secrets.

Cependant, il se peut qu’aucun utilisateur avec votre nom ne soit présent. Cela peut arriver si vous n’avez pas créé la keyvault via le portail ou si ce n’est pas vous qui l’avez créée ou bien encore si vous avez créé la keyvault avec aucun droits.

Si c’est le cas, pensez à rajouter une access policy pour vous-même avec les droits “Get” , “List” et “Set” afin que vous puissiez rajouter le secret par la suite.

 

  1. Dans l’écran add access policy, sélectionnez “Get” et “List” dans la dropdown list des secrets. Une azure Function n’a pas besoin de plus pour la lecture des configurations.

capture écran add access policy

  1. Puis cliquez sur Select Principal, saisissez le nom de votre azure function. Sélectionnez là lorsqu’elle apparaitra dans la liste, cliquez sur Select et enfin cliquez sur Add.
    Capture écran
  2. Attention, ce n’est pas encore fini. Je me fais avoir encore très régulièrement après des années de pratique. Vos modifications ne sont enregistrées que lorsque vous aurez cliqué sur “Save” !!! Sans quoi, retour à la case départ, il n’y a aucun message d’avertissement.
    capture save

Voilà, maintenant votre Azure Function possède les droits pour lire les secrets dans votre Keyvault. Passons à l’étape suivante pour la création du secret proprement dit.

 

Etape 2 : sécuriser votre configuration Azure Function

  1. Ajoutons l’Application Insight Instrumentation Key dans la keyvault.
    Allez dans la partie secrets de votre KeyVault puis cliquez sur Generate/Import pour ajouter un secret.
    capture partie secret de la keyvault
  2. Donnez un joli nom à votre secret. Saisissez Sa valeur puis cliquez sur create
    capture saisie du nom de la Secret
  3. Votre secret doit avoir été ajouté à la liste. Cliquez lui dessus pour afficher les détails. Cliquez sur la dernière version. Dans notre cas, il n’y a qu’une version car nous venons de la créer. Puis notez l’url du secret.
    capture détail de la Secretcapture détail de la Secretcapture détail de la Secret

  4. Retournez maintenant sur votre Azure Function puis affichez la partie configuration. Cliquez sur l’icone Edit de APPINSIGHT
    capture AppInsightcapture AppInsight
  5. Remplacez la valeur par cette valeur :@Microsoft.KeyVault(SecretUri=
    < L’url du secret que vous avez copiée dans le point 3 >)

    puis cliquez sur Ok, enregistrez et indiquez continue pour valider.
    capture insertion key

capture insertion key
capture insertion key

  1. Après avoir rafraîchi la page, vous devriez voir apparaître un petit texte et une icône “check” verte indiquant que nous avons affaire à une référence KeyVault et plus un texte simple.
    Cette petite coquetterie est très récente mais ultra pratique.

Capture rafraichir la page

Si vous voyez par contre une icone rouge : icone rouge

Cela indique que :

    • soit l’url vers le secret est incorrecte,
    • soit l’azure function ne possède pas les droits pour lire le secret.

Cliquez sur l’icône “crayon” pour éditer votre secret et avoir plus d’info sur cette erreur.
icone crayonBon à savoir : 

Si l’on supprime la version dans l’url du secret ( comme ceci : @Microsoft.KeyVault(SecretUri=https://keyvaultarticle.vault.azure.net/secrets/AppInsightInstrumentationKey/) ), la valeur que l’on récupérera pour la configuration est bien la bonne, la dernière version active notre secret.

Néanmoins, Microsoft indique que la version est obligatoire. J’imagine que lors d’une évolution future, ce comportement pourrait changer donc attention.

  1. Impacter votre code

Quel modifications apporter à votre code ?

Aucune. C’est ça qui est magique. Surtout cela respecte l’esprit low code des azure functions.

Vous n’avez pas à changer votre code. Si vous savez récupérer une valeur de la configuration, il n’y a rien à faire de plus.

Vous savez donc sécuriser vos secrets dans une keyvault en quelques clics seulement.

 

 

Comment ça marche ?

 

Fonctionnement du System Assigned Managed Identity (SAMI)

Évoquons l’implémentation des droits par SAMI ( j’aime bien cet acronyme ).

Pour faire simple, le System Assigned Managed Identity permet de définir une identité pour une ressource Azure.

Cette identité est enregistrée dans l’Active Azure Directory.

Dans notre cas, il s’agit d’une identité associée à une Azure Function.

Cette identité est transparente pour le développeur. En effet des librairies système permettent de détecter, de manière sécurisée, l’identité associée à une ressource azure en cours d’exécution. Nous n’avons pas besoin de stocker un id utilisateur ou un token, tout est transparent et réalisé lors du runtime.

Cette identité, assimilable à un user AD classique, peut ensuite être utilisée pour donner des droits.

Nous pourrions également utiliser cette identité pour appeler de manière transparente d’autres Azure Functions ou accéder à d’autres ressources comme des Blobs Storage par exemple.

Vous trouverez plus d’informations sur l’article de la documentation Microsoft à ce sujet.

 

Récupération de la valeur du secret ?

 

L’Azure function récupère la valeur du secret en effectuant ce que vous auriez fait dans le code. Pour chaque valeur dans la configuration qui a une référence vers une keyvault :

  • Créer une connexion vers la Keyvault en indiquant d’utiliser SAMI (décidément, j’adore cet acronyme),
  • récupérer la valeur de configuration définie par l’url “SecretUri=”
  • Remplacer en mémoire la valeur @Microsoft.Keyvault(……) par la valeur telle qu’elle est entrée dans le secret.

 

Que se passe-t-il si le secret ne peut pas être récupéré

 

Si, pour n’importe quelle raison, le secret ne peut pas être récupéré, la valeur restera @Microsoft.Keyvault(……).

Cela peut arriver si les droits changent, si le secret ou si la version que l’on cherche en particulier n’existent plus.

 

Avantages

  • Low Code intégral : vous pouvez passer d’un POC à un système avec secrets sécurisés sans aucune modification sur votre code existant.
  • Ultra performant car le secret est récupéré une fois pour toute au démarrage de la function
  • 1 seule requête par secret vers la keyvault lors du démarrage.
  • Pas de connexion TCP supplémentaire.

 

Inconvénients

  • Les certificats ne fonctionnent pas avec ce mot clé magique. Ou du moins pas encore.
  • Si le secret est mis à jour, il faut redémarrer la fonction. Ou bien utiliser une Azure App Configuration, mais c’est l’objet d’un autre article à venir.

 

Pour conclure

 

Vous allez me dire, tout cet article juste pour un mot clé. Et vous n’auriez pas entièrement tord.

Je préfère toujours mettre l’accent sur ce que ce tutoriel pourra vous apporter.

N’hésitez pas à nous contacter pour nous signaler un sujet que vous aimeriez que nous approfondissions.

Dans mon prochain article, j’évoquerai les Event Hub et comment en tirer le maximum de performances avec vos Azure Functions.