Deploy Certificates to VMs from customer-managed Key Vault

[Updated: 2015/09/01 - Fixed 'Updating Certificates' section; added Update-AzureVM command that was missing]
[Updated: 2016/09/14 - This post is now obsolete. It's here for posterity. Please refer to the updated post.]

Hello,

As promised in our last blog we are back again with a blog about deploying certificates to VMs. Amit explains how you can reduce the risk to your certificates by eliminating the need to embed them in VM images or application payload. The certificates are stored in the key vault and only injected into your VM when the VM is deployed. ....

As usual, we value your input so please take a moment to follow us, join our advisory boardsend us private feedback,and/or visit our forum .   

---

Hello, this is Amit. Consider this scenario: You have a cloud application running in a VM in Azure. This application needs a certificate, it could be for SSL for a website, for signing, or for authenticating (to Azure AD for example). How do you get this certificate into this VM today?

  • Embedded in the VM image, which means you have to rebuild the images when you update your certificates
  • bundled with the application package that gets deployed when the VM is created, which means those certificates also end up in some source code control system, and are accessible by a lot more people
  • some script running at startup provisions it by accessing it from a storage account, which means you still need to store the credentials to this storage account with the script

These certificates are of high value and if in wrong hands it could compromise your application's security or security of your data. All the three methods described above can leave your certificate vulnerable to exposure. How can we make sure that the certificate stays safe until it is injected in the VM, when the VM gets created? You can with Azure Key Vault! Read on...

With Azure VMs deployed through Azure Resource Manager you can now store such certificates in Azure Key Vault and then Azure (Microsoft.Compute resource provider to be specific) will push them into your VMs when the VMs are deployed. Certificates can be used in many scenarios: SSL, encryption, certificate based authentication are just some examples.

By using this method, you can keep the certificate safe. It's now not in the VM image, or in the applications configuration files or some other unsafe locations. By setting appropriate access policy for the key vault you can also control who gets access to your certificate. Another benefit is that you can manage all your certificates in one place in Azure Key Vault.

Here is a quick overview of the process

  • You need a certificate in .PFX format
  • Create a Key Vault (either using template, or use the simple script)
  • Make sure you have turned on the EnabledForDeployment switch
  • Upload the certificate as a secret (see the sample script)
  • Now grab the template, provide all the parameter values and then deploy the template

When you hit deploy, the VM is created, Azure (Microsoft.Compute resource provider) gets the secret stored in the Key Vault and places it in the specified Certificate Store. The EnabledForDeployment flag explicitly gives Azure permission to use the certificates stored as secrets for this deployment. Now applications can access this certificate from the specified Certificate Store.

Deploying VMs

As you know, there are always many ways to do the same thing. Here I'll describe one method in detail and also mention alternative methods where they exist.

Here's an example script, that creates a key vault, and then stores a certificate stored in the .pfx file in a local directory, to the Key Vault as a secret.

$vaultName = "contosovault"$resourceGroup = "contosovaultrg"$location = "eastus"$secretName = "servicecert"$fileName = "keyvault.pfx"$certPassword = "abcd1234"# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=$fileContentBytes = get-content $fileName -Encoding Byte$fileContentEncoded = [System.Convert]::ToBase64String($fileContentBytes)$jsonObject = @"{"data": "$filecontentencoded","dataType" :"pfx","password": "$certPassword"}"@$jsonObjectBytes = [System.Text.Encoding]::UTF8.GetBytes($jsonObject)$jsonEncoded = [System.Convert]::ToBase64String($jsonObjectBytes)Switch-AzureMode -Name AzureResourceManagerNew-AzureResourceGroup -Name $resourceGroup -Location $locationNew-AzureKeyVault -VaultName $vaultName -ResourceGroupName $resourceGroup -Location $location -sku standard -EnabledForDeployment$secret = ConvertTo-SecureString -String $jsonEncoded -AsPlainText -ForceSet-AzureKeyVaultSecret -VaultName $vaultName -Name $secretName -SecretValue $secret 

The first part of the script reads the .pfx file and then stores it as a JSON object with the file content base64 encoded. Then the JSON object is also base64 encoded.

Next it creates a new resource group and then create a key vault. Note the last parameter to the New-AzureKeyVault command, '-EnabledForDeployment', which grants access to Azure (Microsoft.Compute resource provider, if you want to be very specific) to read secrets from the Key Vault for deployments.

The last command simply stores the base64 encoded JSON object in the the Key Vault as a secret.

Here's sample output from the above script

VERBOSE: 12:43:16 PM - Created resource group 'contosovaultrg' in location'eastus'ResourceGroupName : contosovaultrgLocation          : eastusProvisioningState : SucceededTags              :Permissions       :Actions NotActions======= ==========*ResourceId        : /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-dd149b4aeb56/resourceGroups/contosovaultrgVaultUri             : https://contosovault.vault.azure.netTenantId             : xxxxxxxx-xxxx-xxxx-xxxx-2d7cd011db47TenantName           : xxxxxxxxSku                  : standardEnabledForDeployment : TrueAccessPolicies       : {xxxxxxxx-xxxx-xxxx-xxxx-2d7cd011db47}AccessPoliciesText   :Tenant ID              :xxxxxxxx-xxxx-xxxx-xxxx-2d7cd011db47Object ID              :xxxxxxxx-xxxx-xxxx-xxxx-b092cebf0c80Application ID         :Display Name           : Derick Developer(derick@contoso.com)Permissions to Keys    : get, create, delete,list, update, import, backup, restorePermissions to Secrets : allOriginalVault        : Microsoft.Azure.Management.KeyVault.VaultResourceId           : /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-dd149b4aeb56/resourceGroups/contosovaultrg/providers/Microsoft.KeyVault/vaults/contosovaultVaultName            : contosovaultResourceGroupName    : contosovaultrgLocation             : eastusTags                 : {}TagsTable            :SecretValue     : System.Security.SecureStringSecretValueText : ew0KImRhdGEiOiAiTUlJSkN3SUJBekNDQ01jR0NTcUdTSWIzRFFFSEFhQ0NDTGdFZ2dpME1JSUlzRENDQmdnR0NTcUdTSWIzRFFFSEFhQ0NCZmtFZ2dYMU1JSUY4VENDQmUwR0N5cUdTSWIzRFFFTUNnRUNvSUlFL2pDQ0JQ<<< Output truncated... >>>DQp9Attributes      : Microsoft.Azure.Commands.KeyVault.Models.SecretAttributesVaultName       : contosovaultName            : servicecertVersion         : e3391a126b65414f93f6f9806743a1f7Id              : https://contosovault.vault.azure.net:443/secrets/servicecert                  /e3391a126b65414f93f6f9806743a1f7

Now we are ready to deploy a VM template. Note down the URI of the secret from the output (as highlighted above in green).

You'll need a template located here. You can directly hit the 'Deploy to Azure' button at the top or deploy from Azure PowerShell using the instructions on that page. You'll need to supply values for all the parameters as noted on that page. The parameters of special interest (besides the usual VM parameters) are the Vault Name, Vault Resource Group and the Secret URI (highlighted in green above). And of course you can also download it from GitHub and modify as needed.

When this VM is deployed, Azure will inject the certificate into the VM. On Windows, certificates in PFX file are added with the private key not exportable. The certificate is added to the LocalMachine certificate location, with the certificate store that the user provided. On Linux, the certificate file is placed under the /var/lib/waagent directory, with the file name <UppercaseThumbprint>.crt for the X509 certificate file and <UppercaseThumbpring>.prv for private key. Both of these files are .pem formatted. See Working with PEM files for more information.

The application usually find the certificate using the Thumbprint and doesn't need modification.

Updating certificates

Six months later (or whatever you've set as your certificate's lifetime), you need to create new certificates and push them to the VMs. But this time, the VMs are already running. Hence we need to push the certificates into existing VMs too. First you need to get a new certificate, store it in the key vault (as shown above). You'll notice in the $certUrl below, that it still uses the same secret name, but since this is a new version, the version ID has changed. When we update an existing secret Key Vault will automatically create a new version. You need to grab this new secret version. Then use the 'Add-AzureVMSecret' cmdlet to push the certificate into an existing VM, and finally run Update-AzureVM command so the change takes effect; see the code snippet example below. If new VMs are also going to be created later that need this certificate, you'll also need to update your template with this new secret URI.

$subId = (Get-AzureSubscription -Current).SubscriptionId$vm = Get-AzureVM -ResourceGroupName contosovaultrg -Name contosovm$SourceVaultId = "/subscriptions/" + $subid + "/resourceGroups/RgTest1/providers/Microsoft.KeyVault/vaults/TestVault123";$certStore = "MyStore";$certUrl = "https://contosovault.vault.azure.net:443/secrets/servicecert/013b97ad6d104e3e8a1df89c08b67456";$vm = Add-AzureVMSecret -VM $vm -SourceVaultId $SourceVaultId -CertificateStore $certStore -CertificateUrl $certUrl;Update-AzureVM -ResourceGroupName contosovaultrg -VM $vm

Now that you have the certificate pushed to the VM, you need to 'tell' your application to use this new value. How you do this will be highly dependent on your application, hence I'm leaving that exercise to you.

Retiring certificates

In the above section we showed you how to push a new certificate to your existing VMs. But your old certificate is still in the VM and cannot be removed. For added security you can change the attribute for old secret to 'Disabled' so that even if an old template tries to create a VM with this old version of certificate, it will. Here's how you set a specific secret version disabled:

Set-AzureKeyVaultSecretAttribute -VaultName contosovault -Name servicecert -Version e3391a126b65414f93f6f9806743a1f7 -Enable 0

Conclusion

Now let's revisit our scenario again and see how things are different now:

  • With this new scheme, the certificate can be kept separate from the VM image or the application payload. So we have removed one point of exposure.
  • The certificate can also be renewed and uploaded to the Key Vault without having to re-build the VM image or the application deployment package. The application still needs to be supplied with the new URI for this new certificate version though.
  • By separating the certificate from the VM or the application payload, we have now reduced the number of personnel that will have direct access to the certificate.
  • As an added benefit, you now have one convenient place in key vault to manage all your certificates, including the all the versions that were deployed over time.

There you have it! Azure Key Vault makes it easier to inject certificates into the VMs and keeping them safe.


Comments are disabled, head over to the Azure Key Vault forum to discuss about this blog.