-
Notifications
You must be signed in to change notification settings - Fork 2
docs: add gateway connection integration guide #126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
prjhawar
wants to merge
3
commits into
main
Choose a base branch
from
docs/sandbox-connector-integration
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+282
−0
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,282 @@ | ||
| # Gateway Connections — Integrating Connectors with ACA Sandboxes | ||
|
|
||
| Gateway connections wire Connector Namespace resources (API connections and MCP | ||
| server configs) into Azure Container Apps (ACA) sandbox groups and sandboxes. | ||
| Once wired, sandbox code can call external services — Office 365, Teams, | ||
| SharePoint, GitHub, and more — with plain HTTP requests. The platform handles | ||
| authentication transparently. | ||
|
|
||
| ## How it works | ||
|
|
||
| 1. A **Connector Namespace** holds connections — stored OAuth credentials for | ||
| external services — and optional MCP server configs. | ||
| 2. Connections and MCP server configs are wired to a **sandbox group** via its | ||
| `gatewayConnections[]` property. | ||
| 3. Each **sandbox** references the same gateway connections at creation time. | ||
| 4. The platform writes connection metadata inside the sandbox at create time: | ||
| - **API connections** → `/connections/connections.json` at the sandbox | ||
| filesystem root — a JSON map of connection names to runtime URLs. | ||
| - **MCP server configs** → `/root/.copilot/mcp-config.json` — the MCP tools | ||
| manifest. Requires the sandbox to be booted with `--disk copilot` or | ||
| `--disk claude`; Copilot CLI and Claude pick it up automatically on next | ||
| run. | ||
| 5. The **egress proxy** intercepts outbound calls to runtime URL hosts and | ||
| injects `Authorization: Bearer` tokens automatically using the sandbox | ||
| group's managed identity (system-assigned or user-assigned). | ||
|
|
||
| Gateway connection calls work **even with `defaultAction=Deny`** — the egress | ||
| proxy mediates them independently of egress policy rules. | ||
|
|
||
| --- | ||
|
|
||
| ## Setting up gateway connections | ||
|
|
||
| ### Connection types | ||
|
|
||
| | Type | `resourceId` contains | Runtime URL field | Purpose | | ||
| |------|----------------------|-------------------|---------| | ||
| | **API connection** | `/connections/` | `connectionRuntimeUrl` | Sandbox code calls connector REST operations directly | | ||
| | **MCP server config** | `/mcpServerConfigs/` | `mcpRuntimeUrl` | Exposes connector operations as MCP tools | | ||
|
|
||
| ### Wiring checklist | ||
|
|
||
| | Step | Resource | What to do | | ||
| |------|----------|------------| | ||
| | 1 | Connection | Create + consent OAuth → status `Connected` | | ||
| | 2 | Connection ACL: `gateway-acl` | Grant gateway MI access (for event subscriptions) | | ||
| | 3 | Connection ACL: `sandbox-acl` | Grant sandbox-group MI access (for token minting) | | ||
| | 4 | Sandbox group | Enable a managed identity (system-assigned or user-assigned); PATCH `gatewayConnections[]` with `{resourceId, connectionRuntimeUrl or mcpRuntimeUrl, authentication}` | | ||
| | 5 | Sandbox | Create with `gatewayConnections: [{resourceId}]` in the data-plane PUT body | | ||
|
|
||
| Steps 2 and 3 can run in parallel. The sandbox group PATCH must use GET-merge-PATCH to avoid clobbering existing entries. | ||
|
|
||
| ### Sandbox group `gatewayConnections[]` entry shape | ||
|
|
||
| For API connections: | ||
| ```json | ||
| { | ||
| "resourceId": "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections/{conn}", | ||
| "connectionRuntimeUrl": "https://{host}/apim/{connector}/{id}", | ||
| "authentication": { "type": "SystemAssignedManagedIdentity" } | ||
| } | ||
| ``` | ||
|
|
||
| For MCP server configs: | ||
| ```json | ||
| { | ||
| "resourceId": "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/mcpServerConfigs/{name}", | ||
| "mcpRuntimeUrl": "https://{host}/.../mcp", | ||
| "authentication": { "type": "SystemAssignedManagedIdentity" } | ||
| } | ||
| ``` | ||
|
|
||
| The `authentication` block above uses the sandbox group's system-assigned MI. | ||
| To use a user-assigned MI attached to the sandbox group instead, swap in: | ||
|
|
||
| ```json | ||
| "authentication": { | ||
| "type": "UserAssignedManagedIdentity", | ||
| "identityResourceId": "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{name}" | ||
| } | ||
| ``` | ||
|
|
||
| ### ACA CLI | ||
|
|
||
| The ACA CLI wraps the ARM PATCH shown above and also creates the required ACLs | ||
| on the connection automatically (see [Access policies](#access-policies)). | ||
| Prefer the CLI for routine wiring; fall back to direct ARM PATCH only when the | ||
| CLI doesn't yet support a field you need. | ||
|
|
||
| ```bash | ||
| # Add a gateway connection to a sandbox group (creates ACLs automatically) | ||
| aca sandboxgroup connector add \ | ||
| --group {sg} \ | ||
| --connection-id {arm-resource-id} \ | ||
| --authorization system | ||
|
|
||
| # List configured connections on a sandbox group | ||
| aca sandboxgroup connector list --group {sg} | ||
|
|
||
| # Create sandbox with gateway connections (must already be configured on the group) | ||
| aca sandbox create --disk copilot \ | ||
| --connection-id {resource-id-1} {resource-id-2} | ||
| ``` | ||
|
|
||
| > **Note:** `aca sandbox create --connection-id` passes `gatewayConnections` in | ||
| > the data-plane request. If the ACA CLI version does not support this flag, | ||
| > use `az rest` with a data-plane PUT instead — see | ||
| > [gateway-connections.md](../plugin/skills/aca-sandboxes/references/gateway-connections.md) Step 5. | ||
|
Comment on lines
+90
to
+108
|
||
|
|
||
| ### Validation rules | ||
|
|
||
| The following are enforced by the sandbox-group control plane and the sandbox | ||
| data plane at create/update time: | ||
|
|
||
| - Maximum **10** gateway connections per sandbox. | ||
| - All connections must reference the **same** connector gateway (namespace). | ||
| - All connections must use the **same** authentication type. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this a restriction we enforce? |
||
| - Two authentication types are supported on the `authentication` block: | ||
| - `SystemAssignedManagedIdentity` — requires the sandbox group to have a system-assigned MI; `identityResourceId` must not be specified. | ||
| - `UserAssignedManagedIdentity` — requires `identityResourceId` set to the ARM resource ID of a user-assigned MI attached to the sandbox group. | ||
| - Gateway connections on sandboxes are **immutable** — set at creation, cannot be changed. | ||
| - MCP server config connections are only supported with `copilot` or `claude` disk images, private disk images, or snapshots. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we also mention about the skill we have on sandboxes? |
||
|
|
||
| → Full wiring details: [gateway-connections.md](../plugin/skills/aca-sandboxes/references/gateway-connections.md) | ||
| → Connection CRUD: [connections.md](../plugin/skills/connectors/references/connections.md) | ||
| → OAuth consent: [consent.md](../plugin/skills/aca-sandboxes/references/consent.md) | ||
|
|
||
| --- | ||
|
|
||
| ## Access policies | ||
|
|
||
| Two access policies are required on each connection for gateway connection wiring: | ||
|
|
||
| | Policy name | Principal | Purpose | | ||
| |-------------|-----------|---------| | ||
| | `gateway-acl` | Gateway (connector namespace) MI | Allows the gateway to subscribe to connector events | | ||
| | `sandbox-acl` | Sandbox-group MI | Allows the egress proxy to mint Bearer tokens for runtime URL calls | | ||
|
|
||
| Both use the same schema — `principal.type = "ActiveDirectory"` with the | ||
| principal's `objectId` and `tenantId`. | ||
|
|
||
| The ACA CLI commands above create both ACLs automatically. If you wire the | ||
| gateway connection via direct ARM PATCH, you must create the ACLs yourself. | ||
|
|
||
| --- | ||
|
|
||
| ## Consumption: using connections from inside a sandbox | ||
|
|
||
| ### `/connections/connections.json` | ||
|
|
||
| The platform automatically generates `/connections/connections.json` inside every sandbox that has gateway connections wired. This file maps connection names to their runtime URLs: | ||
|
|
||
| ```json | ||
| { | ||
| "connections": { | ||
| "Teams-web-vet": { | ||
| "type": "http", | ||
| "url": "https://91a8e1cf...azure-apihub.net/apim/teams/fc52d411..." | ||
| }, | ||
| "outlook-conn": { | ||
| "type": "http", | ||
| "url": "https://91a8e1cf...azure-apihub.net/apim/office365/971c415a..." | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Reading and calling connections | ||
|
|
||
| ```bash | ||
| # Get a connection URL by name | ||
| URL=$(jq -r '.connections["Teams-web-vet"].url' /connections/connections.json) | ||
|
|
||
| # Make an API call — authentication is automatic via egress proxy | ||
| curl -s "$URL/beta/me/joinedTeams" | ||
| ``` | ||
|
|
||
| From Python: | ||
|
|
||
| ```python | ||
| import json, requests | ||
|
|
||
| with open("/connections/connections.json") as f: | ||
| connections = json.load(f)["connections"] | ||
|
|
||
| teams_url = connections["Teams-web-vet"]["url"] | ||
|
|
||
| # No auth header needed — egress proxy injects Bearer automatically | ||
| response = requests.get(f"{teams_url}/beta/me/joinedTeams") | ||
| teams = response.json()["value"] | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Operation discovery from inside a sandbox | ||
|
|
||
| ### Swagger via metadata URL | ||
|
|
||
| Derive the metadata URL from the connection URL by replacing `/apim/` with `/metadata/` and appending `?export=true`: | ||
|
|
||
| ```bash | ||
| # Connection URL: https://host/apim/teams/connectionId | ||
| # Metadata URL: https://host/metadata/teams/connectionId?export=true | ||
|
|
||
| URL=$(jq -r '.connections["Teams-web-vet"].url' /connections/connections.json) | ||
| METADATA_URL=$(echo "$URL" | sed 's|/apim/|/metadata/|') | ||
| curl -s "$METADATA_URL?export=true" | jq '.paths | keys' | ||
| ``` | ||
|
|
||
| This returns the Swagger 2.0 spec with available operations, parameters, and `x-ms-dynamic-*` extensions. The response is raw Swagger at the top level — access paths directly via `data["paths"]`. | ||
|
|
||
| ### Operation listing via ARM (outside sandbox) | ||
|
|
||
| For a lightweight operation summary (before sandbox creation), use the ARM catalog: | ||
|
|
||
| ```bash | ||
| az rest --method GET \ | ||
| --url ".../managedApis/{connector}/apiOperations?api-version=2016-06-01" \ | ||
| --query "value[].{name:name, summary:properties.summary, trigger:properties.trigger}" -o table | ||
| ``` | ||
|
|
||
| ### Mapping Swagger operations to runtime URL calls | ||
|
|
||
| | Swagger field | Where it goes | | ||
| |---------------|---------------| | ||
| | `path` (strip `/{connectionId}` prefix) | Append to the connection URL | | ||
| | `in: path` parameters | Substitute into URL path | | ||
| | `in: query` parameters | Append as `?key=value` | | ||
| | `in: body` parameters | Send as JSON request body | | ||
| | `in: header` parameters | Add as HTTP header (but **not** `Authorization`) | | ||
|
|
||
| --- | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| | Symptom | Likely cause | | ||
| |---------|-------------| | ||
| | Runtime URL returns `401` / "AuthorizationToken required" | `gatewayConnections[]` entry missing on sandbox group or per-sandbox | | ||
| | Runtime URL returns `403` | `sandbox-acl` missing on connection, or managed identity not yet propagated (wait 30s) | | ||
| | Connection status not `Connected` | OAuth consent incomplete or expired | | ||
| | `connections.json` empty or missing | Sandbox created without `gatewayConnections` in the data-plane PUT body | | ||
| | DNS error or connection refused from sandbox | Connection not in per-sandbox `gatewayConnections`, or sandbox not running | | ||
|
|
||
| --- | ||
|
|
||
| ## Quick reference | ||
|
|
||
| ```bash | ||
| # --- ARM endpoints --- | ||
|
|
||
| # Connector namespace | ||
| # https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways | ||
| # api-version=2026-05-01-preview | ||
|
|
||
| # Sandbox group | ||
| # https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.App/sandboxGroups | ||
| # api-version=2026-02-01-preview | ||
|
|
||
| # Sandbox data plane (regional) | ||
| # https://management.{region}.azuredevcompute.io/subscriptions/{sub}/resourceGroups/{rg}/sandboxGroups/{sg}/sandboxes | ||
|
|
||
| # List connections on a namespace | ||
| az rest --method GET --url ".../connectorGateways/{ns}/connections?api-version=2026-05-01-preview" | ||
|
Comment on lines
+262
to
+263
|
||
|
|
||
| # Get sandbox group gatewayConnections | ||
| az rest --method GET --url ".../sandboxGroups/{sg}?api-version=2026-02-01-preview" \ | ||
| --query "properties.gatewayConnections" | ||
|
|
||
| # --- From inside a sandbox --- | ||
|
|
||
| # View available gateway connections | ||
| cat /connections/connections.json | ||
|
|
||
| # Get a connection URL | ||
| jq -r '.connections["name"].url' /connections/connections.json | ||
|
|
||
| # Discover operations (metadata swagger) | ||
| curl -s "$(jq -r '.connections["name"].url' /connections/connections.json | sed 's|/apim/|/metadata/|')?export=true" | ||
|
|
||
| # Call a connector operation (auth is automatic) | ||
| curl -s "$(jq -r '.connections["name"].url' /connections/connections.json)/beta/me/joinedTeams" | ||
| ``` | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.