Valet Key avec Azure Function App

Dans le billet « Azure Functions – Introduction au ServerLess en PowerShell« , on a posé les bases. Cette fois, passons à un usage concret avec le pattern Cloud Valet Key. C’est un des principes fondamentaux dans le cloud.

Introduction

Lorsqu’on regarde le fonctionnement des services de stockage Azure, on constate que la finesse / granularité d’affectation des permissions est binaire. Pour un Storage Account, on dispose de deux clés : Primaire et secondaire (les Access Keys). Le problème, c’est le niveau de privilège que ces clés octroient :

clip_image001

 

Heureusement, toute plateforme cloud qui se respecte est capable de nous proposer la capacité à générer des permissions beaucoup plus fines. Faut juste orchestrer la chose :

  • Etape n°1 : une application cliente soumets une demande d’accès auprès d’un maître des clés (notre Valet Key)
  • Etape n°2 : Le valet Key valide l’identité de l’appelant (authentification) et détermine si celui-ci est éligible à une telle demande (autorisation)
  • Etape n°3 : Le valet Key génère une clé d’accès pour le demandeur en appliquant des contraintes (niveau de permission, limitation dans le temps, limitation sur la source IP, …)
  • Etape n°4 : La clé générée est utilisée par le demandeur pour accéder à sa ressource

 

clip_image002

 

Voilà pour les fondamentaux du pattern. Au passage, je vous conseille la lecture des autres Cloud Design Patterns. Pour ce billet, nous allons mettre en place ce mécanisme pour générer des clés d’accès à un Storage Account, plus particulièrement un ensemble de tables.

Shared Access Signature versus Stored Access Policy

Premier sujet, comment générer des clés d’accès. Voilà un sujet que j’avais déjà abordé dans le billet « Shared Access Signature with stored Access Policy« . Avec le SAS ou les Stored Access Policies, on est capable d’assigner les permissions fines avec même la possibilité d’imposer des restrictions (restriction dans le temps, des services de stockage sollicités, voire même du contenu accédé). Bref, on a le choix. Après, il faut retenir quelques points :

  • On ne peut pas révoquer une Shared Access Signature (SAS), il faut renouveler les clés primaires & secondaires, donc révoquer toutes les SAS simultanément
  • Révoquer une Stored Access Policy implique de révoquer toutes les clés SAS liées à une policy.
  • Un Storage Account ne peut avoir que cinq Stored Access Policies
  • Shared Access Signature et Shared Access Policy peuvent s’appliquer par type d’usage (Table, Blog, Queue, File, …)
  • Une Shared Access Signature peut s’appliquer à un ensemble (toutes les tables) alors qu’une Stored Access Policy ne s’appliquera qu’à une seule table

 

Pour ce billet, nous allons mettre en place notre Key Valet en utilisant une Shared Access Signature. C’est un choix de ma part car j’ai besoin de générer des clés pour toutes les tables contenues dans un Storage Account et non pas pour chaque table. C’est ce que nous rappelle le portail.

clip_image003

 

Choix entre Consumption Plan versus App Service Plan

C’est un des avantages des Function App, la capacité à provisionner automatiquement l’infrastructure à la demande. Sur le papier, c’est bien. En plus cela rend notre API gratuite. Le défaut, c’est que si c’est gratuit, c’est tout sauf performant. Pour rappel un App service Plan Free repose sur des cœurs CPU non dédiés et avec uniquement 1Go de mémoire vive. Pour ces raisons, je provisionne notre Function App en utilisant un App Service Plan existant avec un SKU Standard 1, le minimum vital à mon sens pour partir en production (et encore).

clip_image004

 

Au passage, je vais réutiliser le Storage Account créé pour l’occasion pour notre Valet Key.

 

Besoin d’une identité ?

Dans mon billet « Azure Functions – Introduction au ServerLess en PowerShell » mon Azure Function j’avais précisé la démarche pour permettre à mon Azure Function de disposer d’une identité lui permettant de manipuler des ressources dans Azure. Depuis ce billet, la notion de Managed Identity Service est apparue. Cela simplifie grandement la mise en œuvre.

