Safeguarding your passwords when deploying Azure Virtual Machines with Azure PowerShell module and ARM Templates by using Azure Key Vault

Hello everyone,

This blog shows you how to rely on Azure Key Vault in order to avoid passwords written in plain text on PowerShell scripts and/or Azure Resource Manager Templates and it takes you from how passwords were handled on Azure Service Manager (classic virtual machines) all the way up to using ARM Templates with Azure Key Vault.

I usually go onsite with our Microsoft Premier customers to help them adopt Azure and one of the works I do is helping them to deploy virtual machines by using Azure PowerShell or Azure Resource Manager templates.

VMs in Azure requires a local administrator username and password to be deployed. A good thing happened with Azure Resource Manager Powershell module, we introduced a way to send the username and password through PSCredential object, differently from the ASM (Azure Service Manager – Classic) related cmdlets to deploy a VM that only accepted a plain text as password. Below we illustrate the first evolution that we had between ASM and ARM that was accepting PS Credentials on ARM side.

 

Examples

 

ASM – Classic VM – Notice the –Password parameter, clear text

 $image = (Get-AzureVMImage | ? {$_.label -like "windows server 2012 r2 datacenter*"} | select imagename | sort publisheddate -Descending)[0].ImageName
 $vm = New-AzureVMConfig -Name "VMName" -InstanceSize "Standard_D1" -ImageName $image
 Add-AzureProvisioningConfig -VM $vm -Windows -AdminUserName "userName" -Password "PlainTextPassword"
 Set-AzureSubnet -vm  $vm -subnetnames "subnet"
 New-AzureVM -ServiceName "CloudServiceName" -vm $vm -VNetName "VnetName"

 

If we execute a Get-Help on Add-AzureProvisioningConfig cmdlet we see that the –Password is a string object:

 Add-AzureProvisioningConfig -VM <IPersistentVM> [-DisableGuestAgent [<SwitchParameter>]] [-CustomDataFile <String>] -Windows [<SwitchParameter>] [-AdminUsername <String>] [-Password <String>] [-ResetPasswordOnFirstLogon [<SwitchParameter>]] [-DisableAutomaticUpdates [<SwitchParameter>]] [-NoRDPEndpoint [<SwitchParameter>]] [-TimeZone <String>]
 [-Certificates <CertificateSettingList>] [-EnableWinRMHttp [<SwitchParameter>]] [-DisableWinRMHttps [<SwitchParameter>]] [-WinRMCertificate <X509Certificate2>] [-X509Certificates <X509Certificate2[]>] [-NoExportPrivateKey [<SwitchParameter>]] [-NoWinRMEndpoint [<SwitchParameter>]] [-Profile <AzureSMProfile>] [-InformationAction
 <ActionPreference>] [-InformationVariable <String>] [<CommonParameters>]

 

ARM – ARM VM – huge improvement – PSCredential accepted as username and password

    1: $vmRmImage = (Get-AzureRmVMImage -PublisherName "MicrosoftWindowsServer" -Location $location -Offer "WindowsServer" -Skus "2012-R2-Datacenter" | Sort-Object -Descending -Property Version)[0]
    2:  
    3: $vmNic = New-AzureRmNetworkInterface -ResourceGroupName "RgName" -Location "centralus" -Name "vm-nic" -PrivateIpAddress "10.0.0.4" -Subnet "subnet"
    4: $vhdURI = "https://storageaccountname.blob.core.windows.net/vhds/vm01-osdisk.vhd"
    5:  
    6: $vmConfig = New-AzureRmVMConfig -VMName "VM01" -VMSize "Standard_D1"
    7:  
    8: $cred = Get-Credential
    9:  
   10: Set-AzureRmVMOperatingSystem -VM $vmConfig -Windows -ComputerName "VM01" -Credential $cred
   11: Set-AzureRmVMSourceImage -VM $vmConfig -PublisherName $vmRmImage.PublisherName -Offer $vmRmImage.Offer -Skus $vmRmImage.Skus -Version $vmRmImage.Version
   12: Set-AzureRmVMOSDisk -VM $vmConfig -Name "VM01-OSDisk" -VhdUri $vhdURI -Caching ReadWrite -CreateOption fromImage
   13: Add-AzureRmVMNetworkInterface -VM $vmConfig -Id $vmNic.Id

 

At line 8 of the above example we create a PSCredential object using the Get-Credential which is the secure way to obtain a credential. And we also see this $cred object being passed as argument to the Set-AzureRmVmOperatingSystem cmdlet to the parameter –Credential.

 

