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 API Management 1/5 – Introduction

A forcer de créer des API, je vais finir par créer ma licorne. Problème, avec la GDPR qui arrive, si je commence à annoncer à mes futurs investisseurs et clients que je vais développer ma stack sécurité de bout en bout, autant dire que je vais la tuer ma licorne dans l’œuf.

clip_image002

Si développer ma stack sécurité n’est pas une option viable, autant en choisir une qui offre la possibilité de : S’intégrer avec les principaux fournisseurs du marché (Facebook, LinkedIn, Microsoft, Google, …).

  • Proposer un point d’accès unique pour toutes mes API
  • Permettre la mise en place de politiques de contrôle de l’usage de mes API
  • Permettre de gérer la souscription à mes API

Dans la liste des services Azure à destination des développeurs, on trouve Azure API Management Service. Ça fait tout cela et même plus. Dans cette série de billet, nous allons reprendre mon Azure Function proposée dans le billet Azure Function avec Managed Service Identity. Pour rappel voilà son contenu : Une Azure Function qui permet d’extraire un secret de mon Key Vault avec une identité Azure AD utilisant Managed Service Identity.

clip_image004

 

Dans la situation actuelle, tant qu’on dispose de la clé d’authentification associées à la fonction, on peut la solliciter autant qu’on veut et donc gérer une surfacturation. C’est une des fonction d’Azure API Management de limiter la consommation de notre API a un certain nombre d’appel par minutes.

Nous allons commencer par mettre en œuvre une instance du service API Management Service dans un nouveau groupe de ressources. Le service API Management est disponible en trois éditions :

  • Développeur
  • Standard
  • Premium

Pour le détail, je vous renvoie vers la page prévue à cet effet : API Management pricing . J’ai fait le choix de la version développeurs. Au passage, je me désigne comme l’administrateur de ce service.

clip_image005

 

Le provisionning du service API Management est plutôt lent (genre 40 minutes). Azure API Management supporte plusieurs fournisseurs d’identité. On peut :

  • Gérer les utilisateurs accédant à nos API nous-même car il intègre son propre module de gestion des utilisateurs
  • Gérer les utilisateurs en utilisant un Tenant Azure AD, ce qui permet de séparer les comptes des autres ressources Azure, ce qui est déjà bien mieux (mais on gère toujours les utilisateurs)
  • Gérer des utilisateurs issus d’un Tenant Azure AD B2C ce qui est l’option la plus intéressante puisque chaque utilisateur vient avec son identité, nous n’avons plus à la gérer

Remarque : Attention avec Azure AD B2C, au moment de l’écriture de ce billet, il n’est pas encore disponible en CSP. Pour cette raison, nous allons travailler avec le fournisseur d’authentification standard.

Ce qu’il faut comprendre, c’est que ces identités vont permettre à nos consommateurs de se connecter à un portail pour y souscrire l’accès aux API de notre future licorne. Le consommateur ne va donc pas s’authentifier directement avec un compte / mot de passe mais une clé d’accès générée par Azure API Management.

Une fois le provisioning terminé, l’administrateur est notifié par mail de la création du service. Dans le portail Azure, cela ressemble à l’illustration ci-dessous :

clip_image007

 

Première information, le service API Management est accessible selon deux URL :

  • L’URL de la Gateway pour exposer nos API
  • L’URL que nous mettons à disposition de nos partenaires pour gérer la souscription à nos API

clip_image008

 

Avant de poursuivre, on va commencer par configurer la journalisation pour tout envoyer dans Log Analytics.

clip_image009

 

Un peu plus haut, j’avais indiqué que notre service API Management était accessible selon deux URL. En fait, il y en a un peu plus. Bonus, on peut les personnaliser (notion de Custom Domain) ainsi que d’utiliser nos propres certificats.

clip_image011

 

Notre service API Management peut lui-même exposer ses propres API pour le gérer à distance. Par défaut, ce n’est pas activé.

clip_image013

 

Côté Identité, il y a des multiples fournisseurs. Nous allons conserver le fournisseur par défaut. Le service API Management va alors lui-même prendre en charge l’authentification des utilisateurs. Charge à nous administrateur de créer ou inviter des utilisateurs. Nous on va imposer notre marque avec un message de bienvenu sur les services mis à disposition par notre nouvelle licorne.

clip_image015

 

Voilà pour les fondations. Pour la suite, il faudra attendre la publication des prochains billets :

 

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)

Azure Functions – Introduction au ServerLess en PowerShell

Je suis rentré dans Azure Function un peu par hasard. Je cherchais une alternative aux Webhooks de Azure Automation. Le principe est bien mais pose quand même quelques contraintes :

  • Impossibilité de retrouver l’URL du WebHook une fois créée, faut avoir conservé l’information
  • Pas d’authentification. Si on connait l’URL, c’est accessible en mode open-bar
  • Impossibilité de personnaliser paramètres une fois le WebHook créé (le plus problématique pour moi)

C’est pour cette raison que je suis arrivé à Azure Function. En plus, je n’étais pas en terrain inconnu avec du PowerShell :

clip_image001

Certes, c’est encore expérimental mais de ce que j’ai pu tester, c’est assez stable pour envisager l’utiliser. Mon besoin était de disposer d’un mécanisme équivalent aux WebHooks mais avec :

  • Une méthode d’authentification un peu plus évoluée
  • La capacité à passer des paramètres pour exécution
  • La possibilité de retourner les résultats d’exécution du Runbook Azure Automation

Mise en œuvre

Côté mise en œuvre, nous devons commencer avec la mise en place d’une instance du service Function App. On retrouve pas mal de paramètres connus, on ne va s’attarder sur l’un d’entre eux :

clip_image002

 

Pour le Hosting Plan. On a le choix entre :

  • Consumption Plan
  • App Service Plan

Avec Azure Function, on est capable de fonctionner sans infrastructure aussi dit « server-less ». Dans le mode  » App Service Plan », les Azure Functions seront exécutées dans le contexte d’un App Service Plan. Si on paie déjà pour pour héberger un site web pourquoi pas mais mon besoin, c’est juste de pouvoir exposer une API pour exécuter un Runbook Azure Automation. J’ai donc retenu le mode « Consumption Plan ». Une instance du service App Service Plan sera mise à disposition uniquement quand un appel à mon API sera réalisé. Pour les coûts, c’est bien. Par contre, ce n’est pas forcément le mode de fonctionnement qui offrira les performances les plus élevées.

Le mode « App Service Plan » présente certains avantages. Vu que c’est un App Plan, on peut configurer toutes ses fonctionnalités (scale-out, sauvegarde, authentification, …). Personnellement en dessous de S1, ce n’est pas pour de la production.

Créer une Azure Function

On a le choix du langage. Ce qui est intéressant, c’est qu’une Azure Function a nécessairement un déclencheur :

  • Un Trigger HTTP (Déclencher l’appel sur une URL donnée)
  • Un trigger Timer (Déclencher l’appel selon une planification horaire)
  • Un Trigger EventGrid (tout nouveau avec le service Event grid)
  • L’arrivée du message dans une Azure Queue
  • L’arrivée d’un message dans topic Service Bus
  • Autres déclencheurs

En PowerShell, tout ça c’est encore expérimental, nos choix sont pour l’instant limités :

clip_image001[1]

 

Pour mon besoin, le HttpTrigger fera parfaitement l’affaire. Il ne nous reste qu’à trouver un nom à notre première Azure Function.

clip_image003

 

