Archives de catégorie : azure Function

Ma session au Powershell Saturday 2018

Ce samedi s’est déroulé l’édition française du PowerShell Saturday dans les locaux de Cellenza. Pendant, cette édition, j’ai eu l’occasion de présenter l’avancement sur Resource Group As a Service. Resource Group As a Service est un sujet que j’avais déjà présenté lors du Global Azure Bootcamp de 2018.

clip_image001

A l’époque, on était plus proche du PoC of Concept, entre temps, le développement a beaucoup avancé. Aujourd’hui, nous sommes maintenant plus proche du Minimum Viable Product. L’objectif de cette session n’était pas de présenter la solution en elle-même mais ce que son développement m’a permis d’apprendre sur Azure Function, Azure Automation et sur PowerShell lui-même. C’est donc plus une session de retour sur expérience.

Pour ceux que cela intéresse, la présentation ainsi que les exemples PowerShell présentés sont disponibles à cette URL.

 

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

Resource Group As a Service–Enfin l’heure de consommer les API

Après un billet d’introduction et quatre pour la mise en œuvre, il serait peut-être temps de conclure et d’exploiter les API mises à disposition. Pour rappel, notre seconde instance du service Azure Function propose les API suivantes :

 

Avant de pouvoir commencer à consommer, on doit préparer un peu le terrain pour Postman. Pour ceux qui ont suivi le billet Authentifiez vos Azure Function avec Azure AD, ils sauvent que c’est l’heure de la construction des paramètres. Commençons par mettre en place une clé pour Postman au niveau de notre application Azure AD. Ce sera notre secret à consommer depuis Postman.

clip_image001

 

Ensuite, récupérons l’identifiant unique de notre tenant Azure AD (Aka TenantID) avec la commande PowerShell suivante : Get-AzureADCurrentSessionInfo | Format-List

clip_image002

 

Prochaine étape avec l’identifiant unique de notre application Azure AD ainsi que l’URL de Callback pour le retour à Azure Function après authentification. Nous obtiendrons ces deux informations avec la commande PowerShell suivante : Get-AzureRMADApplication -DisplayNameStartWith resourcegroupasaservicepublicapi

clip_image003

 

Maintenant, c’est l’heure de consommer.

 

API Get-AuthorizedSubscriptions

Testons avec la première API : Get-AuthorizedSubscriptions depuis Postman. Pour chaque API, nous allons configurer la méthode d’authentification OAuth 2.0

clip_image005

 

Pour les paramètres, je vous renvoie vers le billet Authentifiez vos Azure Function avec Azure AD. Avec tout cela, on devrait être en mesure de demander un jeton en cliquant sur le bouton Get New Access Token.

clip_image006

 

Normalement, cela devrait nous rediriger vers une mire d’authentification Azure AD. Si tout se passe bien, Azure AD rendra la main à notre application Azure AD via la CallBack URL.

clip_image008

 

Après authentification, on devrait obtenir une interface comme illustré ci-dessous avec un Token que nous allons consommer avec le bouton « Use Token ».

clip_image009

 

Attention, le token obtenu est valable une heure. Passé ce délai, il faudra penser à un demander un nouveau. Si tout se passe bien, notre API Get-AzureAuthorizedSubscriptions devrait finir par nous répondre avec une liste de GUID qui sont en fait la liste des souscriptions qui me sont autorisés pour utiliser le service.

clip_image011

 

Derrière l’appel à l’API nous avons une Azure Function. On peut constater la trace de l’appel à la fonction

clip_image013

 

En fait, quand on regarde le contenu de la table AuthorizedCallers, on comprend que la fonction a récupéré l’identité de l’appelant et recherché si l’utilisateur était autorisé ou non.

clip_image015

 

Dans mon contexte, on comprend que mon compte est autorisé à demander la création de groupes de ressources dans deux souscriptions Azure.

API Get-AuthorizedEnvironments

Chaque utilisateur autorisé sur une souscription est associé à un ou plusieurs environnements. Au final, ce sera un tag sur le groupe de ressources qui sera créé. Côté API, elle attend un paramètre : un identifiant unique de souscription. Ça tombe bien, c’est justement ce que la précédente API nous avait retourné. L’API attend un paramètre nommé SubscriptionID.

clip_image017

 

La demande initiée, on doit pouvoir constater le traitement dans Azure Function. A la lecture des logs, on constate que l’appelant serait autorisé à utiliser une seule valeur pour le tag Environnement.

clip_image019

 

De retour dans Postman, on constate bien que l’appelant pourra uniquement demander la création de groupes de ressources tagués TESTS pour la souscription donnée. Toute demande de création pour un autre environnement sera automatiquement rejetée.

clip_image021

 

API Get-AzureAuthorizedRegions

Resource Group As a service permet de limiter les régions Azure pour la création des groupes de ressources. Quelque part, c’est un peu le rôle de Azure Policy me direz-vous ? Oui mais Azure Policy est intégré à ARM qui n’a aucune idée de qui réalise le déploiement. L’API Get-AzureAuthorizedRegions permet de répondre à cette problématique. Chaque utilisateur accrédité pour une souscription donné est limité à une liste de régions Azure donnée. Logiquement l’API attend un identifiant unique de souscription Azure pour répondre à la question. En retour, nous sommes informés des régions Azure dans lesquelles nous sommes autorisés à demander la création d’un groupe de ressource dans la souscription Azure indiquée en paramètre.

clip_image023

 

Côté Azure Function, on peut voir le déroulement de l’exécution.

clip_image025

 

API Get-AuthorizedCostCenters

