Updated: Deploy Certificates to VMs from customer-managed Key Vault
Hello again,
We have blogged about this topic in the past. Key Vault and Azure PowerShell have gone through many changes since then. Therefore I've updated this blog post with latest information.
As usual, we value your input so please take a moment to join our advisory board, send us private feedback,and/or visit our forum.
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 these certificates into this VM today?
- Embedded in the VM image, which means you have to rebuild the image 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 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 you 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. Now it's 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 (See Note 1)
- Create a Key Vault (either using template, or use the simple script below)
- 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"
$certPassword = "abcd1234"
$fileName = "certforvm.pfx"
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
$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)
New-AzureRmResourceGroup -Name $resourceGroup -Location $location
New-AzureRmKeyVault -VaultName $vaultName -ResourceGroupName $resourceGroup -Location $location -sku standard -EnabledForDeployment
$secret = ConvertTo-SecureString -String $jsonEncoded -AsPlainText –Force
Set-AzureKeyVaultSecret -VaultName "contosovault" -Name "servicecert" -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
ResourceGroupName : contosovaultrg
Location : eastus
ProvisioningState : Succeeded
Tags :
ResourceId : /subscriptions/4b2eb912-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/contosovaultrg
Vault Name : contosovault
Resource Group Name : contosovaultrg
Location : eastus
Resource ID : /subscriptions/4b2eb912-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/contosovaultrg/providers/Microsoft.KeyVault/vaults/contosovault
Vault URI : https://contosovault.vault.azure.net
Tenant ID : 72f988bf-xxxx-xxxx-xxxx-xxxxxxxxxxxx
SKU : standard
Enabled For Deployment? : True
Enabled For Template Deployment? : False
Enabled For Disk Encryption? : False
Access Policies :
Tenant ID : 72f988bf-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Object ID : 50be469a-d7eb-4b26-a2d4-b092cebf0c80
Application ID :
Display Name : Derick Developer (derick@contoso.com)
Permissions to Keys : get, create, delete, list, update, import, backup,
restore
Permissions to Secrets : all
Tags :
Vault Name : contosovault
Name : servicecert
Version : 359804b000ee42febab8a0bb030e200e
Id : https://contosovault.vault.azure.net:443/secrets/servicecert/359804b000ee42febab8a0bb030e200e
Enabled : True
Expires :
Not Before :
Created : 9/14/2016 7:39:42 PM
Updated : 9/14/2016 7:39:42 PM
Content Type :
Tags :
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-AzureRmContext).Subscription.SubscriptionId
$vm = Get-AzureRmVM -ResourceGroupName contosovaultrg -Name contosovm
$SourceVaultId = (Get-AzureRmKeyVault -VaultName contosovault).ResourceId
$certStore = "My";
$certUrl = (Get-AzureKeyVaultSecret -VaultName contosovault -Name servicecert).Id;
$vm = Add-AzureRmVMSecret -VM $vm -SourceVaultId $SourceVaultId -CertificateStore $certStore -CertificateUrl $certUrl;
Update-AzureRmVM -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. If your application configuration finds this certificate by thumbprint, then you will need to update your application configuration with thumbprint of the new certificate.
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.
Notes
Note 1: You can quickly generate a self-signed certificate using this PowerShell snippet.
$certificateName = "certforvm"
$thumbprint = (New-SelfSignedCertificate -DnsName $certificateName -CertStoreLocation Cert:\CurrentUser\My -KeySpec KeyExchange).Thumbprint
$cert = (Get-ChildItem -Path cert:\CurrentUser\My\$thumbprint)
$password = Read-Host -Prompt "Please enter the certificate password." -AsSecureString
Export-PfxCertificate -Cert $cert -FilePath ".\$certificateName.pfx" -Password $password
Comments are disabled, head over to the Azure Key Vault forum to discuss about this blog.