Avant de poursuivre, arrêtons-nous sur la notion d’Authorization Level. C’est l’aspect authentification qui me manquait dans les WebHooks d’Azure Automation. Cela repose sur une clé qui peut être identique à toutes les Azure function (Admin level) ou une clé spécifique à chaque Azure Function (Function level). Je passe sur Anonymous, je pense que vous avez compris, … Ce que ne montre pas l’interface, c’est qu’il est aussi possible d’utiliser toutes les méthodes d’authentification disponibles dans un Web App (si on a choisi le mode de déploiement (App Service plan). Par exemple, il est possible de déporter l’authentification au niveau d’une Azure API management Gateway, .. Dans mon contexte le niveau « Function » sera amplement suffisant pour ce billet.

Par défaut, il nous est proposé un exemple gendre « Hello World ». A droite, on voit bien le paramètre passé en paramètre et le résultat de l’appel. Pour l’instant, à l’exception du passage de paramètre, cela ressemble beaucoup à Azure Automation.

clip_image004

 

On rentre dans le côté Sexy d’Azure Function. Mon déclencheur est une WebRequest mais on peut compléter cet appel en liant l’Azure Function à une source de données tel qu’Azure Table, Azure Queue, Service Bus ou même CosmoDB, même principe pour la sortie. Dans mon contexte, nous allons nous contenter des paramètres par défaut. Ils conviennent pour mon usage :

clip_image005

 

Dans la rubrique Manage, on gère la disponibilité de la fonction mais aussi les fameuses clés d’authentification utilisables. Certes, si on connait l’URL avec la clé, on a accès comme pour les WebHooks dans Azure Automation. Je suis d’accord mais c’est pour cela que l’intégration à un App Service plan est intéressante car on peut alors choisir la méthode d’authentification qui nous intéresse, voire jusqu’à aller utiliser Azure API Gateway.

clip_image006

 

Azure Function propose un mécanisme de supervision. Il a le mérite d’exister mais une intégration avec Azure Application Insight sera bien plus utile. On y reviendra plus tard.

clip_image007

 

Nous avons un socle pour poser notre Azure Function. Pour commencer, la première chose dont nous allons avoir besoin c’est d’authentification. Le code PowerShell que nous allons exécuter devra déclencher un Runbook dans Azure Automation. On va tout de suite commencer par bannir l’idée du compte et mot de passe en clair dans le code PowerShell, c’est juste moche. Nous avons rencontré la même problématique avec Azure Automation dans mon billet Parlons d’identité avec Azure Automation. Ce sera la même approche à deux différences près :

  • Nous allons stocker les informations nécessaires non plus dans Azure Automation mais dans la Web Application, dans les Applications Settings
  • On n’utilisera pas un certificat mais un secret pour l’authentification du Service principal

 

Préparer une identité pour notre Azure Function

Utiliser un certificat serait certainement possible mais faudra que je creuse plus la question. En tout cas, on sait déjà qu’il ne sera pas la peine d’essayer aller le chercher dans un Azure Key Vault. Accéder à un Key Vault implique une authentification préalable, … Ce sera donc une application tout ce qu’il y a de classique avec juste un secret. Nous verrons plus tard comment offusquer ce secret.

$ADApplicationName = « myazurefunction991 »

$ADHomePage = « https://$ADApplicationName.azurewebsite.net« 

$IdentifierUris = $ADHomePage

$azureADApplication = New-AzureRmADApplication -DisplayName $ADApplicationName -HomePage $ADHomePage -IdentifierUris $IdentifierUris -Password « MyVerySecureP@ssw0rd »

$azureADApplication

clip_image008

 

Ensuite, nous allons lier cette application Azure AD nouvellement déclarée à un Service Principal.

$ServicePrincipal = New-AzureRmADServicePrincipal -ApplicationId $azureADApplication.ApplicationId

$ServicePrincipal

clip_image009

 

Ne reste plus qu’à positionner des permissions à notre Service Principal. Dans mon contexte, les ressources que mon Service Principal doit pouvoir manipuler sont dans un groupe de ressources nommé « AzureFunctionLabs ».

New-AzureRmRoleAssignment -RoleDefinitionName Contributor -ServicePrincipalName $azureAdApplication.ApplicationId.Guid -Scope (Get-AzureRmResourceGroup -Name « AzureFunctionLabs »).resourceId

clip_image010

 

Pour finir, on se met de côté quelques informations. Nous en aurons besoin pour mettre en place l’authentification dans notre Azure Function :

$AzureApplicationID = $azureADApplication.ApplicationId.Guid

$AzureADTenantID = (Get-AzureRmTenant).tenantid

$SubscriptionId = (Get-AzureRmContext).Subscription.SubscriptionId

$AzureApplicationID

$AzureADTenantID

$SubscriptionId

clip_image011

 

Avant de passer à Azure Function, on va commencer par poser le code PowerShell. Nous sommes en présence d’un Azure AD Service Principal. Autant pour la construction de la chaine SecureString, rien de change, c’est au niveau de la commande Add-AzureRmAccount que cela change :

$AzureApplicationID = « 0eb602cc-4243-4104-815a-dda4b6f7378a »

$AzureADTenantID = « dfe6b267-4f0f-45e5-a9ed-d0540af967dd »

$subscriptionID = « 85e3593c-6a07-4e81-a447-a06d6f5e6f89 »

$azurePassword = ConvertTo-SecureString « MyVerySecureP@ssw0rd » -AsPlainText -Force

$psCred = New-Object System.Management.Automation.PSCredential($AzureApplicationID, $azurePassword)

$psCred

Add-AzureRmAccount -Credential $psCred -TenantId $AzureADTenantID -ServicePrincipal -SubscriptionId $SubscriptionID

clip_image012

 

L’authentification fonctionne, ne reste plus qu’à transposer cela dans notre Azure Function. Le code PowerShell est strictement identique, juste le Out-File pour retourner le contexte Azure :

$SubscriptionID = ’85e3593c-6a07-4e81-a447-a06d6f5e6f89′

$AzureApplicationID = ‘0eb602cc-4243-4104-815a-dda4b6f7378a’

$TenantID = ‘dfe6b267-4f0f-45e5-a9ed-d0540af967dd’

$azurePassword = ConvertTo-SecureString « MyVerySecureP@ssw0rd » -AsPlainText -Force

$psCred = New-Object System.Management.Automation.PSCredential($AzureApplicationID, $azurePassword)

Add-AzureRmAccount -Credential $psCred -TenantId $TenantID -ServicePrincipal -SubscriptionId $SubscriptionID

$context = Get-AzureRMContext

Out-File -Encoding Ascii -FilePath $res -inputObject $context

clip_image013

 

Nous avons quelque chose de fonctionnel mais qui d’un point de vue sécurité laisse à désirer. Laisser trainer des informations comme l’identifiant de l’application Azure AD ou même son secret associé, ce n’est pas génial. Nous allons déplacer ces informations dans des variables au niveau des Application Settings

Au lieu d’avoir nos paramètres dans le code de la fonction, l’ai retenu de les placer directement dans les Applications Settings, donc au niveau de l’Azure App Service.

clip_image014

 

Certains me dirons que ce n’est pas encore parfait. Tout de suite, on pense à Azure Key Vault mais encore une fois, il faut être authentifié pour accéder à Azure Key Vault. Dans l’état actuel des choses, la seule amélioration de sécurité envisageable serait de ne pas s’authentifier avec un secret mais un certificat. C’est un sujet que j’explorerai ultérieurement.

Pour notre code, ces variables sont disponibles dans PowerShell dans les variables d’environnements. C’est déjà beaucoup mieux pour notre code.

$AzureADPassword = $env:AzureADPassword

$SubscriptionID = $Env:SubscriptionID

$TenantID = $Env:TenantID

$AzureApplicationID = $env:AzureApplicationID

$SecureString = ConvertTo-SecureString $AzureADPassword -AsPlainText -Force

$psCred = New-Object System.Management.Automation.PSCredential($AzureApplicationID, $SecureString)

Add-AzureRmAccount -Credential $psCred -TenantId $TenantID -ServicePrincipal -SubscriptionId $SubscriptionID

$context = Get-AzureRMContext

Out-File -Encoding Ascii -FilePath $res -inputObject $context

clip_image015

 

Retour au besoin initial

Maintenant que nous sommes authentifiés dans Azure, revenons à mon besoin initial : Exécuter un Runbook dans Azure depuis une Azure Function avec passage de paramètre et pouvoir suivre l’avancement.

Pour le Runbook, j’ai pris ce qu’il y a de plus minimaliste possible pour ma démonstration :

param (

    [Parameter(Mandatory=$true)]

    [String]$Name

)

Write-Output « Hello $name »

Je vous laisse comprendre le paramètre en entrée, …

clip_image016

 

Pas grand-chose à dire de plus côté Azure Automation, ce qu’il nous faut maintenant, c’est d’intégrer l’appel du Runbook à notre Azure Function. J’ai commencé par ajouter un paramètre en entrée qui sera obligatoire. Après, ce n’est que du PowerShell tout ce qu’il y a de plus classique :

$requestbody = Get-Content $req -raw | ConvertFrom-JSON

$name = $requestBody.Name

If ($name -ne $null)

{

$AzureADPassword = $env:AzureADPassword

$SubscriptionID = $Env:SubscriptionID

$TenantID = $Env:TenantID

$AzureApplicationID = $env:AzureApplicationID

$SecureString = ConvertTo-SecureString $AzureADPassword -AsPlainText -Force

$psCred = New-Object System.Management.Automation.PSCredential($AzureApplicationID, $SecureString)

Add-AzureRmAccount -Credential $psCred -TenantId $TenantID -ServicePrincipal -SubscriptionId $SubscriptionID

$context = Get-AzureRMContext

$params = @{« Name »=$name}

$AutomationAccount = « MyAutomation992 »

$resourceGroup = « PersonalBackup »

$RunbookName = « HelloAutomationWorld »

$job = Start-AzureRmAutomationRunbook –AutomationAccountName $AutomationAccount –Name $RunbookName -ResourceGroupName $resourceGroup –Parameters $params

Out-File -Encoding Ascii -FilePath $res -inputObject $job

}

Else

{

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

}

<Je suis un boulet, AKA Kevin le boulet> : A ceux qui se demandent pourquoi il utilise une instance Azure Automation qui n’est pas dans le groupe de ressources pour lequel le Service Principal créé a obtenu des permissions, c’est que je suis un boulet. En exécutant L’Azure Function la première fois il m’a été clairement indiqué un manque de permissions. J’ai donc ajouter les permissions uniquement sur l’instance Azure Automation référencé dans mon code </Je suis un boulet, AKA Kevin le boulet>.

C’est maintenant qu’on comprend pourquoi il y a deux boutons « Run » dans l’éditeur. Celui-qui se trouve en bas à droite permet d’appeler notre Azure Function avec le ou les paramètres nécessaires à l’exécution.

clip_image017

 

Volontairement, j’ai retenu de récupérer le JobID du job qui a exécuté le Runbook. Attendre la bonne exécution du Runbook n’a pas de sens avec Azure Function car vous êtes limités dans le temps (voir à la fin du billet). Par contre, ayant récupéré le JobID, rien ne vous interdit de revenir plus tard pour vérifier le bon déroulement de votre Job.

Test en condition réelle

Tester en condition réelle, c’est juste quatre lignes de PowerShell pour invoquer notre Azure Function. Pour cela, encore faut-il connaître l’URL de notre Azure Function avec sa clé.

clip_image018

 

Après, ce n’est que du PowerShell :

$url = « « 

$parameters = @{Name=’BenoitS’}

$json = $parameters |ConvertTo-Json

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

clip_image019

 

Je vous laisse deviner le résultat de l’exécution du Runbook, …

 

Supervision

Pour ceux qui se sont demandé à quoi pouvait bien servir la variable APPINSIGHTS_INSTRUMENTATIONKEY dans les Application Settings, c’est la clé pour permettre de connecter Azure Function à Application Insight. L’intégration entre les deux composants est automatique. Le problème, c’est que pour le portail, cela veut dire de créer une nouvelle instance du service Application Insights ;). Je vous recommande donc de créer votre instance du service Azure Function App puis de la raccorder à Application Insights ultérieurement : Azure Functions now has direct integration with Application Insights. Une fois l’intégration en place, on peut suivre pas mal d’indicateurs de performance.

