How to add Azure Key Vault policies for MSI-enabled VMs from Azure CLI or PowerShell

Managed Service Identity (MSI) is giving Azure services an automatically managed identity in Azure Active Directory. We can use this identity to authenticate to any service that supports Azure AD authentication, including Key Vault, without having any credentials in our code.

How does it work?

MSI VM example

  1. Azure Resource Manager receives a message to enable MSI on a VM.
  2. Azure Resource Manager creates a Service Principal in Azure AD to represent the identity of the VM. The Service Principal is created in the Azure AD tenant that is trusted by this subscription.
  3. Azure Resource Manager configures the Service Principal details in the MSI VM Extension of the VM. This step includes configuring client's ID and certificate used by the extension to get access tokens from Azure AD.
  4. Now that the Service Principal identity of the VM is known, it can be granted access to Azure resources. For example, if our code needs to call Azure Resource Manager, then we should assign the VM’s Service Principal the appropriate role using Role-Based Access Control (RBAC) in Azure AD. If our code needs to call Key Vault, then you should grant your code access to the specific secret or key in Key Vault.
  5. Our code running on the VM requests a token from a local endpoint that is hosted by the MSI VM extension: http://localhost:50342/oauth2/token. The resource parameter specifies the service to which the token is sent. For example, if we want our code to authenticate to Azure Resource Manager, you would use resource=https://management.azure.com/.
  6. The MSI VM Extension uses its configured client ID and certificate to request an access token from Azure AD. Azure AD returns a JSON Web Token (JWT) access token.
  7. Our code sends the access token on a call to a service that supports Azure AD authentication.

Key Vault policies

The solution described above (highlighted part) works out of box but requires a manual step (in Azure Portal). When we enable MSI for a VM and we want it to call Key Vault using MSI, we also need to add an Access Policy for VM's managed identity. It's quick and easy in Azure Portal. We just need to search for VM's principal (VM's identity) on the principals list:

Access Policy creation for MSI

But if you want to do the same from Azure CLI, PowerShell or directly with REST API it is not so easy because of... lack of documentation.
Here we can find a documentation page where Windows VM scenario is described. A Linux VM scenario is available here. Both describes how to create an Access Policy for VM principal using Azure Portal. There is no information in the documentation about how to do it with Azure CLI, PowerShell or REST API.

It looks simple, so let's try with Access Policy creation with Azure CLI and PowerShell. For CLI it will be az keyvault set-policy command:

AZ Key Vault Policy Set

For PowerShell we will use Set-AzureRmKeyVaultAccessPolicy

Set-AzureRmKeyVaultAccessPolicy

Set-AzureRmKeyVaultAccessPolicy

In both tools we have the same set of parameters we need to provide. I will focus on Azure CLI.

There is one required parameter - key vault name - and of course it is not a problem. We are also able to provide resource group with no problem. The question is what to provide as a principal that will receive permissions?

We have three options:

  • --object-id: A GUID that identifies the principal that will receive permissions.
  • --spn: Name of a service principal that will receive permissions.
  • --upn: Name of a user principal that will receive permissions.

First thought is a --spn option. MSI is creating service principal for the VM, so it should be simple as list SPs in Azure AD tenant and pick one created for the VM. But if we will list SPs on the tenant, we will notice that VMs MSI SPs are not listed:

Service Prinicpals list

Key Vault REST API

Let's check what do we need to create the Access Policy: Key Vault REST API Reference

A set of methods we need to check is "Vaults - Create Or Update". Find AccessPolicyEntry definition and you will get the set of parameters needed to create an Access Policy.

AccessPolicyEntry is an identity that have access to the key vault. All identities in the array must use the same tenant ID as the key vault's tenant ID.

applicationId - Application ID of the client making request on behalf of a principal

objectId - The object ID of a user, service principal or security group in the Azure Active Directory tenant for the vault. The object ID must be unique for the list of access policies.

permissions - Permissions the identity has for keys, secrets and certificates.

tenantId - The Azure Active Directory tenant ID that should be used for authenticating requests to the key vault.

As you can see, all we need is the object ID of a user, service principal or security group in the Azure Active Directory tenant for the vault. Azure CLI or PowerShell parameters for upn or sun is just translating to objectId.

So, how to get an objectId of the VM principal in Azure AD?

Solution

We have two options. First one is to list all Service Principals in the tenant using CLI, PowerShell or REST API (not Azure Portal). My example VM's name with MSI enabled is dsctest. We need to query the output of SP list command for displayName equal to our VM's name:

az ad sp list --query "[?displayName == 'dsctest']"

We will get an SP object:

[
  {
    "appId": "pppppppp-pppp-pppp-pppp-pppppppppppp",
    "displayName": "dsctest",
    "objectId": "iiiiiiii-iiii-iiii-iiii-iiiiiiiiiiii",
    "objectType": "ServicePrincipal",
    "servicePrincipalNames": [
      "pppppppp-pppp-pppp-pppp-pppppppppppp",
      "https://identity.azure.net/cpDjQ7EyGS0Jn/NiUaR6SIhEmDGLbLHmmDoiPcdR4xA="
    ]
  }
]

objectId's value is what we need:

az ad sp list --query "[?displayName == 'dsctest'].objectId" --output tsv

the output will be a string:

iiiiiiii-iiii-iiii-iiii-iiiiiiiiiiii

This solution works, but large list filtering is needed. The second solution is less complicated and let say... prettier. Let's get a detailed information about our VM:

az vm show -d -g dsctest -n dsctest

The output will be a large JSON describing our VM. At the beginning of this object we will find an identity part:

"identity": {
    "principalId": "iiiiiiii-iiii-iiii-iiii-iiiiiiiiiiii",
    "tenantId": "tttttttt-tttt-tttt-tttt-tttttttttttt",
    "type": "SystemAssigned"
 }

Of course it is the same objectId as in the first example. And of course what we need is just a value of the principalId key:

az vm show -d -g dsctest -n dsctest --query "identity.principalId" --output tsv

How to

Let's create an Access Policy for our MSi-enabled VM with List and Get permissions for secrets:

az keyvault set-policy -n dsctest -g dsctest --object-id \`az vm show -d -g dsctest -n dsctest --query "identity.principalId" --output tsv\` --secret-permissions list get
comments powered by Disqus