The problem comes when we really need to fully automate a script like this, what we commonly see, despite of not being recommended, is this type of code:

 $password = ConvertTo-SecureString -String "PlainTextPassword" -AsPlainText -Force
 $cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ("LocalAdminUserName", $password) 

 

And some questions arises, can we do it better? How to deal with these hard coded clear text passwords?

 

Solution

 

Yes, we can do better, we can leverage the security features of Azure Key Vault an Azure service that helps safeguard cryptographic keys and secrets used by cloud applications and services. By using Key Vault, you can encrypt keys and secrets (such as authentication keys, storage account keys, data encryption keys, .PFX files, and passwords) by using keys that are protected by hardware security modules (HSMs) or software security modules, For more information please refer to https://azure.microsoft.com/en-us/services/key-vault .

 

Execute the following steps only one time, to deploy your Key Vault and deploy your secret to your key vault. Make sure you’re using the latest Azure PowerShell module, it can be installed from here.

 

  1. Open a PowerShell command prompt

  2. Authenticate to your Azure Account

     Add-AzureRmAccount
    
  3. Create a Key Vault

     New-AzureRmKeyVault -VaultName "KeyVaultName" -ResourceGroupName "ResourceGroupName" -Location "location"
    

    Notice that the Key Vault name must be unique within .vault.azure.net namespace.

  4. Securely add the password as a secret to the Key Vault. This will prompt for a username and password, type anything as username and make sure you enter the correct password in the password field

     Set-AzureKeyVaultSecret -VaultName "KeyVaultName" -Name "AdminPassword" -SecretValue (Get-Credential).Password
    

 

At this point you created a Key Vault and included a secret, this secret can later be fetched with the following cmdlet:

 $password = Get-AzureKeyVaultSecret -VaultName "KeyVaultName" -Name "AdminPassword"

 

This is your secret object that contains a secure string property (SecretValue) and a clear text property (SecretValueText) as you can see below:

image

 

Going back to our examples, we can do small modifications in the code and increase the security of our deployments, avoiding clear text passwords stored in text files like scripts.

 

Revisited ASM example:

 # Getting the secret from Key Vault
 $password = Get-AzureKeyVaultSecret -VaultName "KeyVaultName" -Name "AdminPassword"
  
 $image = (Get-AzureVMImage | ? {$_.label -like "windows server 2012 r2 datacenter*"} | select imagename | sort publisheddate -Descending)[0].ImageName
 $vm = New-AzureVMConfig -Name "VMName" -InstanceSize "Standard_D1" -ImageName $image
 Add-AzureProvisioningConfig -VM $vm -Windows -AdminUserName "userName" -Password $password.SecretValueText
 Set-AzureSubnet -vm  $vm -subnetnames "subnet"
 New-AzureVM -ServiceName "CloudServiceName" -vm $vm -VNetName "VnetName"

 

Revisited ARM example

 $vmRmImage = (Get-AzureRmVMImage -PublisherName "MicrosoftWindowsServer" -Location $location -Offer "WindowsServer" -Skus "2012-R2-Datacenter" | Sort-Object -Descending -Property Version)[0]
  
 $vmNic = New-AzureRmNetworkInterface -ResourceGroupName "RgName" -Location "centralus" -Name "vm-nic" -PrivateIpAddress "10.0.0.4" -Subnet "subnet"
 $vhdURI = "https://storageaccountname.blob.core.windows.net/vhds/vm01-osdisk.vhd"
  
 $vmConfig = New-AzureRmVMConfig -VMName "VM01" -VMSize "Standard_D1"
  
 # Getting the secret from Key Vault
 $password = Get-AzureKeyVaultSecret -VaultName "KeyVaultName" -Name "AdminPassword"
  
 # Building the PS Credential object securely
 $cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ("localAdminName", $password.SecretValue)  
  
 Set-AzureRmVMOperatingSystem -VM $vmConfig -Windows -ComputerName "VM01" -Credential $cred
 Set-AzureRmVMSourceImage -VM $vmConfig -PublisherName $vmRmImage.PublisherName -Offer $vmRmImage.Offer -Skus $vmRmImage.Skus -Version $vmRmImage.Version
 Set-AzureRmVMOSDisk -VM $vmConfig -Name "VM01-OSDisk" -VhdUri $vhdURI -Caching ReadWrite -CreateOption fromImage

 

What about ARM templates?

ARM Templates supports getting secrets from Azure Key Vault and the first step towards making this happen is to enable your key vault for template deployment, using the same command prompt where you created your Azure Key Vault (otherwise open PowerShell command prompt and authenticate with Add-AzureRmAccount using the same user that you created the Key Vault) type:

 Set-AzureRmKeyVaultAccessPolicy -VaultName <key vault name> -ResourceGroupName <resource group> -EnabledForTemplateDeployment

 

