Setting up an Azure Key Vault is relatively very easy than some Azure deployment. Microsoft Azure supports Web Portal, PowerShell, Shell Scripts, CLI, ARM templates and other scripting languages.
Key decision points are ACL, Service Principal and managing access secrets. The article would discuss all of these aspects of the journey. Let us start from simple and then we would move to advance options (for DevOps).
Setup Azure Key Vault using Azure Admin Portal
- Login to Azure Portal, click on the Add button (left side navigation) and find Azure Key Vault.
- Add Key Vault Name, select Azure Subscription, Resource Group, Location and Pricing Tier.A1 Standard do not use HSM backed keys, but it stores keys at rest. However, these keys are encrypted and secured by HSM keys.P1 Premium supports HSM backed keys. HSM backed keys can not be exported with private key, you can only access public key. You can import PFX key instead of generating with HSM and keep the local copy of PFX if it is essential for Application Solution Design.Keeping a local copy of certificates and secrets defeat the purpose thus the decision should be based on scientific methods.
- Apply minimal access policies for Administrator. I would be writing a separate article discussing the subject in depth.However, the thumb-rule should be any use either should have access to Key Management Operations or Cryptography Operations. It is not advisable to enable All Key Operations (as displayed in the figure).You can use blanket authorization for non-production environments, testing and development. Though, it is not a good practice.
- Configuring Advance Access Policy is very much straight forward, the options are self-exoplanetary.Apply these options as applicable with the usage and purpose.
- Click on Create button; the portal would provision you a Azure Key Vault.
You can upload or generate new Key or Secret.
PowerShell Script to create or provision an Azure Key Vault
Modified auto-generated PowerShell script for automation and advanced use.
<#
.SYNOPSIS
Deploys a template to Azure
.DESCRIPTION
Deploys an Azure Resource Manager template
.PARAMETER subscriptionId
.PARAMETER resourceGroupName
.PARAMETER resourceGroupLocation
.PARAMETER deploymentName
.PARAMETER templateFilePath
.PARAMETER parametersFilePath
param(
[Parameter(Mandatory=$True)]
[string]
$subscriptionId,
[Parameter(Mandatory=$True)]
[string]
$resourceGroupName,
[string]
$resourceGroupLocation,
[Parameter(Mandatory=$True)]
[string]
$deploymentName,
[string]
$templateFilePath = "template.json",
[string]
$parametersFilePath = "parameters.json"
)
<#
.SYNOPSIS
Registers RPs
#>
Function RegisterRP {
Param(
[string]$ResourceProviderNamespace
)
Write-Host "Registering resource provider '$ResourceProviderNamespace'";
Register-AzureRmResourceProvider -ProviderNamespace $ResourceProviderNamespace;
}
#******************************************************************************
# Script body
# Execution begins here
#******************************************************************************
$ErrorActionPreference = "Stop"
# sign in
Write-Host "Logging in...";
Login-AzureRmAccount;
# select subscription
Write-Host "Selecting subscription '$subscriptionId'";
Select-AzureRmSubscription -SubscriptionID $subscriptionId;
# Register RPs
$resourceProviders = @("microsoft.keyvault");
if($resourceProviders.length) {
Write-Host "Registering resource providers"
foreach($resourceProvider in $resourceProviders) {
RegisterRP($resourceProvider);
}
}
#Create or check for existing resource group
$resourceGroup = Get-AzureRmResourceGroup -Name $resourceGroupName -ErrorAction SilentlyContinue
if(!$resourceGroup)
{
Write-Host "Resource group '$resourceGroupName' does not exist. To create a new resource group, please enter a location.";
if(!$resourceGroupLocation) {
$resourceGroupLocation = Read-Host "resourceGroupLocation";
}
Write-Host "Creating resource group '$resourceGroupName' in location '$resourceGroupLocation'";
New-AzureRmResourceGroup -Name $resourceGroupName -Location $resourceGroupLocation
}
else{
Write-Host "Using existing resource group '$resourceGroupName'";
}
# Start the deployment
Write-Host "Starting deployment...";
if(Test-Path $parametersFilePath) {
New-AzureRmResourceGroupDeployment -ResourceGroupName $resourceGroupName -TemplateFile $templateFilePath -TemplateParameterFile $parametersFilePath;
} else {
New-AzureRmResourceGroupDeployment -ResourceGroupName $resourceGroupName -TemplateFile $templateFilePath;
}
ARM Schema and Template
/* Parameters */
{
"$schema": "<a href="https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#">https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#</a>",
"contentVersion": "1.0.0.0",
"parameters": {
"name": {
"value": ""
},
"location": {
"value": ""
},
"sku": {
"value": ""
},
"accessPolicies": {
"value": [
{
"objectId": "00000000-0000-0000-0000-000000000000",
"tenantId": "00000000-0000-0000-0000-000000000000",
/* As recommended not good policy */
"permissions": {
"keys": [
"All"
],
"secrets": [
"All"
],
"certificates": []
}
}
]
},
"tenant": {
"value": "00000000-0000-0000-0000-000000000000"
},
"enabledForDeployment": {
"value": false
},
"enabledForTemplateDeployment": {
"value": false
},
"enabledForDiskEncryption": {
"value": false
}
}
}
/* Template */
"resources": [
{
"apiVersion": "2015-06-01",
"name": "[parameters('name')]",
"location": "[parameters('location')]",
"type": "Microsoft.KeyVault/vaults",
"properties": {
"enabledForDeployment": "[parameters('enabledForDeployment')]",
"enabledForTemplateDeployment": "[parameters('enabledForTemplateDeployment')]",
"enabledForDiskEncryption": "[parameters('enabledForDiskEncryption')]",
"accessPolicies": "[parameters('accessPolicies')]",
"tenantId": "[parameters('tenant')]",
"sku": {
"name": "[parameters('sku')]",
"family": "A"
}
}
}
]
Programmatically
class DeploymentHelper
{
string subscriptionId = "your-subscription-id";
string clientId = "your-service-principal-clientId";
string clientSecret = "your-service-principal-client-secret";
string resourceGroupName = "resource-group-name";
string deploymentName = "deployment-name";
string resourceGroupLocation = "resource-group-location"; // must be specified for creating a new resource group
string pathToTemplateFile = "path-to-template.json-on-disk";
string pathToParameterFile = "path-to-parameters.json-on-disk";
string tenantId = "tenant-id";
public async void Run()
{
// Try to obtain the service credentials
var serviceCreds = await ApplicationTokenProvider.LoginSilentAsync(tenantId, clientId, clientSecret);
// Read the template and parameter file contents
JObject templateFileContents = GetJsonFileContents(pathToTemplateFile);
JObject parameterFileContents = GetJsonFileContents(pathToParameterFile);
// Create the resource manager client
var resourceManagementClient = new ResourceManagementClient(serviceCreds);
resourceManagementClient.SubscriptionId = subscriptionId;
// Create or check that resource group exists
EnsureResourceGroupExists(resourceManagementClient, resourceGroupName, resourceGroupLocation);
// Start a deployment
DeployTemplate(resourceManagementClient, resourceGroupName, deploymentName, templateFileContents, parameterFileContents);
}
/// <summary>
/// Reads a JSON file from the specified path
/// </summary>
/// <param name="pathToJson">The full path to the JSON file</param>
/// <returns>The JSON file contents</returns>
private JObject GetJsonFileContents(string pathToJson)
{
JObject templatefileContent = new JObject();
using (StreamReader file = File.OpenText(pathToJson))
{
using (JsonTextReader reader = new JsonTextReader(file))
{
templatefileContent = (JObject)JToken.ReadFrom(reader);
return templatefileContent;
}
}
}
/// <summary>
/// Ensures that a resource group with the specified name exists. If it does not, will attempt to create one.
/// </summary>
/// <param name="resourceManagementClient">The resource manager client.</param>
/// <param name="resourceGroupName">The name of the resource group.</param>
/// <param name="resourceGroupLocation">The resource group location. Required when creating a new resource group.</param>
private static void EnsureResourceGroupExists(ResourceManagementClient resourceManagementClient, string resourceGroupName, string resourceGroupLocation)
{
if (resourceManagementClient.ResourceGroups.CheckExistence(resourceGroupName) != true)
{
Console.WriteLine(string.Format("Creating resource group '{0}' in location '{1}'", resourceGroupName, resourceGroupLocation));
var resourceGroup = new ResourceGroup();
resourceGroup.Location = resourceGroupLocation;
resourceManagementClient.ResourceGroups.CreateOrUpdate(resourceGroupName, resourceGroup);
}
else
{
Console.WriteLine(string.Format("Using existing resource group '{0}'", resourceGroupName));
}
}
/// <summary>
/// Starts a template deployment.
/// </summary>
/// <param name="resourceManagementClient">The resource manager client.</param>
/// <param name="resourceGroupName">The name of the resource group.</param>
/// <param name="deploymentName">The name of the deployment.</param>
/// <param name="templateFileContents">The template file contents.</param>
/// <param name="parameterFileContents">The parameter file contents.</param>
private static void DeployTemplate(ResourceManagementClient resourceManagementClient, string resourceGroupName, string deploymentName, JObject templateFileContents, JObject parameterFileContents)
{
Console.WriteLine(string.Format("Starting template deployment '{0}' in resource group '{1}'", deploymentName, resourceGroupName));
var deployment = new Deployment();
deployment.Properties = new DeploymentProperties
{
Mode = DeploymentMode.Incremental,
Template = templateFileContents,
Parameters = parameterFileContents["parameters"].ToObject<JObject>()
};
var deploymentResult = resourceManagementClient.Deployments.CreateOrUpdate(resourceGroupName, deploymentName, deployment);
Console.WriteLine(string.Format("Deployment status: {0}", deploymentResult.Properties.ProvisioningState));
}
}
As demonstrated, creating Azure Key Vault would be a simple and a straightforward step.