Question : Ais-je besoin d’une identité (et donc d’un Service Principal) pour accéder à Azure ? Beaucoup vont répondre oui en pensant qu’il faut nécessairement s’authentifier auprès d’Azure pour ensuite demander l’accès aux clés de stockage avec la commande PowerShell Get-AzureRmStorageAccountKey. D’un point de vue technique, c’est vrai, on peut extraire l’information mais combien cela coute-t-il en termes de performance ? Dans le contexte de notre démonstration sur le Key Valet, nous allons volontairement nous en passer, c’est un sujet sur lequel nous reviendrons ultérieurement.

Choix du langage ?

On ne va pas faire de mystère, je vais pas sortir un C# du chapeau (je laisse cela aux experts). Le but de ce billet, c’est de démontrer qu’on peut écrire des API en PowerShell, dont acte, …

clip_image005

 

A ce jour, le support de PowerShell est expérimental (je confirme). Au moment de l’écriture de ce billet, c’est la version 4.0 qui est proposée dans Function App. C’est un point à prendre en considération dans le développement de vos scripts (amère expérience, …). A ce jour seul trois types de déclencheurs sont disponibles pour Azure Function en PowerShell :

  • Le déclencheur HTTP
  • Le déclencheur sur planification
  • Le déclencheur sur injection d’un message dans une Azure Queue d’un Storage Account

 

Le but de ce billet étant d’écrire une API en PowerShell, c’est donc le choix HTTPTrigger qui a été retenu.

Authentification auprès du Key Valet

Mon API sera accessible publiquement via une URL. Si tout le monde peut demander des clés d’accès pour mes Azure Table, ça pose un sérieux problème. C’est pour cela que Function App propose trois niveaux d’authentification :

  • Function : Secret d’accès propre à chaque Azure Function
  • Anonymous
  • Admin : Secret d’accès partagé par toutes les Azure Function hébergées dans une même instance Function App.

clip_image006

 

Dans mon contexte, j’ai retenu le niveau « Function ». Au final, L’URL de mon API est la suivante. Après, on peut aussi exploiter les fonctionnalités natives d’Azure Web App (Azure Active Directory, Google, Microsoft Live, …) mais aussi Azure API Management Gateway.

clip_image007

 

Pour ceux qui ont suivi, notre API est prête pour son premier appel.

 

Premier appel

Avec notre choix HttpTrigger, nous avons automatiquement un premier exemple de code PowerShell pour notre API. Cela se passe de commentaire, c’est un Hello World.

clip_image008

 

Ce qui est bien, c’est que cela permet de poser les bases de l’appel à notre API. API développée en PowerShell, appel en PowerShell, logique :

$HttpRequestUrl= « « 

$parameters = @{name=’SimplebyDesign’}

$json = $parameters |ConvertTo-Json

Invoke-RestMethod -Uri $HttpRequestUrl -Method POST -ContentType ‘application/json’ -Body $json

clip_image009

 

Passons au PowerShell qui tache

En fait, il ne tache pas tant que cela. On commence par récupérer deux variables dans les Application Settings. Celles-ci nous permettent de construire un contexte d’accès à notre Storage Account (commande New-AzureStorageContext). De là, il n’y a plus qu’à générer une clé SAS (commande New-AzureStorageAccountSASToken). Dans le contexte de mon besoin, mon Key valet doit me générer des clés permettant :

  • L’accès à un seul service dans le Storage Account : Table
  • La permission obtenue doit être limitée au stricte nécessaire pour lire les données dans les Azure Table
  • La permission doit être limitée dans le temps
  • L’utilisation du protocole HTTPS doit être obligatoire

 

$StorageAccountName = $env:StorageAccountName

$key = $env:StorageKey

$startdate = Get-date

Try

