Appearance
Azure Front Door
Azure Front Door (AFD) Standard is the global entry point for all Microtec ERP traffic. It provides TLS termination, WAF protection, custom domain routing, and origin health monitoring for both backend Container Apps and frontend Static Web Apps.
Architecture Overview
AFD Profile Inventory
All AFD profiles reside in the shared mic-erp-global-rg resource group (not per-environment RGs):
| AFD Profile | Environments | DDoS Protection |
|---|---|---|
mic-erp-fd | dev, stage | Disabled |
mic-erp-fd-2 | preprod, uat | Disabled |
mic-erp-prod-fd | production | Disabled |
All AFD Profiles in mic-erp-global-rg
AFD profiles are shared infrastructure. They are NOT deployed into per-environment resource groups. enableDdosProtection is false for all profiles.
Custom Domains per Environment
Each environment has its own set of custom domains configured in AFD:
| Environment | Gateway / API Domain | Auth Domain | Frontend Domain |
|---|---|---|---|
| dev | gateway.microtec-test.com | auth.microtec-test.com | *.microtec-test.com |
| stage | gateway.microtecstage.com | auth.microtecstage.com | *.microtecstage.com |
| preprod | gateway.microtec-preprod.com | auth.microtec-preprod.com | *.microtec-preprod.com |
| uat | gateway.microtec-uat.com | auth.microtec-uat.com | *.microtec-uat.com |
| production | gateway.onlinemicrotec.com.sa | auth.onlinemicrotec.com.sa | *.onlinemicrotec.com.sa |
TLS certificates are managed by AFD (auto-renewed via DigiCert). Wildcard certificates cover all subdomains per environment.
Origin Groups
Backend Origin Group
Routes requests to the Public CAE container apps:
bicep
resource backendOriginGroup 'Microsoft.Cdn/profiles/originGroups@2023-05-01' = {
name: 'backend-${environment}'
parent: afdProfile
properties: {
loadBalancingSettings: {
sampleSize: 4
successfulSamplesRequired: 3
additionalLatencyInMilliseconds: 50
}
healthProbeSettings: {
probePath: '/health/live'
probeRequestType: 'GET'
probeProtocol: 'Https'
probeIntervalInSeconds: 30
}
sessionAffinityState: 'Disabled'
}
}Origins in this group:
| Service | Origin FQDN | Port |
|---|---|---|
| Gateway.API | {cae-name}.{region}.azurecontainerapps.io | 443 |
| Keycloak | {cae-name}.{region}.azurecontainerapps.io | 443 |
Frontend Origin Group
Routes requests to Static Web Apps or Blob Storage:
bicep
resource frontendOriginGroup 'Microsoft.Cdn/profiles/originGroups@2023-05-01' = {
name: 'frontend-${environment}'
parent: afdProfile
properties: {
loadBalancingSettings: {
sampleSize: 4
successfulSamplesRequired: 2
additionalLatencyInMilliseconds: 0
}
healthProbeSettings: {
probePath: '/index.html'
probeRequestType: 'HEAD'
probeProtocol: 'Https'
probeIntervalInSeconds: 60
}
}
}Route Rules
AFD routes requests based on domain + path pattern:
| Route Name | Domain Pattern | Path Pattern | Origin Group | Description |
|---|---|---|---|---|
api-route | api.* | /* | Backend | All API calls → Gateway |
auth-route | auth.* | /* | Backend (Keycloak) | Keycloak OIDC traffic |
frontend-route | app.* | /* | Frontend | Angular MFE apps |
admin-block | auth.* | /admin/* | — | WAF rule blocks non-office IPs |
Bicep Route Example
bicep
resource apiRoute 'Microsoft.Cdn/profiles/afdEndpoints/routes@2023-05-01' = {
name: 'api-route'
parent: afdEndpoint
properties: {
originGroup: { id: backendOriginGroup.id }
supportedProtocols: ['Https']
patternsToMatch: ['/*']
forwardingProtocol: 'HttpsOnly'
linkToDefaultDomain: 'Enabled'
httpsRedirect: 'Enabled'
customDomains: [{ id: apiCustomDomain.id }]
cacheConfiguration: {
queryStringCachingBehavior: 'IgnoreQueryString'
compressionSettings: {
contentTypesToCompress: ['application/json']
isCompressionEnabled: true
}
}
}
}WAF Policy
AFD Standard uses a Standard_AzureFrontDoor SKU WAF policy attached to the profile. Each environment gets its own WAF policy for independent tuning.
Default Ruleset
bicep
resource wafPolicy 'Microsoft.Network/FrontDoorWebApplicationFirewallPolicies@2022-05-01' = {
name: 'mic-erp-${environment}-waf'
sku: { name: 'Standard_AzureFrontDoor' }
properties: {
policySettings: {
mode: 'Prevention'
enabledState: 'Enabled'
}
managedRules: {
managedRuleSets: [
{
ruleSetType: 'Microsoft_DefaultRuleSet'
ruleSetVersion: '2.1'
ruleSetAction: 'Block'
}
{
ruleSetType: 'Microsoft_BotManagerRuleSet'
ruleSetVersion: '1.0'
}
]
}
}
}Custom Rules
| Rule Name | Priority | Condition | Action |
|---|---|---|---|
BlockKeycloakAdmin | 100 | Path starts with /admin/ AND IP not in office range | Block |
AllowOfficeIPs | 90 | Source IP in {office-ip-list} | Allow |
RateLimitLogin | 200 | Path /protocol/openid-connect/token | Rate limit (100 req/min per IP) |
BlockSQLi | 300 | Detected SQL injection pattern | Block |
GeoBlockHighRisk | 400 | Source country in high-risk list | Block (prod only) |
WAF Detection Mode for New Rules
When adding new custom WAF rules, first set them to Detection mode for 48 hours and monitor the WAF logs before switching to Prevention mode. Overly broad rules have caused legitimate traffic to be blocked in the past.
Health Probes
AFD actively probes each origin to detect failures:
| Endpoint | Interval | Threshold | Action on Failure |
|---|---|---|---|
/health/live (Gateway) | 30s | 3 failures | Mark origin unhealthy, re-route |
/health/ready (Keycloak) | 30s | 3 failures | Mark origin unhealthy |
/index.html (Frontend) | 60s | 2 failures | Fall back to secondary origin |
Health probe requests originate from AFD edge nodes. Ensure the backend health/live endpoint does NOT require authentication and does NOT count against rate limits.
Cache Configuration
AFD caches static assets for frontend origins:
| Content Type | Cache Duration | Rules |
|---|---|---|
text/html | No cache | Always fetch (index.html must be fresh) |
application/javascript | 30 days | Cache by URL (includes content hash) |
text/css | 30 days | Cache by URL |
image/*, font/* | 365 days | Long-term cache |
application/json (API) | No cache | APIs are not cached |
Angular MFE builds output content-hashed filenames (e.g., main.abc12345.js), making long-term caching safe for JS/CSS assets.
Cache Purge
Cache is purged automatically by the update-afd.yml pipeline template after each frontend deployment:
bash
az afd endpoint purge \
--resource-group mic-erp-fr-{env}-network-rg \
--profile-name mic-erp-afd \
--endpoint-name mic-erp-{env}-endpoint \
--domains app.{env-domain} \
--content-paths '/*'Logs and Diagnostics
AFD logs are sent to the Log Analytics Workspace:
bash
# Query WAF block events (last hour)
az monitor log-analytics query \
--workspace {workspace-id} \
--analytics-query \
"AzureDiagnostics
| where Category == 'FrontdoorWebApplicationFirewallLog'
| where action_s == 'Block'
| where TimeGenerated > ago(1h)
| project TimeGenerated, clientIP_s, requestUri_s, ruleName_s"Related Documentation
- Container Apps — Public CAE and ingress configuration
- Static Web Apps — Frontend origin details
- Networking — VNet integration and private endpoints
- CI/CD Orchestrators —
update-afd.ymltemplate