Archives par étiquette : Azure Function App

Resource Group As a service – Mise en place du stockage

Après, un précédent billet pour poser les bases, il est temps de poser les fondations pour nos API. Première brique, un Resource Group. L’intégralité des ressources Azure sont stockées dans un groupe de ressources : New-AzureRMResourceGroup -ResourceGroupName ResourceGroupAsAService -Location « West Europe »

clip_image001

 

Côté stockage, j’ai identifié trois usages. Etant donné que nous allons avoir deux instances du service Azure Function, nous avons déjà deux instances de Storage Account. Techniquement, on peut très bien mutualiser mais je préfère isoler. Cela ne change rien au niveau de la facturation. La dernière instance, sera destinée à stocker les données utilisées par les API (Azure Table). La nature de ces données étant relativement sensibles, j’ai volontairement retenu une instance dédiée. Ci-dessous la commande PowerShell pour créer la première instance du Storage Account :

New-AzureRmStorageAccount -ResourceGroupName resourcegroupasaservice -AccountName resourcegroupasaservice1 -Location « West Europe » -SKUName « Standard_LRS » -Kind StorageV2 -EnableHttpsTrafficOnly $True

clip_image002

 

Puis la seconde instance : New-AzureRmStorageAccount -ResourceGroupName resourcegroupasaservice -AccountName resourcegroupasaservice2 -Location « West Europe » -SKUName « Standard_LRS » -Kind StorageV2 -EnableHttpsTrafficOnly $True

clip_image003

 

Et enfin la troisième instance : New-AzureRmStorageAccount -ResourceGroupName resourcegroupasaservice -AccountName resourcegroupasaservice3 -Location « West Europe » -SKUName « Standard_LRS » -Kind StorageV2 -EnableHttpsTrafficOnly $True

clip_image004

 

Nous allons passer un peu de temps avec cette troisième instance puisqu’elle va contenir des Azure tables qui vont être utilisées par les API. Commençons par récupérer les clés de cette instance de Storage Account : $keys = Get-AzureRmStorageAccountKey -ResourceGroupName resourcegroupasaservice -Name resourcegroupasaservice3

$keys

clip_image005

 

Nous allons utiliser la première clé pour nous créer un contexte pour créer les Azure tables suivantes :

  • AuthorizedCallers
  • AuthorizedEnvironments
  • AuthorizedIAMTemplates
  • DefaultCostCenter

 

$context = New-AzureStorageContext -StorageAccountName resourcegroupasaservice3 -StorageAccountKey $keys[0].value

« AuthorizedCallers AuthorizedEnvironments AuthorizedIAMTemplateRole AuthorizedPolicyAssignment DefaultCostCenter ».Split() | new-AzureStorageTable -Context $context

clip_image006

 

Avoir des tables c’est bien mais on a aussi besoin d’un peu de contenu. Pour cela, nous allons faire appel au module PowerShell AzureRmStorageTable disponible sur la PowerShell Gallery. Commençons par poser quelques bases :

Install-Module -Name AzureRmStorageTable

$resourcegroupname = « resourcegroupasaservice »

$Storageaccountname = « resourcegroupasaservice3 »

$location = « West Europe »

clip_image007

 

Jusqu’ici rien de bien sorcier. Mes tables sont localisées dans le groupe de ressources resourcegroupasaservice3, lui-même dépendant du Storage Account resourcegroupasaservice. Continuons avec l’alimentation de la première table Authorizedcallers. Celle-ci permet aux API de déterminer :

  • Si l’utilisateur est accrédité à utiliser le service ou non pour une souscription Azure donnée
  • La liste des valeurs acceptées pour le tag CostCenter qui sera associé au groupe de ressources à créer en fonction du demandeur
  • La liste des valeurs acceptées pour le tag Environnment qui sera associé au groupe de ressources à créer en fonction du demandeur
  • La liste des régions azure autorisées pour la création du groupe de ressources en fonction du demandeur

 

$table = Get-AzureStorageTableTable -resourceGroup $resourceGroupname -tableName « Authorizedcallers » -storageAccountName $Storageaccountname

$partitionkey = (Get-AzureRmContext).Subscription.ID

$rowkey = ‘Simplebydesign@bsautierehotmail.onmicrosoft.com’

Add-StorageTableRow -table $table -partitionKey $partitionKey -rowKey $rowkey -property @{« Authorized »= »true »; »AuthorizedCostCenters »= »ITDEPT,FINANCEDEPT »; »AuthorizedDefaultRegion »= »WestEurope »;

« AuthorizedEnvironments »= »TESTS »; »AuthorizedRegions »= »WestEurope,NorthEurope »}

clip_image008

 

L’utilisateur indiqué est déclaré autorisé à demander la création d’un groupe de ressources dans la souscription courante avec des valeurs prédéfinies pour les tags CostCenter et Environment. Voilà le mécanisme d’autorisation.

Passons à la table AuthorizedEnvironments. Lors de l’appel à l’API Request-ResourceGroup, l’appelant peut préciser quelle valeur associer au tag Environnement (dans la liste des valeurs autorisées). Si l’appelant ne précise aucun environnement en particulier, alors, l’API va consulter cette table pour déterminer la valeur par défaut.

$table = Get-AzureStorageTableTable -resourceGroup $resourceGroupname -tableName « AuthorizedEnvironments » -storageAccountName $Storageaccountname

$partitionkey = (Get-AzureRmContext).Subscription.ID

$rowKey = « TESTS »

Add-StorageTableRow -table $table -partitionKey $partitionKey -rowKey $rowkey

clip_image009

 

Maintenant, si nous appelions l’API Request-ResourceGroup sans préciser de valeur pour le tag Environnement, la valeur par défaut retenue serait « TESTS ». Pour le tag CostCenter, c’est le même principe. Si l’appelant ne précise pas une des valeurs qui lui sont autorisées pour la création d’un groupe de ressources dans une souscription Azure donnée, alors, l’API ira chercher la valeur par défaut à utiliser dans la table DefaultCostCenter

$table = Get-AzureStorageTableTable -resourceGroup $resourceGroupname -tableName « DefaultCostCenter » -storageAccountName $Storageaccountname

$partitionkey = (Get-AzureRmContext).Subscription.ID

$rowkey = ‘Simplebydesign@bsautierehotmail.onmicrosoft.com’

Add-StorageTableRow -table $table -partitionKey $partitionKey -rowKey $rowkey -property @{« CostCenter »= »ITDEPT »}

clip_image010

 

Ceci clos la partie stockage. Prochaine étape, on s’attaque à la gestion des secrets dans la solution avec du KeyVault à tout va.

 

BenoîtS – Simple and Secure by Design but Business compliant

Resource Group as A service – Introduction

Lors de ma session sur la gouvernance Azure au Global Azure Bootcamp, j’avais fait la démonstration du premier prototype de mon ensemble d’API « Resource Group as a Service ». Pour rappel, l’idée générale était de répondre à la problématique de la gouvernance Azure, en particulier au niveau des groupes de ressources. Le but de Resource Group as a service est de proposer un intermédiaire qui prend en charge la création et la configuration des Resources Groups pour le compte des demandeurs. Le développement a maintenant bien avancé (Pas encore une V1 mais au moins une Beta stable). Cette série de billets sera consacrée à cette API. Nous allons commencer par les Design Principles retenus pour ce développement.

Design Principles / contraintes imposées

  • Langage de développement : OK, c’est pas un vrai Design Principle. Normalement, le langage utilisé ne rentre pas en ligne de compte. Dans mon contexte, c’est plus ma contrainte imposée pour permettre un développement simple : PowerShell
  • Support de nos API : Azure Function. C’est mon choix. Pas la peine de mettre en œuvre une instance de Service Fabric pour porter quatre API. Pour rappel, une Azure Function a un temps d’exécution limité à 300 secondes, ce qui sera largement suffisant pour créer un groupe de Ressource. Ici aussi ce choix implique une contrainte car :

    • A ce jour le support de PowerShell en encore expérimental dans Azure Function
    • Le niveau de performance ne sera pas équivalent si on avait retenu C#
  • Authentification : Chaque appel à nos différentes API devront être authentifiés. Dans le contexte d’Azure Function, le choix le plus simple a été retenu avec la prise en charge de l’authentification via Azure AD. On reviendra sur ce choix ultérieurement car il aura quelques conséquences
  • Autorisation : L’authentification et l’autorisation sont deux choses bien distinctes. Ce n’est pas par ce qu’on va avoir accès aux API que tout nous sera autorisé. Dans mes Design Principles, j’ai retenu les points suivants :

    • Chaque utilisateur devra être enregistré comme autorisé pour pouvoir soumettre une demande de création de groupe de ressource
    • Le niveau d’autorisation sera individualisé au niveau de chaque souscription prise en charge par l’API
    • Le niveau d’autorisation prendra la notion de CostCenter au niveau de chaque souscription. Dans notre contexte, c’est un TAG positionné au niveau du groupe de ressources. Chaque utilisateur sera accrédité à un ou plusieurs codes d’imputation comptables qui sera associé au tag CostCenter
    • Le niveau d’autorisation prendra en compte la notion d’environnement au niveau de chaque souscription. Dans notre contexte, c’est un TAG positionné au niveau du groupe de ressources. Chaque utilisateur sera accrédité à demander la création d’un groupe de ressources avec une sélection de valeurs pour le TAG environnement.
    • Le niveau d’autorisation prendra en compte la notion de région pour limiter la création du groupe de ressources pour un ensemble de régions Azure donnée
  • Contexte d’exécution : Pour créer des ressources Azure, on a besoin d’une identité. Hors de question de coder des comptes / mots de passe ou même des clés d’accès au stockage. Choix a été fait d’utiliser la fonctionnalité Managed Identity Services pour nos Azure Function. Cela a quand même quelques implications :

    • Toutes les API regroupées dans une même instance du service Azure Function utiliseront donc la même identité
    • L’identité en question prendra la forme d’une application Azure AD et d’un Service Principal, lequel devra disposer des rôles nécessaires
  • Support de multiples souscriptions : Un contexte d’exécution propre à chaque souscription Azure sera nécessaire. Etant donné que ces multiples souscriptions peuvent ne pas utiliser le même tenant Azure AD comme fournisseur d’authentification, il ne sera pas possible d’utiliser l’identité des Azure Function (Managed Identity Services). Pour adresser cette problématique, il a été retenu que la solution associer un contexte d’exécution pour chaque souscription. Les secrets nécessaires pour chaque souscription seront stockés dans une instance distincte du service KeyVault. Cette séparation des secrets permettra de proposer aux gestionnaires des souscriptions de mettre à jour eux même le contexte de chaque souscription
  • Informer les consommateurs de leurs droits : Les principes retenus pour le mécanisme d’autorisation font que les consommateurs pourront disposer de privilèges bien différents selon les souscriptions. Afin de les informer des privilèges qui leurs sont associés, des API spécifiques seront proposées afin leur permettre de déterminer :

    • Les souscriptions auxquelles ils ont accès
    • Les tags Environnements qu’ils peuvent utiliser au sein d’une souscription Azure donnée
    • Les tags CostCenters qu’ils peuvent utiliser au sein d’une souscription Azure donnée
    • Les régions Azure qu’ils peuvent utiliser au sein d’une souscription Azure donnée
  • Stockage des données : Dans notre contexte, il a été retenu d’utiliser le service Azure Table pour gérer le mécanisme d’autorisation des API
  • Principe du moindre privilège : Principe essentiel pour le développement des API. A chaque fois que ce sera possible, le niveau d’accès de « moindre privilège » sera utilisé.

 

Ressource créée dans la souscription

  • Groupe de ressources : C’est quand même le but. Je n’ai pas encore prévu d’intégrer de contrôle sur le charte de nommage
  • Tags : Les groupes de ressources créé par l’API seront configurés avec un ensemble de Tags obligatoires et d’autres optionnels. Le notions d’environnements et de CostCenter sont eux obligatoires. L’utilisation de certains tags sera contrainte par les autorisations précisées dans les différentes tables Azure Table.
  • RBAC : Selon la configuration mise en place au niveau des Azure table, les groupes Azure AD seront positionnés avec des rôles builtin ou custom en fonction de la souscription Azure
  • Policies : Selon la configuration documentée dans les Azure Table, une ou plusieurs Azure policies seront positionnées en fonction de la souscription Azure.

 

Liste des API

Resource Group as A service, ce n’est pas qu’une seule API. En fait, c’est un peu plus compliqué que cela. Le tableau ci-dessous résume les API qui seront proposées via Azure Function :

Nom

Accès public

Méthode d’authentification

Managed Identity Service

Get-AuthorizedSubscription Oui Azure AD Activé
Get-AuthorisedEnvironments Oui Azure AD Activé
Get-AuthorizedCostCenters Oui Azure AD Activé
Get-AuthorizedRegions Oui Azure AD Activé
Request-ResourceGroup Oui Azure AD Activé
Get-ValetkeyForAzureTableRead Non Function Key Activé

 

Pour les quatre premières API, on comprend que c’est l’implémentation de la fonctionnalité permettant aux consommateurs de déterminer de quels privilèges ils disposent au sein des souscriptions prises en charge. Logiquement, ces APIs sont directement accessibles par les consommateurs pour peu qu’ils soient correctement authentifiés par Azure AD. Il en est de même pour l’API Request-ResourceGroup as a Service. Par contre, pour l’API Get-ValetkeyForAzureTableRead, la méthode d’authentification sera différente. Cette API sera utilisée pour respecter le principe du moindre privilège. Toutes les données relatives au mécanisme d’autorisation seront stockées dans des Azure Table au sein d’un Storage Account. La clé primaire de ce Storage Account sera bien stockée dans un Key Vault mais nous allons utiliser le Cloud Design Pattern Valet-Key pour générer un contexte d’accès limité à la permission Read sur les Azure Table, avec une durée de vie limitée.

clip_image001

 

Cette API ne sera pas consommée directement par les consommateurs mais en tant qu’API interne pour générer une clé d’accès au stockage. Il aurait été possible d’utiliser Azure AD comme méthode d’authentification (avec Managed Service Identity) mais cela introduisait un risque. Dès lors qu’un consommateur de l’API connait l’URL et se présente avec une identité Azure AD vérifiée, il aurait eu la possibilité de se générer des clés d’accès au stockage. Clairement une fausse bonne idée d’un point de vue sécurité. Pour cette raison, nous allons introduire une segmentation au niveau des API. Cette segmentation permet :

  • D’isoler une API qui donne accès à des informations sensible (il sera nécessaire de connaitre la Function Key qui sert de secret d’accès)
  • De distinguer les identités (Managed Identity Services) qui vont accéder aux instances de KeyVault et donc respecter le principe du moindre privilège

Quand on sait que toutes les API hébergées dans une même instance Azure Function partagent la même identité Managed Identity Service et la même méthode d’authentification, il apparait donc nécessaire d’introduire deux instances du service Azure Function pour héberger nos API.

  • Resourcegroupasaservicepublicapi
  • Resourcegroupasaserviceinternalapi

Voilà pour l’introduction. Dans le prochain billet, on va rentrer dans le dur du sujet.

 

BenoîtS – Simple and Secure by Design but Business compliant

Azure API Management 4/5 – C’est l’heure de consommer

Nous avons une API exposée avec des nouvelles fonctionnalités :

  • Point d’accès unique pour toutes mes futures API
  • Une méthode d’authentification unique pour toutes mes API
  • Une journalisation centralisée et non plus gérée au niveau de chaque API
  • Une politique de limitation d’usage

 

La prochaine étape, c’est donc nécessairement de consommer. Azure API Management est capable de s’interfacer avec bon nombre de systèmes d’authentification. Dans le contexte de cette série de billets, nous allons utiliser le fournisseur d’identité par défaut. Chaque utilisateur doit disposer d’un compte et d’un mot de passe pour accéder au service. On peut soit créer des utilisateurs ou les inviter. L’avantage de l’invitation, c’est que c’est l’invité qui sera chargé de configurer son mot de passe. J’ai donc retenu de lui envoyer une invitation.

clip_image002

 

Le client de ma licorne reçoit un mail qui l’invite à se connecter au portail. A noter que ces éléments de communication (template de mails) sont configurables dans Azure API Management.

clip_image004

 

Point d’attention, la politique de mot de passe d’Azure API Management est assez pointue. N’essayez pas d’utiliser des séquences continues dans vos mots de passe.

clip_image006

 

Une fois authentifié, on arrive sur le portail développeurs de notre instance du service Azure API Management. Dans la rubrique « Products », on constate qu’il nous est proposé la souscription d’un service. La notion de souscription à un produit est importante car pour chaque souscription de produit, une paire de clés d’accès est générée.

clip_image008

 

Au sens Azure API Management, un produit permet aux futurs clients de ma licorne de souscrire à nos API. Dans l’exemple ci-dessous, Azure API Management va imposer une Policy qui limite le nombre d’appels par minutes / semaines. Dans mon Business plan de la mort qui tue, j’ai un mode gratuit mais faut pas non plus qu’il me coute les yeux de la tête. Pour l’instant, il n’y a qu’une seule API.