clip_image020

 

Côté performance, avec un peu de tunning, on obtient des performances plus acceptables pour une API.

clip_image021

 

Quelques astuces pour finir

La liste des trucs sur lesquels j’ai buté avant de comprendre. Pour certains d’entre eux, ça a juste été des heures de perdues :

  • La version actuellement supportée de PowerShell est la version 4.0. Pensez-y.
  • Si un Runbook Azure Automation peut s’exécuter pendant 300 minutes, une Azure function, c’est limité à 300 secondes. Voilà pourquoi je ne voulais pas attendre le résultat de l’exécution de mon Runbook.
  • Le paramètre NoNewLine pour Out-File qui n’existe qu’en PowerShell 5 : The PowerShell 5 NoNewLine Parameter (Grand merci Mr Scripting Guy pour toutes ces années depuis VbScript)
  • Le contournement à l’absence du Paramètre NoNewLine en PowerShell 4.0 : HTTP Binding in PowerShell Azure Functions
  • Ne cherchez pas à importer les modules Powershell, uploader les en FTP: Using PowerShell Modules in Azure Functions
  • Penser à configurer votre App Service Plan pour la plateforme X64 et non comme par défaut, c’est meilleur pour les performances.

 

Conclusion

Maintenant avec Azure Function, je suis capable d’appeler un Runbook Azure Automation avec authentification et surtout en étant capable de passer aussi un paramètre. Ce qui va nous intéresser d’autant plus, c’est l’aspect facturation. Pour une instance Azure Automation 500 minutes d’exécution dans un mois, c’est juste 1€, soit dans mon échelle trois capsules de Nespresso :

clip_image022

 

Avec Function App, pour un usage peu consommateur en mémoire et des API bien écrites, on a de grandes chances de ne même pas être facturé ou alors pas assez pour payer un café.

clip_image023

 

Ça change grandement la perception sur la conception d’applications, … A bientôt pour d’autres sujets Azure Function, toujours en PowerShell, …

BenoitS – Simple and Secure by design but Business compliant (with disruptive flag enabled)

BGP dans Azure

Azure est un univers merveilleux. Le problème, c’est avant de pouvoir découvrir ses merveilles, il faut en revenir aux fondamentaux avec le premier d’entre eux : Le réseau. Sans lui pas de merveilles. Normalement, c’est une phase qui se passe à peu près bien dès lors que vos interlocuteurs réseau et sécurité sont de bonne composition. De temps en temps, il arrive que cette phase pose un challenge particulier. C’est le sujet de ce billet. Voilà ma situation de départ :

clip_image001

Côté expression de besoin, c’est assez simple : Permettre aux utilisateurs du siège d’accéder aux ressources (IaaS) de toutes les souscriptions en utilisant une future connexion qui pourrait être de type ExpressRoute (mais ce n’est pas obligatoire pour commencer). Histoire de corser le sujet, on a quelques contraintes :

  • Les souscriptions ne sont pas liées au même tenant Azure Active Directory (sinon c’est pas drôle)
  • Les souscriptions sont dans des régions Azure différentes
  • L’extension du modèle avec de nouvelles souscriptions doit être simple

Mon sujet c’est l’interconnexion des différents Virtual Networks et d’assurer le routage entre eux tout en gardant en tête que cela devra fonctionner lors de la mise en place de l’interconnexion avec les réseaux locaux composant le siège.

A première vue pour l’interconnexion des Virtual Networks, le VNet Peering, c’est mort. Les contraintes sont juste bloquantes (tout du moins à ce jour). La seule solution qui nous reste, c’est donc le VNET To VNET. Ça tombe bien car :

  • VNET to VNET est capable d’interconnecter des Virtual Networks dépendant de souscriptions différentes
  • VNET to VNET est capable d’interconnecter des Virtual Networks dans des régions Azure sans distinction

