Access Policies In Key Vault Using Azure Bicep - How To Create New, Keep Existing, Managed Identity
In this post, we will discuss key vault access policies in detail, i.e. what it is, what is it for, its schema, how to deploy, and some solutions to common use cases, all this in the context of Azure Bicep.
Access policy is an access-control model for Azure Key Vault. This model allows creating access policies which define permissions for different Azure AD security principals over key vault specific scopes (keys, secrets, certificates).
Access policies are still widely used when working with Azure Key Vaults even though newer Azure RBAC model exists. So, it is definitely worth learning more about key vault access policies.
Contents:
- Overview
- What is Access Policy?
- Resource Schema For Access Policy
- Creating Access Policies In Bicep
- Managed Identity Access Policy
- Keep Existing Access Policies When Redeploying a Key Vault
- Related Posts
- Useful Links
Overview
In one of the previous posts Key Vault & Secrets Management With Azure Bicep, we talked about the key vault itself. In this post, we will cover access policies and how to work with them using Azure Bicep.
We start by defining what is an access policy and discussing how it is different from the role-based access control model which is also available in Azure Key Vault.
Next, we cover the resource schema, and in particular talk about tenantId
, objectId
, permissions
, applicationId
properties and what are they for.
Creating Access Policies In Bicep section contains examples how to deploy access policies as part of a key vault deployment as well as how to add access policies to an existing key vault.
The last two sections are about possible use cases when working with access policies in Azure Bicep. First one is assigning an access policy for a managed identity, and the second one is a solution for how to redeploy a key vault but keep existing access policies which aren’t defined in the template.
What is Access Policy?
Access policy model is one of the two permission models available for Azure Key Vault. Historically, access policies were introduced earlier than Azure RBAC, and the latter now is the recommended approach.
An access policy specifies what actions a particular security principal (user, group, service principal, or managed identity) is allowed to perform over different scopes (keys, secrets, certificates).
According to the docs, each key vault can have only up to 1024 access policies, so it is possible to hit the limit if permissions are assigned to each user individually rather than to groups of users.
Access Policies vs Role-Based Access Control (RBAC)
As already mentioned, there is an alternative permissions model which is called Azure RBAC. It is widely used across Azure resources and, as a result, provides more uniform experience.
There are pros and cons for both models, but in general, Azure RBAC allows more fine-grained control and is now the recommended way of managing permissions for key vaults. Also, please see comparison in this post Access Policies vs Azure RBAC.
Resource Schema For Access Policy
In Azure Resource Manager, and therefore in Azure Bicep, an access policy is a child resource of a key vault resource. It is also reflected in the resource name: Microsoft.KeyVault/vaults/accessPolicies.
Being a child resource means that an access policy must be created in the context of some key vault, and it cannot exist on its own. Read more about Child Resources In Azure Bicep - 3 Ways To Declare, Loops, Conditions.
The schema with detailed description can be found in the documentation: Microsoft.KeyVault/vaults/accessPolicies. However, we will still give a short summary of each property.
- tenantId - tenant in Azure Active Directory against which requests will be authenticated.
- objectId - identifier of a user, service principal, or security group. It exists for each security principal and is unique within an AAD tenant. Note that it’s different from an application ID.
- permissions - a set of allowed actions a security principal can perform on keys, secrets, or certificates, for example,
get
,list
, etc. - applicationId - an ID of an application through which the security principal is allowed to access the key vault, this is known as a On-Behalf-Of flow. Usually, this option is rarely used in key vault’s access policies. Read more about application ID in this blog post.
NOTE: When deploying a key vault, the accessPolicies
property is required unless the mode is set to “recover” or RBAC is enabled. Sometimes, people want to preserve existing access policies which are not defined in the template, then they resort to work-arounds like Keep Existing Access Policies When Redeploying Key Vault.
// ============ accesspolicies-schema.bicep ============
resource symbolicname 'Microsoft.KeyVault/vaults/accessPolicies@2019-09-01' = {
name: 'string'
parent: parentSymbolicName
properties: {
accessPolicies: [
{
applicationId: 'string'
objectId: 'string'
permissions: {
certificates: ['string']
keys: ['string']
secrets: ['string']
storage: ['string']
}
tenantId: 'string'
}
]
}
}
Creating Access Policies In Bicep
Given that we already know the schema and what values we want to put where, now we need to decide how to organize our Bicep template and deploy our access policies.
In general, I prefer to think about access policies deployment in terms of two categories:
- Deploying access policies in the same template with a key vault
- Deploying access policies separately from a key vault
The following sections illustrate the two use cases mentioned above.
[Template With KV] Deploy Access Policies Together With Key Vault
When deploying a key vault using Bicep, accessPolicies
property is required and is also a part of the key vault schema Microsoft.KeyVault/vaults.
Therefore, if your template already contains a key vault definition, then the simplest way is to just specify access policies as part of the key vault as shown in the code snippet below.
// ============ keyvault-with-accesspolicies.bicep ============
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' = {
name: 'kv-contoso'
location: resourceGroup().location
properties: {
sku: {
family: 'A'
name: 'standard'
}
tenantId: '51636b66-283b-4600-8ce6-4ad411dcfb42'
accessPolicies: [
{
objectId: '90589367-f0f1-4289-bbce-ad5bb54cef23'
tenantId: '51636b66-283b-4600-8ce6-4ad411dcfb42'
permissions: {
secrets: [
'list'
]
certificates: [
'list'
]
}
}
{
objectId: '2d267b98-52fc-405b-8071-70846363fd40'
tenantId: '51636b66-283b-4600-8ce6-4ad411dcfb42'
permissions: {
secrets: [
'all'
]
certificates: [
'get'
'create'
]
keys: [
'encrypt'
'decrypt'
]
}
}
]
}
}
[Template Without KV] Add Access Policy To Existing Key Vault
Now, let’s assume that we want to deploy access policies separately from their key vault, for example, this could be in the following use cases:
- Add access policies to an existing key vault without deploying the vault itself.
- Deploy a key vault in one module and access policies in another module, e.g. keeping key vault and access policies separately.
To add access policies, we have to define a resource of a type Microsoft.KeyVault/vaults/accessPolicies.
On line 6, we can see that name consists of two segments separated by a slash /
where the first part is the key vault name (e.g. kv-contoso
), and the second part is always add
. As a result, we get a value kv-contoso/add
.
NOTE: An alternative approach is to pass key vault reference in the parent
property of the access policies resource, in such case the name
property will have only one segment and be just add
without the key vault name. See an example in this post about child resources.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ============ add-accesspolicies.bicep ============
param keyVaultName string = 'kv-contoso'
resource accessPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2019-09-01' = {
name: '${keyVaultName}/add'
properties: {
accessPolicies: [
{
objectId: '90589367-f0f1-4289-bbce-ad5bb54cef23'
tenantId: '51636b66-283b-4600-8ce6-4ad411dcfb42'
permissions: {
secrets: [
'all'
]
certificates: [
'list'
]
}
}
]
}
}
Managed Identity Access Policy
Managed identity in Azure is a concept that allows offloading of credential management from a developer to the Azure infrastructure. This way, applications can use managed identities provided by Azure to authenticate to Azure Active Directory.
And since Key Vault integrates with Azure AD, managed identities are often used by applications to retrieve secrets/certificates from the key vault. To enable this, managed identity must have relevant permissions assigned to it inside the key vault, and one of the ways to achieve it is to create an access policy.
In Azure, managed identities come in two flavors: user-assigned and system-assigned. In terms of granting permissions through access policies, there is no difference between these two.
NOTES:
- Managed identity must be in the same tenant where the key vault is. As of January 2022, cross-tenant access for managed identities is not supported, see Managed identities FAQ - Azure AD
- We need to know
objectId
of the managed identity, for example, we can find it in the Azure Portal:- User-assigned: “Overview” → “Object (principal) ID”
- System-assigned (e.g. for a virtual machine): “Identity” → “System assigned” → “Object (principal) ID”
// ========== managed-identity-accesspolicy.bicep ==========
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' = {
name: 'kv-contoso'
location: resourceGroup().location
properties: {
sku: {
family: 'A'
name: 'standard'
}
tenantId: subscription().tenantId
accessPolicies: [
{
objectId: '61248cef-973e-4471-92fb-fe3653b1d804' // This is the objectId of our managed identity
tenantId: subscription().tenantId
permissions: {
secrets: [
'get'
]
}
}
]
}
}
Keep Existing Access Policies When Redeploying a Key Vault
Let’s imagine the following situation:
- We want to deploy a key vault which may or may not already exist.
- If it exists then we want to redeploy it but keep existing access policies.
- If it doesn’t exist, we just create it with an empty or default set of access policies.
Unfortunately, there is no straightforward way in Azure Bicep or ARM templates to check whether a resource already exists or not, though it would be very helpful in this situation.
Therefore, we need to use a work-around, and here are is a possible approach:
- If we have knowledge about whether the key vault exists or not when the deployment is performed, we can use reference() function to retrieve access policies of the existing key vault during deployment.
- If we don’t know whether the key vault exists, then we can use Azure PowerShell or Azure CLI to check the key vault existence and then use the template from the previous step to deploy the key vault.
1. Get Existing Access Policies Using Reference() Function
For now, let’s assume that we know whether a key vault already exists or not, this information is somehow passed to the template through a boolean flag, e.g. isNewKeyVault
.
Given this information, we can now easily pass necessary access policies when defining a key vault. We will use ARM template reference()
function to achieve this.
Our Azure Bicep deployment will consist of two files, here is an overview for each of them:
keyvault-existing-accesspolicies.bicep
- main file where we receiveisNewKeyVault
parameter and if it’sfalse
then usereference()
function to retrieve existing access policies, otherwise just pass empty array. Read more about Reference() Function Explained With Examples - ARM Template.keyvault-existing-accesspolicies.module.bicep
- module file that expectsaccessPolicies
parameter and deploys a key vault using this parameter value. Learn more about Modules In Azure Bicep - Basics To Advanced, How It Works, Nested Modules, Outputs, Scopes.
// ========== keyvault-existing-accesspolicies.bicep ==========
param keyVaultName string = 'kv-contoso'
param isNewKeyVault bool = false
// If it's a new key vault, we pass empty array but it could be any static set of access policies
var accessPolicies = isNewKeyVault ? [] : reference(resourceId('Microsoft.KeyVault/vaults', keyVaultName), '2019-09-01').accessPolicies
module keyVaultModule 'keyvault-existing-accesspolicies.module.bicep' = {
name: 'keyVaultDeployment'
params: {
keyVaultName: keyVaultName
accessPolicies: accessPolicies
}
}
// ========== keyvault-existing-accesspolicies.module.bicep ==========
// This is a module which is invoked from 'keyvault-existing-accesspolicies.bicep'
param keyVaultName string
param accessPolicies array // Expecting access policies to be passed through this parameter
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' = {
name: keyVaultName
location: resourceGroup().location
properties: {
sku: {
family: 'A'
name: 'standard'
}
tenantId: subscription().tenantId
accessPolicies: accessPolicies
}
}
2. Use Azure PowerShell To Check Key Vault Existence
The approach in the previous section works well if the template knows whether a new key vault is being created or it’s an existing one.
We can easily find out whether a key vault already exists using Azure PowerShell / Azure CLI / REST API, and then pass this knowledge to the template to make our solution be able to handle both cases.
NOTE: In this example, we are using Azure PowerShell to check the existence of Azure Key Vault.
So, the high-level steps are:
- Check whether the key vault exists, for example, using Get-AzKeyVault function.
This should look similar to the following:$KeyVault = Get-AzKeyVault -VaultName kv-contoso -ResourceGroupName rg-contoso $IsNewKeyVault = $KeyVault -eq $null
- Deploy
keyvault-existing-accesspolicies.bicep
template from the previous step using New-AzResourceGroupDeployment. An example how to do that is available in 5 Ways To Deploy Bicep File With Parameters - Azure DevOps, PowerShell, CLI, Portal, Cloud Shell.
And this is basically it! Now you should be able to deploy a new or existing key vault and preserve existing access policies if there are any.
If you are interested how to implement this in CI/CD pipeline, I’d recommend checking out the post Deploy Azure Bicep In YAML and Classic Release Pipelines (CI/CD) - Azure DevOps.
Related Posts
- Key Vault & Secrets Management With Azure Bicep - Create, Reference, Output Examples
- Using Key Vault Secrets As Secure Parameters In Azure Bicep - Template & Module Inputs
- Deploy Azure Bicep In YAML and Classic Release Pipelines (CI/CD) - Azure DevOps
- Child Resources In Azure Bicep - 3 Ways To Declare, Loops, Conditions
- Reference() Function Explained With Examples - ARM Template
- Reference New Or Existing Resource In Azure Bicep
- Modules In Azure Bicep - Basics To Advanced, How It Works, Nested Modules, Outputs, Scopes
- 5 Ways To Deploy Bicep File With Parameters - Azure DevOps, PowerShell, CLI, Portal, Cloud Shell
Useful Links
- Microsoft.KeyVault/vaults - Bicep & ARM template reference | Microsoft Docs
- Microsoft.KeyVault/vaults/accessPolicies - Bicep & ARM template reference | Microsoft Docs
- Assign an Azure Key Vault access policy (CLI) | Microsoft Docs
- AZIdentity | Getting It Right: Key Vault Access Policies
- Managed identities for Azure resources | Microsoft Docs