{

$StorageContext = New-AzureStorageContext -StorageAccountName $StorageAccountName -StorageAccountKey $key

[DateTime]$ExpirityTime = (Get-Date).AddMinutes(15)

$sastoken = New-AzureStorageAccountSASToken -Service Table -ResourceType Service,Container,Object -Permission « rl » -Context $StorageContext -Protocol HttpsOnly -ExpiryTime $ExpirityTime

[System.IO.File]::WriteAllText($res,$sastoken)

« Generated $([math]::round((New-TimeSpan -Start $startdate -End (get-date)).TotalSeconds,2)) TotalSeconds »

}

Catch

{

Out-File -Encoding Ascii -FilePath $res -inputObject « INTERNALERROR »

}

 

Petit point d’attention : Pour ceux qui se posent la question pourquoi c’est si compliqué pour retourner le résultat avec la commande : « [System.IO.File]::WriteAllText($res,$sastoken) « . Un Simple Out-File fait la même chose. Oui et non. En PowerShell 4.0 a cette fâcheuse manie d’ajouter un retour à la ligne, ce qui fausse le résultat retourné à l’appelant. Dans PowerShell 5.0, on a bien un paramètre « -NoNewline ». Problème, c’est du PowerShell 4.0

clip_image010

 

Le goudron et les plumes ?

Mon API est prête. Elle a même déjà délivré sa première clé. Certains ont déjà compris que l’Access Key à mon Storage Account est référencé en clair dans les Applications settings et sont déjà parti chercher le goudron et les plumes.

clip_image011

 

Oui, il aurait été possible de ne pas coder l’information en clair dans les Application Settings et d’utiliser un Key Vault. Avec un Service Principal on aurait pu s’authentifier auprès d’Azure et accéder aux Access Keys. C’est ce dernier point qui m’a posé problème. Lorsque j’ai mis en place mon API pour une application que je développe, j’ai été surpris par le temps de réponse moyen : 20 secondes en pleine charge. WTF !!!!!!!!!!!

Heureusement, pour comprendre, j’avais réalisé l’intégration de mon Azure Function App avec Application Insights histoire d’avoir de la télémétrie : Data Driven Decision. Aussi étrange que cela puisse paraître, lorsqu’on consomme directement l’Access Key au lieu de l’extraire, on obtient une performance bien différente comme illustré ci-dessous :

clip_image012

 

Sur la même ligne temporelle on peut constater le niveau de réponse moyen avant et après optimisation. Sur un appel individuel on passe de quatre secondes à une seconde. Mon application est fortement consommatrice de l’API. Il y a donc beaucoup d’accès concurrentiels et mon choix de n’avoir qu’une seule instance de l’App Service Plan en SKU S1 n’aide pas à la performance. Pourtant, une fois la mise à niveau réalisée, la performance de l’API est restée constante : Autour d’une seconde.

Une amélioration serait d’utiliser Managed Identity Service pour ensuite accéder à un Key Vault. Je rentre pas dans le détail, d’autres ont exploré le sujet : « Enabling and using Managed Service Identity to access an Azure Key Vault with Azure PowerShell Functions« . A première vue, cela ne dégraderait pas trop les performances.

Consommons notre Valet Key

Nous avons notre Valet Key, ne nous manque plus qu’une application qui le consomme. Je suis allé au plus simple. L’appel à l’API n’a pas changé. La clé SAS nous est retournée, pour être consommée afin de générer un contexte d’accès au stockage. De là, il n’y a plus qu’à accéder à une table dans le Storage Account :

$HttpRequestUrl= « « 

$token = Invoke-RestMethod -Uri $HttpRequestUrl -Method POST -ContentType ‘application/json’

$newcontext = New-AzureStorageContext -StorageAccountName « tpserverlessstorage » -SasToken $token

Get-AzureStorageTable -Name TestTable -Context $newcontext

clip_image013

 

Voilà un exemple concret d’utilisation des Azure Function App. Après, on peut grandement l’améliorer (méthode d’authentification, différenciation des niveaux d’accès, exposition avec Azure API Management Gateway, utilisation du Key Vault, …)

 

BenoîtS – Simple and secure by design but Business compliant (with disruptive flag enabled)

Benoit

Simple, yes, Secure Maybe, by design for sure, Business compliant always!

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.