2026-05-28 — Settings: VNet peering panel¶
Motivation¶
The OpenAPI ingress for the control plane only listens on a private IP
inside the platform VNet (10.224.0.7 in the default AKS auto-VNet
10.224.0.0/12 range). Researchers running an analysis VM in another
VNet inside the same Azure subscription previously had no in-product
path to reach it — they had to drop into the Azure portal, pick the
right resource group / VNet, and create a bidirectional peering by
hand.
The peering helper itself already existed
(ensure_vnet_peering_with_target in
api/tasks/azure/peering.py
and the POST /api/settings/vnet-peering route in
api/routes/settings/vnet_peering.py),
but it was never wired into the SPA. This change surfaces it so the
whole flow — pick AKS, pick target VNet, peer, probe /openapi.json
on the private IP — happens inside the dashboard.
User-facing change¶
A new "VNet peering" section appears in the Settings panel between Public HTTPS and Sizing:
- AKS cluster (source side) dropdowns: subscription → resource
group → cluster. The helper auto-resolves the AKS auto-VNet from
the cluster's
node_resource_group, so the user does not pick the source VNet manually. - Target side dropdowns: subscription → resource group → VNet. Defaults to the AKS subscription / RG so the common "VM in the same RG" case is one click.
- Target IP (default
10.224.0.7) and Target path (default/openapi.json) are editable text inputs. - Peer & probe button POSTs to
/api/settings/vnet-peering, renders the probe status as a Badge, lists both peering legs (aks_to_target,target_to_aks) with theirstate, and shows therecovery_commandsnippet for retry / RBAC troubleshooting.
There is no behaviour change for users who do not open the new
section. The route already required require_caller, so the auth
contract is unchanged.
API / IaC diff summary¶
- No new backend route. The SPA discovers the target VNet through the
existing direct-ARM pattern in
web/src/api/arm.ts (the same MSAL bearer
flow already used by
listSubscriptions/listResourceGroups/listStorageAccounts/listAcrs), which keeps target visibility consistent with what the user can see in the Azure portal. - No new task / Celery work: the peering helper and probe were already implemented and tested; this change just exposes them in the UI.
- Frontend: new typed clients
listVnetsin web/src/api/arm.ts andsettingsApi.peerVnetin web/src/api/settings.ts; newVnetPeeringSectionrendered from web/src/components/SettingsPanel.tsx. - Infra: no Bicep change. The peering itself is created at
runtime by the shared user-assigned MI
(
id-elb-dashboard-*), which already has Network Contributor on the AKS auto-VNet RG; the target RG needs the same role for the call to succeed (the section surfaces therecovery_commandstring if it doesn't).
Validation evidence¶
uv run pytest -q api/tests— all tests pass. Backend coverage of thePOST /api/settings/vnet-peeringroute lives in api/tests/test_settings_vnet_peering.py, and the_FACADE_CONTRACTguard in api/tests/test_tasks_facade_contract.py was extended for the two new monkeypatch targets (ensure_vnet_peering_with_target,httpx.get).uv run ruff check api— all checks passed.cd web && npm run build— built in 10.42 s, no TypeScript errors.
curl smoke (with AUTH_DEV_BYPASS=true on a host-mode dev loop)¶
# trigger peering + private-IP probe
curl -sX POST http://127.0.0.1:8085/api/settings/vnet-peering \
-H 'content-type: application/json' \
-d '{
"subscription_id": "'"$SUB"'",
"resource_group": "rg-elb-dashboard",
"cluster_name": "aks-elb-dashboard",
"target_subscription_id": "'"$SUB"'",
"target_resource_group": "rg-target",
"target_vnet_name": "vnet-analyst-01",
"target_ip": "10.224.0.7",
"target_path": "/openapi.json"
}' | jq '{probe, peerings: [.peerings[].state]}'