Skip to content

Front Door Per App Split

Daniel Truong edited this page Oct 14, 2026 · 19 revisions

Front Door per-app split

Reference page for how Azure Front Door routes app traffic, repo digitalspace/eagle-edge. See Azure Hosting for the profile-wide setup.

Per-app origin groups

Front Door holds one origin group per app on both estates and matches on UrlPath. Test and prod share the same routes and patterns; only the origin hostname differs:

Origin group Route(s) Pattern Test origin Prod origin Probe
og-eagle-public default /* Storage static website Storage static website —
og-eagle-api eagle-api /api, /api/*, /eguide, /eguide/* eagle-api-6cdc9e-test.apps.silver.devops.gov.bc.ca eagle-api-6cdc9e-prod.apps.silver.devops.gov.bc.ca (Route eagle-api-direct) GET /api/health
og-eagle-admin admin /admin, /admin/* eagle-admin-6cdc9e-test.apps.silver.devops.gov.bc.ca, unused; the OpenShift objects are retired eagle-admin-6cdc9e-prod.apps.silver.devops.gov.bc.ca (Route eagle-admin-direct), unused while adminOrigin = static; kept for rollback only HEAD /admin/
og-eagle-admin-static admin /admin, /admin/* Storage static website eagleadmtestvymaysch2agd Storage static website eagleadmprodrhj3zycszo6j —
og-demi demi, demi-cached, demi-documents /demi-search/*, /demi-projects/*, /s/*, /demi/api/* (demi-cached: /demi-search/documents/recent-uploads only, cached, query string kept; demi-documents: /demi-search/documents/*, cached per query string) demi-apim-test.azure-api.net demi-apim-prod.azure-api.net none — APIM Consumption bills per call
og-analytics usage, analytics /api/usage/*, /api/analytics, /analytics, /analytics/* demi-apim-test.azure-api.net demi-apim-prod.azure-api.net —

On /demi/api/* the demi rule set allows only GET, HEAD and OPTIONS, and only the read prefixes projects, documents, users, groups, inspections, lists, organizations, comment-periods, comments, updates, notifications, counts and me under /demi/api/; other requests go to /edge-blocked (azure/modules/front-door.bicep, eagle-edge).

Front Door rule sets carry the path rewrites for these routes: rulesdemi rewrites /demi-search/search to /api/search, /demi-projects/<id> to /api/projects/<id>, and /s/ to /api/s/; rulesusage rewrites /api/usage/ to /analytics/ and /api/analytics to /analytics; ruleseagleapi carries the /api/v2/projects to /api/public/project alias. rulesadmin holds the adminslash redirect rule and ruleseagleapi also holds the ssdfactsheet redirect rule, both carried over from the retired rulesbackend set (source: eagle-edge/azure/modules/front-door.bicep lines ~956-959).

eagle-admin origin

The admin route has two origin groups, og-eagle-admin (OpenShift Route, retired on test, kept for rollback on prod) and og-eagle-admin-static (storage web endpoint). Bicep param adminOrigin in azure/main.<env>.bicepparam picks one:

Env adminOrigin Serves /admin/ from
Test static Storage static website eagleadmtestvymaysch2agd, resource group c4b0a8-test-rg, profile eagle-edge-test
Prod static Storage static website eagleadmprodrhj3zycszo6j, resource group rg-eagle-public-prod, profile eagle-edge-prod, endpoint eagle-public-prod

Dev has no admin app: the OpenShift eagle-admin objects in 6cdc9e-dev and 6cdc9e-test are retired (Helm release uninstalled, Routes eagle-admin and eagle-admin-direct deleted) and there is no Azure dev edge. Prod keeps Deployment eagle-admin (0 replicas), its Service, Route eagle-admin-direct and ConfigMap eagle-admin-env-js in 6cdc9e-prod, and the 6cdc9e-tools image streams and build configs, for rollback only (Rollback Procedures).

deployAdminSite creates the storage account, og-eagle-admin-static and the extra rulesadmin rules. adminUploaderPrincipalId is the object id of the CI identity eagle-admin-cicd-<env>; it grants that identity its roles on the account and the profile.

Rule set rulesadmin:

Rule Effect
adminslash 301 /admin to /admin/
spafallback Rewrites to /admin/index.html when the last path segment has no dot and the path is not under /admin/api
spafallbackmatrix Same rewrite when the last segment has matrix params (;)
securityheaders HSTS, X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy, and a Content-Security-Policy header, enforced on test and prod. connect-src allows the Keycloak host and https://*.in.applicationinsights.azure.com

A missing asset returns 404. /admin/api/* is never rewritten. /api/* goes to the eagle-api route on the same host; eagle-api runs on OpenShift.

Front Door drops a leading slash from request-path condition values, so a RegEx value must not start with /.

A route switch between the two origin groups takes about 20 minutes to reach every edge location. The origins carry different builds, so during that window users can get 404 for hashed assets.

Rollback for test and prod: Rollback Procedures. Full parameter notes: "eagle-admin static origin" in the eagle-edge README.

Origin gates

The origins refuse requests that skip Front Door. Both gates ship off on prod until the rollout; test has them on (eagle-api) or in log mode (DEMI).

App Gate How it decides Exempt
eagle-api Middleware api/middleware/edgeOnly.js For requests to the direct Route host, 403 unless X-Azure-FDID equals FRONT_DOOR_ID /api/health; other hosts (in-cluster callers, the console Route)
DEMI dispatch in src/http/router.js, setting EDGE_GATE 403 in enforce mode unless the request came through APIM and carries the edge secret, X-APIM-Subscription or X-Api-Key /health*

eagle-api turns the gate on with Helm value directRoute.edgeOnly; see Configuration Management. DEMI modes and the log query are in the eagle-demi wiki page Azure-Environments.

The eagle-api gate has two modes, set by EDGE_ONLY_MODE. enforce answers 403. log serves the request and records it. Both modes record every refusal as the App Insights custom event edge-gate-refusal, with properties path, host and mode, whatever LOG_LEVEL is. Count them:

AppEvents
| where Name == "edge-gate-refusal"
| summarize n=count() by path=tostring(Properties.path), mode=tostring(Properties.mode)
| order by n desc

Prod rollout: turn the gate on in log mode, read the query for a few days, then switch to enforce.

The og-eagle-api probe asks /api/health, which the gate exempts and which does not touch the database.

Legacy document links

Rules in ruleseagleapi send GET and HEAD requests for the old eagle-api document paths to /demi-search/documents/<id>/download?inline=1. redirect=1 is not sent, so DEMI answers with the file itself instead of a second 302 to the object store. The address bar stays on the site host. Paths and rule names are in Eagle-Search.

Route demi-documents (/demi-search/documents/*, origin group og-demi, rule set rulesdemi) caches those answers. The cache key is the path plus the query string, compression is off, and /demi-search/documents/recent-uploads still matches demi-cached first. DEMI sets public, max-age=300 on a public file and no-store or private on everything else, so only the file is cached, for 5 minutes. Front Door revalidates with Last-Modified; it ignores ETag.

Rule demidocumentinline in rulesdemi (order 12) catches links that still carry redirect=1&inline=1, which eagle-public's Angular line builds. It matches a 24-hex id and a query string holding both parameters, in any order, and answers 302 to the same path with ?inline=1 alone. A redirect=1 link without inline=1, and a UUID id, are not touched.

Deploy the edge after DEMI. Until DEMI runs the stream mode, ?inline=1 alone returns JSON, not the file. The route and rules are on test and prod (eagle-edge v1.4.6 and later). On prod, DEMI v0.172.2 and later runs stream mode. The APIM policy buffer-response="false" is live on test and prod. After a deploy, Front Door edges converge unevenly for 7 to 10 minutes: some edges apply the new route's rules while demi still strips only /demi-search, and every document download answers 404. Deploy at low traffic and re-check downloads 10 minutes after the deploy job finishes. DEMI side: ADR-011-Public-Read-Surface and Operating-DEMI-in-Azure.

Scraper user agent

Rule blockscraperua is in ruleseagleapi (order 8), rulesdemi (order 4) and rulesdemidocs (order 2). A request whose User-Agent contains document research, propertypdfresearch or rldata-ingest (any case) gets a 302 to /robots.txt and the rule set stops, so no origin sees it. Rules attach to routes, so *.azurefd.net hostnames are covered.

Deploys are Incremental, so removing the rule from the template leaves it on the profile. Remove it per rule set:

az afd rule delete -g <rg> --profile-name eagle-edge-<env> --rule-set-name ruleseagleapi -n blockscraperua
az afd rule delete -g <rg> --profile-name eagle-edge-<env> --rule-set-name rulesdemi -n blockscraperua
az afd rule delete -g <rg> --profile-name eagle-edge-<env> --rule-set-name rulesdemidocs -n blockscraperua

WAF security policy binding

Security policy sp-<env> binds the WAF policy to the custom domains and to the endpoints, so *.azurefd.net hostnames are inspected too. The WAF policy itself is managed outside the template.

robots.txt and sitemap

eagle-public ships public/robots.txt on both release lines. It sits at the $web root and Front Door serves it as a file. It allows all crawlers except under /analytics and /admin, and carries a Sitemap: line.

GET /api/public/sitemap.xml on eagle-api lists projects only, as <PUBLIC_SITE_URL>/p/<project id>. The response is cached for one hour (Cache-Control: public, max-age=3600).

rproxy retirement

The rproxy Deployment in 6cdc9e-test is scaled to 0 replicas. eagle-test.apps.silver.devops.gov.bc.ca (the OpenShift route that fronted test app traffic) returns 503; nothing routes app traffic to it.

The rproxy Deployment in 6cdc9e-prod runs 0 replicas. It is scaled to 0 directly on the cluster, outside Helm. The deployed Helm release still has replicaCount: 2 (release revision 30, 2026-09-10).

replicaCount: 0 is in helm/rproxy/values-prod.yaml only on the eao-nginx master branch. That change is newer than the highest tag, v2.7.40. Deploy to Prod deploys a tag, so the change has not reached prod.

A helm rollback, an upgrade that reuses the release values, or a Deploy to Prod run with tag v2.7.40 or older sets rproxy back to 2 replicas. Do not run any of these for rproxy until a tag that contains replicaCount: 0 exists.

Front Door routes projects.eao.gov.bc.ca and www.projects.eao.gov.bc.ca straight to the per-app origins above.

Rollback:

  • Test: oc scale deploy rproxy -n 6cdc9e-test --replicas=2.
  • Prod: oc scale deploy rproxy -n 6cdc9e-prod --replicas=2, then repoint Front Door's custom domains at the OpenShift route again.

Front Door's routes are not reverted by scaling rproxy back up; rolling back the edge itself needs the per-app origin params redeployed empty so the estate falls back to og-eagle-backend / api-passthrough, or a redeploy of the pre-split tag.

Clone this wiki locally