Skip to content

Instantly share code, notes, and snippets.

@Jaykul
Last active August 16, 2022 10:52
Show Gist options
  • Save Jaykul/4d214a960fd66825fedc5b9fb5c39531 to your computer and use it in GitHub Desktop.
Save Jaykul/4d214a960fd66825fedc5b9fb5c39531 to your computer and use it in GitHub Desktop.
Azure Front Door with Rules Engine

This is a working template for deploying Azure Front Door together with Rules Engine rules.

Basically, there's currently an unavoidable circular dependency:

  1. Routing Rules are part of the Front Door
  2. Rules Engines are sub resources (which require the FrontDoor to exist before you can deploy them)
  3. For Rules Engines to work, the Routing Rules have to reference them ...

Obviously you can't reference the Rules Engine configuration on initial deploy, because it doesn't exist yet.

My work-around is to have a first-time-only clean deployment that creates the FrontDoor, and from then on to always create the RoutingRules before configuring the FrontDoor (which may then contain RoutingRules which reference the RulesEngine(s).

New-AzResourceGroupDeployment -ResourceGroupName YourRG -TemplateFile FrontDoor.bicep -FrontDoorName YourUniqueName -FirstTimeCleanDeployment:$true

If you deploy it the first time without setting FirstTimeCleanDeployment true, it will fail, so you can't forget to pass it the first time. Unfortunately, I have not yet found a way to prevent redeploying with FirstTimeCleanDeployment true -- which would wipe the configuration before recreating it.

Note: In my example FrontDoorActual.bicep file there's only a single backend and frontend and the only rule engine is just there to allow Cross-Origin requests, but it's just an example and I didn't want to make it too complicated. The idea is that you could take the FrontDoorClean.bicep template and use it the way I am here, but write your own FrontDoorActual.bicep to actually configure your front door.

Of course, we really just need Microsoft to fix this. Perhaps by making the RulesEngine independent of the FrontDoor.

@description('The name for the FrontDoor. Must be globally unique, one of a kind!')
param frontDoorName string
@description('This needs to be TRUE for the first deployment, but ONLY the first deployment')
param FirstTimeCleanDeployment bool = false
@description('A domain name that serves as the source of content for this FrontDoor')
param sourceDomain string = 'huddledmasses.org'
@description('Additional domains that will use javascripts from this CDN and must be added to the CORS rule')
param corsHosts array = [
'poshcode.org'
]
module doorway 'FrontDoorClean.bicep' = if(FirstTimeCleanDeployment) {
name: '${deployment().name}_afd0'
params: {
frontDoorName: frontDoorName
}
}
module frontDoor 'FrontDoorActual.bicep' = {
name: '${deployment().name}_afd2'
dependsOn: [
doorway
]
params: {
frontDoorName: frontDoorName
corsHosts: corsHosts
sourceDomain: sourceDomain
}
}
// This template requires the frontDoor to already exist
@description('The name for the FrontDoor. Must be globally unique, one of a kind!')
param frontDoorName string
@description('A domain name that serves as the source of content for this FrontDoor')
param sourceDomain string
@description('Additional domains that will use javascripts from this CDN and must be added to the CORS rule')
param corsHosts array = []
resource ruleEngines 'Microsoft.Network/frontDoors/rulesEngines@2020-05-01' = if (!empty(corsHosts)) {
name: '${frontDoorName}/${frontDoorName}'
properties: {
rules: [
{
name: 'AllowCORS'
priority: 0
action: {
responseHeaderActions: [
{
headerActionType: 'Overwrite'
headerName: 'Access-Control-Allow-Origin'
value: '*'
}
]
}
matchConditions: [
{
selector: 'Origin'
rulesEngineMatchVariable: 'RequestHeader'
rulesEngineOperator: 'Equal'
rulesEngineMatchValue: corsHosts
negateCondition: false
transforms: [
'Lowercase'
]
}
]
matchProcessingBehavior: 'Continue'
}
]
}
}
resource frontDoor 'Microsoft.Network/frontdoors@2020-05-01' = {
name: frontDoorName
dependsOn: [
ruleEngines
]
location: 'Global'
properties: {
friendlyName: frontDoorName
enabledState: 'Enabled'
healthProbeSettings: [
{
name: 'simple'
properties: {
path: '/'
protocol: 'Https'
intervalInSeconds: 30
healthProbeMethod: 'HEAD'
enabledState: 'Enabled'
}
}
{
name: 'ping'
properties: {
path: '/ping'
protocol: 'Https'
intervalInSeconds: 30
healthProbeMethod: 'HEAD'
enabledState: 'Enabled'
}
}
]
loadBalancingSettings: [
{
name: 'simple'
properties: {
sampleSize: 4
successfulSamplesRequired: 2
}
}
]
frontendEndpoints: [
{
name: frontDoorName
properties: {
hostName: '${frontDoorName}.azurefd.net'
sessionAffinityEnabledState: 'Disabled'
}
}
]
backendPools: [
{
name: frontDoorName
properties: {
backends: [
{
address: sourceDomain
backendHostHeader: sourceDomain
enabledState: 'Enabled'
httpPort: 80
httpsPort: 443
priority: 1 // lowest first
weight: 50 // weight among even priorities
}
]
loadBalancingSettings: {
id: resourceId('Microsoft.Network/FrontDoors/loadBalancingSettings', frontDoorName, 'simple')
}
healthProbeSettings: {
id: resourceId('Microsoft.Network/FrontDoors/healthProbeSettings', frontDoorName, 'simple')
}
}
}
]
routingRules: [
{
name: frontDoorName
properties: union({
frontendEndpoints: [
{
id: resourceId('Microsoft.Network/FrontDoors/FrontendEndpoints', frontDoorName, frontDoorName)
}
]
acceptedProtocols: [
'Https'
]
patternsToMatch: [
'/*'
]
routeConfiguration: {
'@odata.type': '#Microsoft.Azure.FrontDoor.Models.FrontdoorForwardingConfiguration'
backendPool: {
id: resourceId('Microsoft.Network/FrontDoors/BackendPools', frontDoorName, frontDoorName)
}
forwardingProtocol: 'HttpsOnly'
}
// don't try to link the rules on first run (and this only applies if you have CORS)
}, !empty(corsHosts) ? {
rulesEngine: {
id: resourceId('Microsoft.Network/FrontDoors/RulesEngines', frontDoorName, frontDoorName)
}
} : {})
}
{
name: 'httptohttps'
properties: {
frontendEndpoints: [
{
id: resourceId('Microsoft.Network/FrontDoors/FrontendEndpoints', frontDoorName, frontDoorName)
}
]
acceptedProtocols: [
'Http'
]
patternsToMatch: [
'/*'
]
routeConfiguration: {
'@odata.type': '#Microsoft.Azure.FrontDoor.Models.FrontdoorRedirectConfiguration'
redirectProtocol: 'HttpsOnly'
redirectType: 'Moved'
}
}
}
]
backendPoolsSettings: {
enforceCertificateNameCheck: 'Enabled'
sendRecvTimeoutSeconds: 30
}
}
}
param frontDoorName string
resource frontDoor 'Microsoft.Network/frontdoors@2020-05-01' = {
name: frontDoorName
location: 'Global'
properties: {
friendlyName: frontDoorName
enabledState: 'Enabled'
frontendEndpoints: [
{
name: frontDoorName
properties: {
hostName: '${frontDoorName}.azurefd.net'
sessionAffinityEnabledState: 'Disabled'
}
}
]
healthProbeSettings: [
{
name: 'simple'
properties: {
path: '/'
protocol: 'Https'
intervalInSeconds: 30
healthProbeMethod: 'HEAD'
enabledState: 'Enabled'
}
}
]
loadBalancingSettings: [
{
name: 'simple'
properties: {
sampleSize: 4
successfulSamplesRequired: 2
}
}
]
// backendPools: []
routingRules: [
{
name: 'HttpsOnly'
properties: {
frontendEndpoints: [
{
id: resourceId('Microsoft.Network/FrontDoors/FrontendEndpoints', frontDoorName, frontDoorName)
}
]
acceptedProtocols: [
'Http'
]
patternsToMatch: [
'/*'
]
routeConfiguration: {
'@odata.type': '#Microsoft.Azure.FrontDoor.Models.FrontdoorRedirectConfiguration'
redirectProtocol: 'HttpsOnly'
redirectType: 'Moved'
}
}
}
]
backendPoolsSettings: {
enforceCertificateNameCheck: 'Enabled'
sendRecvTimeoutSeconds: 30
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment