Archives de catégorie : Azure AD

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

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)