Secure storage account keys in Azure KeyVault using Resource Manager (ARM) Scripts

Easiest way to keep yourself from losing your keys? Don’t carry them.

Security is more important than ever these days, especially with all the public data breaches in the news. As developers we are trusted with the keys to the kingdoms for all the systems that we work on. We are also human. We make mistakes. We screw up. We will lose things.

lost-keys.jpg
Damnit

With that in mind, think of all the connection strings checked into source code repositories over the world. Various files like appsettings.Production.json sitting on people’s desktops and being emailed between people so that they can debug a live system issue. All those files are out there, and maybe some of the keys have been changed/rotated, but I bet that a lot of them haven’t.

The safest way to avoid losing keys? Never have them in the first place. Microsoft Azure has a great tool called “Key Vault” it’s a great way to secure your certificates and secrets in the cloud, locked down and encrypted. You can secure keys with access policies for users, applications, or even external entities (with a client id and secret).

There are dotnet core libraries that allow you to integrate keyvault into your IConfiguration in an ASP dotnet core application, so you can just access them like any other setting, merged with the environment variables your app already has access to.

var someAppSetting = Configuration.GetValue("AppSetting");
var someKeyvaultSecret = Configuration.GetValue("SecretName);

Both of these work perfectly well if you include this in your csproj file.

<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="2.2.0" />

 

But, how to avoid keeping either a keyvault secret/id in your source code to access your secrets? Simple. At deployment time set your web app to have a system managed identity in your azure deployment file like this.

Screenshot 2019-03-24 at 21.01.14

Then when you create your keyvault later on in the document, create an access policy for that site (No where in your code are you storing the client/object id for the application, or a secret to access the vault). This grants the instance of your app that this script deploys, access to your keyvault with the premissions you provide here. See an example below:

{
"type": "Microsoft.KeyVault/vaults",
"name": "[variables('keyvault_name')]",
"apiVersion": "2016-10-01",
"location": "uksouth",
"tags": {},
"scale": null,
"properties": {
"sku": {
"family": "A",
"name": "Premium"
},
"tenantId": "{YOUR_AZURE_AD_TENANT_ID}",
"accessPolicies": [
{
"tenantId": "[reference(concat('Microsoft.Web/sites/', variables('appservice_name')), '2016-08-01', 'Full').identity.tenantId]",
"objectId": "[reference(concat('Microsoft.Web/sites/', variables('appservice_name')), '2016-08-01', 'Full').identity.principalId]",
"permissions": {
"keys": [
"all"
],
"secrets": [
"all"
]
}
}
],
"enabledForDeployment": true,
"enabledForDiskEncryption": true,
"enabledForTemplateDeployment": true
},
"dependsOn": [
"[resourceId('Microsoft.Web/sites', variables('appservice_name'))]"
]
}
view raw gistfile1.txt hosted with ❤ by GitHub

Once you create a storage account, you can get the keys to in the script, and add it to the keyvault directly, all without ever seeing or exposing your keys. This below example adds a secret called “StorageAccountKey” that can be referenced in your code like any other app setting (if you use the extensions).

{
"type": "Microsoft.KeyVault/vaults/secrets",
"name": "[concat(variables('keyvault_name'), '/', 'StorageAccountKey')]",
"apiVersion": "2016-10-01",
"location": "uksouth",
"scale": null,
"properties": {
"value": "[listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2016-01-01').keys[0].value]",
"contentType": "string",
"attributes": {
"enabled": true
}
},
"dependsOn": [
"[resourceId('Microsoft.KeyVault/vaults', variables('keyvault_name'))]",
"[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
]
}

The only caveat to this is that this access policy is tied to one instance of your web app in the cloud… not every time you run it. So running on your local machine will get you an error, as you won’t have access to the keyvault. For development, this is fine. Use the command to add some local secrets to your development machine. Surround the addition of keyvault to your config with an “IsProduction” check, so that when running on your local machine, it doesn’t try to integrate with keyvault.

$ dotnet user-secrets set "somekey" "somevalue"

THESE SHOULD ONLY EVER BE THE DEVELOPMENT SECRETS!

So, your secrets are never in source code, your cloud app can get them directly from keyvault. For production, you just use the same arm script you used to create your dev environment, but never add the secrets to your local machine, even in user-secrets. When you deploy your app to the cloud, it, and only it, will be able to access them.

Using these techniques, you can develop locally, your secrets are not in source repos, and your production secrets only exist in keyvault, and you can’t actually access them yourself, only your application has an access policy.

Stay safe.

Leave a comment

Blog at WordPress.com.

Up ↑