azureazure-resource-managerazure-bicepazure-front-door

Bicep Error: The resource is not defined in the template


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: [
            '/*'
          ]
        }
      ]
    }
  }
}

Solution

  • 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
      }
    }