Resource Group As a Service contribue à la maitrise des coûts. Chaque groupe de ressources qui sera créé se verra assigné un tag CostCenter. Chaque utilisateur autorisé pour une souscription donnée est associé à une liste de valeurs autorisées pour ce tag. Encore une fois, il faut préciser la souscription Azure pour laquelle on veut connaitre les valeurs du tag qui nous sont autorisées.

clip_image027

 

En retour, on obtient. On une liste des valeurs que l’on pourra utiliser pour demander la création de notre groupe de ressources.

 

API Request-ResourceGroup

On a enfin tous les paramètres pour demander la création d’un groupe de ressources. C’est le rôle de l’API Request-ResourceGroup. A ce niveau, on a un peu plus de monde dans la section Body. En fait, on retrouve beaucoup des informations que nous avons déjà abordées :

  • ResourceGroupName : Pas la peine d’expliquer
  • Region : La région Azure dans laquelle créer le groupe de ressources (doit être autorisée)
  • ProjectName : Tag optionnel
  • SubscriptionID : Pas la peine d’expliquer
  • Environment : Une des valeurs qui nous sont autorisées
  • Backup : Tag optionnel
  • CreatedOn : Tag optionnel
  • SLA : Tag optionnel

 

clip_image029

 

Il faut être un peu patient, entre l’authentification, la création du groupe de ressources et la mise en place des tags, ce n’est pas instantané mais 14 secondes, c’est pas cher payé quand on a toute une équipe de développeurs qui attend la création d’un groupe de ressources pour travailler (je ne parle même pas du TJM cumulé, …).

clip_image031

 

Le résultat

Au final, ce qui nous intéresse, c’est quand même le résultat. Jusqu’à maintenant, on est capable de déclencher la création d’un groupe de ressources dans une souscription Azure.

clip_image001[1]

 

En regardant d’un peu plus près, on retrouve même tous les tags demandés. Pour certains, il y a même eu interprétation (CreatedOn, Owner, …)

clip_image002[1]

 

Pourtant, il y a un truc qui manque, les permissions. C’est là ou Resource Group As a Service a besoin de nous. Si on ne lui dit rien sur ce sujet, il ne fera rien. Par contre, si on lui communique les bonnes indications dans la table AuthorizedIAMTemplateRole, ça changera du tout au tout. Le contenu attendu est le suivant :

  • PartitionKey : L’identifiant unique de votre souscription
  • RowKey : Guid unique au sein de la Partition Key
  • Azure AD Group : Groupe Azure AD qui sera utilisé pour assigner un rôle
  • Role : Nom du Rôle Builtin / custom à assigner au niveau du groupe de ressources
  • Environment : Valeur du tag Environnement

 

Chaque ligne dans la table AuthorizedIAMTemplateRole représente donc une assignation de rôle Builtin / custom pour un environnement donné et une souscription donnée.

C’est grâce à cela que Resource Group As a Service prend tout son intérêt. Les permissions positionnées sur le groupe de ressources dépendront de la valeur du tag Environnment utilisée par le demandeur. Ce qui manque, c’est quelques entrées dans une table Azure. Easy en quelques lignes de PowerShell :

$RGName = « <Groupe de ressources contenant le Storage Account contenant les tables> »

$storageAccountName = « <Nom du storage Account> »

$SubscriptionID = « <Azure Subscription ID> »

$AuthorizedIAMTemplateRoleTableName = « AuthorizedIAMTemplateRole »

$keys = Get-AzureRmStorageAccountKey -ResourceGroupName $RGName -Name $storageAccountName

Import-Module -Name AzureRmStorageTable

$IAMTable = Get-AzureStorageTableTable -resourceGroup $RGName -tableName $AuthorizedIAMTemplateRoleTableName -storageAccountName $storageAccountName

Add-StorageTableRow -table $IAMTable -partitionKey $SubscriptionID -rowKey (new-guid).guid -property @{« AzureADGroup »= »GROUPE »; « Environment »= »TESTS »; »Role »= »Reader »}

clip_image003[1]

 

Si on recommence la création du groupe de ressources, on pourra alors constater qu’il y a bien un assignement de rôle qui a été réalisé pour le groupe Azure AD indiqué et le groupe de ressources nouvellement créé.

$AdGroup = Get-AzureADGroup -SearchString GROUPE

Get-AzureRMRoleAssignment -ObjectID $groupe.ObjectID

clip_image004

 

Maintenant Resource Group As a Service prend tout son sens.Si vous êtes arrivés jusque-là, c’est que votre implémentation manuelle de Resource Group As a Service est opérationnelle, félicitations. Pour les plus fainéants, la prochaine version sera 90% industrialisée et comprendra le déblocage de quelques features actuellement cachées.

 

Benoît – Simple and secure by design but business compliant.

Mise en place de l’Azure Function Resourcegroupasaservicepublicapi

La première instance Azure Function était dédiée à une API interne. Maintenant, nouvelle Azure Function dédiée à l’hébergement des API accessibles par les consommateurs. C’est au travers de ces API que les consommateurs pourront :

  • Savoir s’ils ont accès au service Resource Group As a Service
  • Dans quelle souscription pourront-ils demander la création de groupes de ressources
  • Quelles régions Azure sont autorisées
  • Quel CostCenter associer au groupe de ressource
  • Demander la création d’un groupe de ressources

Pour cette nouvelle instance d’Azure Function, j’ai encore fait le choix de la performance e choisissant le mode de fonctionnement « Hosting Plan ». Certes, ça ne fait pas très Server-Less. La création de l’instance est tout ce qu’il y a de plus classique. Un point de détail, le choix du Storage Account associé. J’aurai pu utiliser un seul Storage Account pour mes deux Azure Function mais je voulais maintenir une isolation entre un composant font-end et un composant backend. En plus, de cette manière ce sont des clés d’accès au stockage bien distinctes.