clip_image009

 

Il est possible de personnaliser le nom de chaque souscription réalisée. C’est intéressant si on souscrit plusieurs fois au même produit.

clip_image011

 

C’est souscrit, pour accéder à mon API, je peux constater que j’ai deux clés.

clip_image013

 

En tant que client consommateur de l’API, je peux aussi référencer ma propre application qui va changer la face du monde.

clip_image015

 

Je peux la soumettre à publication, reste à voir si l’administrateur de l’Azure API Management service est prêt à m’aider à démarrer et qui sait devenir demain le nouveau Facebook 3.0.

clip_image017

 

Nous approchons de la fin de cette série de billets. Ne nous reste plus qu’à nous placer en tant que client pour consommer notre API. Ce sera le sujet du dernier billet.

 

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

Azure API Management 3/5 – Exposition de notre première API

A partir de maintenant, tout va se passer dans Azure API Management. Notre Azure Function est prête à être référencée comme API. Dans l’interface de gestion du service, en cliquant sur « API », on arrive à l’interface ci-dessous :

clip_image001

Azure API Management prend en charge plusieurs langages de description pour nos API. Par chance, l’intégration avec Azure Function est nativement supportée ou presque. L’interface nous propose bien de sélectionner notre Function App (ce qui porte notre fonction) mais pas la fonction en elle-même. C’est pour cela que je spécifie le paramètre API Url suffixe correspondant le nom de la fonction.

clip_image002

Autre sujet important, l’accès authentifié à notre Azure Function. Pour rappel, chaque fonction que nous avons développée dispose de sa propre URL et clé d’accès :

clip_image003

Si je ne fais rien, aucune authentification sera présentée à mon Azure Function. Il faut donc pouvoir intégrer ce code d’authentification dans l’appel depuis Azure API Management. Vu que c’est un secret, on va commencer par le stocker dans une Named Value. Au moment de l’écriture de cette série de billets, il n’est pas encore possible de stocker ses secrets dans un Azure KeyVault. A ce jour, c’est un feedback qui a été remonté à Microsoft : Integration with Azure KeyVault. En attendant KeyVault, nous allons utiliser les Named Values.

clip_image004

Azure API Management permet d’injecter des paramètres additionnels dans le Header. C’est une des nombreuses fonctionnalités offertes par les Azure Policies d’Azure API Management. Nous allons travailler au niveau « Inbound Processing »

clip_image005

Notre opération consistera à ajouter un nouveau paramètre à notre requête HTTP. Pour éditer ma policy, je suis passé en mode « Code-View » pour intégrer une règle de transformation nommée « Set query request parameter ». De cette manière, on va injecter le paramètre « Code » avec la valeur contenue dans la Named Value {{AzureFunctionAPICode}}. Reste à ne pas oublier de cliquer sur le bouton Save.

clip_image006

De retour dans la view « From View », c’est plus simple à comprendre.

clip_image007

Attention, l’utilisation du Named Parameters, dans la vue form view ne conservera pas le named parameters mais va juste remplacer le contenu. D’ou le passage en mode « Form View ».

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

Azure API Management 2/5 – Préparer l’exposition de notre Azure Function

Notre service Azure API Management est maintenant posé. Nous allons y revenir. Pour commencer, nous allons rendre notre Azure Function consommable par mon service API Management. Il faut un peu de préparation. A ce stade, il manque une description contenant :

  • Ce que produit notre API
  • Les paramètres qu’elle attend en entrée
  • Les paramètres qu’elle retourne
  • La ou ses différentes versions

Première étape, on va commencer par limiter ce que va accepter notre Azure Function en termes de verbes. Au niveau du nœud « Integrate » de notre fonction. Nous allons affiner la listes méthodes HTTP pour ne retenir que POST sans oublier de sauvegarder notre modification.

clip_image001

 

Prochaine étape, documenter notre API. Dans le contexte Azure Function, c’est le Framework Swagger qui a été retenu par Microsoft pour l’intégration avec Azure API management. Le Framework Swagger utiliser les spécifications OpenAPI. Allons configurer la définition de notre API en cliquant sur « API Definition ».

clip_image002

 

Azure Function peut être exposé sous forme d’API de deux manières :

  • Function (Preview pour l’intégration avec Azure API Management)
  • External URL (Utilisation d’un service tierce)

 

