Bicep Interfaces
This chapter details the interfaces/schemas for the AVM Resource Modules features/extension resources as referenced in RMFR4 and RMFR5.
Diagnostic Settings
Important
Allowed values for logs and metric categories or category groups MUST NOT be specified to keep the module implementation evergreen for any new categories or category groups added by RPs, without module owners having to update a list of allowed values and cut a new release of their module.
// ============== //
// Parameters //
// ============== //
import { diagnosticSettingFullType } from 'br/public:avm/utl/types/avm-common-types:>version<'
@description('Optional. The diagnostic settings of the service.')
param diagnosticSettings diagnosticSettingFullType[]?
// ============= //
// Resources //
// ============= //
resource >singularMainResourceType<_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [for (diagnosticSetting, index) in (diagnosticSettings ?? []): {
name: diagnosticSetting.?name ?? '${name}-diagnosticSettings'
properties: {
storageAccountId: diagnosticSetting.?storageAccountResourceId
workspaceId: diagnosticSetting.?workspaceResourceId
eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId
eventHubName: diagnosticSetting.?eventHubName
metrics: [for group in (diagnosticSetting.?metricCategories ?? [ { category: 'AllMetrics' } ]): {
category: group.category
enabled: group.?enabled ?? true
timeGrain: null
}]
logs: [for group in (diagnosticSetting.?logCategoriesAndGroups ?? [ { categoryGroup: 'allLogs' } ]): {
categoryGroup: group.?categoryGroup
category: group.?category
enabled: group.?enabled ?? true
}]
marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId
logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType
}
scope: >singularMainResourceType<
}]
diagnosticSettings: [
{
name: 'diagSetting1'
logCategoriesAndGroups: [
{
category: 'AzurePolicyEvaluationDetails'
}
{
category: 'AuditEvent'
}
]
metricCategories: [
{
category: 'AllMetrics'
}
]
logAnalyticsDestinationType: 'Dedicated'
workspaceResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}'
storageAccountResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccounts/{storageAccountName}'
eventHubAuthorizationRuleResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.EventHub/namespaces/{namespaceName}/eventhubs/{eventHubName}/authorizationrules/{authorizationRuleName}'
eventHubName: '{eventHubName}'
marketplacePartnerResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{partnerResourceProvider}/{partnerResourceType}/{partnerResourceName}'
}
]
// ============== //
// Parameters //
// ============== //
import { diagnosticSettingMetricsOnlyType } from 'br/public:avm/utl/types/avm-common-types:>version<'
@description('Optional. The diagnostic settings of the service.')
param diagnosticSettings diagnosticSettingMetricsOnlyType[]?
// ============= //
// Resources //
// ============= //
resource >singularMainResourceType<_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [for (diagnosticSetting, index) in (diagnosticSettings ?? []): {
name: diagnosticSetting.?name ?? '${name}-diagnosticSettings'
properties: {
storageAccountId: diagnosticSetting.?storageAccountResourceId
workspaceId: diagnosticSetting.?workspaceResourceId
eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId
eventHubName: diagnosticSetting.?eventHubName
metrics: [for group in (diagnosticSetting.?metricCategories ?? [ { category: 'AllMetrics' } ]): {
category: group.category
enabled: group.?enabled ?? true
timeGrain: null
}]
marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId
logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType
}
scope: >singularMainResourceType<
}]
diagnosticSettings: [
{
name: 'diagSetting1'
metricCategories: [
{
category: 'AllMetrics'
}
]
logAnalyticsDestinationType: 'Dedicated'
workspaceResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}'
storageAccountResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccounts/{storageAccountName}'
eventHubAuthorizationRuleResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.EventHub/namespaces/{namespaceName}/eventhubs/{eventHubName}/authorizationrules/{authorizationRuleName}'
eventHubName: '{eventHubName}'
marketplacePartnerResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{partnerResourceProvider}/{partnerResourceType}/{partnerResourceName}'
}
]
// ============== //
// Parameters //
// ============== //
import { diagnosticSettingLogsOnlyType } from 'br/public:avm/utl/types/avm-common-types:>version<'
@description('Optional. The diagnostic settings of the service.')
param diagnosticSettings diagnosticSettingLogsOnlyType[]?
// ============= //
// Resources //
// ============= //
resource >singularMainResourceType<_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [for (diagnosticSetting, index) in (diagnosticSettings ?? []): {
name: diagnosticSetting.?name ?? '${name}-diagnosticSettings'
properties: {
storageAccountId: diagnosticSetting.?storageAccountResourceId
workspaceId: diagnosticSetting.?workspaceResourceId
eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId
eventHubName: diagnosticSetting.?eventHubName
logs: [for group in (diagnosticSetting.?logCategoriesAndGroups ?? [ { categoryGroup: 'allLogs' } ]): {
categoryGroup: group.?categoryGroup
category: group.?category
enabled: group.?enabled ?? true
}]
marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId
logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType
}
scope: >singularMainResourceType<
}]
diagnosticSettings: [
{
name: 'diagSetting1'
logCategoriesAndGroups: [
{
category: 'AzurePolicyEvaluationDetails'
}
{
category: 'AuditEvent'
}
]
logAnalyticsDestinationType: 'Dedicated'
workspaceResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}'
storageAccountResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccounts/{storageAccountName}'
eventHubAuthorizationRuleResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.EventHub/namespaces/{namespaceName}/eventhubs/{eventHubName}/authorizationrules/{authorizationRuleName}'
eventHubName: '{eventHubName}'
marketplacePartnerResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{partnerResourceProvider}/{partnerResourceType}/{partnerResourceName}'
}
]
Note
In the provided example for Diagnostic Settings, both logs and metrics are enabled for the associated resource. However, it is IMPORTANT to note that certain resources may not support both diagnostic setting types/categories. In such cases, the resource configuration MUST be modified accordingly to ensure proper functionality and compliance with system requirements.
Role Assignments
// ============== //
// Parameters //
// ============== //
import { roleAssignmentType } from 'br/public:avm/utl/types/avm-common-types:>version<'
@description('Optional. Array of role assignments to create.')
param roleAssignments roleAssignmentType[]?
// ============= //
// Variables //
// ============= //
var builtInRoleNames = {
// Add other relevant built-in roles here for your resource as per BCPNFR5
Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')
Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')
Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')
'Role Based Access Control Administrator (Preview)': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')
'User Access Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')
}
var formattedRoleAssignments = [
for (roleAssignment, index) in (roleAssignments ?? []): union(roleAssignment, {
roleDefinitionId: builtInRoleNames[?roleAssignment.roleDefinitionIdOrName] ?? (contains(roleAssignment.roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/')
? roleAssignment.roleDefinitionIdOrName
: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleAssignment.roleDefinitionIdOrName))
})
]
// ============= //
// Resources //
// ============= //
resource >singularMainResourceType<_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [
for (roleAssignment, index) in (formattedRoleAssignments ?? []): {
name: roleAssignment.?name ?? guid(>singularMainResourceType<.id, roleAssignment.principalId, roleAssignment.roleDefinitionId)
properties: {
roleDefinitionId: roleAssignment.roleDefinitionId
principalId: roleAssignment.principalId
description: roleAssignment.?description
principalType: roleAssignment.?principalType
condition: roleAssignment.?condition
conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set
delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId
}
scope: >singularMainResourceType<
}
]
roleAssignments: [
{
roleDefinitionIdOrName: 'Owner'
principalId: nestedDependencies.outputs.managedIdentityPrincipalId
principalType: 'ServicePrincipal'
}
{
roleDefinitionIdOrName: 'b24988ac-6180-42a0-ab88-20f7382dd24c'
principalId: nestedDependencies.outputs.managedIdentityPrincipalId
principalType: 'ServicePrincipal'
}
{
roleDefinitionIdOrName: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')
principalId: nestedDependencies.outputs.managedIdentityPrincipalId
principalType: 'ServicePrincipal'
}
{
name: guid('Custom role assignment name seed')
roleDefinitionIdOrName: 'Storage Blob Data Reader'
principalId: '00000000-0000-0000-0000-000000000000'
principalType: 'Group'
description: 'Group with read-only access'
condition: '@Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase 'foo_storage_container''
conditionVersion: '2.0'
}
]
Details on child, extension and cross-referenced resources:
- Modules MUST support Role Assignments on child, extension and cross-referenced resources as well as the primary resource via parameters/variables
Resource Locks
// ============== //
// Parameters //
// ============== //
import { lockType } from 'br/public:avm/utl/types/avm-common-types:>version<'
@description('Optional. The lock settings of the service.')
param lock lockType?
// ============= //
// Resources //
// ============= //
resource >singularMainResourceType<_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock ?? {}) && lock.?kind != 'None') {
name: lock.?name ?? 'lock-${name}'
properties: {
level: lock.?kind ?? ''
notes: lock.?kind == 'CanNotDelete' ? 'Cannot delete resource or child resources.' : 'Cannot delete or modify the resource or child resources.'
}
scope: >singularMainResourceType<
}
lock: 'CanNotDelete'
Details on child and extension resources:
- Locks SHOULD be able to be set for child resources of the primary resource in resource modules
Details on cross-referenced resources:
- Locks MUST be automatically applied to cross-referenced resources if the primary resource has a lock applied.
- This MUST also be able to be turned off for each of the cross-referenced resources by the module consumer via a parameter/variable if they desire
An example of this is a Key Vault module that has a Private Endpoints enabled. If a lock is applied to the Key Vault via the lock
parameter/variable then the lock should also be applied to the Private Endpoint automatically, unless the privateEndpointLock/private_endpoint_lock
(example name) parameter/variable is set to None
Tags
@description('Optional. Tags of the resource.')
param tags object?
tags: {
key: 'value'
'another-key': 'another-value'
integers: 123
}
Details on child, extension and cross-referenced resources:
- Tags MUST be automatically applied to child, extension and cross-referenced resources, if tags are applied to the primary resource.
- By default, all tags set for the primary resource will automatically be passed down to child, extension and cross-referenced resources.
- This MUST be able to be overridden by the module consumer so they can specify alternate tags for child, extension and cross-referenced resources, if they desire via a parameter/variable
- If overridden by the module consumer, no merge/union of tags will take place from the primary resource and only the tags specified for the child, extension and cross-referenced resources will be applied
Managed Identities
// ============== //
// Parameters //
// ============== //
import { managedIdentityAllType } from 'br/public:avm/utl/types/avm-common-types:>version<'
@description('Optional. The managed identity definition for this resource.')
param managedIdentities managedIdentityAllType?
// ============= //
// Variables //
// ============= //
var formattedUserAssignedIdentities = reduce(map((managedIdentities.?userAssignedResourceIds ?? []), (id) => { '${id}': {} }), {}, (cur, next) => union(cur, next)) // Converts the flat array to an object like { '${id1}': {}, '${id2}': {} }
var identity = !empty(managedIdentities) ? {
type: (managedIdentities.?systemAssigned ?? false) ? (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'SystemAssigned,UserAssigned' : 'SystemAssigned') : (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'UserAssigned' : null)
userAssignedIdentities: !empty(formattedUserAssignedIdentities) ? formattedUserAssignedIdentities : null
} : null
// ============= //
// Resources //
// ============= //
resource >singularMainResourceType< '>providerNamespace</>resourceType<@>apiVersion<' = {
name: name
identity: identity
properties: {
... // other properties
}
}
// =========== //
// Outputs //
// =========== //
@description('The principal ID of the system assigned identity.')
output systemAssignedMIPrincipalId string? = >singularMainResourceType<.?identity.?principalId
managedIdentities: {
systemAssigned: true
userAssignedResourceIds: [
'/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identityName}'
'/subscriptions/{subscriptionId2}/resourceGroups/{resourceGroupName2}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identityName2}'
]
}
// ============== //
// Parameters //
// ============== //
import { managedIdentityOnlySysAssignedType } from 'br/public:avm/utl/types/avm-common-types:>version<'
@description('Optional. The managed identity definition for this resource.')
param managedIdentities managedIdentityOnlySysAssignedType?
// ============= //
// Variables //
// ============= //
var identity = !empty(managedIdentities)
? {
type: (managedIdentities.?systemAssigned ?? false) ? 'SystemAssigned' : null
}
: null
// ============= //
// Resources //
// ============= //
resource >singularMainResourceType< '>providerNamespace</>resourceType<@>apiVersion<' = {
name: name
identity: identity
properties: {
... // other properties
}
}
// =========== //
// Outputs //
// =========== //
@description('The principal ID of the system assigned identity.')
output systemAssignedMIPrincipalId string? = >singularMainResourceType<.?identity.?principalId
managedIdentities: {
systemAssigned: true
}
// ============== //
// Parameters //
// ============== //
import { managedIdentityOnlyUserAssignedType } from 'br/public:avm/utl/types/avm-common-types:>version<'
@description('Optional. The managed identity definition for this resource.')
param managedIdentities managedIdentityOnlyUserAssignedType?
// ============= //
// Variables //
// ============= //
var formattedUserAssignedIdentities = reduce(map((managedIdentities.?userAssignedResourceIds ?? []), (id) => { '${id}': {} }), {}, (cur, next) => union(cur, next)) // Converts the flat array to an object like { '${id1}': {}, '${id2}': {} }
var identity = !empty(managedIdentities)
? {
type: !empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'UserAssigned' : 'None'
userAssignedIdentities: !empty(formattedUserAssignedIdentities) ? formattedUserAssignedIdentities : null
}
: null
// ============= //
// Resources //
// ============= //
resource >singularMainResourceType< '>providerNamespace</>resourceType<@>apiVersion<' = {
name: name
identity: identity
properties: {
... // other properties
}
}
managedIdentities: {
userAssignedResourceIds: [
'/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identityName}'
'/subscriptions/{subscriptionId2}/resourceGroups/{resourceGroupName2}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identityName2}'
]
}
Reason for differences in User Assigned data type in languages:
- We do not foresee the Managed Identity Resource Provider team to ever add additional properties within the empty object (
{}
) value required on the input of a User Assigned Managed Identity. - In Bicep we therefore have removed the need for this to be declared and just converted it to a simple array of Resource IDs
Private Endpoints
E.g., for services that only have one private endpoint type.
// ============== //
// Parameters //
// ============== //
import { privateEndpointSingleServiceType } from 'br/public:avm/utl/types/avm-common-types:>version<'
@description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.')
param privateEndpoints privateEndpointSingleServiceType[]?
// ============= //
// Resources //
// ============= //
module >singularMainResourceType<_privateEndpoints 'br/public:avm/res/network/private-endpoint:>version<' = [for (privateEndpoint, index) in (privateEndpoints ?? []): {
name: '${uniqueString(deployment().name, location)}->singularMainResourceType<-PrivateEndpoint-${index}'
scope: !empty(privateEndpoint.?resourceGroupResourceId)
? resourceGroup(
split((privateEndpoint.?resourceGroupResourceId ?? '//'), '/')[2],
split((privateEndpoint.?resourceGroupResourceId ?? '////'), '/')[4]
)
: resourceGroup(
split((privateEndpoint.?subnetResourceId ?? '//'), '/')[2],
split((privateEndpoint.?subnetResourceId ?? '////'), '/')[4]
)
params: {
// Variant 1: A default service can be assumed (i.e., for services that only have one private endpoint type)
name: privateEndpoint.?name ?? 'pep-${last(split(>singularMainResourceType<.id, '/'))}-${privateEndpoint.?service ?? '>defaultServiceName<'}-${index}'
privateLinkServiceConnections: privateEndpoint.?isManualConnection != true ? [
{
name: privateEndpoint.?privateLinkServiceConnectionName ?? '${last(split(>singularMainResourceType<.id, '/'))}-${privateEndpoint.?service ?? '>defaultServiceName<'}-${index}'
properties: {
privateLinkServiceId: >singularMainResourceType<.id
groupIds: [
privateEndpoint.?service ?? '>defaultServiceName<'
]
}
}
] : null
manualPrivateLinkServiceConnections: privateEndpoint.?isManualConnection == true ? [
{
name: privateEndpoint.?privateLinkServiceConnectionName ?? '${last(split(>singularMainResourceType<.id, '/'))}-${privateEndpoint.?service ?? '>defaultServiceName<'}-${index}'
properties: {
privateLinkServiceId: >singularMainResourceType<.id
groupIds: [
privateEndpoint.?service ?? '>defaultServiceName<'
]
requestMessage: privateEndpoint.?manualConnectionRequestMessage ?? 'Manual approval required.'
}
}
] : null
subnetResourceId: privateEndpoint.subnetResourceId
enableTelemetry: privateEndpoint.?enableTelemetry ?? enableTelemetry
location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location
lock: privateEndpoint.?lock ?? lock
privateDnsZoneGroup: privateEndpoint.?privateDnsZoneGroup
roleAssignments: privateEndpoint.?roleAssignments
tags: privateEndpoint.?tags ?? tags
customDnsConfigs: privateEndpoint.?customDnsConfigs
ipConfigurations: privateEndpoint.?ipConfigurations
applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds
customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName
}
}]
privateEndpoints: {
{
name: 'myPeName'
privateLinkServiceConnectionName: 'myPrivateLinkConnectionName'
lock: 'CanNotDelete'
tags: {
'hidden-title': 'This is visible in the resource name'
}
subnetResourceId: '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRg/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mysubnet'
resourceGroupResourceId: '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRg'
applicationSecurityGroupResourceIds: [
'/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRg/providers/Microsoft.Network/applicationSecurityGroups/myAsg'
]
privateDnsZoneGroup: {
privateDnsZoneGroupConfigs: [
{
name: 'config'
privateDnsZoneResourceId: '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRg/providers/Microsoft.Network/privateDnsZones/myZone'
}
]
}
customDnsConfigs: [
{
fqdn: 'fqdn1.example.com'
ipAddresses: [
'10.0.0.1',
'10.0.0.2'
]
}
]
networkInterfaceName: 'nic1'
ipConfigurations: [
{
name: 'ipconfig1'
groupId: 'vault'
memberName: 'default'
privateIpAddress: '10.0.0.7'
}
]
roleAssignments: [
{
roleDefinitionIdOrName: 'Owner'
principalId: '11111111-1111-1111-1111-111111111111'
principalType: 'ServicePrincipal'
}
{
roleDefinitionIdOrName: subscriptionResourceId('Microsoft.Authorization/roleDefinitions','acdd72a7-3385-48ef-bd42-f606fba81ae7')
principalId: '11111111-1111-1111-1111-111111111111'
principalType: 'ServicePrincipal'
}
]
}
}
E.g., for services that have more than one private endpoint type, like a Storage Account (blob, file, etc.)
// ============== //
// Parameters //
// ============== //
import { privateEndpointMultiServiceType } from 'br/public:avm/utl/types/avm-common-types:>version<'
@description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.')
param privateEndpoints privateEndpointMultiServiceType[]?
// ============= //
// Resources //
// ============= //
module >singularMainResourceType<_privateEndpoints 'br/public:avm/res/network/private-endpoint:>version<' = [for (privateEndpoint, index) in (privateEndpoints ?? []): {
name: '${uniqueString(deployment().name, location)}->singularMainResourceType<-PrivateEndpoint-${index}'
scope: !empty(privateEndpoint.?resourceGroupResourceId)
? resourceGroup(
split((privateEndpoint.?resourceGroupResourceId ?? '//'), '/')[2],
split((privateEndpoint.?resourceGroupResourceId ?? '////'), '/')[4]
)
: resourceGroup(
split((privateEndpoint.?subnetResourceId ?? '//'), '/')[2],
split((privateEndpoint.?subnetResourceId ?? '////'), '/')[4]
)
params: {
// Variant 2: A default service cannot be assumed (i.e., for services that have more than one private endpoint type, like Storage Account)
name: privateEndpoint.?name ?? 'pep-${last(split(>singularMainResourceType<.id, '/'))}-${privateEndpoint.service}-${index}'
privateLinkServiceConnections: privateEndpoint.?isManualConnection != true ? [
{
name: privateEndpoint.?privateLinkServiceConnectionName ?? '${last(split(>singularMainResourceType<.id, '/'))}-${privateEndpoint.service}-${index}'
properties: {
privateLinkServiceId: >singularMainResourceType<.id
groupIds: [
privateEndpoint.service
]
}
}
] : null
manualPrivateLinkServiceConnections: privateEndpoint.?isManualConnection == true ? [
{
name: privateEndpoint.?privateLinkServiceConnectionName ?? '${last(split(>singularMainResourceType<.id, '/'))}-${privateEndpoint.service}-${index}'
properties: {
privateLinkServiceId: >singularMainResourceType<.id
groupIds: [
privateEndpoint.service
]
requestMessage: privateEndpoint.?manualConnectionRequestMessage ?? 'Manual approval required.'
}
}
] : null
subnetResourceId: privateEndpoint.subnetResourceId
enableTelemetry: privateEndpoint.?enableTelemetry ?? enableTelemetry
location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location
lock: privateEndpoint.?lock ?? lock
privateDnsZoneGroup: privateEndpoint.?privateDnsZoneGroup
roleAssignments: privateEndpoint.?roleAssignments
tags: privateEndpoint.?tags ?? tags
customDnsConfigs: privateEndpoint.?customDnsConfigs
ipConfigurations: privateEndpoint.?ipConfigurations
applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds
customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName
}
}]
privateEndpoints: {
{
name: 'myPeName'
privateLinkServiceConnectionName: 'myPrivateLinkConnectionName'
lock: 'CanNotDelete'
tags: {
'hidden-title': 'This is visible in the resource name'
}
service: 'blob'
subnetResourceId: '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRg/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mysubnet'
resourceGroupResourceId: '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRg'
applicationSecurityGroupResourceIds: [
'/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRg/providers/Microsoft.Network/applicationSecurityGroups/myAsg'
]
privateDnsZoneGroup: {
privateDnsZoneGroupConfigs: [
{
name: 'config'
privateDnsZoneResourceId: '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRg/providers/Microsoft.Network/privateDnsZones/myZone'
}
]
}
customDnsConfigs: [
{
fqdn: 'fqdn1.example.com'
ipAddresses: [
'10.0.0.1',
'10.0.0.2'
]
}
]
networkInterfaceName: 'nic1'
ipConfigurations: [
{
name: 'ipconfig1'
groupId: 'blob'
memberName: 'default'
privateIpAddress: '10.0.0.7'
}
]
roleAssignments: [
{
roleDefinitionIdOrName: 'Owner'
principalId: '11111111-1111-1111-1111-111111111111'
principalType: 'ServicePrincipal'
}
{
roleDefinitionIdOrName: subscriptionResourceId('Microsoft.Authorization/roleDefinitions','acdd72a7-3385-48ef-bd42-f606fba81ae7')
principalId: '11111111-1111-1111-1111-111111111111'
principalType: 'ServicePrincipal'
}
]
}
}
Notes:
- The properties defined in the schema above are the minimum amount of properties expected to be exposed for Private Endpoints in AVM Resource Modules.
- A module owner MAY chose to expose additional properties of the Private Endpoint resource
- However, module owners considering this SHOULD contact the AVM core team first to consult on how the property should be exposed to avoid future breaking changes to the schema that may be enforced upon them
- A module owner MAY chose to expose additional properties of the Private Endpoint resource
- Module owners MAY chose to define a list of allowed value for the ‘service’ (a.k.a.
groupIds
) property- However, they should do so with caution as should a new service appear for their resource module, a new release will need to be cut to add this new service to the allowed values
- Whereas not specifying allowed values will allow flexibility from day 0 without the need for any changes and releases to be made
- However, they should do so with caution as should a new service appear for their resource module, a new release will need to be cut to add this new service to the allowed values
Customer Managed Keys
// ============== //
// Parameters //
// ============== //
import { customerManagedKeyType } from 'br/public:avm/utl/types/avm-common-types:>version<'
@description('Optional. The customer managed key definition.')
param customerManagedKey customerManagedKeyType?
// ============= //
// Resources //
// ============= //
resource cMKKeyVault 'Microsoft.KeyVault/vaults@2023-02-01' existing = if (!empty(customerManagedKey.?keyVaultResourceId)) {
name: last(split((customerManagedKey.?keyVaultResourceId ?? 'dummyVault'), '/'))
scope: resourceGroup(
split((customerManagedKey.?keyVaultResourceId ?? '//'), '/')[2],
split((customerManagedKey.?keyVaultResourceId ?? '////'), '/')[4]
)
resource cMKKey 'keys@2023-02-01' existing = if (!empty(customerManagedKey.?keyVaultResourceId) && !empty(customerManagedKey.?keyName)) {
name: customerManagedKey.?keyName ?? 'dummyKey'
}
}
resource cMKUserAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = if (!empty(customerManagedKey.?userAssignedIdentityResourceId)) {
name: last(split(customerManagedKey.?userAssignedIdentityResourceId ?? 'dummyMsi', '/'))
scope: resourceGroup(
split((customerManagedKey.?userAssignedIdentityResourceId ?? '//'), '/')[2],
split((customerManagedKey.?userAssignedIdentityResourceId ?? '////'), '/')[4]
)
}
resource >singularMainResourceType< '>providerNamespace</>resourceType<@>apiVersion<' = {
name: '>exampleResource<'
properties: {
... // other properties
encryption: !empty(customerManagedKey)
? {
keySource: 'Microsoft.KeyVault'
keyVaultProperties: {
keyVaultUri: cMKKeyVault.properties.vaultUri
keyName: customerManagedKey!.keyName
keyVersion: !empty(customerManagedKey.?keyVersion ?? '')
? customerManagedKey!.keyVersion
: last(split(cMKKeyVault::cMKKey.properties.keyUriWithVersion, '/'))
keyIdentifier: !empty(customerManagedKey.?keyVersion ?? '')
? '${cMKKeyVault::cMKKey.properties.keyUri}/${customerManagedKey!.keyVersion}'
: cMKKeyVault::cMKKey.properties.keyUriWithVersion
identityClientId: !empty(customerManagedKey.?userAssignedIdentityResourceId ?? '')
? cMKUserAssignedIdentity.properties.clientId
: null
identity: !empty(customerManagedKey.?userAssignedIdentityResourceId)
? {
userAssignedIdentity: cMKUserAssignedIdentity.id
}
: null
}
}
: null
}
}
customerManagedKey: {
keyVaultResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.KeyVault/vaults/{keyVaultName}'
keyName: '{keyName}'
keyVersion: '{keyVersion}'
userAssignedIdentityResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{uamiName}'
}
// ============== //
// Parameters //
// ============== //
import { customerManagedKeyWithAutoRotateType } from 'br/public:avm/utl/types/avm-common-types:>version<'
@description('Optional. The customer managed key definition.')
param customerManagedKey customerManagedKeyWithAutoRotateType?
// ============= //
// Resources //
// ============= //
resource cMKKeyVault 'Microsoft.KeyVault/vaults@2023-02-01' existing = if (!empty(customerManagedKey.?keyVaultResourceId)) {
name: last(split((customerManagedKey.?keyVaultResourceId ?? 'dummyVault'), '/'))
scope: resourceGroup(
split((customerManagedKey.?keyVaultResourceId ?? '//'), '/')[2],
split((customerManagedKey.?keyVaultResourceId ?? '////'), '/')[4]
)
resource cMKKey 'keys@2023-02-01' existing = if (!empty(customerManagedKey.?keyVaultResourceId) && !empty(customerManagedKey.?keyName)) {
name: customerManagedKey.?keyName ?? 'dummyKey'
}
}
resource cMKUserAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = if (!empty(customerManagedKey.?userAssignedIdentityResourceId)) {
name: last(split(customerManagedKey.?userAssignedIdentityResourceId ?? 'dummyMsi', '/'))
scope: resourceGroup(
split((customerManagedKey.?userAssignedIdentityResourceId ?? '//'), '/')[2],
split((customerManagedKey.?userAssignedIdentityResourceId ?? '////'), '/')[4]
)
}
resource >singularMainResourceType< '>providerNamespace</>resourceType<@>apiVersion<' = {
name: '>exampleResource<'
properties: {
... // other properties
encryption: !empty(customerManagedKey)
? {
keySource: 'Microsoft.KeyVault'
keyVaultProperties: {
keyVaultUri: cMKKeyVault.properties.vaultUri
keyName: customerManagedKey!.keyName
keyVersion: !empty(customerManagedKey.?keyVersion ?? '')
? customerManagedKey!.keyVersion
: (customerManagedKey.?autoRotationEnabled ?? true)
? null
: last(split(cMKKeyVault::cMKKey.properties.keyUriWithVersion, '/'))
keyIdentifier: !empty(customerManagedKey.?keyVersion ?? '')
? '${cMKKeyVault::cMKKey.properties.keyUri}/${customerManagedKey!.keyVersion}'
: (customerManagedKey.?autoRotationEnabled ?? true)
? cMKKeyVault::cMKKey.properties.keyUri
: cMKKeyVault::cMKKey.properties.keyUriWithVersion
identityClientId: !empty(customerManagedKey.?userAssignedIdentityResourceId ?? '')
? cMKUserAssignedIdentity.properties.clientId
: null
identity: !empty(customerManagedKey.?userAssignedIdentityResourceId)
? {
userAssignedIdentity: cMKUserAssignedIdentity.id
}
: null
}
}
: null
}
}
customerManagedKey: {
keyVaultResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.KeyVault/vaults/{keyVaultName}'
keyName: '{keyName}'
autoRotationEnabled: {autoRotationEnabled}
userAssignedIdentityResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{uamiName}'
}
Secrets export
Secrets used inside a module can be exported to a Key Vault reference provided as per the below schema.
This implementation provides a secure way around the current limitation of Bicep on providing a secure template output (that can be used for secrets).
The user MUST
- provide the resource Id to a Key Vault. The principal used for the deployment MUST be allowed to set secrets in this Key Vault.
- provide a name for each secret they want to store (opt-in). The module will suggest which secrets are available via the implemented user-defined type.
The module returns an output table where the key is the name of the secret the user provided, and the value contains both the secret’s resource Id and URI.
Important
The feature MUST be implemented as per the below schema. Diversions are only allowed in places marked as >text<
to ensure a consistent user experience across modules.
User Defined Type, Parameter & Resource Example
// ============== //
// Parameters //
// ============== //
@description('Optional. Key vault reference and secret settings for the module\'s secrets export.')
param secretsExportConfiguration secretsExportConfigurationType?
// ============= //
// Resources //
// ============= //
module secretsExport 'modules/keyVaultExport.bicep' = if (secretsExportConfiguration != null) {
name: '${uniqueString(deployment().name, location)}-secrets-kv'
scope: resourceGroup(
split((secretsExportConfiguration.?keyVaultResourceId ?? '//'), '/')[2],
split((secretsExportConfiguration.?keyVaultResourceId ?? '////'), '/')[4]
)
params: {
keyVaultName: last(split(secretsExportConfiguration.?keyVaultResourceId ?? '//', '/'))
secretsToSet: union(
[],
contains(secretsExportConfiguration!, '>secretToExport1<Name')
? [
{
name: secretsExportConfiguration!.>secretToExport1<Name
value: >secretReference1< // e.g., >singularMainResourceType<.listKeys().primaryMasterKey
}
]
: [],
contains(secretsExportConfiguration!, '>secretToExport2<Name')
? [
{
name: secretsExportConfiguration!.>secretToExport2<Name
value:>secretReference2< // e.g., >singularMainResourceType<.listKeys().secondaryMasterKey
}
]
: []
// (...)
)
}
}
// =========== //
// Outputs //
// =========== //
import { secretsOutputType } from 'br/public:avm/utl/types/avm-common-types:>version<'
@description('A hashtable of references to the secrets exported to the provided Key Vault. The key of each reference is each secret\'s name.')
output exportedSecrets secretsOutputType = (secretsExportConfiguration != null)
? toObject(secretsExport.outputs.secretsSet, secret => last(split(secret.secretResourceId, '/')), secret => secret)
: {}
// =============== //
// Definitions //
// =============== //
@export()
type secretsExportConfigurationType = {
@description('Required. The resource ID of the key vault where to store the secrets of this module.')
keyVaultResourceId: string
@description('Optional. The >secretToExport1< secret name to create.')
>secretToExport1<Name: string?
@description('Optional. The >secretToExport2< secret name to create.')
>secretToExport2<Name: string?
// (...)
}
Input Example with Values
// ============== //
// Parameters //
// ============== //
@description('Optional. Key vault reference and secret settings for the module\'s secrets export.')
param secretsExportConfiguration secretsExportConfigurationType?
// ============= //
// Resources //
// ============= //
module secretsExport 'modules/keyVaultExport.bicep' = if (secretsExportConfiguration != null) {
name: '${uniqueString(deployment().name, location)}-secrets-kv'
scope: resourceGroup(
split((secretsExportConfiguration.?keyVaultResourceId ?? '//'), '/')[2],
split((secretsExportConfiguration.?keyVaultResourceId ?? '////'), '/')[4]
)
params: {
keyVaultName: last(split(secretsExportConfiguration.?keyVaultResourceId ?? '//', '/'))
secretsToSet: union(
[],
contains(secretsExportConfiguration!, '>secretToExport1<Name')
? [
{
name: secretsExportConfiguration!.>secretToExport1<Name
value: >secretReference1< // e.g., >singularMainResourceType<.listKeys().primaryMasterKey
}
]
: [],
contains(secretsExportConfiguration!, '>secretToExport2<Name')
? [
{
name: secretsExportConfiguration!.>secretToExport2<Name
value:>secretReference2< // e.g., >singularMainResourceType<.listKeys().secondaryMasterKey
}
]
: []
// (...)
)
}
}
// =========== //
// Outputs //
// =========== //
import { secretsOutputType } from 'br/public:avm/utl/types/avm-common-types:>version<'
@description('A hashtable of references to the secrets exported to the provided Key Vault. The key of each reference is each secret\'s name.')
output exportedSecrets secretsOutputType = (secretsExportConfiguration != null)
? toObject(secretsExport.outputs.secretsSet, secret => last(split(secret.secretResourceId, '/')), secret => secret)
: {}
// =============== //
// Definitions //
// =============== //
@export()
type secretsExportConfigurationType = {
@description('Required. The resource ID of the key vault where to store the secrets of this module.')
keyVaultResourceId: string
@description('Optional. The >secretToExport1< secret name to create.')
>secretToExport1<Name: string?
@description('Optional. The >secretToExport2< secret name to create.')
>secretToExport2<Name: string?
// (...)
}
[modules/keyVaultExport.bicep] file
// ============== //
// Parameters //
// ============== //
@description('Required. The name of the Key Vault to set the secrets in.')
param keyVaultName string
import { secretToSetType } from 'br/public:avm/utl/types/avm-common-types:>version<'
@description('Required. The secrets to set in the Key Vault.')
param secretsToSet secretToSetType[]
// ============= //
// Resources //
// ============= //
resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = {
name: keyVaultName
}
resource secrets 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = [
for secret in secretsToSet: {
name: secret.name
parent: keyVault
properties: {
value: secret.value
}
}
]
// =========== //
// Outputs //
// =========== //
import { secretSetOutputType } from 'br/public:avm/utl/types/avm-common-types:>version<'
@description('The references to the secrets exported to the provided Key Vault.')
output secretsSet secretSetOutputType[] = [
#disable-next-line outputs-should-not-contain-secrets // Only returning the references, not a secret value
for index in range(0, length(secretsToSet ?? [])): {
secretResourceId: secrets[index].id
secretUri: secrets[index].properties.secretUri
secretUriWithVersion: secrets[index].properties.secretUriWithVersion
}
]
Output Usage Example
When using a module that implements the above interface, you can access its outputs for example in the following ways:
// ============== //
// Parameters //
// ============== //
@description('Optional. Key vault reference and secret settings for the module\'s secrets export.')
param secretsExportConfiguration secretsExportConfigurationType?
// ============= //
// Resources //
// ============= //
module secretsExport 'modules/keyVaultExport.bicep' = if (secretsExportConfiguration != null) {
name: '${uniqueString(deployment().name, location)}-secrets-kv'
scope: resourceGroup(
split((secretsExportConfiguration.?keyVaultResourceId ?? '//'), '/')[2],
split((secretsExportConfiguration.?keyVaultResourceId ?? '////'), '/')[4]
)
params: {
keyVaultName: last(split(secretsExportConfiguration.?keyVaultResourceId ?? '//', '/'))
secretsToSet: union(
[],
contains(secretsExportConfiguration!, '>secretToExport1<Name')
? [
{
name: secretsExportConfiguration!.>secretToExport1<Name
value: >secretReference1< // e.g., >singularMainResourceType<.listKeys().primaryMasterKey
}
]
: [],
contains(secretsExportConfiguration!, '>secretToExport2<Name')
? [
{
name: secretsExportConfiguration!.>secretToExport2<Name
value:>secretReference2< // e.g., >singularMainResourceType<.listKeys().secondaryMasterKey
}
]
: []
// (...)
)
}
}
// =========== //
// Outputs //
// =========== //
import { secretsOutputType } from 'br/public:avm/utl/types/avm-common-types:>version<'
@description('A hashtable of references to the secrets exported to the provided Key Vault. The key of each reference is each secret\'s name.')
output exportedSecrets secretsOutputType = (secretsExportConfiguration != null)
? toObject(secretsExport.outputs.secretsSet, secret => last(split(secret.secretResourceId, '/')), secret => secret)
: {}
// =============== //
// Definitions //
// =============== //
@export()
type secretsExportConfigurationType = {
@description('Required. The resource ID of the key vault where to store the secrets of this module.')
keyVaultResourceId: string
@description('Optional. The >secretToExport1< secret name to create.')
>secretToExport1<Name: string?
@description('Optional. The >secretToExport2< secret name to create.')
>secretToExport2<Name: string?
// (...)
}
Which returns a JSON-formatted output like:
{
"exportedSecrets": {
"Type": "Object",
"Value": {
">secretToExportName1<": {
"secretResourceId": "/subscriptions/<subId>/resourceGroups/<rgName>providers/Microsoft.KeyVault/vaults/<vaultName>/secrets/>secretToExportName1<",
"secretUri": "https://<vaultName>.vault.azure.net/secrets/>secretToExportName1<"
},
">secretToExportName2<": {
"secretResourceId": "/subscriptions/<subId>/resourceGroups/<rgName>providers/Microsoft.KeyVault/vaults/<vaultName>/secrets/>secretToExportName2<",
"secretUri": "https://<vaultName>.vault.azure.net/secrets/>secretToExportName2<"
}
}
},
"specificSecret": {
"Type": "String",
"Value": "/subscriptions/<subId>/resourceGroups/<rgName>providers/Microsoft.KeyVault/vaults/<vaultName>/secrets/>secretToExportName1<"
},
"exportedSecretResourceIds": {
"Type": "Array",
"Value": [
"/subscriptions/<subId>/resourceGroups/<rgName>providers/Microsoft.KeyVault/vaults/<vaultName>/secrets/>secretToExportName1<",
"/subscriptions/<subId>/resourceGroups/<rgName>providers/Microsoft.KeyVault/vaults/<vaultName>/secrets/>secretToExportName2<"
]
}
}
Azure Monitor Alerts
Note
This interface is a SHOULD instead of a MUST and therefore the AVM core team have not mandated a interface schema to use.