clip_image001

 

Tout comme pour la première instance Azure Function, nous allons uploader le contenu de nos différentes fonctions sous la forme d’un fichier Zip. Quand j’aurai le temps, j’industrialiserai le tout avec un beau template ARM disponible sur mon GitHub.

clip_image002

 

L’importation du contenu se déroule de la même manière que pour la première instance d’Azure Function, pas grand-chose à dire de plus sur ce sujet.

$username = « <Compte FTP> »

$password = « <Mot de passe FTP> »

$filePath = « <emplacement local du fichier resourcegroupasaservicepublicapi.zip> »

$apiUrl = « « 

$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes((« {0}:{1} » -f $username, $password)))

$userAgent = « powershell/1.0 »

Invoke-RestMethod -Uri $apiUrl -Headers @{Authorization=(« Basic {0} » -f $base64AuthInfo)} -UserAgent $userAgent -Method POST -InFile $filePath -ContentType « multipart/form-data »

clip_image003

 

Par contre, c’est au niveau des Applications Settings que cela se complique. Il y en a beaucoup plus à configurer, d’où l’intérêt d’avoir un peu de PowerShell pour réaliser l’opération.

Nom

Contenu

AuthorizationModuleAuthorizeTableName AuthorizedCallers
AuthorizationModuleBackupTagName Backup
AuthorizationModuleCostCenterTagName CostCenter
AuthorizationModuleCreatedOnTagName CreatedOn
AuthorizationModuleDefaultCostCenterTableName DefaultCostCenter
AuthorizationModuleEnvironnementTagname Environnement
AuthorizationModuleIAMTemplateRoleTableName AuthorizedIAMTemplateRole
AuthorizationModuleKeyVault <Nom du groupe de ressources de la solution>
AuthorizationModuleOwnerTagName Owner
AuthorizationModulePolicyAssignmentTableName AuthorizedPolicyAssignment
AuthorizationModuleProjectTagName ProjectName
AuthorizationModuleSLAName SLALevel
AuthorizationModuleStorageAccountMasterKey AuthorizationModuleStorageAccountMasterKey
AuthorizationModuleStorageAccountName Nom du Storage Account de la solution
AuthorizationModuleReadKeyvaletURL <URL API du Key-Valet avec secret>

 

On va distinguer trois types de paramètres :

  • Ceux qui désignent des constantes pour le code des API
  • Ceux qui désignent des Tags
  • Ceux qui sont utilisés pour configurer la solution

 

Dans mon développement, mes constantes, ce sont principalement des noms de table Azure table :

  • AuthorizationModuleAuthorizeTableName : Cette table référence les souscriptions accessibles pour chaque consommateur.
  • AuthorizationModuleIAMTemplateRoleTableName : Nom de la table dans Azure Table qui référence les identités et les rôles à associer lors de la création d’un groupe de ressources pour le Role-Based Access Control
  • AuthorizationModulePolicyAssignmentTableName : Nom de la table dans Azure Table qui référence les Azure Policy à positionner sur les groupes de ressources nouvellement créés.

La solution va positionner des tags sur les groupes de ressources nouvellement créés. En externalisant les noms dans les Applications Settings, je vous permets de personnaliser la solution en fonction de votre environnement.

  • AuthorizationModuleBackupTagName : Nom du tag qui devra être positionné sur le groupe de ressources nouvellement créé pour indiquer le besoin de sauvegarder ou non le contenu du groupe de ressources.
  • AuthorizationModuleCostCenterTagName : Nom du Tag qui devra être positionné sur le groupe de ressources nouvellement créé pour référencer le centre de coût pour la refacturation des usages.
  • AuthorizationModuleCreatedOnTagName : Nom du Tag qui devra être positionné sur le groupe de ressources nouvellement créé pour indiquer la date de création de la ressource (utile pour distinguer des ressources éphémères)
  • AuthorizationModuleDefaultCostCenterTableName : Nom de la table qui référence le CostCenter qui devra être utilisé lors de la création d’un groupe de ressources si le consommateur ne précise rien.
  • AuthorizationModuleEnvironnementTagName : Nom du Tag qui devra être positionné sur le groupe de ressources nouvellement créé pour désigner le type d’environnement (production, dev, …)
  • AuthorizationModuleOwnerTagName : Désigne le nom du Tag qui devra être positionné sur le groupe de ressources nouvellement créé pour désigner le propriétaire / responsable des ressources hébergées/
  • AuthorizationModuleProjectTagName : Nom du Tag qui devra être positionné sur le groupe de ressources nouvellement créé pour regrouper les ressources composant un même projet
  • AuthorizationModuleSLAName : Nom du Tag qui devra être positionné sur le groupe de ressources nouvellement créé pour signaler le niveau de SLA associé aux ressources qui sont contenues dans le groupe de ressources.

 

Il ne nous reste plus que les paramètres propres au fonctionnement de la solution.

  • AuthorizationModuleKeyVault : Désigne le nom de l’instance KeyVault qui a été mise en place pour stocker les secrets de la solution. Dans cette version, on référence le nom. Quand j’aurai un peu de temps, on référencera l’URL complète pour permettre à la solution de fonctionner dans tous les environnements Azure, y compris Azure Stack.
  • AuthorizationModuleStorageAccountMasterKey : Désigne le nom du secret dans l’instance de Key Vault de la solution qui contient la clé primaire du Storage Account qui héberge mes Azure table.
  • AuthorizationModuleStorageAccountName : Désigne le nom du Storage Account qui contient les Azure Table de la solution
  • AuthorizationModuleReadKeyvaletURL : Référence l’URL de l’API Get-ValetKeyforAzureTableRead hébergé par notre première instance Azure Function. Attention à bien référencer l’URL avec la Function Key qui sert de mécanisme d’authentification. Clairement, dans ma todo-list, c’est un truc qu’il faudra réviser pour stocker cette information dans le Key vault.

 