Ce qui était bien avec le VNET Peering, c’est qu’il s’occupait automatiquement du routage. Lors de la mise en œuvre, le réseau distant était déclaré automatiquement dans le routage. On peut le constater si on regarde les routes effectives pour une machines virtuelle comme dans l’exemple ci-dessous :

clip_image002

Remarque : A ce jour, le User-Defined Routing ne prend pas encore en charge le VNET Peering comme choix de Next Hop type. Faudra patienter de ce côté.

Avec VNET to VNET, nous allons devoir travailler un peu. Quand on pense routage dans Azure, on pense à la fonctionnalité Azure Route Table. Quand j’ai révisé le sujet des Azure Route Table, je suis tombé ce cet avertissement.

clip_image003

Pas de problème pour envoyer du trafic mais comment le traiter de l’autre côté? UDR (User-Defined Routing) ne s’applique pas au trafic réseau entrant, … Par expérience, je regarde toujours les sujets dans les feedbacks Azure. Un sujet a attiré mon attention : Allow a UDR to specify any routable « next hop » IP address (not limited to the VNet or Region) :

clip_image004

Bref, on passe au plan B. En plus, UDR n’est pas dynamique. C’est du déclaratif. Bingo, c’est du routage dynamique dont j’ai besoin. Encore une fois, c’est un retour aux bons vieux fondamentaux. C’est à partir de maintenant que cela devient fun. On repasse en mode matériel mais dans Azure !

Il faut recommencer

C’est ma première leçon apprise sur le sujet de BGP dans Azure. C’est possible mais :

  • Ce n’est disponible que pour les Gateway de type Route-Based (routage dynamique)
  • Ce n’est pas par ce que la Gateway a été provisionnée en Route-Based qu’elle est prête à accepter de nouvelles informations de routage et de les propager

Ci-dessous deux Gateway créées avec le portail Azure. Quand on regarde spécifiquement la configuration de BGP, on a un problème :

clip_image005

 

Nos deux Gateway ont été configurées pour annoncer le même ASN. Autant vous dire tout de suite que ça ne va pas le faire. Le problème, c’est que le portail ne permet pas de configurer l’ASN annoncé lors de la création de la Gateway. Il faut donc recommencer la création de la Gateway pour préciser notre configuration de BGP.

Dans les choix des ASN, faites attention à ne pas réutiliser un ASN utilisé pour un service Microsoft voire même un de ceux utilisés On-Premises par vos équipes réseau. La lecture de la FAQ Overview of BGP with Azure VPN Gateways vous apprendra que dans la liste des ASN réservés, on a :

  • Azure Public ASNs: 8075, 8076, 12076
  • Azure Private ASNs: 65515, 65517, 65518, 65519, 65520

Seconde leçon, ce n’est pas par ce qu’on annonce utiliser un ASN que BGP est activé. Là encore, le portail ne nous pose pas la question. C’est à nous de l’exiger à la création de la Azure Virtual Network Gateway. J’ai jamais créé autant de Gateway que ces jours-ci!

Le portail Azure évolue tous les jours. En ce jour du 24 Aout 2017, j’ai pu constater une évolution du côté du portail lors de la création dune Azure Virtual Network Gateway. Il est désormais possible d’activer le support de BGP et l’ASN annoncé :

Tout ce dont j’ai besoin, c’est de trois Virtual Networks :

  • VNETLab1
  • VNETLab2
  • VNETInter

Bien entendu, pas d’overlaping des espaces d’adressage, sinon ce ne sera pas drôle pour la suite. Histoire d’être clair, notre socle réseau à mettre en œuvre est le suivant :

clip_image006

 

On va commencer par reconstruire la Gateway VNETLab1 avec un peu de PowerShell :

$Lab1PublicIPName = « LABVNET1-IP »

$Lab1ResourceGroupName = « VNETLAB1 »

$Lab1Location = « West Europe »

$Lab1gatewayIPConfigName = « LAB1GATEWAYIP »

$Lab1VnetName = « VNETLab1 »

$Lab1GatewayName = « LABVNET1Gateway »

$Lab1gatewayBGPASN = 65011

$Lab1GatewayIP = New-AzureRMPublicIpAddress -Name $Lab1PublicIPName -ResourceGroupName $Lab1ResourceGroupName -Location $Lab1Location -AllocationMethod Dynamic

$Lab1VNET = Get-AzureRmVirtualNetwork -ResourceGroupName $Lab1ResourceGroupName -Name $Lab1VnetName

$Lab1GatewaySubnet = Get-AzureRmVirtualNetworkSubnetConfig -Name « GatewaySubnet » -VirtualNetwork $Lab1VNET

$GatewayIpConfigLab1 = New-AzureRmVirtualNetworkGatewayIpConfig -Name $Lab1gatewayIPConfigName -Subnet $Lab1GatewaySubnet -PublicIpAddress $Lab1GatewayIP

New-AzureRmVirtualNetworkGateway -Name $Lab1GatewayName -ResourceGroupName $Lab1ResourceGroupName -Location $Lab1Location -IpConfigurations $GatewayIpConfigLab1 -GatewayType Vpn -VpnType RouteBased -GatewaySku VPNGW1 -Asn $Lab1gatewayBGPASN -EnableBGP $True

clip_image007

 

C’est lors de la création de la Gateway qu’on associe le numéro d’ASN et qu’on active le support de BGP. Pas après. Ceci dit, faisons de même pour notre seconde Gateway :

$Lab2PublicIPName = « LABVNET2-IP »

$Lab2ResourceGroupName = « VNETLAB2 »

$Lab2Location = « North Europe »

$Lab2gatewayIPConfigName = « LAB2GATEWAYIP »

$Lab2VnetName = « VNETLab2 »

$Lab2GatewayName = « LABVNET2Gateway »

$Lab2gatewayBGPASN = 65012

$Lab2GatewayIP = New-AzureRMPublicIpAddress -Name $Lab2PublicIPName -ResourceGroupName $Lab2ResourceGroupName -Location $Lab2Location -AllocationMethod Dynamic

$Lab2VNET = Get-AzureRmVirtualNetwork -ResourceGroupName $Lab2ResourceGroupName -Name $Lab2VnetName

$Lab2GatewaySubnet = Get-AzureRmVirtualNetworkSubnetConfig -Name « GatewaySubnet » -VirtualNetwork $Lab2VNET

$GatewayIpConfigLab2 = New-AzureRmVirtualNetworkGatewayIpConfig -Name $Lab2gatewayIPConfigName -Subnet $Lab2GatewaySubnet -PublicIpAddress $Lab2GatewayIP

New-AzureRmVirtualNetworkGateway -Name $Lab2GatewayName -ResourceGroupName $Lab2ResourceGroupName -Location $Lab2Location -IpConfigurations $GatewayIpConfigLab2 -GatewayType Vpn -VpnType RouteBased -GatewaySku VPNGW1 -Asn $Lab2gatewayBGPASN -EnableBGP $True

clip_image008

 

Entre les deux, on va avoir notre Gateway d’interconnexion nommée LABVNETInterGateway :

$LabInterPublicIPName = « LABVNETINTER-IP »

$LabInterResourceGroupName = « VNETLabInterconnect »

$LabInterLocation = « West Europe »

$LabIntergatewayIPConfigName = « LABINTERGATEWAYIP »

$LabInterVnetName = « VNETLabInterconnect »

$LabInterGatewayName = « LABVNETInterGateway »

$LabIntergatewayBGPASN = 65013

$LabInterGatewayIP = New-AzureRMPublicIpAddress -Name $LabInterPublicIPName -ResourceGroupName $LabInterResourceGroupName -Location $LabInterLocation -AllocationMethod Dynamic

$LabInterVNET = Get-AzureRmVirtualNetwork -ResourceGroupName $LabInterResourceGroupName -Name $LabInterVnetName

