Archives de catégorie : Managed Service Identity

Managed Service Identity pour machines virtuelles Windows

Managed Service Identity est une réponse élégante au problème de la sécurité du contexte d’exécution de code pour des services comme Virtual Machines, Web App et Azure Functions pour ne citer qu’eux (pas encore disponible pour d’autres ressources providers pendant la phase de preview). Pour les machines virtuelles, c’est intéressant car cela va nous permettre de ne plus stocker de credentials directement dans la machine virtuelle pour exécuter du code. En fait, Managed Service Identity aurait pu trouver sa place dans un billet de 2016 : Cacher les mots de passe dans les scripts PowerShell.

Dans le contexte d’une machine virtuelle Azure, Managed Services Identity propose une API accessible localement uniquement. Un secret local sera présenté à cette API qui va ensuite nous permettre de récupérer un « Access Token » que nous allons pouvoir consommer pour accéder à différentes ressources dans Azure.

A ce jour, Managed Service Identity est en Preview. La fonctionnalité s’active dans le portail, au niveau du blade « Configuration » d’une machine virtuelle. Activer cette fonctionnalité va réaliser plusieurs opérations que nous allons tenter de détailler dans ce billet.

clip_image001

 

La partie la plus visible de Managed Service Identity, c’est la présence d’une VM Extension au sein de notre machine virtuelle. Voilà pour la surface des choses.

clip_image002

 

Pourtant, on ne parle pas encore d’identité. En fait Managed Service Identity a déjà fait le job (pour peu qu’on dispose des privilèges Azure AD). Si on explore un peu notre Azure AD, on constatera la présence d’un nouveau Service Principal au nom de notre machine virtuelle :

Get-AzureRMADServicePrincipal | Where {$_.displayname -eq « <Nom machine virtuelle> »} | Fl -Property *

clip_image003

 

Sujet intéressant, on peut constater que ce Service Principal utilise un certificat auto-signé comme méthode d’authentification. A ce stade, cela me pose un problème. Pour utiliser Managed Service Identity, il faut impérativement avoir le rôle d’administration le plus élevé dans Azure AD. Il n’est pas encore possible d’utiliser un Service Principal existant. Ce Service Principal, on peut l’utiliser pour positionner des permissions. Dans l’illustration ci-dessous, j’associe le rôle « Virtual Machine Contributor » pour permettre à la machine virtuelle de gérer les ressources de type machine virtuelle dans le groupe de ressources « TESTVSIVM ».

clip_image004

 

Maintenant, allons voir ce qui se passe au sein de la machine virtuelle. S’il y a une VM Extension installée, on doit en trouver la trace. En fait, on trouve même mieux que cela avec la configuration de l’API présente au sein de notre machine virtuelle. La VM Extension, c’est une API qui écoute en localhost sur le port 50432 (par défaut). D’un point de vue sécurité, c’est parfait.

clip_image005

 

En creusant un peu dans le même répertoire, on va mettre la main sur le Service Principal à consommer par la VM Extension :

[xml]$xmldocument = Get-Content C:\Packages\Plugins\Microsoft.ManagedIdentity.ManagedIdentityExtensionForWindows\1.0.0.10\RuntimeSettings\20180211172356Z.xml

$xmldocument.RDConfig.SecureIdentity

clip_image006

 

On connait l’identité, reste maintenant, le certificat associé au Service Principal. Logiquement, on le trouve dans le magasin personnel de d’ordinateur : Get-ChildItem Cert:\LocalMachine\My

clip_image007

 

Ne cherchez pas, la clé privée du certificat n’est pas exportable. Pour la suite, il faut prendre soin de disposer d’une version d’Azure PowerShell datant d’au moins Janvier 2018

clip_image008

 

Avant de pouvoir nous authentifier, nous devons demander un jeton d’accès auprès de l’API. C’est ce jeton d’accès que nous allons utiliser pour nous authentifier avec la commande Login-AzureRmAccount en spécifiant :

  • L’utilisation d’un AccessToken
  • L’identité que nous utilisons (‘MSI@50342’ par défaut

$response = Invoke-WebRequest -Uri http://localhost:50342/oauth2/token -Method GET -Body @{resource= »https://management.azure.com/ »} -Headers @{Metadata= »true »}

$content =$response.Content | ConvertFrom-Json

$access_token = $content.access_token

Login-AzureRmAccount -AccessToken $access_token -AccountId « MSI@50342 »

Get-AzureRmResourceGroup

Get-AzureRmVM

clip_image009

 

L’authentification Azure est bien fonctionnelle et nous sommes bien capables d’accéder aux ressources auxquelles le Service Principal a été accrédité.

Bonus

Allons plus loin avec un KeyVault, en accordant au Service Principal la possibilité d’accéder à des secrets (Get et List pour les permissions).

clip_image010

 

Pour vérifier, voilà le secret à révéler dans le coffre-fort numérique.

clip_image011

 

$response = Invoke-WebRequest -Uri http://localhost:50342/oauth2/token -Method GET -Body @{resource= »https://vault.azure.net »} -Headers @{Metadata= »true »}

$content = $response.Content | ConvertFrom-Json

$KeyVaultToken = $content.access_token

$return = (Invoke-WebRequest -Uri -Method GET -Headers @{Authorization= »Bearer $KeyVaultToken »}).content

$return

($return | convertfrom-json).value

clip_image012

 

Conclusion

Au moment de l’écriture de ce billet, la fonctionnalité Managed Service Identity est encore en preview et ne concerne pas encore tous les services. La seule contrainte identifiée à ce jour, c’est l’impossibilité de spécifier le Service Principal à utiliser. Sinon, c’est un excellent moyen pour ne plus code ses credentials dans le code de ses machines virtuelles.

 

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)