Tout comme pour la première instance, nous allons importer ces Applications Settings en prenant soin de ne pas écraser les paramètres déjà positionnés.

$webapp = Get-AzureRmWebApp -ResourceGroupName ResourceGroupAsAService -Name resourcegroupasaservicepublicapi

$AppSettings = @{}

$AppSettings = $webapp.SiteConfig.AppSettings

$hash = @{}

ForEach ($kvp in $AppSettings) {

$hash[$kvp.Name] = $kvp.Value

}

$hash[‘AuthorizationModuleAuthorizeTableName’] = « AuthorizedCallers »

$hash[‘AuthorizationModuleBackupTagName’] = « BACKUP »

$hash[‘AuthorizationModuleCostCenterTagName’] = « CostCenter »

$hash[‘AuthorizationModuleCreatedOnTagName’] = « CreatedOn »

$hash[‘AuthorizationModuleDefaultCostCenterTableName’] = « DefaultCostCenter »

$hash[‘AuthorizationModuleDisplayName’] = « DisplayName »

$hash[‘AuthorizationModuleEnvironnementTagname’] = « Environnement »

$hash[‘AuthorizationModuleIAMTemplateRoleTable’] = « AuthorizedIAMTemplateRole »

$hash[‘AuthorizationModuleKeyVault’] = « resourcegroupasaservice »

$hash[‘AuthorizationModuleKeyVaultSubscriptionLogin’] = « SubscriptionLogin »

$hash[‘AuthorizationModuleKeyVaultSubscriptionPassword’] = « SubscriptionPassword »

$hash[‘AuthorizationModuleOwnerTagName’] = « Owner »

$hash[‘AuthorizationModulePolicyAssignmentTable’] = « AuthorizedPolicyAssignment »

$hash[‘AuthorizationModuleProjectTagName’] = « ProjectName »

$hash[‘AuthorizationModuleReadKeyvaletURL’] = «  clé>« 

$hash[‘AuthorizationModuleSLAName’] = « SLALevel »

$hash[‘AuthorizationModuleStorageAccountName’] = « resourcegroupasaservice3 »

Set-AzureRMWebApp -ResourceGroupName resourcegroupasaservice -AppServicePlan resourcegroupasaservicepublicapi -Name resourcegroupasaservicepublicapi -AppSettings $hash

clip_image004

 

Pour cette nouvelle instance d’Azure Function, nous allons aussi activer la fonctionnalité Managed Service Identity. La fonctionnalité sera utilisé par l’API Request-ResourceGroup pour extraire les secrets nécessaires de l’instance Key Vault de la solution.

clip_image005

 

Maintenant, la partie la plus intéressante, à savoir la mise en place de l’authentification Azure AD. Comme déjà vu dans le billet Authentifiez vos Azure Function avec Azure AD , nous commençons par activer la fonctionnalité.

clip_image006

 

C’est ici que cela se complique. Après avoir activé la fonctionnalité et exigé l’authentification, nous allons demander la création d’une application Azure AD.

clip_image007

 

A ce niveau, cela implique que notre compte dispose du rôle « Global Administrator » pour déclarer l’application Azure AD. Cette application Azure AD devra disposer de permissions déléguées pour Azure Active Directory.

clip_image008

 

Côté application, c’est OK. Maintenant passons côté utilisateurs. Il faut autoriser nos utilisateurs à accéder à notre application. J’ai retenu de faire simple, tous les utilisateurs de mon Tenant Azure AD seront accrédités à consommer mon application. Plus tard dans le développement de Resource Group as a service, nous introduirons la notion de rôle au sein de l’application (consommateur versus administrateur).

clip_image009

 

C’est maintenant qu’on passe sous la moquette et qu’on sort le PowerShell. Commençons par identifier le Service Principal associé à la fonctionnalité Managed Service Identity pour lui accorder le droit de lire les secrets dans l’instance de Key Vault utilisé par notre solution.

$ADDObject = Get-AzureADServicePrincipal | where {$_.displayname -eq « resourcegroupasaservicepublicapi »}

$ADDObject

Set-AzureRmKeyVaultAccessPolicy -VaultName resourcegroupasaservice -ObjectId $ADDObject.ObjectID -PermissionsToSecrets Get, List

clip_image010

 

A ce stade, les permissions sur notre première instance de KeyVault doivent ressembler à l’illustration ci-dessous :

(Get-AzureRmKeyVault -VaultName resourcegroupasaservice).accesspolicies

clip_image011

 

Cependant, il ne faut pas oublier la seconde instance. Dans mon design j’ai retenu de séparer les secrets de chaque souscription dans des instances de Key Vault séparées. Notre Azure Function portant nos API publique doit pouvoir extraire les secrets (Login & mot de passe) pour se connecter à la souscription Azure donnée :

$AzureADApplication = Get-AzureRmADApplication -DisplayNameStartWith resourcegroupasaservicepublicapi

$AzureADApplication

Set-AzureRmKeyVaultAccessPolicy -VaultName $KeyVaultName -ObjectID $AzureADApplication.ObjectId -PermissionsToSecrets Get

clip_image012

 

Ne reste plus qu’à positionner les secrets dans cette souscription. Pour chaque souscription utilisable par l’API, nous avons une instance de KeyVault dédiée avec toujours les mêmes secrets :

  • SubscriptionLogin
  • SubscriptionPassword

 

