I am getting this weird issue in Bicep where when I try to deploy the security policy for Azure Front Door it works when custom domain is defined but when no custom domain is defined and trying to use the frontdoor endpoint it throws the below error. There seem to be some validation issue in trying to reference the FrontDoor when using the FrontDoor Endpoint Id to associate to the Security Policy.
"message": "Deployment template validation failed: 'The resource 'Microsoft.Cdn/profiles/afd-test' is not defined in the template. Please see https://aka.ms/arm-syntax for usage details.'."
main.bicep
module azureFrontDoor 'FrontDoorEndpoint.bicep' = {
name: 'azureFrontDoor${frontDoorProfileName}'
scope: resourceGroup(SharedResourceGroup)
params: {
frontDoorProfileName: frontDoorProfileName
endpointName: frontDoorEndpointName
wafRateLimitThreshold: wafRateLimitThreshold
}
}
WafPolicy.bicep
param wafManagedRuleSets array = [
{
ruleSetType: 'Microsoft_DefaultRuleSet'
ruleSetVersion: '2.1'
ruleSetAction: 'Block'
}
{
ruleSetType: 'Microsoft_BotManagerRuleSet'
ruleSetVersion: '1.0'
}
]
var wafRateLimitRuleForDDoSCustomRuleSet = [
{
action: 'Block'
enabledState: 'Enabled'
matchConditions: [
{
matchValue: [
'::/0'
]
matchVariable: 'SocketAddr'
negateCondition: false
operator: 'IPMatch'
}
]
name: 'RateLimitRuleForDDoS'
priority: 100
rateLimitDurationInMinutes: 5
rateLimitThreshold: wafRateLimitThreshold
ruleType: 'RateLimitRule'
}
]
resource wafPolicy 'Microsoft.Network/FrontDoorWebApplicationFirewallPolicies@2022-05-01' = {
name: wadPolicyName
location: 'global'
sku: {
name: skuName
}
properties: {
policySettings: {
enabledState: 'Enabled'
mode: wafMode
}
managedRules: {
managedRuleSets: wafManagedRuleSets
}
customRules: {
rules: empty(wafCustomRuleSets) ? wafRateLimitRuleForDDoSCustomRuleSet : concat(wafRateLimitRuleForDDoSCustomRuleSet, wafCustomRuleSets)
}
}
}
output wafPolicyId string = wafPolicy.id
FrontDooEndpoint.bicep
resource frontDoorProfile 'Microsoft.Cdn/profiles@2023-05-01' existing = {
name: frontDoorProfileName
}
resource wafPolicy 'Microsoft.Network/FrontDoorWebApplicationFirewallPolicies@2022-05-01' existing = if (wafPolicyName != '') {
name: wafPolicyName
scope: resourceGroup(wafPolicyResourceGroup)
}
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' existing = if(!empty(keyVaultName)) {
name: keyVaultName
scope: resourceGroup(keyVaultRG)
}
resource frontDoorEndpoint 'Microsoft.Cdn/profiles/afdEndpoints@2023-05-01' = {
name: endpointName
parent: frontDoorProfile
location: 'global'
properties: {
autoGeneratedDomainNameLabelScope: 'SubscriptionReuse'
enabledState: 'Enabled'
}
}
module wafPolicyModule './WafPolicy.bicep' = if(empty(wafPolicyName)) {
name: 'wafPolicy'
params: {
wadPolicyName: replace('WAF${endpointName}', '-', '')
wafRateLimitThreshold: wafRateLimitThreshold
skuName: skuName
wafMode: wafMode
wafCustomRuleSets: wafCustomRuleSets
}
}
resource secret 'Microsoft.Cdn/profiles/secrets@2023-05-01' = if(!empty(customDomainName)) {
parent: frontDoorProfile
name: certificateName
properties: {
parameters: {
type: 'CustomerCertificate'
useLatestVersion: true
secretSource: {
id: '${keyVault.id}/secrets/${certificateName}'
}
}
}
}
resource customDomain 'Microsoft.Cdn/profiles/customDomains@2023-05-01' = if(!empty(customDomainName)) {
parent: frontDoorProfile
name: replace(replace(customDomainName, '.', '-'), '-', '')
properties: {
hostName: customDomainName
tlsSettings: {
certificateType: 'CustomerCertificate'
minimumTlsVersion: 'TLS12'
secret: {
id: secret.id
}
}
}
}
resource securityPolicy 'Microsoft.Cdn/profiles/securityPolicies@2023-05-01' = {
name: replace('Security${endpointName}', '-', '')
parent: frontDoorProfile
properties: {
parameters: {
type: 'WebApplicationFirewall'
wafPolicy: {
id: !empty(wafPolicyName) ? wafPolicy.id : wafPolicyModule.outputs.wafPolicyId
}
associations: [
{
domains: [
{
id: !empty(customDomainName) ? customDomain.id : frontDoorEndpoint.id
}
]
patternsToMatch: [
'/*'
]
}
]
}
}
}
The main issues for this deployment are the empty
"checks".
Even if you set a condition, the resource name can't be empty as ARM checks the number segments in the resource id.
This will throw an error:
resource frontDoorProfile 'Microsoft.Cdn/profiles@2023-05-01' existing = if (!empty(frontDoorProfileName)) {
name: frontDoorProfileName
}
This won't
resource frontDoorProfile 'Microsoft.Cdn/profiles@2023-05-01' existing = if (!empty(frontDoorProfileName)) {
name: !empty(frontDoorProfileName) ? frontDoorProfileName : 'notapplicable'
}
I created a CustomDomain.bicep
module that is executed only when a custom domain is required:
param keyVaultRG string
param keyVaultName string
param frontDoorProfileName string
param customDomainName string
param certificateName string
// Return custom domain id
output id string = customDomain.id
// Get a reference to the existing front door
resource frontDoorProfile 'Microsoft.Cdn/profiles@2023-05-01' existing = {
name: frontDoorProfileName
}
// Get a ference to existing key vault
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
name: keyVaultName
scope: resourceGroup(keyVaultRG)
}
// Create the secret
resource secret 'Microsoft.Cdn/profiles/secrets@2023-05-01' = {
parent: frontDoorProfile
name: certificateName
properties: {
parameters: {
type: 'CustomerCertificate'
useLatestVersion: true
secretSource: {
id: '${keyVault.id}/secrets/${certificateName}'
}
}
}
}
// Create the custom domain
resource customDomain 'Microsoft.Cdn/profiles/customDomains@2023-05-01' = {
parent: frontDoorProfile
name: replace(replace(customDomainName, '.', '-'), '-', '')
properties: {
hostName: customDomainName
tlsSettings: {
certificateType: 'CustomerCertificate'
minimumTlsVersion: 'TLS12'
secret: {
id: secret.id
}
}
}
}
The FrontDoorEndpoint.bicep
module then looks like this:
param frontDoorProfileName string
param wafPolicyName string = ''
param wafPolicyResourceGroup string = ''
param keyVaultName string = ''
param keyVaultRG string = ''
param endpointName string
param customDomainName string = ''
param certificateName string = ''
param wafRateLimitThreshold int
param skuName string = 'Premium_AzureFrontDoor'
param wafMode string = 'Detection'
param wafCustomRuleSets array = []
// Get a reference to the existing front door
resource frontDoorProfile 'Microsoft.Cdn/profiles@2023-05-01' existing = {
name: frontDoorProfileName
}
// Create the endpoint
resource frontDoorEndpoint 'Microsoft.Cdn/profiles/afdEndpoints@2023-05-01' = {
name: endpointName
parent: frontDoorProfile
location: 'global'
properties: {
autoGeneratedDomainNameLabelScope: 'SubscriptionReuse'
enabledState: 'Enabled'
}
}
// If there is an existing waf, use it
resource wafPolicy 'Microsoft.Network/FrontDoorWebApplicationFirewallPolicies@2022-05-01' existing = if (!empty(wafPolicyName)) {
name: !empty(wafPolicyName) ? wafPolicyName : 'notapplicable'
scope: resourceGroup(wafPolicyResourceGroup)
}
// Otherwise create a new/default one
module wafPolicyModule './WafPolicy.bicep' = if (empty(wafPolicyName)) {
name: 'wafPolicy'
params: {
wadPolicyName: replace('WAF${endpointName}', '-', '')
wafRateLimitThreshold: wafRateLimitThreshold
skuName: skuName
wafMode: wafMode
wafCustomRuleSets: wafCustomRuleSets
}
}
// Create a custom domain if required
var addCustomDomain = !empty(customDomainName) && !empty(certificateName) && !empty(keyVaultRG) && !empty(keyVaultName)
module customDomainModule './CustomDomain.bicep' = if (addCustomDomain) {
name: 'CustomDomain'
params: {
frontDoorProfileName: frontDoorProfile.name
keyVaultRG: keyVaultRG
keyVaultName: keyVaultName
certificateName: certificateName
customDomainName: customDomainName
}
}
// Create the security policy
resource securityPolicy 'Microsoft.Cdn/profiles/securityPolicies@2023-05-01' = {
name: replace('Security${endpointName}', '-', '')
parent: frontDoorProfile
properties: {
parameters: {
type: 'WebApplicationFirewall'
wafPolicy: {
id: !empty(wafPolicyName) ? wafPolicy.id : wafPolicyModule.outputs.wafPolicyId
}
associations: [
{
domains: [
{
id: addCustomDomain ? customDomainModule.outputs.id : frontDoorEndpoint.id
}
]
patternsToMatch: [
'/*'
]
}
]
}
}
}
Then from the maim deployment it is invoked like that:
param frontDoorProfileName string = 'fd-thomastest-001'
param SharedResourceGroup string = 'stackoverflow'
param frontDoorEndpointName string = 'fde-thomastest-001'
param wafRateLimitThreshold int = 1000
module azureFrontDoor 'FrontDoorEndpoint.bicep' = {
name: 'azureFrontDoor${frontDoorProfileName}'
scope: resourceGroup(SharedResourceGroup)
params: {
frontDoorProfileName: frontDoorProfileName
endpointName: frontDoorEndpointName
wafRateLimitThreshold: wafRateLimitThreshold
}
}