$LabInterGatewaySubnet = Get-AzureRmVirtualNetworkSubnetConfig -Name « GatewaySubnet » -VirtualNetwork $LabInterVNET

$GatewayIpConfigInter = New-AzureRmVirtualNetworkGatewayIpConfig -Name $LabIntergatewayIPConfigName -Subnet $LabInterGatewaySubnet -PublicIpAddress $LabInterGatewayIP

New-AzureRmVirtualNetworkGateway -Name $LabInterGatewayName -ResourceGroupName $LabInterResourceGroupName -Location $LabInterLocation -IpConfigurations $GatewayIpConfigInter -GatewayType Vpn -VpnType RouteBased -GatewaySku VPNGW1 -Asn $LabIntergatewayBGPASN -EnableBGP $True

clip_image009

 

Personnaliser les policies IPSEC, ce n’est pas obligatoire mais par expérience, quand plusieurs équipements réseau On-Premises vont venir se connecter sur une Gateway, on a pas toujours les mêmes capacités sur tous les équipements. Là encore, c’est pas possible à configurer depuis le portail. Dans le cadre de ce billet, c’est optionnel :

$ipsecpolicy2 = New-AzureRmIpsecPolicy -IkeEncryption AES128 -IkeIntegrity SHA1 -DhGroup DHGroup14 -ipsecEncryption GCMAES128 -IpsecIntegrity GCMAES128 -PfsGroup PFS24 -SALifeTimeSeconds 7200 -SADataSizeKilobytes 4096

clip_image010

 

Vu qu’on va mettre en place des connections, on va travailler au niveau de nos Virtual Network Gateway, commençons par récupérer les informations en vue de construire les connections :

$gatewayLab1 = Get-AzureRmVirtualNetworkGateway -ResourceGroupName $Lab1ResourceGroupName -Name $Lab1GatewayName

$gatewayLab2 = Get-AzureRmVirtualNetworkGateway -ResourceGroupName $Lab2ResourceGroupName -Name $Lab2GatewayName

$gatewayLabInter = Get-AzureRmVirtualNetworkGateway -ResourceGroupName $LabInterResourceGroupName -Name $LabInterGatewayName

clip_image011

 

On commence par les connections entre Lab1 et LabInterGateway

$ConnectionLabOneToLabInter = « ConnectionLab1toLabInter »

New-AzureRmVirtualNetworkGatewayConnection -Name $ConnectionLabOneToLabInter -ResourceGroupName $Lab1ResourceGroupName -VirtualNetworkGateway1 $gatewayLab1 -VirtualNetworkGateway2 $gatewayLabInter -Location $Lab1Location -ConnectionType Vnet2Vnet -IpsecPolicies $ipsecpolicy2 -SharedKey ‘AzureA1b2C3’ -EnableBGP $True

$ConnectionLabInterToLabOne = »ConnectionLabIntertoLab1″

New-AzureRmVirtualNetworkGatewayConnection -Name $ConnectionLabInterToLabOne -ResourceGroupname $LabInterResourceGroupName -VirtualNetworkGateway1 $gatewayLabInter -VirtualNetworkGateway2 $gatewayLab1 -Location $LabInterLocation -ConnectionType Vnet2Vnet -IpsecPolicies $ipsecpolicy2 -SharedKey ‘AzureA1b2C3’ -EnableBGP $True

clip_image012

 

Puis on poursuit avec les connections entre Lab2 et LabInterGateway

$ConnectionLabTwoToLabInter = « ConnectionLab2toLabInter »

New-AzureRmVirtualNetworkGatewayConnection -Name $ConnectionLabTwoToLabInter -ResourceGroupName $Lab2ResourceGroupName -VirtualNetworkGateway1 $gatewayLab2 -VirtualNetworkGateway2 $gatewayLabInter -Location $Lab2Location -ConnectionType Vnet2Vnet -IpsecPolicies $ipsecpolicy2 -SharedKey ‘AzureA1b2C3’ -EnableBGP $True

$ConnectionLabInterToLabtwo = »ConnectionLabIntertoLab2″

New-AzureRmVirtualNetworkGatewayConnection -Name $ConnectionLabInterToLabtwo -ResourceGroupname $LabInterResourceGroupName -VirtualNetworkGateway1 $gatewayLabInter -VirtualNetworkGateway2 $gatewayLab2 -Location $LabInterLocation -ConnectionType Vnet2Vnet -IpsecPolicies $ipsecpolicy2 -SharedKey ‘AzureA1b2C3’ -EnableBGP $True

clip_image013

 

Voilà, c’est en place.

clip_image014

Ce qui est intéressant, ce sont les effectives routes annoncées. Ci-dessous celles liée à une interface réseau liée à un sous-réseau dépendant de VNETLab1. La route vers VNETLab2 est bien annoncée avec la Virtual Network Gateway de VNETLab1 pour passer par la Gateway LABVNETInterGateway.

clip_image015

 

Même réponse depuis le Network Watcher : pour joindre une machine virtuelle dépendant de VNETLab2. Il annonce la Gateway de VNETLab1 :

clip_image016

 

Prochaine étape, quand j’aurai un peu de temps, l’intégration du VPN vers les réseaux On-Premises, toujours avec BGP.

 

BenoitS – Simple and Secure by design but Business compliant (with disruptive flag enabled)

A la découverte d’Hybrid Worker dans Azure Automation

Après avoir abordé le sujet identité Azure Automation, continuons dans ce domaine avec le mode Hybride d’Azure Automation. Ci-dessous un rappel de Azure Automation pour poser le décor. Par défaut, un Runbook est exécuté dans Azure Automation donc dans Azure. C’est parfait pour manipuler des ressources Azure. Par contre, quand on essaie de l’utiliser pour manipuler les machines virtuelles, cela implique que celles-ci soient accessibles sur Internet. C’est faisable mais d’un point de vue sécurité, c’est tout sauf sexy que ce soit dans Azure ou On-Premises.

clip_image001

 

C’est là que le mode hybride prend tout son intérêt. Azure Automation Hybrid Worker permet d’exécuter nos Runbooks sur un serveur qui a accès à Internet et qui sera le seul à accéder aux machines virtuelles à administrer. Cette approche présente beaucoup d’avantages :

  • L’hôte sur lequel on installe le Hybrid Worker n’a pas besoin d’une adresse IP publique. En fait, l’hôte a juste besoin de pouvoir accéder à Internet
  • D’un point de vue réseau, les flux partent du Hybrid Worker pour aller vers Azure mais l’inverse. Il n’y a donc pas de flux entrant vers nos infrastructures
  • Possibilité de configurer le Hybrid Worker pour utiliser un proxy pour accéder Azure
  • L’hôte sur lequel on installe le Hybrid Worker sera le seul qui devra accéder aux machines virtuelles que l’on va gérer. On peut donc limiter les flux réseau au strict minimum.

 

Mise en œuvre d’un Hybrid Worker

Côté mise en œuvre, il y a quelques prérequis à respecter au niveau OMS Log Analytics :

  • Une instance du service Azure Automation à laquelle on va raccorder notre Hybrid Worker
  • Une instance du service OMS Log Analytics
  • La solution Automation Hybrid Worker préalablement activée dans OMS Log Analytics

 

Pour notre futur Hybrid Worker, nous avons besoin :

  • Une machine virtuelle avec 2VCPU et 4Go de mémoire vive
  • D’un OS Windows Server 2012 minimum disposant d’un Accès Internet
  • L’agent OMS Log Analytics préalablement installé sur notre futur serveur Hybrid Worker

 

Normalement avec le socle Windows Server 2012, on a automatiquement PowerShell 4.0. Ça ne coute rien de vérifier en regardant ce que nous retourne la variable $PSVersionTable.

clip_image002

 