$SubscriptionCredential = Get-Credential (Get-AzureRmContext).account.ID

$SubscriptionLoginSecret = ConvertTo-SecureString -String $SubscriptionCredential.username -AsPlainText -Force

Set-AzureKeyVaultSecret -VaultName $KeyVaultName -Name ‘SubscriptionLogin’ -SecretValue $SubscriptionLoginSecret

Set-AzureKeyVaultSecret -VaultName $KeyVaultName -Name ‘SubscriptionPassword’ -SecretValue $SubscriptionCredential.Password

clip_image013

 

Maintenant, ce qui manque, c’est comment l’API va déterminer dans quelle instance de Key Vault se trouve les secrets d’une souscription Azure donnée. Tout simplement en créant un secret ayant comme nom l’identifiant unique de la souscription. Ce secret référencera l’URL de l’instance de Key Vault contenant les secrets pour la souscription. Il ne nous reste donc plus qu’à créer un secret dans l’instance de Key Vault utilisée pour les secrets partagés par les API :

$SubscriptionVault = Get-AzurermKeyVault -VaultName $KeyVaultName -ResourceGroupName $resourcegroupname

$SubscriptionSecret = ConvertTo-SecureString -String $($SubscriptionVault.VaultUri) -AsPlainText -Force

Set-AzureKeyVaultSecret -VaultName resourcegroupasaservice -Name $((Get-AzureRmContext).Subscription.ID) -SecretValue $SubscriptionSecret

clip_image014

 

D’un point de vue technique, l’API est maintenant en place. Prochaine étape, consommer nos différentes API avec Postman.

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

Resource Group as a Service–Mise en place de l’API resourcegroupasaserviceinternalapi

C’est maintenant qu’on commence à rentrer dans les API. Pour commencer nous allons mettre en place l’instance Azure Function qui portera les API à usage interne. J’ai fait le choix d’utiliser le Hosting Plan « App Service Plan » pour des raisons de performance. Cela aura un coût mais mon instance App Service Plan sera disponible 24/24 avec une SKU Standard S1, c’est amplement suffisant. Ce choix permet de disposer d’un service de sauvegarde mais aussi de la fonctionnalité Scale Up. Les logs de cette instance d’Azure Function seront stockés dans le premier Storage account créé. C’est un choix de ma part car lorsqu’on va procéder au renouvellement des clés (primaires / secondaires), avoir trop de dépendances à corriger serait plus que risqué.

clip_image001

Par ce que je veux aller vite, nous allons uploader le contenu de l’API directement dans Azure Function. Pour cela, nous avons besoin d’un peu de FTP. Comme pour une simple WebApp, on peut activer la prise en charge de FTP/FTPS pour charger du contenu, voire même récupérer des logs. J’ai donc reconfiguré le FTP avec un compte et noté le mot de passe dans un coin.

clip_image002

Le contenu que nous allons importer est le fichier resourcegroupasaserviceinternalaip.zip disponible sur mon Github. Une fois récupéré, nous allons utiliser la fonctionnalité Zip Push Deployment pour uploader le contenu via FTP. Derrière, c’est du WebDeploy. Quelques lignes de PowerShell et zou, uploadé dans notre première instance d’Azure Function.

$username = « <Compte FTP> »

$password = « <Mot de passe FTP> »

$filePath = « <emplacement local du fichier resourcegroupasaserviceinternalapi.zip> »

$apiUrl = « « 

$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes((« {0}:{1} » -f $username, $password)))

$userAgent = « powershell/1.0 »

Invoke-RestMethod -Uri $apiUrl -Headers @{Authorization=(« Basic {0} » -f $base64AuthInfo)} -UserAgent $userAgent -Method POST -InFile $filePath -ContentType « multipart/form-data »

clip_image003

Dans l’état actuel des choses, mon processus d’importation ne prend pas encore la configuration des Application Settings. En fait, c’est une bonne chose car il faut préserver la configuration déjà en place pour certaines variables essentielles comme AzureWebJobsDashboard ou AzureWebJobStorage). Nous devons juste ajouter les variables ci-dessous documentées ci-dessous :

Nom

Contenu

AuthorizationModuleExpirationPeriod 30
AuthorizationModuleKeyVault resourcegroupasaservice
AuthorizationModuleStorageAccountMasterKey AuthorizationModuleStorageAccountMasterKey
AuthorizationModuleStorageAccountName Resourcegroupasaservice3

 

La variable AuthorizationModuleExpirationPeriod est utilisée pour configurer la durée de vie de la clé du SAS Token qui sera générée par l’API Get-ValetKeyforAzureTableRead. La variable AuthorizationModuleKeyVault désigne le nom de l’instance du service KeyVault qui sera utilisée pour stocker les secrets consommés par les API. La variable AuthorizationModuleStorageAccountName désigne le nom du Storage account qui contient les Azure Tables de configuration. Enfin, la variable AuthorizationModuleStorageAccountMasterKey désigne le nom du secret référençant la clé primaire du stockage désigné par la variable AuthorizationModuleStorageAccountName.

En attendant une industrialisation avec un beau Template ARM, voilà quelques lignes de PowerShell pour configurer nos premières Application Settings. C’est encore artisanal, et donc perfectible.

$webapp = Get-AzureRmWebApp -ResourceGroupName ResourceGroupAsAService -Name resourcegroupasaserviceinternalapi

$AppSettings = @{}

$AppSettings = $webapp.SiteConfig.AppSettings

$hash = @{}

ForEach ($kvp in $AppSettings)

{

$hash[$kvp.Name] = $kvp.Value

}

$hash[‘AuthorizationModuleExpirationPeriod’] = « 30 »