Now, looking at a very basic ARM Template snippet, the way we are used to prompt for credentials looks like this:

image

 

Which looks fine at a first glance, and when deploying directly from PowerShell by using New-AzureRmResourceGroupDeployment and not passing the AdminPassword parameter to it, PowerShell will prompt you for it, which in automation scenarios is not a good idea because that will break your automation.

We do have another option that is obtaining the secret by using the steps that was already mentioned in the beginning of this blog and passing it as a parameter to the New-AzureRmResourceGroupDeployment cmdlet, for example:

 

 $adminUserName = "localAdminName"
 $password = Get-AzureKeyVaultSecret -VaultName "KeyVaultName" -Name "AdminPassword"
  
 $parameters = @{"AdminUsername"=$adminUserName;"AdminPassword"=$Password}
  
 New-AzureRmResourceGroupDeployment -Name "myDeploymentName" `
                                    -ResourceGroupName "myResourceGroupName" `
                                    -Mode Incremental `
                                    -TemplateFile .\azuredeploy.json `
                                    -TemplateParameterObject $parameters `
                                    -Force –Verbose 

 

But this adds two factors, you will depend on PowerShell (or CLI) to deploy your template (see how to work with Azure Key Vault from CLI here) and what if you have several parameters and don’t want to create them during deployment time and instead be using the parameter file? The parameter file in an ARM template is used to provide values to parameters that are defined in an ARM template. In this case you cannot have half of the parameters in the file and half through passing a hash table of parameters from PowerShell, you have one or the other. An example of the parameter file content would be:

image

 

If you use Visual Studio to create and deploy your ARM templates you will notice that it is very easy to record this password in the parameters file from there, just follow these steps:

 

  1. With Visual Studio and your ARM Template solution opened, right-click the project name that represents your ARM deployment at Solution Explorer window, select Deploy->New Deployment

    image

  2. Click “Edit Parameters” button

    image

  3. A window with the parameters will appear and AdminPassword is one of them, notice that the password here is shown as hidden but if you click on “Save” button with the check box “Save passwords as plain text in the parameters file”, that is what will happen, your password will be there in the parameters file just as shown before.

    image

Do you see any issues in this approach? Sure, the password is in clear text in the parameters file, and just to make this example complete, this is how you would start the deployment using the parameter file in PowerShell:

 New-AzureRmResourceGroupDeployment -Name "myDeploymentName" `
                                    -ResourceGroupName "myResourceGroupName" `
                                    -Mode Incremental `
                                    -TemplateFile .\azuredeploy.json `
                                    -TemplateParameterFile .\azuredeploy.parameters.json `
                                    -Force -Verbose 

 

Repeating my first question, can we make it better?

 

Yes. If you are using Visual Studio 2015 (I didn’t test it on 2013) with Azure SDK 2.9 to deploy your templates or even just to create them, you can improve the parameters file and make it use a secret in your Azure Key Vault.

 

If you notice, below on AdminPassword field when deploying and editing the parameters, you will see a small Key icon on right.

image

 

This icon will make you create a new secret on Azure Key Vault or retrieve an existing one, clicking this icon will show the following:

image

 

Subscription is the current subscription where you Key Vault is located, Key Vault is the key vault that you’re using (you can have multiple Key Vaults) and Key Vault Secret will be also a combo box with all secrets defined or you have the option to create a new one as follows:

image

 

You will notice now that the parameters window looks different, it now contains a kind of Key Vault reference and the “Save passwords as plain text in the parameters file” is grayed out.

image

If we click “Save” button we will notice the difference.

image

 

No more clear text passwords in the parameters file. The reference that we see now is the ID of our Key Vault.

 

Note

Be aware that the parameter file contains the Key Vault Id hard coded and its format must comply to the format below:

 /subscriptions/{guid}/resourceGroups/{group-name}/providers/Microsoft.KeyVault/vaults/{vault-name}

 

If you need to deploy this same template in another subscription you need to change the {guid} part, as well all other items between { } that may be different in the other subscription.

A complete ARM Template example can be found at Azure Quickstart repository on GitHub, here is the link.

 

References

What is Azure Key Vault?

Pass secure values during deployment

Manage Key Vault using CLI

Azure Key Vault Cmdlets

Security considerations for Azure Resource Manager

Pass secure values during deployment

 

That’s it for this blog, hope that this helps increasing the level of security of your deployments by don’t hard code passwords on deployment scripts or ARM Templates.