Make the elb-openapi deploy self-contained + hide unreachable Swagger UI link¶
Motivation¶
Two gaps surfaced after a fresh dashboard deploy:
- Deployed pod returned
503 "API authentication is not configured. Set ELB_OPENAPI_API_TOKEN..."on every/v1/*Try-It call.api.tasks.openapi.deploy_openapi_serviceonly wroteELB_OPENAPI_API_TOKENinto the deployment manifest when the api sidecar already had one (env var or runtime cache). On the first deploy both are empty, sobuild_manifestsskipped the token env entry entirely and the siblingelb-openapirequire_api_tokenfail-closed branch returned 503 until the operator opened the API menu and clicked Generate. That post-deploy step was undocumented and easy to miss. - "Swagger UI" link in the API Reference header pointed at the AKS
internal LoadBalancer IP (
http://10.224.0.7/docs). The browser cannot reach an RFC1918 host from the public dashboard origin, and even if it could, that would be a plain-HTTP top-level navigation from an HTTPS page. The SPA's own API Reference is the intended surface in that case.
User-facing change¶
- Running Deploy elb-openapi is now self-contained: when no token is
cached anywhere, the deploy task mints a fresh
secrets.token_urlsafe(32)token, writes it into the deployment manifest, persists it to the runtime cache, and updates the api sidecar process env./v1/*Try-It works immediately after the rollout completes — no manual Generate click required. - The API menu's Generate / Refresh flow still works for rotation
(now its only purpose). The success payload from the deploy task gains
openapi_deploy.api_token_source(env|runtime_cache|auto_generated) so the audit log records which path was taken. - The "Swagger UI" button in the API Reference header is hidden when
the resolved service
baseUrlis a private / loopback / link-local IPv4 host. The SPA's API Reference page already proxies every call through the api sidecar, so operators do not lose any functionality.
API / IaC diff summary¶
| Layer | File | Change |
|---|---|---|
| Tasks | api/tasks/openapi/deploy.py | New token-resolution block: env → runtime cache → mint. Mint path persists the token via save_openapi_api_token and updates os.environ. openapi_deploy.api_token_source added to the success payload. |
| Frontend | web/src/pages/apiReference/ApiHero.tsx | Added isReachableUpstream(baseUrl) guard around the <a href="${baseUrl}/docs"> Swagger UI link. RFC1918 / 127.0.0.0/8 / 169.254.0.0/16 / 172.16.0.0/12 / 192.168.0.0/16 hosts are treated as unreachable. |
No new dependency. No IaC change. No new env var.
HTTPS / transport posture (unchanged)¶
- The api sidecar →
elb-openapiService hop stays plain-HTTP inside the AKS VNet, gated by_is_private_ipv4(and the existingOPENAPI_ALLOW_PUBLIC_LBopt-in). No change here. - The Container App's public ingress remains HTTPS-only; SPA → api hops are unchanged.
- The minted API token never leaves the server side: the manifest
carries it to AKS over the kube-apiserver TLS channel, the runtime
cache write goes to ops Redis on the loopback, and the SPA only
receives the token after an authenticated GET/POST to
/api/aks/openapi/token(the same path as before).
Validation evidence¶
uv run ruff check api/tasks/openapi/deploy.py→ clean.uv run pytest -q api/tests→ 1558 passed.cd web && npx tsc -p tsconfig.json --noEmit→ clean.cd web && npx eslint src/pages/apiReference/ApiHero.tsx→ clean.