PowerShell 4.0, c’est bien mais en ce qui me concerne je préfère avoir la même version de PowerShell, que nos Runbooks se comportent de la même manière dans Azure que sur notre future instance du Hybrid Worker. On va donc commencer par installer le Management Framework 5.1 disponible ici.

clip_image003

 

Une fois l’installation terminée, on peut constater la nouvelle version de PowerShell qui est maintenant 5.1.

clip_image004

 

Prochaine étape, les modules PowerShell. C’est un prérequis à l’installation qui va suivre mais aussi un process qu’on va devoir gérer. Dans Azure Automation, on est capable de maîtriser les modules PowerShell (modules & version) qui sont utilisés. Au moment de l’écriture de ce billet, il n’est pas encore possible de gérer les modules sur le Hybrid Worker. J’en ai fait l’amère expérience pendant un développement de Runbook. Un des avantages de PowerShell 5.0 (ou supérieur), c’est la capacité à utiliser la PowerShell Gallery comme unique repositoy de nos modules. On doit juste autoriser son utilisation :

Set-PSRepository -Name PSGallery -InstallationPolicy Trusted

clip_image005

 

Etant donné que je vais manipuler beaucoup de ressources Azure, il est logique de demander l’installation des modules PowerShell en relation avec Azure. N’oubliez pas d’installer vos propres modules sinon ça va poser quelques problèmes.

C’est maintenant que commence la configuration de notre futur Hybrid Worker. L’avantage est que tout le paramétrage est prévu dans le script « New-OnPremiseHybridWorker« . Nous n’avons qu’à le télécharger depuis la PowerShell Gallery avec la commande suivante :

Install-Script -Name New-OnPremiseHybridWorker -RequiredVersion 1.0

clip_image006

 

Nous devons accepter l’installation des PowershellGet NuGet.

clip_image007

 

Ne reste plus qu’à réaliser l’installation de notre Hybrid Runbook Worker avec le code ci-dessous :

$AutomationAccountName = « <nom instance Azure Automation> »

$AutomationResourceGroupName = « <groupe de ressources> »

$AzureSubscriptionID = « <Identifiant unique de votre souscription> »

$OMSWorkSpace = « <Nom de votre instance OMS> »

New-OnPremiseHybridWorker.ps1 -AutomationAccountName $AutomationAccountName -ResourceGroupName $AutomationResourceGroupName -HybridGroupName « Hybrid01 » -SubscriptionId $AzureSubscriptionID -WorkspaceName $OMSWorkSpace

clip_image008

 

Comme tout s’est bien passé, on doit pouvoir constater l’arrivée d’une instance Hybrid Worker group dans Azure Automation. C’est à ce groupe qu’a été associé notre serveur. Un groupe peut regrouper plusieurs instances Hybrid Worker, histoire de proposer de la haute disponibilité et la répartition de l’exécution des Runbooks.

clip_image009

 

Maintenant, un sujet sécurité. Dans le billet identité Azure Automation, nous avons abordés le sujet de l’identité sous laquelle nos Runbooks sont exécutés sous Azure. Dans l’état actuel de la configuration, notre Hybrid Worker ne dispose d’aucune identité pour faire fonctionner les Runbooks.

clip_image010

 

On peut tenter de fonctionner dans ces conditions mais on n’ira jamais plus loin que le Hybrid Worker. Le problème c’est qu’avec le contexte d’exécution par défaut, c’est celui du contexte d’exécution du Hybrid Worker. On va donc commencer par mettre en place un contexte d’exécution pour nos Runbooks. Dans Azure Automation, cela prend la forme d’un objet credential. Ce contexte de sécurité, nous le référençons dans Azure sous la forme d’un credential.

clip_image011

 

Inutile de rappeler que le compte devra exister sur toutes les machines virtuelles que l’on voudra manipuler depuis un Runbook exécuté avec notre instance du Hybrid Worker. Ne reste plus qu’à configurer ce contexte d’exécution pour notre instance du Hybrid Worker.

clip_image012

 

Remarque : N’oubliez pas le bouton « Save », …

 

Un dernier détail pour notre Hybrid Worker. Nous allons l’utiliser comme pivot pour exécuter du code PowerShell sur des systèmes distants. Erreur classique en PowerShell, on tente de faire du Remote PowerShell sur un système qui ne peut pas être approuvé (pas membre du domaine), ça échoue. Pourtant, un simple « Get-ChildItem WSMAN:LocalHost\Client » aurait dû vous faire comprendre qu’il y allait avoir un problème.

clip_image013

 

Dans le cadre de ce billet, on va faire au plus simple, en autorisant toutes destinations depuis le serveur Hybrid Worker. Après, d’un point de vue sécurité, c’est très discutable. Les bonnes pratiques de sécurité voudraient qu’on référence la liste des adresses IP vers lesquelles notre serveur va communiquer.

clip_image014

 

Remarque : Volontairement, je suis laxiste sur ce point de sécurité. J’y reviendrai plus tard.

 

Un client pour tester

Pour valider le bon fonctionnement de tout cela, il nous faut un client. J’ai donc mis en œuvre une nouvelle machine virtuelle, sur le même réseau que notre Hybrid Worker. Le Runbook que nous allons créer va s’exécuter sur le Hybrid Worker et devra accéder à notre nouvelle machine virtuelle en PowerShell (Donc du Remote PowerShell). C’est un sujet connu, on va donc aller très vite. Nous avons besoin de :

  • Disposer d’un certificat (auto-signé pour les besoins de ma démonstration)
  • Activer la fonctionnalité PowerShell Remoting
  • Associer le certificat à WINRM pour faire du PowerShell avec un peu de sécurité
  • Créer une règle de pare-feu

Pour faire simple, le code ci-dessous réalise toutes ces opérations. Ici encore, il reste très perfectible d’un point de vue sécurité mais il fonctionnera pour notre démonstration.

$fqdn = $env:computername

$cert = New-SelfSignedCertificate -DNSName $fqdn -certStorelocation « cert:\LocalMachine\My »

Enable-PSRemoting -SkipNetworkProfileCheck -Force

New-Item -Path WSMan:\LocalHost\Listener -Transport HTTPS -Address * -CertificateThumbPrint $Cert.Thumbprint –Force

New-NetFirewallRule -DisplayName « RemotePowerShell » -Direction Inbound –LocalPort 5986 -Protocol TCP -Action Allow -RemoteAddress 10.0.0.5

clip_image015

 

Pour finir on va introduire un peu de sécurité au niveau réseau. Mon client étant une machine virtuelle dans Azure, ci-dessous un extrait des règles entrantes du Network Security Group associé à la carte réseau :

clip_image016

 

Globalement le seul moyen pour joindre cette machine virtuelle, ce sera PowerShell (et en mode sécurisé seulement), uniquement depuis une seule adresse IP, celle de notre Hybrid Worker. Pour les règles sortantes, c’est encore plus strict, aucun moyen de joindre Internet :

clip_image017

 

Un Runbook magique

Notre client est prêt, ne nous manque plus que le Runbook « magique ». En fait, point de magie, juste du Remote PowerShell à l’ancienne. On va juste ajouter un peu d’Azure Automation en récupérant le « SecureString » contenant le compte de service que nous avons mis en œuvre et construire une authentification WSMAN pour exécuter du PowerShell sur une machine virtuelle distante désignée par son adresse IP qui a été passée en paramètre du Runbook.

param (

    [Parameter(Mandatory=$true)]

    [String] $RemoteIP

)

[OutputType([String])]

#

# Récupération des credentials dans un objet SecureString

#

$credential = Get-AutomationPSCredential -Name ‘Hybrid01_Credential’

« Host d’exécution : $env:computername. »

#

# Construction de l’authentification

#

$sessionOptions =New-PSSessionOption -SkipRevocationCheck -SkipCACheck -SkipCNCheck

$session = New-PSSession -ComputerName $RemoteIP -SessionOption $sessionOptions -Credential $Credential -UseSSL