$hash[‘AuthorizationModuleKeyVault’] = « resourcegroupasaservice »

$hash[‘AuthorizationModuleStorageAccountMasterKey’] = « AuthorizationModuleStorageAccountMasterKey »

$hash[‘AuthorizationModuleStorageAccountName’] = « resourcegroupasaservice3 »

Set-AzureRMWebApp -ResourceGroupName resourcegroupasaservice -AppServicePlan resourcegroupasaserviceinternalapi -Name resourcegroupasaserviceinternalapi -AppSettings $hash

clip_image004

Au sein de notre instance Azure Function, notre fonction va devoir accéder aux secrets contenus dans l’instance du service Key Vault dédié à la solution. Pour cela, nous allons utiliser la fonctionnalité Managed Service Identity. Une fois activée, il faut juste penser à ne pas oublier de cliquer sur le bouton Save.

clip_image005

A ce stade, notre application dispose d’une identité dans Azure AD ainsi que d’un Service Principal qui lui est associé. C’est celui-ci que nous allons référencer comme ayant les permissions de parcourir la liste des secrets et accéder à ceux-ci.

$ADDObject = Get-AzureADServicePrincipal | where {$_.displayname -eq « resourcegroupasaserviceinternalapi »}

$ADDObject

Set-AzureRmKeyVaultAccessPolicy -VaultName resourcegroupasaservice -ObjectId $ADDObject.ObjectID -PermissionsToSecrets Get, List

clip_image006

Pour valider que le tout fonctionne bien, nous pouvons appeler notre Azure Function à l’aide du bouton « Run » dans l’éditeur de code d’Azure Function. Normalement, on doit constater qu’un SAS Token nous a été retournée en résultat.

clip_image007

 

Par sécurité, nous devons restreindre l’accès à notre fonction en mettant en imposant l’utilisation d’une Function Key pour utiliser notre fonction et nous allons restreindre les verbes HTTP utilisables à POST uniquement.

clip_image008

Imposer l’utilisation d’une Function Key, c’est aussi en créer une comme illustré ci-dessous.

clip_image009

De retour dans l’éditeur de code de notre Azure Function, en cliquant sur le lien « Get Function URL », on peut retrouver les URL d’accès de notre fonction. Dans la zone de liste, j’ai sélectionné le nom de ma Function Key, ce qui me permet de récupérer l’URL complète.

A partir de maintenant, c’est en utilisant cette URL associée à cette Function Key qu’il sera possible d’appeler l’API Get-ValetKeyforAzureTableRead.

$url = « https://resourcegroupasaserviceinternalapi.azurewebsites.net/api/Get-ValetKeyforAzureTableRead?code=<FunctionKey> »

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


clip_image011

 

En retour, nous avons un SAS Token consommable pour s’authentifier auprès de mon troisième Storage Account. Pour rappel, c’est lui qui contient les Azure Tables que nous avons mis en œuvre.

Voilà pour la mise en place de la première API. La démarche sera sensiblement la même pour la seconde instance d’Azure Function. On va juste ajouter la brique Azure AD pour l’authentification.

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

Resource Group As a service–Mise en place des Key Vaults

Le stockage, c’était facile. Maintenant, on va s’attarder sur la sécurité. Pour le développement de nos API, il n’était pas question de références des comptes / mot de passe ou clé d’accès à un Storage Account en clair. On va donc avoir recours au Service Key Vault. En fait, nous allons avoir plusieurs usages. Dans mes Design Principles, j’ai retenu le support de multiples souscriptions Azure et la contrainte des moindres privilèges. En stockant tous les secrets dans une même instance de Key Vault. Problème, les permissions ne sont pas positionnées individuellement sur chaque secret mais sur les secrets. Afin de segmenter les informations, la solution implémente deux types de KeyVault

  • Le KeyVault nécessaire au stockage des secrets utilisés par l’API
  • Les KeyVault mis en œuvre pour stocker les secrets d’accès d’une souscription Azure

 

Commençons par la mise en place de la première instance dédiée aux secrets de la solution :

New-AzureRmKeyVault -Name resourcegroupasaservice -ResourceGroupName resourcegroupasaservice -Location « West Europe » -Sku Standard

clip_image001

 

Note : La commande PowerShell New-AzureRmKeyVault nous rappelle que contrairement au portail Azure, elle ne positionne pas d’Access Policy pour l’instance de KeyVault nouvellement créée. Nous devons donc configurer notre compte pour disposer des permissions sur les secrets qui seront stockés :

Set-AzureRmKeyVaultAccessPolicy -VaultName ‘resourcegroupasaservice’ -UserPrincipalName ‘<Votre compte>’ -PermissionsToKeys create, import, delete, list -PermissionsToSecrets set, delete

clip_image002

 

On reviendra plus tard sur les permissions d’accès au secret pour cette instance de Key Vault. Pour l’instant, on va positionner notre premier secret, à savoir la clé primaire du Storage Account que nous avions configuré dans le billet précédent pour le stockage de nos Azure Tables. Les identités qui pourront accéder aux secrets pourront donc accéder aux Azure Tables de la solution.

$Secret = ConvertTo-SecureString -String $Keys[0].value -AsPlainText -Force

Set-AzureKeyVaultSecret -VaultName ‘resourcegroupasaservice’ -Name ‘AuthorizationModuleStorageAccountMasterKey’ -SecretValue $Secret

clip_image003

 

J’avais indiqué que nous avions plusieurs usages du service Key Vault. Le second usage, c’est pour stocker les credentials d’accès aux souscriptions dans lesquelles la solution va créer (Dans cette première version, c’est un compte Azure AD. Dans la prochaine, il est déjà prévu de supporter les Services Principals avec certificats). Nous avons déjà vu que les permissions se positionnent sur les secrets et non sur un secret donné. Pour cette raison, j’ai retenu de créer autant d’instances de Key Vault que de souscriptions qui seront configurées pour la solution. Dans la table AuthorizedCallers, nous avons référencé la souscription courante. Ce dont l’API a besoin maintenant, c’est d’une instance de KeyVault pour stocker les secrets nécessaires à l’accès à cette souscription. Les secrets contenus dans cette instance de KeyVault seront accessibles à :

  • Nous même
  • L’application Azure AD qui représente l’instance Azure Function portant nos API publiques (celles pour lesquelles une authentification Azure AD est exigée).

Etant donné que les noms des instances de Key Vault sont publics dans l’espace de nom « vault.azure.net », j’ai pris la précaution de générer des noms aléatoires sur la base de la chaine de caractères « mysecrets ».

$KeyVaultName = « mysecrets » + (get-random -Minimum 1 -Maximum 9999)

New-AzureRmKeyVault -Name $KeyVaultName -ResourceGroupName $resourcegroupname -Location $location -Sku Standard

clip_image004

 

Vu que c’est notre souscription, nous allons donc positionner des permissions nous permettant de gérer les secrets pour cette instance de Key Vault. Ce sont nos secrets, nous en sommes responsables.

Set-AzureRmKeyVaultAccessPolicy -VaultName $KeyVaultName -UserPrincipalName « <nom compte Azure AD> » -PermissionsToKeys create, import, delete, list -PermissionsToSecrets set, delete, List, Get

clip_image005

 

Nous reviendrons ultérieurement sur cette seconde instance de Key Vault. Avant de poursuivre, il nous faudra mettre en œuvre nos Azure Function et leur accorder la possibilité de consulter les secrets mis à disposition dans les instances de Key Vault. Ce sera pour le prochain billet.

 

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

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

Authentifiez vos Azure Function avec Azure AD

De retour sur Azure Function. Après avoir exploré les possibilité d’intégration entre Azure Function et Azure API Management, revenons dans Azure Function avec un aspect sécurité. Comment sécuriser l’accès à vos API? Techniquement, Azure API Management nous propose ce type de service avec la capacité de supporter de multiples fournisseurs, cependant lorsqu’on a qu’une seule Azure Function à exposer, ça fait un peu lourd et cher. On va donc voir ce que nous propose App Service out of the box que nous allons aborder en plusieurs étapes :

  • Mise en place des fondations
  • Prise en charge de l’authentification Azure AD
  • Bonus : Postman

 

Mise en place des fondations

La première chose dont on va avoir besoin, c’est effectivement d’une instance du service Azure Function. Jusque-là, rien de bien compliqué.

clip_image001

De là, il ne nous reste plus qu’à mettre en place notre Azure Function à sécuriser. Celle-ci sera configurée sans authentification. Elle sera donc librement accessible à toute personne connaissant l’URL. La fonction d’authentification sera déportée dans Azure AD plus tard.

clip_image002

 

Pour finir notre mise en place, on a juste besoin d’une petite dose de code, de PowerShell en fait. Ce qu’il faut comprendre, c’est que Web App sui sert de socle à Azure Function va faire la majorité du travail pour nous. Tout ce que nous attendons, c’est l’identité de l’utilisateur connecté que nous allons retrouver dans les Server Variables dans notre session. Ce que va faire Azure Web App, c’est peupler des variables pour nous dont la variable « HTTP_X_MS_CLIENT_PRINCIPAL_NAME ». Attention, nous allons n’avoir que l’identité de l’appelant à notre Azure Function pas le claims obtenu (un peu plus de travail). En PowerShell, c’est aussi simple que cela :

$requestBody = Get-Content $req -Raw | ConvertFrom-Json

$result = Get-Variable -name REQ_HEADERS_X-MS-CLIENT-PRINCIPAL-NAME -ErrorAction SilentlyContinue

if ($result.name -ne $null)

{

Out-File -Encoding Ascii -FilePath $res -inputObject « Hello $($result.value) »

}

else

{

Out-File -Encoding Ascii -FilePath $res -inputObject « Unauthenticated. »

}

 

On peut tester immédiatement, l’authentification Azure AD n’étant pas encore en place, la variable n’existe pas pour l’instant. D’où le retour « Unauthentified ».

clip_image003

 

Prise en charge de l’authentification Azure AD

C’est maintenant que les choses se compliquent. On a bien une fonctionnalité « Authentication / Authorization ». Activer la fonctionnalité va impacter toutes les Azure Functions qui sont portées par l’instance de notre service.

clip_image004

 

Activer la fonctionnalité, c’est ce qu’il y a de plus simple. On va donc forcer l’authentification pour toutes les connexions et indiquer que nous allons utiliser le fournisseur Azure Active Directory.

clip_image005

 

Pour les besoins de la démonstration, on sa se contenter de la configuration Express. Maintenant challenge : avez-vous le privilège Administrateur général au niveau AD ? Si oui, alors on peut créer immédiatement l’application (et le service principal associé). Dans le cas contraire, il faudra demander à celui qui détient les droits de gérer cela pour vous pour ensuite sélectionner l’application Azure AD nouvellement déclarée. Dans notre contexte, je dispose des privilèges, on continue.

clip_image006

 

Ne surtout pas oublier de sauvegarder la configuration.

clip_image007

 

Derrière, on sait tout de suite que cela fonctionne, il suffit de tenter de réexécuter notre Azure Function. Il est clairement indiqué que l’authentification bloque l’utilisation du débugger.

clip_image008

 

