Table of Contents
Bicep Module for Azure App Configuration
Summary: This is a bicep module that I use to deploy Azure App Configurations, a cloud service I use to store variables.
Date: 4 January 2025
Refactor: 9 January 2025: Added deployment information and an additional module to assign roles.
Read the post to learn more about Azure App Configuration and:
- How to deploy an Azure App Configuration Store using a Bicep module
- How to set the name and sku based on the environment parameter
- How to deploy a private endpoint for an Azure App Configuration Store using a Bicep module
- How to set permissions on the Azure App Configuration Store using a Bicep module
- Permissions are set on an array of teams which are defined in a separate parameter file
Check here for an additional post for a dashboard to monitor the Azure App Configuration Store.
What is Azure App Configuration
Azure App Configuration is a service that allows you to centralize your application settings and feature flags. It provides a way to manage the settings of your application in a single place, and it can be used to store key-value pairs that can be accessed by your application at runtime. I found it very useful as it also allows you to import variables into your Azure DevOps pipeline, allowing you to also manage your pipeline variables in a single place.
Important to Know about Azure App Configuration
Some settings are not available for the free sku. For example, you can't configure a replica and it's also not possible to enable a private endpoint. The free sku is also limited in the amount of requests you can make. Usually I recommend to start with the free sku and then upgrade if needed. Also, when using Infrastructure as Code such as Bicep or Terraform, I would deploy the free SKU as a test deployment and use the standard edition for your production environment. Note that you are only allowed to deploy one free SKU per subscription.
Bicep Module App Configuration
The module below deploys an Azure App Configuration Store. Notice that some settings are dependent on the SKU. For example, the replica is only deployed when the SKU is set to standard. The module also deploys a diagnostic setting to log all logs and audit logs to a Log Analytics Workspace.
/* DESCRIPTION Deploys a App Configuration Store LINKS Resource: https://learn.microsoft.com/en-us/azure/templates/microsoft.appconfiguration/configurationstores Resource replica: https://learn.microsoft.com/en-us/azure/templates/microsoft.appconfiguration/configurationstores/replicas Some settings are not available for the free sku: https://azure.microsoft.com/en-us/pricing/details/app-configuration/ NOTES If your delete the replica you have to pick a new name as the old one is purge protected. The networking setup for a app configuration is set by default as: "With a private endpoint, public network access will be automatically disabled. If there is no private endpoint present, public network access is automatically enabled." */ */ // input parameters param location string param appConfigurationName string param lawSubscriptionId string param lawResourceGroup string param lawName string @description('Specifies the SKU of the app configuration store.') param skuName string // deploy app configuration store resource configStore 'Microsoft.AppConfiguration/configurationStores@2023-03-01' = { name: appConfigurationName location: location sku: { name: skuName } properties: (skuName == 'standard') ? { disableLocalAuth: true softDeleteRetentionInDays: 7 enablePurgeProtection: true } : { disableLocalAuth: true } } // replica resource configStoreReplica 'Microsoft.AppConfiguration/configurationStores/replicas@2023-08-01-preview' = if (skuName == 'standard') { parent: configStore name: 'eunorthreplica' location: 'northeurope' } resource diagnosticLogs 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { scope: configStore name: 'diag-${appConfigurationName}' properties: { workspaceId: resourceId(lawSubscriptionId, lawResourceGroup, 'Microsoft.OperationalInsights/workspaces', lawName) logs: [ { categoryGroup: 'alllogs' enabled: true } { categoryGroup: 'audit' enabled: true } ] metrics: [ { category: 'AllMetrics' enabled: true } ] } } output appConfigurationName string = configStore.name
Deploy Example
The module above can be deployed using the code below. Note that both the App Configuration name and the sku are set based on the environment:
targetScope = 'resourceGroup' // parameters param location string = resourceGroup().location param environment string // log analytics workspace param lawSubId string param lawRg string param lawName string // App Configuration parameters var appConfigurationName = { 'dev': 'appcs-dev' 'prd': 'appcs-prd' }[environment] var appConfigSkuName = { 'dev': 'free' 'prd': 'standard' }[environment] // deploy app configuration module appConfig '../modules/appconfiguration.bicep' = { name: 'appc-${resourceGroupName}-${deploymentSuffix}' params: { location: location appConfigurationName: appConfigurationName skuName: appConfigSkuName lawSubscriptionId: lawSubId lawResourceGroup: lawRg lawName: lawName } }
Bicep Module App Configuration Private Endpoint
The module below deploys a private endpoint for an Azure App Configuration Store. The private endpoint allows you to access the App Configuration Store from your virtual network. Note that this module requires the vnet, subnet, and app configuration to be deployed before deploying the private endpoint. The module also deploys a DNS record so the private endpoint can be resolved. Note that the private DNS zone also already must be deployed.
/* DESCRIPTION Deploys a app configuration private endpoint LINKS Resource Private Endpoint: https://learn.microsoft.com/en-us/azure/templates/microsoft.network/privateendpoints Resource Private DNS Zonegroup: https://learn.microsoft.com/en-us/azure/templates/microsoft.network/privateendpoints/privatednszonegroups GroupId private links: https://learn.microsoft.com/en-us/azure/private-link/private-endpoint-overview NOTES DNS Zone forwarding: https://learn.microsoft.com/en-us/azure/private-link/private-endpoint-dns */ // input parameters param location string // app configuration param appConfigurationName string // network - the virtual network where the pep/nic comes @description('The subscription id that hosts the virtual network for the pep') param vnetSubscriptionId string @description('The resource group that hosts the virtual network for the pep') param vNetRg string @description('The name of the virtual network that hosts the private endpoints subnet') param vnetName string @description('The name of the private endpoints subnet') param subnetName string // dns param subIdDNS string param rgDNS string // defined parameters param privateLinkZone string = 'privatelink.azconfig.io' // variables var pepName = 'pep-${appConfigurationName}' var nicName = 'nic-${pepName}' var pepConnectionName = 'pep-con-${appConfigurationName}' // existing resource vNet 'Microsoft.Network/virtualNetworks@2020-11-01' existing = { scope: resourceGroup(vnetSubscriptionId, vNetRg) name: vnetName } resource subnetPrivateEndpoints 'Microsoft.Network/virtualNetworks/subnets@2022-09-01' existing = { parent: vNet name: subnetName } resource appConfig 'Microsoft.AppConfiguration/configurationStores@2023-03-01' existing = { name: appConfigurationName } // deploy private endpoint resource saPrivateEndpoint 'Microsoft.Network/privateEndpoints@2022-01-01' = { name: pepName location: location properties: { privateLinkServiceConnections: [ { name: pepConnectionName properties: { groupIds: [ 'configurationStores' ] privateLinkServiceId: appConfig.id } } ] customNetworkInterfaceName: nicName subnet: { id: subnetPrivateEndpoints.id } } } // deploy dns record resource privateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2022-09-01' = { parent: saPrivateEndpoint name: 'dnsgroup' properties: { privateDnsZoneConfigs: [ { name: 'config' properties: { privateDnsZoneId: resourceId(subIdDNS, rgDNS, 'Microsoft.Network/privateDnsZones', privateLinkZone) } } ] } }
Bicep Module App Configuration Role Assignment
The module below assigns a role to a user, group, or identity on an Azure App Configuration Store. The module requires the app configuration store to be deployed before deploying the role assignment.
/* DESCRIPTION Assigns a role to a user, group or identity on an azure app configuration store LINKS Resource: https://learn.microsoft.com/en-us/azure/templates/microsoft.authorization/roleassignments */ targetScope = 'resourceGroup' // input parameters param appConfigurationName string @allowed([ 'App Configuration Data Owner' 'App Configuration Data Reader' 'Contributor' ]) param roleName string param principalId string @allowed([ 'Device' 'ForeignGroup' 'Group' 'ServicePrincipal' 'User' ]) param principalType string // variables var roleIds = { 'App Configuration Data Owner': resourceId('Microsoft.Authorization/roleAssignments', '5ae67dd6-50cb-40e7-96ff-dc2bfa4b606b') 'App Configuration Data Reader': resourceId('Microsoft.Authorization/roleAssignments', '516239f1-63e1-4d78-a4de-a74fb236a071') 'Contributor': resourceId('Microsoft.Authorization/roleAssignments', 'b24988ac-6180-42a0-ab88-20f7382dd24c') } // exists resource appConfig 'Microsoft.AppConfiguration/configurationStores@2023-03-01' existing = { name: appConfigurationName } resource roleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { scope: appConfig name: guid(roleIds[roleName], principalId, resourceId('Microsoft.AppConfiguration/configurationStores', appConfigurationName)) properties: { roleDefinitionId: roleIds[roleName] principalId: principalId principalType: principalType } }
Deploy Example
The following code is used to deploy permissions to the app configuration store. Note that a uniqueString function is used on the devTeam.teamName to ensure that the role assignment name is not too long as the maximum length is 64 characters, and that the teamName is coming from an array which is defined in a separate parameter file:
// scrum teams param scrumTeams array // permissions param AzureADEnterpriseAppId string // Permissions for a set of teams module roleAssignmentTeamAccess '../modules/role-assignment-appconfiguration.bicep' = [for devTeam in devTeams: { name: 'rbac-team-${uniqueString(devTeam.teamName)}' dependsOn: [ appConfig ] params: { appConfigurationName: appConfig.outputs.appConfigurationName roleName: 'App Configuration Data Reader' principalId: devTeam.objectId principalType: 'Group' } }] // Permissions for a service principal module rolePipeline '../modules/role-assignment-appconfiguration.bicep' = { name: 'rbac-sp-${appConfigurationName}' dependsOn: [ appConfig ] params: { appConfigurationName: appConfig.outputs.appConfigurationName roleName: 'App Configuration Data Owner' principalId: AzureADEnterpriseAppId principalType: 'ServicePrincipal' } }
This is the parameters file for the devTeams parameter above:
{ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { // these are the names and objectIds of the dev teams "devTeams": { "value": [ { "teamName": "Dev team frontend", "objectId": "4e3b4ef3-4495-4cdc-a981-f009ad6f3ebf" }, { "teamName": "Dev team backend", "objectId": "fecefe42-46bf-4ff7-af8a-cc0b7bf6ea85" }, { "teamName": "Dev team api", "objectId": "a704c4a1-9397-43cd-a37f-3b210bf6eb04" } ] } } }
As such, to deploy such a bicep file using the azure cli you would use the following command:
az deployment group create \ --resource-group myResourceGroup \ --name "appconfig-permissions" \ --template-file appconfiguration.bicep \ --parameters AzureADEntAppId="d60692dc-666c-410f-bb14-bb045fd36dd9" \ --parameters devteams-parameterfile.jsonc