Deploying the Front Door using Bicep

Bicep is a very nice language that rests on top of ARM. I have always preferred ARM to scripting as “wanted state” is a much better way of looking at anything relating to infrastructure.

But this reliance on ARM also means that whatever template definition is output by the product, that is what you must use. Case in point: Azure Front Door.

Circular reference

A circular reference in an age old problem in coding. You cannot use circual references because, logically, it does not work. It is a series of references where the last object references the first, resulting in a closed loop.

Within Azure Front Door (or AFD) we have this problem, a lot. Within AFD it refers to itself, and if you add a Rules Engine (RE), that refers to the AFD, that refers to the RE.

If you do like I do: Setup something the first time using the portal, and the extract the ARM using the Export Template, or Insert Resource in VS Code to get the Bicep Code directly, you will find that the generated ARM contains a lot of errors due to circular references.

We need to overcome this, and solve some other issues.

The first problem

The first problem comes about 5 rows in, when we need to define the (first) routing rule. You need to assign it an ID. This ID consist of the resource ID for the AFD that we are just about to create!

This is the first of many circular references.

The solution

I solved this, and all other circular references, by using variables and string interpolation. Simply put: Create the resource ID yourself. You know what the ID will be, because you are just about to create it.

Create two useful variables at the top of the bicep file.

var frontDoorName = 'contoso-afd-test'
var frontDoorResourceId = '{resourceGroup().id}/providers/Microsoft.Network/frontDoors/{frontDoorName}'

You must deploy the AFD within the context of a resource group, even if the AFD is a global service. You get the start of the resource ID by reffering to this, and the add the rest. The resulting frontDoorResourceId would be:

/subscriptions/GUIDHERE/resourcegroups/contoso-TEST-rg/providers/Microsoft.Network/frontdoors/contoso-afd-test

Now, we can use the frontDoorResourceId variable, every time a sub-resource needs to be defined, such as a routing rule or backend pool, as defined below.

routingRules: [
  {
    id: '{frontDoorResourceId}/RoutingRules/APIm'
    name: 'APIm'
    properties: {
      routeConfiguration: {
        forwardingProtocol: 'HttpsOnly'
        backendPool: {
          id: '{frontDoorResourceId}/BackendPools/ApimEurope${Env}'
        }
        '@odata.type': '#Microsoft.Azure.FrontDoor.Models.FrontdoorForwardingConfiguration'
      }
      ...
    }
  }
]

The second problem

The next problem is the rules engines. In the template definition it looks like you need to add another Resource ID, but there is a problem. The Rules Engines is a totally different resource type and it needs to be created separately. This means you simply cannot refer to it in the same way as with the frontDoorResourceId above.

The solution

It is very easy: Do not add the property to the AFD Definition. This property is not read only, but it will be assigned at deploy time by the Rules Engines Definition you add to the same Bicep File.

resource frontDoorRulesEngine 'Microsoft.Network/frontDoors/rulesEngines@2020-05-01' = {
  name: rulesEngineName
  parent: frontDoor
  properties: {
    rules: [
      {
        name: 'RuleNumberOne'
        priority: 0
        action: {
          routeConfigurationOverride: {
            forwardingProtocol: 'HttpsOnly'
            backendPool: {
              id: '${frontDoorResourceId}/BackendPools/FightClub'
            }
            '@odata.type': '#Microsoft.Azure.FrontDoor.Models.FrontdoorForwardingConfiguration'
          }
          requestHeaderActions: []
          responseHeaderActions: []
        }
  ...
  // Rule settings go here
  ...

      }
    ]
  }
}

You assign the AFD you are about to create as the rules engine’s parent. This will connect the AFD and the RE at deploytime.

Note the use of the frontDoorResourceId to refer to the Backend Pool.

The third (last) problem

The this is only a problem if you are using the Web Application Firewall (WAF) and you should. If you need to enable the WAF you must refer to a policy, or rather a FrontDoorWebApplicationFirewallPolicies resource. You do this when you create your frontendEndpoints.

A solution

This is not the solution, but I know it works for me. I define the WAF policies in a separate Bicep file as these tend to be separate from the Front Door. Remember, you can reuse the same policy for different WAFs.

Create a separate file with the resource definition. If you want to send a customized error message you need to run the base64 string function on it before deployment. This is much more useful than converting it to Base64 and pasting that sting into the Bicep-file.

Name the policy using a parameter.

param PolicyName string

resource defaultPolicy 'Microsoft.Network/FrontDoorWebApplicationFirewallPolicies@2020-11-01' = {
  name: PolicyName
   policySettings: {
     enabledState: 'Enabled'
     mode: 'Prevention'
     customBlockResponseStatusCode: 406
     customBlockResponseBody: base64('<html><head><title>You are blocked</title></head><body bgcolor="#FFB299"><p><h1>WAF custom response</h1>You are being blocked. If you need access, contact support with this reference{{azure-ref}}</p></body></html>')
   }
}

Send the policy name parameter to your AFD bicep and construct the reference using string interpolation. My naming below assumes that the policy is deployed in the same resource group as the AFD. If that is not your case, create a reference to that resource group in the bicep file, or update your bicep file for the policy to use an output parameter and send that to the AFD bicep deployment.

About the error message and the use of azure-ref, consult this documentation.

output policyResourceID string = myPolicy.id

This is how I constructed the resource reference in my AFD Bicep file. Note the liberal use of the frontDoorResourceId and frontDoorName variables.

frontendEndpoints: [
  {
    id: '{frontDoorResourceId}/FrontendEndpoints/{frontDoorName}-azurefd-net'
    name: '{frontDoorName}-azurefd-net'
    properties: {
      hostName: '{frontDoorName}.azurefd.net'
      sessionAffinityEnabledState: 'Enabled'
      sessionAffinityTtlSeconds: 0
      webApplicationFirewallPolicyLink: {
        id: '{resourceGroup().id}/providers/Microsoft.Network/frontdoorwebapplicationfirewallpolicies/{WAFPolicyName}'
      }
    }
  }
]

Conclusion

Deploying AFD is possible but also a bit of a pain using Bicep. This is notdue to the language, but the ARM template. AFD is a very old service that was in use by the XBOX network for years before being made available in Azure. I think this shows, and I hope for a revisional update at some point.

Still: This should NOT deter you from using Bicep (or ARM) and infrastructure as code for your AFD installations. Please avoid PowerShell and Azure Cli.