Key Vault for Azure virtual machines extension

On November 14th Microsoft announced the general availability of the Azure Key Vault Virtual Machine extension. This extension makes it easier for applications running on virtual machines to use certificates from a key vault, it achieves this by abstracting away some of the more tedious tasks. It has also been build with some of Microsoft’s best practices regarding authentication, network error handling, caching, periodic refreshes of certificate from the key vault, and binding the certificate for Transport Layer Security (TLS).

This is great for when you want to take advantage of the capabilities that Key Vault offers you out of the box, along with all the technicalities that comes with fetching tokens or writing a whole slew of Azure Automation scripts to make it all work together.

Operating System requirements

The Key Vault VM extension supports the following versions of Windows:

  • Windows Server 2019
  • Windows Server 2016
  • Windows Server 2012

Let’s not forget that Linux is supported as well. The extension works with these Linux distributions, though take into account that that RHEL is missing:

  • Ubuntu 16.04
  • Ubuntu 18.04
  • Debian 9
  • Suse 15

Deploying the extension solely through a Powershell script

The official documentation does an excellent job explaining how you should deploy the extension by using an ARM template so I will not cover it here, instead we will do it entirely through Powershell.

Script dependencies

You will need the Azure Powershell.

If you are macOS or Linux user, running Powershell 6.2+ as I am, you might want to install the following module through the Powershell gallery. If you’re on Windows you can optionally skip this step if you want to run the script on Powershell 5.1, though if you want to run it through Powershell Core you will still need to install the following module.

*Note that this module will not do you much good if you try to execute the extension script inside of a Docker container running Powershell Core.

78
Install-Module -Name Microsoft.PowerShell.GraphicalTools

Script walk-through

Let’s start by login in to your Azure tenant, afterwards you will be prompted to choose a subscript and subsequently a resource group.

Connect-AzAccount
Get-AzSubscription | Out-GridView -PassThru | Select-AzSubscription
$rg = Get-AzResourceGroup | Out-GridView -PassThru

Next up we will select a key vault, which should reside inside the resource group you just selected. Afterwards you will select a single certificate, from which we will create a vault secret id.

 5
 6
 7
 8
 9
10
11
#Get Certificate Secret
$vault = Get-AzKeyVault -ResourceGroupName $rg.ResourceGroupName | Out-GridView -PassThru
$vault = Get-AzKeyVault -VaultName $vault.VaultName
$certificate = Get-AzKeyVaultCertificate -VaultName $vault.VaultName  | Out-GridView -PassThru
$certificate = Get-AzKeyVaultCertificate -VaultName $vault.VaultName  -Name $certificate.Name
$vaultSecret = Get-AzKeyVaultSecret -VaultName $vault.VaultName -Name $certificate.Name
$vaultSecretId = $vaultSecret.Id.Substring(0,$vaultSecret.Id.LastIndexOf('/'))

To make it so our extension is able to get access tokens to use with certain Azure Key Vault we will need to enable a system assigned managed identity.

A managed identity is a essentially an object in the resource its subscription, its associated Azure Active Directory. The object represents an Azure resource, this allows for the VM to authenticate without having to store credentials on the machine. You are basically trusting this specific Azure VM to access certain Azure resources. A system-assigned managed identity is enabled directly on a single Azure service instance.

13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#Get Azure VM
$vm = Get-AzVM -Status -ResourceGroupName $rg.ResourceGroupName | Out-GridView -PassThru

#Check for Azure Managed Identity
If ($null -eq $vm.Identity)
{
    Write-Host ("WARNING: Managed Identity is not enabled yet for the VM '{0}'" -f $vm.Name) -ForegroundColor Yellow
    Write-Host ("This is required to enable this feature!") -ForegroundColor Yellow
    Write-Host ("Do you want to enable it now?") -ForegroundColor Yellow
    do
    {
        $confirmation = Read-Host -Prompt "Enable? (Y/N)"
    } while ($confirmation -ne "y" -and $confirmation -ne "n")

    if ($confirmation -eq "n")
    {
        Write-Host -ForegroundColor Yellow "Aborting..."
        return
    }

    Update-AzVM -ResourceGroupName $vm.ResourceGroupName -VM $vm -AssignIdentity:$SystemAssigned

    $vm = Get-AzVM -ResourceGroupName $rg.ResourceGroupName -Name $vm.Name
}

