Lessons learned: Assigning Access Policies to KeyVault with Bicep
Example architecture
The chart below describes a quite typical architecture where Microservice resources and KeyVault have their own resource groups. KeyVault is typically located in its own resource group because it's often considered as an environment-specific resource that is used by multiple resources/services. This blog post covers what I learned when deploying this kind of infrastructure as a code with Bicep when you have to interact with multiple resource groups.
Bicep implementation
App Service module
This module creates an App Service, enables Managed Identity, and sets the same preferred settings. The module returns the principal Id of App Service when the resource is created.
@description('Name of the App Service Plan')
param appServicePlanName string
@description('The SKU of App Service Plan.')
param appServicePlanSku string = 'S1'
@allowed([
'Win'
'Linux'
])
@description('Select the OS type to deploy.')
param appServicePlanPlatform string
@description('Name of the App Service')
param appServiceName string
param tags object
param appSettings array
resource appServicePlan 'Microsoft.Web/serverfarms@2021-02-01' = {
name: appServicePlanName
location: resourceGroup().location
sku: {
name: appServicePlanSku
}
kind: ((appServicePlanPlatform == 'Linux') ? 'linux' : 'windows')
tags:tags
}
resource appService 'Microsoft.Web/sites@2021-02-01' = {
name: appServiceName
location: resourceGroup().location
identity: {
type: 'SystemAssigned'
}
properties:{
serverFarmId: appServicePlan.id
siteConfig:{
alwaysOn: true
ftpsState: 'Disabled'
netFrameworkVersion: 'v6.0'
ipSecurityRestrictions:[
{
ipAddress: 'xxx.xxx.xxx.xxx/32'
action: 'Allow'
tag: 'Default'
priority: 100
name: 'Allow Access from my IP'
}
{
ipAddress: 'Any'
action: 'Deny'
priority: 2147483647
name: 'Deny all'
description: 'Deny all access'
}
]
scmIpSecurityRestrictionsUseMain: true
appSettings: appSettings
}
httpsOnly: true
}
tags:tags
}
output principalId string = appService.identity.principalId
Main Bicep file
The main file orchestrates the creation of infrastructure.
Parameter declaration
@description('Name of the App Service Plan')
param appServicePlanName string
@description('Name of the Application Insights')
param applicationInsightsName string
@description('The SKU of App Service Plan.')
param appServicePlanSku string = 'S1'
@allowed([
'Win'
'Linux'
])
@description('Select the OS type to deploy.')
param appServicePlanPlatform string
@description('Name of the App Service')
param appServiceName string
Utilize the App Service Module to create an App Service
module appService './appservice.bicep' = {
name: 'appService'
params: {
appServiceName: appServiceName
appServicePlanName: appServicePlanName
appServicePlanPlatform: appServicePlanPlatform
appServicePlanSku: appServicePlanSku
tags:defaultTags
appSettings: appSettings
}
}
Now Managed Identity enabled App Service is created. Next access policies to KeyVault should be created to enable App Service communication with KeyVault.
Failed tryouts
- Referencing existing KeyVault in the Main Bicep file
I first created a reference to the existing KeyVault like this. With scope, I explicitly specified the resource group of KeyVault.
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
name: keyVaultResourceName
scope: resourceGroup(subscription().subscriptionId, 'rg-keyvault')
}
KeyVault reference seems to be working fine. Access policies of KeyVault should be added next. I found several examples to assign access policies like this:
resource keyVaultPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2021-06-01-preview' = {
dependsOn:[
keyVault
]
name: '${keyVault.name}/add'
properties: {
accessPolicies: [
{
objectId: appService.outputs.principalId
permissions: {
secrets: [
'get'
]
}
tenantId: subscription().tenantId
}
]
}
}
Everything looks good so let's deploy infrastructure with the following Azure CLI command
NOTE: 'rg-bicep' is a Resource Group where App Service will be created. KeyVault is not located in that Resource Group!
az deployment group create --resource-group rg-bicep --template-file main.bicep --parameters parameters/dev.parameters.json
This didn't work because the following error occurred:
{
"status": "Failed",
"error": {
"code": "ParentResourceNotFound",
"message": "Can not perform requested operation on nested resource. Parent resource 'MYKEYVAULTRESOURCE' not found."
}
}
Seems that KeyVault is not found. KeyVault is tried to find from the same Resource Group where App Service is located.
NOTE: This is working if KeyVault is located in the same Resource Group
- Changed scope of Access Policy resource declaration in Main Bicep file
Next, I tried to add scope to the Access Policy resource declaration similar way to in KeyVault resource declaration. Visual Studio Code shows immediately a warning that this is not allowed: "The root resource scope must match that of the Bicep file. To deploy a resource to a different root scope, use a module".
That warning was really good because it revealed that this is not the right way to handle this kind of scenario.
Refactored solution
KeyVault Policy Module
Handling of KeyVault Access Policy is now separated to the own Bicep Module (keyvaultpolicy.bicep)
@description('Name of the KeyVault resource ex. kv-myservice.')
param keyVaultResourceName string
@description('Principal Id of the Azure resource (Managed Identity).')
param principalId string
@description('Assigned permissions for Principal Id (Managed Identity)')
param keyVaultPermissions object
@allowed([
'add'
'remove'
'replace'
])
@description('Policy action.')
param policyAction string
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
name: keyVaultResourceName
resource keyVaultPolicies 'accessPolicies' = {
dependsOn:[
keyVault
]
name: policyAction
properties: {
accessPolicies: [
{
objectId: principalId
permissions: keyVaultPermissions
tenantId: subscription().tenantId
}
]
}
}
}
Main Bicep file
Scope configuration of the Module enables us to configure different resource groups for the module than is used in the main Bicep file context.
var keyVaultPermissions = {
secrets: [
'get'
]
}
module keyVault './keyvaultpolicy.bicep' = {
dependsOn: [
appService
]
scope: resourceGroup('rg-keyvault')
name: 'keyVault'
params: {
keyVaultResourceName: keyVaultResourceName
principalId: appService.outputs.principalId
keyVaultPermissions: keyVaultPermissions
policyAction: 'add'
}
}
This solved the problem!
Comments