This article is contributed. See the original author and article here.

One of the most common challenge faced by developers while maintaining source code on Azure resources is: how to store and retrieve the secrets without having to save any credentials whatsoever. Moreover, this is strictly discouraged in accordance with Defence-in-depth approach.


 


Managed identities on Azure solve this challenge by assigning service principals to the identities on Azure AD. 


 


To understand how it works, let’s build a setup with Ubuntu VM running on Azure, Key Vault to fetch secrets, and Azure AAD to register the VM as a managed identity.


For brevity, I have already spun up a Ubuntu 18.04 VM.


1. Let’s create a AKV account and save dummy credentials on it, which needs to be fetched by application running on VM.


 

PS C:WINDOWSsystem32> New-AzKeyVault -Name "dummy-keyvault" -ResourceGroupName "ManagedIdentityLab" -Location "East US2"


Vault Name                          : dummy-keyvault
Resource Group Name                 : ManagedIdentityLab
Location                            : East US2
Resource ID                         : /subscriptions/<redacted>/resourceGroups/ManagedIdentityLab/providers/Microsoft.KeyVault/vaults/dummy-keyvault
Vault URI                           : https://dummy-keyvault.vault.azure.net/

PS C:WINDOWSsystem32> $secretvalue = ConvertTo-SecureString 'abc@123' -AsPlainText -Force
PS C:WINDOWSsystem32> $secret = Set-AzKeyVaultSecret -VaultName 'dummy-keyvault' -Name 'ExamplePassword' -SecretValue $secretvalue

 


 


2. Now, let’s assign a system-assigned identity to the ubuntu VM, so that applications running on it will use this identity to access other Azure resources like AKV.


 


 

PS C:WINDOWSsystem32> $vm = Get-AzVM -ResourceGroupName ManagedIdentityLab -Name  vm-running-app

PS C:WINDOWSsystem32> Update-AzVM -ResourceGroupName ManagedIdentityLab -VM $vm -IdentityType SystemAssigned

RequestId IsSuccessStatusCode StatusCode ReasonPhrase
--------- ------------------- ---------- ------------
                         True         OK OK
PS C:WINDOWSsystem32> $spID = (Get-AzVM -ResourceGroupName ManagedIdentityLab  -Name vm-running-app).identity.principalid
PS C:WINDOWSsystem32> $spID
f9733206-1fcb-42c9-abc9-a75d1b68d52b

 


 


3. Now we can assign this principal ID Reader access to Azure Key Vault we created in first step. We do this by assigning RBAC role: Reader to the principal ID assigned to the VM in step 2.


 


 

PS C:WINDOWSsystem32> New-AzRoleAssignment -ObjectId $spID -RoleDefinitionName "Reader" -Scope /subscriptions/<redacted>/resourceGroups/ManagedIdentityLab/providers/Microsoft.KeyVault/vaults/dummy-keyvault


RoleAssignmentId   : /subscriptions/<redacted>/resourceGroups/ManagedIdentityLab/providers/Microsoft.KeyVault/vaults/dummy-keyvault/providers/Microsoft.Authorization/roleAssignments/780e1100-f4a0-44a8-a8ee-79c5
                     588c3e17
Scope              : /subscriptions/<redacted>/resourceGroups/ManagedIdentityLab/providers/Microsoft.KeyVault/vaults/dummy-keyvault
DisplayName        : vm-running-app
SignInName         :
RoleDefinitionName : Reader
RoleDefinitionId   : acdd72a7-3385-48ef-bd32-f606fba81ae7
ObjectId           : f9733206-1fcb-42c9-abc9-a75d1b68d52b
ObjectType         : ServicePrincipal
CanDelegate        : False
Description        :
ConditionVersion   :
Condition          :

PS C:WINDOWSsystem32>

 


 


 


4. We are now all set to access the keyvault from the python3 interactive shell on ubuntu VM


 


 


 

>>> from azure.identity import ManagedIdentityCredential
>>> credentials=ManagedIdentityCredential()
>>> import os
>>> import cmd
>>> keyVaultName = os.environ["KEY_VAULT_NAME"]
>>> KVUri = f"https://{keyVaultName}.vault.azure.net"
>>> from azure.keyvault.secrets import SecretClient
>>> client = SecretClient(vault_url=KVUri, credential=credentials)
>>> retrieved_secret = client.get_secret("ExamplePassword")
>>>
>>> retrieved_secret
<KeyVaultSecret [https://dummy-keyvault.vault.azure.net/secrets/ExamplePassword/4d1ef64abc4d4c2e8741df66ee0f0065]>
>>> retrieved_secret.
retrieved_secret.id          retrieved_secret.name        retrieved_secret.properties  retrieved_secret.value
>>> retrieved_secret.value
'abc@123'
>>>

 


 


 


Voila! We can fetch the secret from the AKV without having to store the secret anywhere. The class ManagedIdentityCredential in azure-identity package fetches the VM’s managed identity client ID.


 


The magic that happens behind the scene is described below:

1. As soon as the client.get_secret method is called to fetch secret from AKV, an http request is made to the AKV url. The response back is http 401 error, since the request header does not have a valid token.


 


 

INFO:azure.core.pipeline.policies.http_logging_policy:Request URL: 'https://dummy-keyvault.vault.azure.net/secrets/ExamplePassword/?api-version=REDACTED'
INFO:azure.core.pipeline.policies.http_logging_policy:Request method: 'GET'
INFO:azure.core.pipeline.policies.http_logging_policy:Response status: 401

 


 


2. Post this failure, another request is made to the AAD to get the token for the managed identity associated with the ubuntu VM. The response returned back is the access token :


 


 

INFO:azure.core.pipeline.policies.http_logging_policy:Request URL: 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=REDACTED&resource=REDACTED'
INFO:azure.core.pipeline.policies.http_logging_policy:Request method: 'GET'
INFO:azure.core.pipeline.policies.http_logging_policy:Response status: 200
INFO:azure.core.pipeline.policies.http_logging_policy:Response headers:

    "response": {
        "access_token": "********",
        "client_id": "1699a4a0-2f2a-4696-b483-dd32a950349a",
        "expires_in": "85295",
        "expires_on": 1610038816,
        "ext_expires_in": "86399",
        "not_before": "1609952116",
        "resource": "https://vault.azure.net",
        "token_type": "Bearer"
    },
    "scope": [
        "https://vault.azure.net/.default"
    ]
}

 


 


3. Finally, the access token retrieved from step 2 is used in the header to reattempt step 1. When AKV receives the request this time, it validates the access token against AAD. Since the access token is valid, the request is allowed to read the secret and we get a http 200 response back.


 


 

INFO:azure.identity._internal.decorators:ManagedIdentityCredential.get_token succeeded
INFO:azure.core.pipeline.policies.http_logging_policy:Request URL: 'https://dummy-keyvault.vault.azure.net/secrets/ExamplePassword/?api-version=REDACTED'
INFO:azure.core.pipeline.policies.http_logging_policy:Request method: 'GET'
INFO:azure.core.pipeline.policies.http_logging_policy:Request headers:
INFO:azure.core.pipeline.policies.http_logging_policy:Response status: 200

 


 


To summarize, managed identity allows the apps running on Azure resources to authenticate against AAD without having to store credentials. This is super convenient and compliant, also eliminates the need to rotate the client secret upon compromise or expiration.


 

Brought to you by Dr. Ware, Microsoft Office 365 Silver Partner, Charleston SC.