$session

#

# Exécution de code Powershell à distance

#

$retour = Invoke-Command -Session $session -scriptblock { « Host d’exécution : $env:computername. » }

$retour

Remove-PsSession $Session

A l’Exécution de notre Runbook, on constate qu’il est possible de l’exécuter dans Azure et aussi sur un Hybrid Worker que nous pouvons sélectionner dans la liste de liste.

clip_image018

 

En fin d’exécution, on devrait avoir le résultat ci-dessous :

clip_image019

 

Le Runbook a bien commencé son exécution sur le Hybrid Worker pour ensuite créer une session WSMAN distante sur le second serveur et exécuter du code.

 

Bonus : Un peu plus de sécurité

Si vous avez suivi mon billet sur l’identité dans Azure Automation, vous savez comment créer un Service principal qui sera mis en œuvre uniquement pour un Hybrid Worker. Avantage si vous avez plusieurs instances d’Hybrid Worker, chaque instance dispose de sa propre identité pour se connecter à Azure avec un certificat. Pour améliorer la sécurité, on peut aussi :

 

Et continuer avec quelques saines lectures sur la sécurité PowerShell :

 

BenoitS – Simple and Secure by design but Business compliant (with disruptive flag enabled)

Parlons d’identité avec Azure Automation

Azure Automation, est un composant avec lequel je passe beaucoup de temps en ce moment. C’est un composant intéressant qui mérite qu’on y passe un peu de temps, surtout lorsque celui-ci nous parle d’identité. C’est ce qui arrive lors de la création d’une instance de ce service si on ne dispose pas des privilèges nécessaires. C’est le cas illustré ci-dessous. Ce qu’il faut comprendre, c’est que nous ne pouvons pas créer un certain nombre d’objets dans le tenant Azure AD et positionner des permissions.

clip_image001

Pourquoi est-ce important ? Tout simplement parce que c’est sous cette identité que nous allons exécuter nos futurs Runbooks. Dans ma situation, j’ai bien le niveau de privilège « Owner » sur mon groupe de ressources mais absolument aucun privilège dans le tenant Azure AD lié à ma souscription Azure. Du point de vue Azure Automation, il m’indique bien qu’il a un problème avec les « Run As Accounts » ?

clip_image002

 

On notera qu’il existe deux type de « Run As Accounts » :

  • Azure Run As Account
  • Azure Classic Run As Account

Pour faire court, le premier est destiné à gérer les ressources de type ARM et le second les ressources de type ASM, donc l’ancien Azure. Dans mon cas, ça ne sera pas possible puisque je suis sur une souscription Azure CSP. Nous allons donc devoir créer un « Azure Run As Account ». C’est maintenant que cela commence à piquer un peu. Le portail Azure nous donne un indice, ce qui manque, c’est une identité, un Service Principal qui va être associé à une application dans Azure AD. Si nous n’avons pas les privilèges sur le Tenant Azure AD lié à notre souscription Azure, il faudra réaliser la création manuellement. L’opération comprendra plusieurs étapes :

  • La création d’un certificat
  • Créer une application Azure AD
  • Assigner un rôle RBAC
  • Déclaration du Certificat dans Azure Automation
  • Déclaration de la connexion dans Azure Automation
  • Consommer l’identité dans un Runbook

 

La création d’un certificat

L’instance du Service Azure Automation a besoin d’un certificat. Ce certificat sera référencé comme un « Asset » dans Azure Automation., nous y reviendrons. Pour le certificat en lui-même, il peut être auto-signé ou délivré par une autorité de certification. Lorsqu’on laisse faire le portail, il va générer un certificat auto-signé d’une durée de vie d’un an. C’est court. Est-ce qu’on va se souvenir que ce certificat va expirer si le portail a fait tout cela pour nous ? Non. Le portail Azure ne nous préviendra pas. La seule chose qu’il va nous proposer, c’est l’option « Rewnew certificate » comme illustré ci-dessous :

Celui-ci sera lié à notre Application Azure AD. Vu qu’on va avoir besoin du certificat pour créer notre Application Azure AD, on va commencer par lui. Notre première décision sera de définir les caractéristiques principales de ce certificat :

  • Nom
  • Date d’expiration

clip_image003

Par contre, ce qui est bien, c’est qu’on a maintenant sous les yeux tous les ingrédients. Pour notre démonstration, nous allons créer un certificat auto-signé d’une durée de vie de deux ans :

$CertName = « automation.simplebydesignlabs.fr »

$CurrentDate =Get-Date

$EndDate = $currentDate.AddYears(2)

$NotAfter = $endDate.AddYears(2)

$CertStore = “Cert:\LocalMachine\My”

$CertThumbprint =(New-SelfSignedCertificate -DnsName « $certName » -CertStoreLocation $CertStore -KeyExportPolicy Exportable -Provider « Microsoft Enhanced RSA and AES Cryptographic Provider » -NotAfter $notAfter).Thumbprint

$CertThumbprint

clip_image004

Ce n’est pas précisé mais le certificat est exportable et c’est vivement recommandé si on veut plus tard déployer des Hybrid Worker Group. Nous allons exporter ce certificat.

$pfxPassword = ConvertTo-SecureString -String « LeMotDePasseIlAChangé! » -AsPlainText -Force

$pfxFilepath = « C:\Temp\automation.PFX »

Export-PfxCertificate -Cert « $($certStore)\$($certThumbprint) » -FilePath $pfxFilepath -Password $pfxPassword

clip_image005

C’est maintenant que cela se complique. Pour permettre à notre instance Azure Automation de s’authentifier, il faut la référencer sous la forme d’une application Azure AD. Cette application devra présenter le certificat exporté. Ce sera sa méthode d’authentification. Nous devons donc préparer un objet « PSADKeyCredential » dans lequel nous allons référencer le certificat et ses principales caractéristiques :

Import-Module -Name AzureRM.Resources

$Cert =New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate -ArgumentList @($pfxFilepath, $pfxPassword)

$KeyValue =[System.Convert]::ToBase64String($cert.GetRawCertData())

$KeyId =[guid]::NewGuid()

$KeyCredential = New-Object -TypeName Microsoft.Azure.Commands.Resources.Models.ActiveDirectory.PSADKeyCredential

$KeyCredential.StartDate = $currentDate

$KeyCredential.EndDate = $endDate

$KeyCredential.KeyId = $keyId

$KeyCredential.CertValue = $keyValue

$KeyCredential

clip_image006

 

Créer une application Azure AD

Prochaine étape, déclarer notre application dans Azure AD et y associer un Service principal :

$AADAppName = « Automation_APP »

$adAppHomePage = « « 

$adAppIdentifierUri = « « 

$AADApp = New-AzureRmADApplication -DisplayName $AADAppName -HomePage $adAppHomePage -IdentifierUris $adAppIdentifierUri -KeyCredentials $KeyCredential

New-AzureRmADServicePrincipal -ApplicationId $AADApp.ApplicationId -KeyCredential $KeyCredential

clip_image007

 

Assigner un rôle RBAC

Nous avons maintenant une identité. Encore faut-il que celle-ci dispose de privilèges dans la souscription Azure. Dans mon contexte, j’ai fait au plus restreint, à savoir limité à un groupe de ressources nommé Gestion, pour lequel le Service principal dispose du niveau de privilèges « Contributor » (gestion des ressources existantes dans le groupe de ressources).

New-AzureRmRoleAssignment -RoleDefinitionName Contributor -ServicePrincipalName $($AADApp.ApplicationId) -ResourceGroupName Gestion

clip_image008

Au passage, si on laisse faire le portail, c’est le niveau de privilège « Owner » qui est positionné sur la souscription. Votre RSSI appréciera, ou pas.

 

Déclaration du Certificat dans Azure Automation

La première opération qu’un Runbook Azure Automation, c’est de s’authentifier auprès d’Azure. Cette authentification sera faite sous l’identité du Service Principal. Pour que l’authentification fonctionne, encore faut-il qu’Azure Automation dispose du certificat. Azure Automation va mettre à disposition le certificat lors de chaque exécution de Runbook. On va donc référencer cet « Asset » dans notre instance du service Azure Automation sous le nom « AzureRunAsCertificate ». On verra plus tard que le nom a son importance.

$AutomationCertAsset = « AzureRunAsCertificate »

$AutomationAccountName = « Automation »

$AutomationResourceGroupname = « Gestion »

New-AzureRmAutomationCertificate -Name $AutomationCertAsset -Path $pfxFilepath -Password $pfxPassword -AutomationAccountName $AutomationAccountName -ResourceGroupName $AutomationResourceGroupname

clip_image009

On notera que ce certificat est maintenant impossible à exporter. C’est une bonne chose d’un point de vue sécurité.

Déclaration de la connexion dans Azure Automation

C’est maintenant qu’on va créer le « Run As Account ». Dans notre cas, nous allons créer un « Run As Account » pour un Service Principal qui utilise un certificat pour s’authentifier. Nous devons disposer d’un certain nombre de paramètres :

  • L’identifiant unique de l’application Azure AD précédemment créée
  • L’identifiant unique du tenant Azure AD dans lequel l’Application Azure AD a été créée
  • L’empreinte numérique du certificat que nous avons associé à l’Application Azure AD
  • La souscription Azure dans laquelle l’application Azure AD va se connecter

$ConnectionAssetName = « AzureRunAsConnection »

$ConnectionFieldValues = @{« ApplicationId » = $AADApp.ApplicationId; « TenantId » = (Get-AzureRmContext).Tenant.Id; « CertificateThumbprint » = $CertThumbprint; « SubscriptionId » = (Get-AzureRmContext).Subscription.Id}

$ConnectionFieldValues

New-AzureRmAutomationConnection -ResourceGroupName $AutomationResourceGroupname -AutomationAccountName $AutomationAccountName -Name $ConnectionAssetName -ConnectionTypeName AzureServicePrincipal -ConnectionFieldValues $ConnectionFieldValues

clip_image010

 

Si tout se passe bien, voilà ce que vous devriez avoir dans les « Run As Accounts » de votre instance Azure Automation :

clip_image011

 

On retrouve bien toutes les informations que nous avons renseigné.

clip_image012

 

Maintenant le truc sur lequel j’ai passé le plus de temps. Si vous avez scrupuleusement respecté les étapes indiquées, cela fonctionne. Par contre, si vous avez eu le malheur de changer le nom de l' »Asset » du certificat, le portail n’est pas d’accord. Il vous indique que votre configuration est incomplète. Pour le portail, c’est clairement le certificat qui est en cause.

clip_image013

 

C’est un problème lié au portail, ce qui n’empêche pas le fonctionnement des Runbooks. Le problème a été remonté chez Microsoft car si la configuration est considérée comme incomplète, le portail Azure ne nous permet pas de renouveler notre certificat.

 

Consommer l’identité dans un Runbook

Créer une identité, c’est bien, la consommer c’est mieux. J’ai fait au plus court avec un Runbook PowerShell des familles :

[OutputType([String])]

$connectionName = « AzureRunAsConnection »

try

{

# Get the connection « AzureRunAsConnection « 

$servicePrincipalConnection=Get-AutomationConnection -Name $connectionName

« Logging in to Azure… »

Add-AzureRmAccount `

-ServicePrincipal `

-TenantId $servicePrincipalConnection.TenantId `

-ApplicationId $servicePrincipalConnection.ApplicationId `

-CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint

« Logged to Azure »

}

catch {

if (!$servicePrincipalConnection)

{

$ErrorMessage = « Connection $connectionName not found. »

throw $ErrorMessage

} else{

Write-Error -Message $_.Exception

throw $_.Exception

}

}

Get-ChildItem cert:\currentuser\my

 

Lors de l’exécution, vous allez découvrir que le certificat utilisé est dans le magasin de l’utilisateur et non de l’ordinateur :

clip_image014

 

Voilà tout ce qui se cache derrière le portail Azure lors de la création d’une instance du service Azure Automation en mode « Click Next Finish ».

 

BenoitS – Simple and Secure by design but Business compliant (with disruptive flag enabled)

Des certificats gratuits avec Let’s Encrypt dans IIS

Ça fait longtemps que j’avais ce billet dans les cartons, en fait depuis ma série de billets sur Let’s Encrypt avec les WebApp (plus d’un an déjà). Je l’utilise depuis longtemps dans mes machines virtuelles (tant qu’elles sont joignables sur Internet avec un nom public).

Let’s Encrypt, ce n’est pas tout jeune mais c’est toujours pas un citoyen de première classe dans IIS. La solution ne dispose pas d’un client natif, il faut en passer par un client tiers. Personnellement, je suis resté sur le premier que j’ai trouvé à l’époque : LetsEncrypt-Win-Simple, disponible sur Github. Comme MVP, j’ai beau avoir accès à des solutions gratuites comme Digicert mais je suis resté sur Let’s Encrypt, allez savoir, …

Avant de commencer, quelques rappels avec les limitations de Let’s Encrypt disponibles ici. Ceci posé, on va retenir la contrainte suivante :

The main limit is Certificates per Registered Domain (20 per week). A registered domain is, generally speaking, the part of the domain you purchased from your domain name registrar. For instance, in the name www.example.com, the registered domain is example.com. In new.blog.example.co.uk, the registered domain is example.co.uk. We use the Public Suffix List to calculate the registered domain.

Dans le contexte d’Azure, ça prend toute son importance car par défaut, mes machines virtuelles partagent toutes un même domaine DNS public : Azure.com. Si on tente d’utiliser Let’s Encrypt dans ces conditions, on arrive à la situation ci-dessous :

clip_image001

 

Ce point résolu, on sait maintenant il nous faut un nom DNS distinct. J’ai donc créé un enregistrement DNS de type CNAME dans mon domaine DNS public, pointant sur le FQDN public de ma machine virtuelle.

clip_image002

 

Dans ma machine virtuelle, j’ai choisi la simplicité en installant IIS. C’est la méthode la plus simple pour gérer un certificat. Il suffit juste d’ajouter un hostname à un site web existant. Je référence ici l’enregistrement DNS CNAME enregistré dans mon domaine public.

clip_image003

 

Le client Let’s Encrypt utilisé (LetsEncrypt-Win-Simple) détecte automatiquement le FQDN précédemment référencé dans IIS.

clip_image004

 

On notera que le certificat généré est bien exportable. C’est bien pratique.

clip_image005

 

La particularité des certificats délivrés par Let’s Encrypt, c’est leur durée de vie à 90 jours. Pour moi, ce n’est pas une contrainte vue la durée de vie de mes maquettes. Le client LetsEncrypt-Win-Simple propose tout de même de mettre en place une tâche planifiée qui se chargera du renouvellement à votre place, nice to have.

clip_image006

 

Les informations collectées (compte disposant du niveau de privilège administrateur local) sont nécessaires pour mettre en place une tâche planifiée pour gérer le renouvellement automatique de notre certificat tous les trois mois.

clip_image007

 

Dans IIS, on constate bien la présence d’un nouveau binding pour du SSL.

clip_image008

 

Petite subtilité, le certificat n’a pas été placé dans le magasin « LocalMachine\My » mais dans le magasin « LocalMachine\WebHosting ». C’est un peu différent mais c’est très utile quand on a une ferme de serveurs web devant partager le même certificat.

clip_image009

 

Au passage, on remarquera la durée de vie de trois mois. C’est tout pour let’s Encrypt. En même temps, ça respecte ma formule magique : Simple and secure by design but Business compliant.

 

BenoitS – Simple and Secure by design but Business compliant (with disruptive flag enabled)