Great, we’ve got our managed identity provisioned. All that’s left for us to do now is to set an Key Vault Access Policy that will allow our VM, with its Key Vault extension, to call in to our Key Vault of choice.

Since the managed identity is set to the Azure VM itself any process will be able to call into the Key Vault as long as it knows the URL to the Key Vault, needless to say that we want to apply the principle of least privilege here. We will allow our managed identity to perform get and list operations on keys and secrets.

48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#Get Vault again for latest Access Policies
$vault = Get-AzKeyVault -VaultName $vault.VaultName

#Check Permissions details in Keyvault
$policies = $vault.AccessPolicies | Where-Object {$_.ObjectId -eq $managedId.Id}
$keyPermissions = $policies.PermissionsToKeys.ToArray()
$secretPermissions = $policies.PermissionsToSecrets.ToArray()
#Check list-permission for Keys
if (!($keyPermissions -icontains "list"))
{
    $keyPermissions += "list"
}
#Check get-permission for Keys
if (!($keyPermissions -icontains "get"))
{
    $keyPermissions += "get"
}
#Check get-permission for Secrets
if (!($secretPermissions -icontains "list"))
{
    $secretPermissions += "list"
}
#Check get-permission for Secrets
if (!($secretPermissions -icontains "get"))
{
    $secretPermissions += "get"
}
#Update Permissions
Set-AzKeyVaultAccessPolicy -VaultName $vault.VaultName -ObjectId $managedId.Id -PermissionsToKeys $keyPermissions -PermissionsToSecrets $secretPermissions

To provision the Key Vault Extension for Windows you can simply set the following settings and pass it off to the Azure Resource Manager. You may remember our $vaultSecretId from earlier, we have to pass it along in the settings in order to have the extension monitor it for changes. Feel free to adjust the settings to whatever suits you.

Be careful to pass in a string as a value for the pollingIntervalInS, a number will not do the job.

78
79
80
81
82
83
84
85
86
87
88
$KeyVaultForWindowsSettings = @{
    "secretsManagementSettings" = @{
        "pollingIntervalInS" = "3600"
        "certificateStoreName" = "MY"
        "certificateStoreLocation" = "LocalMachine" # LocalMachine or CurrentUser (case sensitive)
        "observedCertificates" = @($vaultSecretId)
    }
}
Set-AzVMExtension -ResourceGroupName $vm.ResourceGroupName -Location $vm.Location -VMName $vm.Name `
    -Name "KeyVaultForWindows" -Type "KeyVaultForWindows" `
    -Publisher "Microsoft.Azure.KeyVault.Edp" -TypeHandlerVersion "1.0" -Settings $KeyVaultForWindowsSettings

As for Linux, you will only need to alter the script ever so slightly.

Again, pass in a string as a value for the pollingIntervalInS and not a number.

78
79
80
81
82
83
84
85
86
87
88
$KeyVaultForLinuxSettings = @{
    "secretsManagementSettings" = @{
        "pollingIntervalInS" = "3600"
        # "certificateStoreName" = "" # This property is ignored on Linux
        "certificateStoreLocation" = "/var/lib/waagent/Microsoft.Azure.KeyVault" # Default path is "/var/lib/waagent/Microsoft.Azure.KeyVault"
        "observedCertificates" = @($vaultSecretId)
    }
}
Set-AzVMExtension -ResourceGroupName $vm.ResourceGroupName -Location $vm.Location -VMName $vm.Name `
    -Name "KeyVaultForLinux" -Type "KeyVaultForLinux" `
    -Publisher "Microsoft.Azure.KeyVault.Edp" -TypeHandlerVersion "1.0" -Settings $KeyVaultForLinuxSettings

And with that said I hope the Key Vault for Azure virtual machines extension becomes just a little less daunting for you.