Skip to content

Fix first-time provisioning; add serverless Cosmos, custom domain, and Front Door options - #27

Merged
dsghi merged 1 commit into
mainfrom
feat/serverless-cosmos-option
Oct 5, 2026
Merged

dsghi merged 1 commit into
mainfrom
feat/serverless-cosmos-option

Conversation

@dsghi

@dsghi dsghi commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A brand-new azd provision of DevBrain 2.0 can't finish: the Container App sits in "InProgress" for 20 minutes and fails with ContainerAppOperationError: … Operation expired. Deploying into a new tenant turned up two independent causes, plus a broken README snippet. This PR fixes all three and adds opt-in options that new tenants are likely to need.

Fixes

  • AcrPull deadlock on first provision. The Container App pulled from ACR with its system-assigned identity, but that identity only exists once the app does, so its AcrPull role assignment depended on the app. The first revision retried Failed to construct registry secret … Ensure the managed identity 'system' has the correct permissions until the operation expired. Image pulls now use the existing user-assigned identity, which is granted AcrPull before the app is created. The old system-identity assignment is left in place so existing deployments see no churn.
  • Placeholder image on the wrong port. containerapps-helloworld listens on port 80, while ingress and the /healthz startup, readiness, and liveness probes target port 8080, so the placeholder revision could never become healthy. It's replaced with mcr.microsoft.com/dotnet/samples:aspnetapp, which listens on 8080 and serves /healthz.
  • README app-role snippet. ConvertTo-Json -AsArray @(@{…}) wraps the array a second time and truncates it at the default depth of 2, so az ad app update --app-roles fails with 'list' object has no attribute 'get'. It now pipes the array with -Depth 5.

New opt-in options (all default off; existing deployments are unchanged)

azd value Effect
COSMOS_SERVERLESS Creates the Cosmos account in serverless mode, for subscriptions whose single free-tier Cosmos account is already taken
CUSTOM_DOMAIN_NAME Drives OAuth__BaseUrl, AllowedHosts, and the OAUTH_REDIRECT_URI output, replacing the README's "edit main.bicep" instruction
FRONT_DOOR_PROFILE_NAME / _RESOURCE_GROUP / _SKU Publishes through an existing Front Door Standard/Premium profile. It adds a dedicated endpoint, route, origin (health probes off so scale-to-zero still works), a managed-cert custom domain, and a custom-rule WAF policy. Nothing already on the profile is modified.
DNS_ZONE_NAME / _RESOURCE_GROUP Writes the CNAME and _dnsauth TXT validation records into an existing Azure DNS zone
FRONT_DOOR_ALLOWED_COUNTRIES Optional country allow list in the WAF

The WAF policy has these rules: a path allow list (/mcp, /.well-known/*, /register, /authorize, /callback, /token, /healthz); 30 requests per minute per IP on the OAuth endpoints (/register is open by design under RFC 7591); and 1,000 requests per 5 minutes per IP overall.

App change: a new opt-in FrontDoor:Id setting. When it's set, the server rejects requests whose X-Azure-FDID header doesn't contain this profile's ID, except /healthz, which Container Apps probes reach directly. This follows Microsoft's origin-lockdown guidance and stops the *.azurecontainerapps.io host name from bypassing the WAF. Provisioning sets it automatically when Front Door is enabled.

New outputs: DEVBRAIN_PUBLIC_URL and DEVBRAIN_MCP_URL. The README's verify step now uses them, because with the origin check the platform host name returns 403 for everything except /healthz.

Testing

  • dotnet test devbrain.slnx: 162 passed, including new server tests for the Front Door check (missing header, wrong ID, comma-separated chain, and /healthz exemption).
  • Real deployment into a new tenant (serverless Cosmos, existing Front Door Standard profile and Azure DNS zone):
    • Through Front Door: /healthz and both /.well-known metadata documents return 200; POST /mcp without a token returns 401 with the correct resource_metadata; /, /wp-login.php, /.env, and /mcpfoo return 403 from the WAF; /.well-known/oauth-protected-resource/mcp passes the WAF and gets the app's 404, which MCP clients rely on for discovery fallback; HTTP redirects to HTTPS; the OAuth rate limit returns 403 after the threshold while other paths stay reachable.
    • Directly on *.azurecontainerapps.io: /healthz returns 200, and every other path returns 403.
    • az deployment group what-if showed only creates on the shared Front Door profile and DNS zone, with no modifications or deletes to existing routes, WAF policies, or security policies.
  • Gap: the user-assigned-identity pull path was validated by re-provisioning over a Container App left behind by the failed first provision. A from-scratch provision with this exact template hasn't been run end to end yet.

🤖 Generated with Claude Code

…options

A fresh `azd provision` could never finish: the Container App timed out
with "Operation expired". Two independent bugs caused it, and both are
fixed here alongside opt-in options found while deploying a new tenant.

- Pull images from ACR with the user-assigned identity. The system-assigned
  identity only exists once the app does, so its AcrPull grant was created
  after the app and the first revision waited on it indefinitely.
- Replace the containerapps-helloworld placeholder (port 80) with
  mcr.microsoft.com/dotnet/samples:aspnetapp, which listens on 8080 and
  serves /healthz, so the first revision passes the template's probes.
- Add COSMOS_SERVERLESS for subscriptions whose single free-tier Cosmos
  account is already taken. Defaults to provisioned throughput.
- Add CUSTOM_DOMAIN_NAME, which drives OAuth__BaseUrl, AllowedHosts, and
  the redirect URI output instead of hand-editing main.bicep.
- Add optional publishing through an existing Front Door Standard/Premium
  profile: dedicated endpoint, route, origin, managed-cert custom domain,
  Azure DNS records, and a custom-rule WAF policy (path allow list, OAuth
  and overall per-IP rate limits, optional country allow list). Nothing
  existing on the profile is modified.
- Add an opt-in FrontDoor:Id origin check that rejects requests without
  the matching X-Azure-FDID header (except /healthz), so the Container
  Apps host name can't bypass the WAF.
- Add DEVBRAIN_PUBLIC_URL and DEVBRAIN_MCP_URL outputs and point the
  README's verify step at them.
- Fix the README's az CLI app-role snippet, which nested the role array
  and truncated it at the default ConvertTo-Json depth.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dsghi
dsghi force-pushed the feat/serverless-cosmos-option branch from 71b8eff to 14378d4 Compare October 5, 2026 02:24
@dsghi
dsghi merged commit f213ceb into main Oct 5, 2026
7 checks passed
@dsghi
dsghi deleted the feat/serverless-cosmos-option branch October 5, 2026 02:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant