From 6098695efe125e4df9ac37cac0e5d0ff326c8ada Mon Sep 17 00:00:00 2001 From: Pranami Jhawar Date: Mon, 1 Jun 2026 18:45:45 -0700 Subject: [PATCH 1/3] docs: add sandbox gateway connection integration guide Comprehensive doc covering setup (gatewayConnections wiring, ACLs, CLI), consumption (/connections/connections.json, egress proxy auth), operation discovery (metadata URL), MCP server configs, and available skills. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/sandbox-connector-integration.md | 260 ++++++++++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 docs/sandbox-connector-integration.md diff --git a/docs/sandbox-connector-integration.md b/docs/sandbox-connector-integration.md new file mode 100644 index 0000000..2b7abf2 --- /dev/null +++ b/docs/sandbox-connector-integration.md @@ -0,0 +1,260 @@ +# 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 generates `/connections/connections.json` inside the sandbox + with the connection names and runtime URLs. +5. The **egress proxy** intercepts outbound calls to runtime URL hosts and + injects `Authorization: Bearer` tokens automatically using the sandbox + group's managed identity. + +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 SystemAssigned MI; PATCH `gatewayConnections[]` with `{resourceId, connectionRuntimeUrl, 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" } +} +``` + +### CLI alternative (ACA CLI) + +```bash +# Add a gateway connection (creates ACLs automatically) +aca sandboxgroup connector add \ + --group {sg} \ + --connection-id {arm-resource-id} \ + --authorization system + +# List configured connections +aca sandboxgroup connector list --group {sg} + +# Create sandbox with gateway connections +aca sandbox create --disk copilot \ + --connection-id {resource-id-1} {resource-id-2} +``` + +### Validation rules + +- Maximum **10** gateway connections per sandbox. +- All connections must reference the **same** connector gateway. +- All connections must use the **same** authentication type. +- `SystemAssignedManagedIdentity` requires the sandbox group to have a system-assigned MI. +- 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. + +→ 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) + +--- + +## 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"] +``` + +### How the egress proxy handles auth + +When sandbox code makes an outbound HTTPS request whose host matches a declared `gatewayConnections[]` runtime URL: + +1. The egress proxy intercepts the connection (independent of egress policy rules) +2. It mints a Bearer token using the sandbox-group's SystemAssigned MI +3. It adds `Authorization: Bearer ` to the outbound request +4. The connector authorizes the call, exchanges the token for stored OAuth credentials, and forwards to the downstream API + +This works **even with `defaultAction=Deny`** — gateway connection calls bypass egress host-allow rules entirely. + +--- + +## 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`) | + +--- + +## 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`. + +--- + +## 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" + +# 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" +``` From 5f909ed883af852cb21ab7b2e751763d2f6d4731 Mon Sep 17 00:00:00 2001 From: Pranami Jhawar Date: Tue, 2 Jun 2026 11:21:37 -0700 Subject: [PATCH 2/3] docs: address review comments on integration guide - Fix wiring checklist to mention both connectionRuntimeUrl and mcpRuntimeUrl - Add note about aca CLI --connection-id availability with az rest fallback - Fix mcpServerConfigs casing to match canonical ARM path (camelCase) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/sandbox-connector-integration.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/sandbox-connector-integration.md b/docs/sandbox-connector-integration.md index 2b7abf2..3d7b8b2 100644 --- a/docs/sandbox-connector-integration.md +++ b/docs/sandbox-connector-integration.md @@ -31,7 +31,7 @@ proxy mediates them independently of egress policy rules. | 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 | +| **MCP server config** | `/mcpServerConfigs/` | `mcpRuntimeUrl` | Exposes connector operations as MCP tools | ### Wiring checklist @@ -40,7 +40,7 @@ proxy mediates them independently of egress policy rules. | 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 SystemAssigned MI; PATCH `gatewayConnections[]` with `{resourceId, connectionRuntimeUrl, authentication}` | +| 4 | Sandbox group | Enable SystemAssigned MI; 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. @@ -59,7 +59,7 @@ For API connections: For MCP server configs: ```json { - "resourceId": "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/mcpserverconfigs/{name}", + "resourceId": "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/mcpServerConfigs/{name}", "mcpRuntimeUrl": "https://{host}/.../mcp", "authentication": { "type": "SystemAssignedManagedIdentity" } } @@ -68,20 +68,25 @@ For MCP server configs: ### CLI alternative (ACA CLI) ```bash -# Add a gateway connection (creates ACLs automatically) +# 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 +# List configured connections on a sandbox group aca sandboxgroup connector list --group {sg} -# Create sandbox with gateway connections +# 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. + ### Validation rules - Maximum **10** gateway connections per sandbox. From abb1f83f3f83cdb330e2dd9f6379016309d26695 Mon Sep 17 00:00:00 2001 From: Pranami Jhawar Date: Mon, 8 Jun 2026 14:34:15 -0700 Subject: [PATCH 3/3] docs: address review feedback on gateway connection guide - Step 4 (How it works): document /connections/connections.json and /root/.copilot/mcp-config.json with which file is for what (verified against InstanceManager.cs in ADC) - Step 5 (How it works): clarify managed identity can be system-assigned or user-assigned - Rename 'CLI alternative (ACA CLI)' -> 'ACA CLI' with bridging text explaining its relationship to the ARM PATCH above - Validation rules: add preamble noting these are platform-enforced; expand auth-type rule to cover both SystemAssignedManagedIdentity and UserAssignedManagedIdentity (with identityResourceId requirement) - Wiring checklist row 4: broaden MI wording to system/user-assigned - gatewayConnections[] entry shape: add UserAssignedManagedIdentity JSON example alongside the system-assigned one - Move Access policies section before Consumption for better ordering - Remove duplicate 'How the egress proxy handles auth' subsection (content already covered in How it works step 5) Addresses review comments from @rarayudu on PR #126. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/sandbox-connector-integration.md | 81 ++++++++++++++++----------- 1 file changed, 49 insertions(+), 32 deletions(-) diff --git a/docs/sandbox-connector-integration.md b/docs/sandbox-connector-integration.md index 3d7b8b2..0a9297a 100644 --- a/docs/sandbox-connector-integration.md +++ b/docs/sandbox-connector-integration.md @@ -13,11 +13,16 @@ authentication transparently. 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 generates `/connections/connections.json` inside the sandbox - with the connection names and runtime URLs. +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. + 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. @@ -40,7 +45,7 @@ proxy mediates them independently of egress policy rules. | 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 SystemAssigned MI; PATCH `gatewayConnections[]` with `{resourceId, connectionRuntimeUrl or mcpRuntimeUrl, authentication}` | +| 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. @@ -65,7 +70,22 @@ For MCP server configs: } ``` -### CLI alternative (ACA CLI) +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) @@ -89,10 +109,15 @@ aca sandbox create --disk copilot \ ### 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. +- All connections must reference the **same** connector gateway (namespace). - All connections must use the **same** authentication type. -- `SystemAssignedManagedIdentity` requires the sandbox group to have a system-assigned MI. +- 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. @@ -102,6 +127,23 @@ aca sandbox create --disk copilot \ --- +## 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` @@ -148,17 +190,6 @@ response = requests.get(f"{teams_url}/beta/me/joinedTeams") teams = response.json()["value"] ``` -### How the egress proxy handles auth - -When sandbox code makes an outbound HTTPS request whose host matches a declared `gatewayConnections[]` runtime URL: - -1. The egress proxy intercepts the connection (independent of egress policy rules) -2. It mints a Bearer token using the sandbox-group's SystemAssigned MI -3. It adds `Authorization: Bearer ` to the outbound request -4. The connector authorizes the call, exchanges the token for stored OAuth credentials, and forwards to the downstream API - -This works **even with `defaultAction=Deny`** — gateway connection calls bypass egress host-allow rules entirely. - --- ## Operation discovery from inside a sandbox @@ -200,20 +231,6 @@ az rest --method GET \ --- -## 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`. - ---- - ## Troubleshooting | Symptom | Likely cause |