Azure API Management étant un service interne Azure, nous allons cliquer sur « Function (Preview) ».

clip_image003

 

Remarque importante, cela ne change strictement rien sur la méthode d’authentification de notre Azure Function. Nous pouvons toujours la solliciter ne direct dès lors que nous disposons de toutes les informations. C’est un sujet sur lequel nous allons revenir dans un prochain billet de la série.

clip_image004

 

Pour décrire notre API, nous allons utiliser les spécifications OpenAPI. L’intégration avec Azure est bien faite puisqu’on nous propose de créer un squelette JSON en cliquant sur le bouton « Generate API Definition template ».

clip_image005

 

OpenAPI va nous permettre de produire une description qui sera consommée par Azure API Management. Heureusement que mon API est simple. Globalement, j’ai indiqué :

  • Que mon API ne supportera que le HTTPS pas le HTTP
  • Que mon API ne supportait que la méthode POST via une URL bien précise
  • Qu’elle consomme ses paramètres au format JSON (même s’il n’y en a pas dans mon exemple)
  • Quelle produira du contenu au format JSON avec un seul message documenté

clip_image006

 

N’oubliez pas d’appuyer sur « Save ».

Avant d’aller plus loin dans l’intégration, nous allons déjà valider que cela fonctionne. Pour cela nous avons besoin d’une clé d’accès pour notre Azure Function. Dans le nœud « Manage » de notre Azure Function, nous allons récupérer une clé d’authentification en cliquant sur le bouton « Copy ».

clip_image007

 

De retour dans la configuration de la définition de notre API, dans la zone droite de l’interface, nous avons la possibilité de valider l’accès à notre future API. Cliquons sur « Change Authentication ».

clip_image008

 

Dans l’interface ci-dessous, nous allons renseigner la clé d’accès précédemment obtenus puis cliquer sur le bouton « Authenticate ».

clip_image009

 

Un peu plus bas dans l’interface, on constate la présence de la description de la seule méthode supportée par notre API : « Post ». En cliquant sur « Try this operation », nous allons réaliser un appel à notre future API avec la clé d’authentification.

clip_image010

 

Côté Request, mon API étant assez simpliste (aucun paramètre en entrée), ayant limité les choix d’utilisation à HTTPS seulement et imposé le JSON pour les paramètres, il ne nous reste qu’à appuyer sur le bouton « Send Request ».

clip_image011

 

Si tout se passe bien, nous devrions avoir le résultat retourné par notre Azure Function.

clip_image012

 

Côté Azure Function, les prérequis sont en place. Prochaine étape, l’exposition de notre première API.

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

Azure Function avec Managed Service Identity

Dans mon précédent billet, j’avais évoqué la possibilité d’utiliser la fonctionnalité Managed Service Identity pour évider de coder une clé d’accès au stockage dans les Application Settings de mon Azure Function App.

clip_image001

Dans le portail, Managed Service Identity se limite à une simple case d’option à activer.

clip_image002

 

Dans les faits, c’est un peu plus compliqué que cela. Déjà, l’interface nous mets sur la piste en nous indiquant que notre Azure Function App sera enregistré auprès d’Azure Active Directory. Première interrogation, le nom du Service Principal. On n’a pas trop le choix, c’est celui du Resource Group qui contient mon Azure Function App :

$cred = Get-Credential

Login-AzureRmAccount -Credential $cred -subscriptionid xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

$ServicePrincipal = Get-AzureRmADServicePrincipal | where {$_.displayname -eq ‘tpserverless’}

clip_image003

 

Voilà pour le premier mystère. Passons au suivant, comment allons-nous consommer ce nouveau service. Au début, j’ai pensé que quelques informations seraient visibles directement depuis les Application Settings. Choux blancs. En relisant la documentation disponible, qu’il y avait bien des informations mais sous forme de variables d’environnement. On peut constater leur existence en passant par Kudu dans la console.

clip_image004

 

Nous avons donc deux variables d’environnement. La première référence l’API de Managed Service Identity provisionnée dans notre Azure Function App. Nous allons utiliser ces deux informations pour nous authentifier auprès de Managed Service Identity. Managed Service Identity nous retournera le token qu’il a négocié pour nous avec l’identité du Service Principal qui a été créé. On notera que l’API mise à disposition répond uniquement sur l’adresse 127.0.0.1. N’essayez pas de la solliciter de l’extérieur, c’est pas possible et c’est normal, c’est un mécanisme d’isolation.

