Skip to content

API Gateway

Daniel Truong edited this page Oct 20, 2026 · 1 revision

API gateway

demi-apim-<env> is the Azure API Management (APIM) Consumption instance in front of the Flex app demi-api-fc-<env>, in test and prod. Its Bicep module is azure/modules/apim.bicep, gated by deployApim (true in main.test.bicepparam and main.prod.bicepparam). The estate around it is in Environment-Reality-and-Operational-Gotchas.

The gateway buys metering and a rate limit, not a security boundary. Consumption has no VNet, so demi-api-fc-<env>.azurewebsites.net stays publicly reachable and demi-api authenticates every caller itself.

APIs and subscriptions

Two APIs share the one backend, in both environments.

API Path Caller Requires
demi-api /api the browser nothing at the gateway
demi-machine /machine server-to-server Ocp-Apim-Subscription-Key, product machine, 300 calls per 60 s per subscription

Product machine holds the subscriptions eagle-api and engage in both environments. Their keys are read from APIM's control plane, never from this repo.

Header policy

The global policy deletes any client-supplied X-Gateway-Secret, X-APIM-Subscription and X-Client-Ip, then sets its own before forwarding:

Header Value
X-Gateway-Secret the named value gateway-secret, from Key Vault secret apim-gateway-secret in demi-kv-<env>
X-APIM-Subscription the resolved subscription name
X-Client-Ip the address APIM saw the request arrive from

demi-api trusts these headers only when the secret matches app setting APIM_GATEWAY_SECRET (a Key Vault reference). The caller's identity then resolves through an apikeys row apim:<subscription-name> with no key material, minted with POST /api/admin/api-keys and id: "apim:<name>".

What reaches the gateway

  • The test DEMI frontend reaches it same-origin: its Front Door endpoint routes /api and /api/* to the gateway (eagle-edge, gated by demiApimDeployed), so /api/config reports API_LOCATION empty. Prod has no DEMI frontend.
  • eagle-public's /demi-search route on eao-nginx proxies to the gateway in both environments (demi origin in values-test.yaml and values-prod.yaml).
  • eagle-api's push into DEMI (api/helpers/demiPush.js) calls demi-machine with the eagle-api subscription key in Ocp-Apim-Subscription-Key (env DEMI_APIM_KEY). DEMI_API_BASE is https://demi-apim-<env>.azure-api.net/machine (eagle-api helm/eagle-api/values-*.yaml), so the 300 calls per 60 s limit covers every push.
  • The extraction host does not use the gateway. It posts chunks straight to the Function App host with its own key, because APIM Consumption caps a request at 30 s and NDJSON ingest can run longer.

Front Door edge secret

Front Door (eagle-edge, profile eagle-edge-<env>) terminates every connection one hop outside APIM. Without a check, the address APIM asserts is Front Door's own egress address, shared by every visitor.

  • Rule sets rulesbackend (the /demi-search/* and /demi-projects/* passthrough) and rulesapi (the DEMI frontend and admin /api/* routes) each carry a rule stampedgesecret. It overwrites the request header X-Edge-Secret with a shared value and drops whatever a client sent.
  • demi-api trusts the header only when it matches app setting EDGE_SECRET, with a constant-time compare (same helper as the gateway secret). A match lets it read X-Azure-SocketIP, the peer Front Door accepted the connection from, as the caller's address.
  • X-Azure-ClientIP is never read: Front Door derives it from the caller's own X-Forwarded-For, so any browser can pick it.
  • An empty EDGE_SECRET is the off switch: demi-api ignores the header and keys the anonymous bulk quota on the address APIM asserts.

The value is one shared secret per environment.

Side Where it lives
DEMI Key Vault secret edge-secret, set by hand from the environment's devbox, read by demi-api-fc-<env> as a reference (see Secret-Sync). Both param files name it in optionalSecretNames.
eagle-edge No Key Vault. scripts/deploy-infra.sh reads EDGE_SECRET from the environment, falling back to OpenShift secret demi-app-secrets in 6cdc9e-test for test only; prod needs it exported. GitHub Actions reads the environment secret EDGE_SECRET.

Rotate both sides together; a mismatch only drops callers back to the APIM path. Deploying eagle-edge with an empty value leaves an existing stampedgesecret rule in place with its old value, because the deploy is Incremental. Removing the header takes an explicit az afd rule delete.

Caller IP reads from the right

src/utils/caller-ip.js feeds the bulk-download quota key, the request log and the audit trail. It walks X-Forwarded-For from the rightmost hop and never takes the first entry. A caller can prepend any value to that header, so only the hop each proxy itself appended is trustworthy.

Which hop wins, and the TRUSTED_PROXY_IPS case where the OpenShift rproxy in front of eagle-public is skipped: Bulk-Download "Access rules".

DEMI Wiki

Home

Current system

Azure

Domain

ADRs

Reference (frozen)

Related

Clone this wiki locally