Revenons sur la configuration de cette application déclarée dans Azure AD. Nous allons accorder à notre application des permissions déléguées pour autoriser l’authentification des utilisateurs et ne surtout pas oublier de sauvegarder la configuration.

clip_image009

 

Le message d’avertissement nous indique que dans la configuration actuelle aucun utilisateur de Tenant Azure Active Directory n’a accès à cette application. On a deux choix :

  • Une assignation manuelle pour chaque utilisateur ou collective (bouton Grant permissions)
  • Une assignation selon appartenance de groupe (Pas disponible avec une SKU Free d’Azure AD)

 

On va donc procéder à une activation massive pour tous les utilisateurs de notre Tenant Azure AD, que ceux-ci soient des utilisateurs dépendant du tenant ou simplement invités.

clip_image010

 

De là, on peut tester immédiatement (si on n’a pas limité le verbe GET dans notre Azure Function) en appelant simplement d’Azure Function depuis notre navigateur. Si nous avons été préalablement authentifiés, notre token sera utilisé. Sinon, une mire d’authentification nous sera proposée.

clip_image011

 

Bonus : Postman

Pour le bonus, pas de PowerShell. En fait, j’ai découvert qu’obtenir un token Oauth2 et le soumettre pour négocier un token pour s’authentifier auprès de notre Azure Function est à ce jour d’une complexité incroyable. Je me suis donc mis en quête d’une méthode plus simple pour tester mon Azure Function : Postman. C’est un client REST qui a pour avantage de prendre en charge Swagger pour générer la documentation associée à note API.

Avant de rentrer dans Postman, nous allons collecter quelques informations nécessaires. La première est l’identité de notre application Azure Active Directory. Celle-ci utilise l’identité d’un Service Principal (équivalent d’un compte de service). On va commencer par collecter l’identifiant unique de notre application déclarée dans Azure.

clip_image012

 

Passons à la suivante avec un peu de PowerShell pour retrouver l’identifiant unique de notre Tenant Azure Active Directory dans l’attribut TenantID :

$cred = get-credential

Connect-AzureAD -Credential $Cred

Get-AzureADCurrentSessionInfo | fl

clip_image013

 

Continuons en PowerShell en récupérant la ReplyURL de notre application enregistrée dans Azure Active Directory : Get-AzureRMADApplication -DisplayNameStartWith authenticatedaz

clip_image014

 

Maintenant, un peu de construction :

Retournons au niveau de notre application publiée dans Azure. Nous avons besoin de générer un secret qui sera utilisé lors de l’authentification. Pour les besoins de la démonstration, nous allons générer ce secret sans date d’expiration. La bonne pratique consiste à renouveler ces secrets régulièrement et bien sûr de ne pas partager un même secret entre plusieurs applications. Attention à copier la clé générée avant de quitter l’interface. Après-cela, elle ne sera plus divulguée.

clip_image015

 

Maintenant, tout se passe dans Postman. Si on tente le direct, on va clairement nous répondre que nous ne sommes pas autorisés.

clip_image016

 

En fait, on a besoin de construire une authentification Oauth2.

clip_image017

 

Ci-dessous la configuration à utiliser avec quelques explications :

  • Token name : Your choice
  • Grant Type : Authorization Code
  • CallBack URL : Celle que nous avons collectée
  • Auth URL : Celle que nous avons générée
  • Client ID : C’est l’Application ID de notre application (ne cherchez pas ClientID dans le portail, ça sert à rien)
  • Client Secret : Normalement, vous l’avez conservé
  • Scope : Inutile dans notre cas
  • State : Inutile dans notre cas
  • Client Authentication : Send client credentials in Body

 

clip_image018

 

Ne reste plus qu’à cliquer sur « Request Token » pour obtenir notre Access Token. Une mire d’authentification Azure AD nous sera proposée. Attention, dans la configuration par défaut d’Azure AD, la durée de vie est d’une heure. Après, c’est personnalisable : Configurable token lifetimes in Azure Active Directory (Public Preview). Et cela fonctionne.

clip_image019

 

C’est une utilisation très basique de Postman car l’outil va bien plus loin que cela, en particulier pour réaliser des tests unitaires, voire même des tests automatiques.

Benoît – Simple and secure by design but business compliant

Azure API Management 5/5 – PowerShell Time

C’est la dernière ligne droite et aussi le temps du PowerShell. Pour Parler PowerShell, tout comme pour Azure Function, cela reposera sur un Invoke-WebRequest. C’est donc presque pareil, à quelques exceptions. La première chose qui change, c’est l’URL. Pour rappel, voilà l’URL de notre Azure Function exposée via notre instance du service Azure API Management.

clip_image001

 

La seconde chose qui change, c’est la clé d’authentification. Nous ne passons plus la clé d’authentification de notre Azure Function (c’est géré au niveau des policies d’Azure API Management) mais une des deux clés dont nous disposons dans la souscription au produit qui présente notre Azure Function. En tant que consommateur, nous pouvons retrouver cette information dans le portail développeur mis à disposition par l’instance du service Azure API Management. Cette clé, nous allons la référencer sous le nom « Ocp-Apim-Subscription-Key »

clip_image003

 

Nous avons toutes les informations, ne nous reste plus qu’à construire notre requête.

$url = « https://tpapimanagement.azure-api.net/AzuretableValetKeyAPI/api/AzuretableValetKeyAPI »

Invoke-RestMethod -Uri $Url -Method POST -ContentType « application/json » -Headers @{« Ocp-Apim-Subscription-Key »= »2fab81140f42490683XXXXXXXXa26ac0 »}

clip_image005

 

Nous y sommes. Nous avons réussi à consommer notre Azure Function exposée par Azure API Management.

 

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

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)