Dans ce billet, nous allons utiliser le jeton d’accès généré pour nous par Managed Identity Services pour accéder à un secret dans un Key Vault. J’ai donc créé une instance du service Key Vault pour lequel j’ai positionné une autorisation pour le Service Principal.

clip_image005

 

Dans cette instance du service Key Vault, j’ai positionné un secret dont je récupère l’URL pour plus tard, cela va servir.

clip_image006

Avec un peu de PowerShell, on retrouve bien l’ObjectID de notre Service Principal ainsi la permission assignée.

$Keyvault = Get-AzureRMKeyVault -VaultName tpserverlesskeyvault -ResourceGroupName tpserverless

$keyvault.AccessPolicies

clip_image007

 

C’est maintenant que cela se complique. Lorsqu’on accède au Key Vault en PowerShell, on commence toujours par s’authentifier avec la commande Login-AzureRmAccount. Le problème, c’est que l’authentification a déjà eu lieu par Managed Service Account. A ce jour, le module PowerShell Azure ne prend pas encore en charge le scénario d’authentification avec Managed Service Identity. Pour cette raison, toutes les commandes PowerShell du module Azure nous répondront invariablement de nous authentifier. On va devoir travailler directement avec les API, en PowerShell.

Rentrons dans le vif du sujet. Ci-dessous le code PowerShell que j’ai positionné dans mon Azure Function :

$start = get-date

$endpoint = $env:MSI_ENDPOINT

$secret = $env:MSI_SECRET

#

# Authentification Managed Service Identity

#

$vaultTokenURI = ‘https://vault.azure.net&api-version=2017-09-01’

$header = @{‘Secret’ = $secret}

$authenticationResult = Invoke-RestMethod -Method Get -Headers $header -Uri ($endpoint +’?resource=’ +$vaultTokenURI)

#

# Accès au Key Vault

#

$vaultSecretURI = ‘https://tpserverlesskeyvault.vault.azure.net/secrets/Secret/1b140f9db62f4ece803d1152a7739aaa?api-version=2015-06-01’

$requestHeader = @{ Authorization = « Bearer $($authenticationResult.access_token) »}

$creds = Invoke-RestMethod -Method GET -Uri $vaultSecretURI -ContentType ‘application/json’ -Headers $requestHeader

$perf = (New-TimeSpan -Start $start -end $(get-date)).Milliseconds

write-output « Credetial Value (ms):  » $($creds.value)

write-output « Performance (ms):  » $perf

clip_image008

 

Pour les quatre premières lignes, c’est juste pour rappeler d’où viennent mes variables :

  • Le endpoint du Managed Service Identity
  • Le secret qui doit lui être présenté

C’est maintenant que cela se complique. Nous allons construire un jeton d’accès pour accéder à une instance de Key Vault (lignes 8-11). Ce qui est intéressant, c’est que nous obtenons un jeton d’accès. C’est ce qu’on peut constater dans les logs d’exécution de notre Azure Function :

clip_image009

 

C’est exactement ce que le module PowerShell Azure recherche et ne trouvera pas. Par contre, nous pouvons l’utiliser pour accéder à notre instance du service Key Vault. Avec la ligne 16, nous construisons le jeton qui sera inclus dans le Header de notre appel au Key Vault. En ligne 17, nous appelons l’API du Key Vault avec notre jeton. En retour, il nous retourne le secret. Pour preuve, dans les logs d’exécution, on retrouve bien le secret qui était visible dans l’interface graphique quelques paragraphe plus haut.

clip_image010

 

Maintenant, est-ce cette approche est performance ? C’est un problème que j’avais rencontré dans mon billet « Valet Key avec Azure Function App« . Visiblement, la réponse est oui car pour toutes ces opérations, j’ai un temps d’exécution de 27 millisecondes :

clip_image011

 

Conclusion

Je suis fan. On ne stocke plus d’information sensible. Les seules informations sensibles à notre disposition ne peuvent être consommées que dans notre Azure Function App et en plus c’est performant. Il faudra certainement une nouvelle version du module PowerShell pour prendre en charge l’utilisation de Managed Service Identity nativement dans le module PowerShell.

Secure by design and Business Compliant quoi.

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

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)