Development Documentation
View as:

Command Channel Provisioning — Admin Out-of-Band Actions

This runbook covers the one-time Azure and Entra ID actions that the deploy SPN (sp-fabric-data-worker) cannot perform, because it deliberately lacks Microsoft.Authorization/roleAssignments/write and Key Vault Crypto permissions. The deploy SPN stays low-privilege; these grants are created once, out-of-band, by an admin who is Owner + User Access Administrator on rg-fabric-dbt-platform.

This is the repo's sanctioned exception to the IaC-only rule — the same pattern already used for the mi-fabric-functions Key Vault grant and the audit_db storage-key access. Terraform does not manage the signing key or any of these role assignments.

Status — DEV: COMPLETED on 2026-06-05. The signing key and all five role assignments below already exist in DEV. This runbook is the procedure to reproduce them in UAT/PROD (or to re-create them in DEV if the resources are ever lost). The exported public key is committed at scripts/fabric_ui/keys/devportal-command-signing.pub.pem.

Prerequisites

  • A fresh interactive az login as an admin who is Owner + User Access Administrator on rg-fabric-dbt-platform (a headless/cached-token session fails MissingSubscription on scoped az role assignment writes — use an interactive login in your own terminal, or the Azure Portal UI).
  • Terraform Phase 0 (infra-deploy) completed — namespace sb-geris-devportal and its agent-commands / agent-results queues must exist.
  • Always pass --subscription dfcc3a1c-fc89-4f3b-ab97-66464c60f9d8 explicitly on every scoped write.

Step 1 — Create RSA-2048 Signing Key in Key Vault

The admin who holds only Key Vault Secrets Officer must first self-grant Key Vault Crypto Officer (Keys plane) before the key can be created:

ADMIN_OID="b7cc889d-ba72-4ff6-b557-bd14d56f38c9"   # geris_fabric_admin@geris.nl
KV_ID="/subscriptions/dfcc3a1c-fc89-4f3b-ab97-66464c60f9d8/resourceGroups/rg-fabric-dbt-platform/providers/Microsoft.KeyVault/vaults/kv-fabric-dbt-keys"

az role assignment create \
  --subscription dfcc3a1c-fc89-4f3b-ab97-66464c60f9d8 \
  --assignee-object-id "$ADMIN_OID" --assignee-principal-type User \
  --role "Key Vault Crypto Officer" --scope "$KV_ID"
# wait ~60s for RBAC propagation, then:

# Create RSA-2048 key; non-exportable private key stays in KV for RS256 signing.
az keyvault key create \
  --vault-name kv-fabric-dbt-keys \
  --name devportal-command-signing \
  --kty RSA --size 2048 --ops sign verify

# Export the PUBLIC key in PEM format (bundled into the laptop agent binary).
az keyvault key download \
  --vault-name kv-fabric-dbt-keys \
  --name devportal-command-signing \
  --encoding PEM \
  --file devportal-command-signing.pub.pem

echo "Public key exported — commit this file at:"
echo "  scripts/fabric_ui/keys/devportal-command-signing.pub.pem"

The matching private key never leaves Key Vault (non-exportable, sign+verify only). The agent embeds only the public PEM to verify RS256 signatures from the cloud.


Step 2 — Grant platform-admin SPN Key Vault Crypto User

Key Vault Crypto User lets the SWA backend call sign(RS256) via CryptographyClient on the private key (and verify) — without ever exporting it.

PLATFORM_ADMIN_OID="37c4e058-c9ba-427c-867a-54aeb6ee01cc"   # sp-fabric-platform-admin
KV_ID="/subscriptions/dfcc3a1c-fc89-4f3b-ab97-66464c60f9d8/resourceGroups/rg-fabric-dbt-platform/providers/Microsoft.KeyVault/vaults/kv-fabric-dbt-keys"

az role assignment create \
  --subscription dfcc3a1c-fc89-4f3b-ab97-66464c60f9d8 \
  --assignee-object-id "$PLATFORM_ADMIN_OID" --assignee-principal-type ServicePrincipal \
  --role "Key Vault Crypto User" --scope "$KV_ID"

Step 3 — Service Bus RBAC (the authorization model)

The deploy SPN has no roleAssignments/write, so all five Service Bus grants are admin actions. The developer-group Data Receiver on agent-commands is the structural authorization gate — only members of the developer Entra group can receive (and therefore execute) commands. This is why the backend no longer performs a Microsoft Graph group-membership check: queue RBAC enforces it natively.

SB_NS_ID="/subscriptions/dfcc3a1c-fc89-4f3b-ab97-66464c60f9d8/resourceGroups/rg-fabric-dbt-platform/providers/Microsoft.ServiceBus/namespaces/sb-geris-devportal"
PLATFORM_ADMIN_OID="37c4e058-c9ba-427c-867a-54aeb6ee01cc"   # sp-fabric-platform-admin
MI_FUNCTIONS_OID="e54bbe5b-5e0e-444d-8025-8a064bd9455c"      # mi-fabric-functions
DEVELOPER_GROUP_OID="41489f76-7dbb-4fbc-a54b-ac07e673392b"   # FP GER Fabric Developers (incl. B2B guests)

# platform-admin SPN: Data Sender on agent-commands (mints + enqueues commands)
az role assignment create \
  --subscription dfcc3a1c-fc89-4f3b-ab97-66464c60f9d8 \
  --assignee-object-id "$PLATFORM_ADMIN_OID" --assignee-principal-type ServicePrincipal \
  --role "Azure Service Bus Data Sender" \
  --scope "${SB_NS_ID}/queues/agent-commands"

# developer group: Data Receiver on agent-commands — THE structural gate
#   (each laptop agent receives its own session; non-members cannot receive)
az role assignment create \
  --subscription dfcc3a1c-fc89-4f3b-ab97-66464c60f9d8 \
  --assignee-object-id "$DEVELOPER_GROUP_OID" --assignee-principal-type Group \
  --role "Azure Service Bus Data Receiver" \
  --scope "${SB_NS_ID}/queues/agent-commands"

# developer group: Data Sender on agent-results (laptop agent posts execution results)
az role assignment create \
  --subscription dfcc3a1c-fc89-4f3b-ab97-66464c60f9d8 \
  --assignee-object-id "$DEVELOPER_GROUP_OID" --assignee-principal-type Group \
  --role "Azure Service Bus Data Sender" \
  --scope "${SB_NS_ID}/queues/agent-results"

# mi-fabric-functions: Data Receiver on agent-results (triggers the serviceBusTrigger consumer)
az role assignment create \
  --subscription dfcc3a1c-fc89-4f3b-ab97-66464c60f9d8 \
  --assignee-object-id "$MI_FUNCTIONS_OID" --assignee-principal-type ServicePrincipal \
  --role "Azure Service Bus Data Receiver" \
  --scope "${SB_NS_ID}/queues/agent-results"

Use --assignee-object-id + explicit --assignee-principal-type (not --assignee) so the call skips the Graph name-lookup — the robust form for SPN / managed-identity / group assignees.


Step 4 — Conditional Access Baseline (IT / Entra — Daan lacks rights)

Apply an MFA conditional access policy scoped to the developer Entra group (41489f76-7dbb-4fbc-a54b-ac07e673392b):

  • Require MFA for all sign-ins by group members
  • No device compliance requirement (supports unmanaged external-consultant devices)
  • B2B guests in the group are included (allowed per design)

Step 5 — Azure Trusted Signing (code signing for agent binary)

The build-tray-installer ADO pipeline signs the PyInstaller .exe using Azure Trusted Signing.

  1. Confirm the ADO build SPN has the Trusted Signing Certificate Profile Signer role on the Trusted Signing account (codesign-geris or equivalent)
  2. Ensure the certificate profile geris-devportal-agent exists in the account
  3. After the first successful signed build, verify the signature: sigcheck.exe -a agent-tray.exe

fabric-dev:// Retirement Notice

As of P-SB5 (Task Group F), the fabric-dev:// URL scheme is no longer an authority-bearing command path. The following actions have been removed from the URI handler and now flow exclusively over the signed Service Bus channel:

Retired URI actionReplacement
fabric-dev://dbt-buildPOST /api/commandsCommandType.DBT_BUILD_LOCAL
fabric-dev://teardown-localPOST /api/commandsCommandType.TEARDOWN_LOCAL
fabric-dev://sync-gold-localPOST /api/commandsCommandType.SYNC_GOLD_LOCAL
fabric-dev://refresh-bronze-localPOST /api/commandsCommandType.REFRESH_BRONZE_LOCAL
fabric-dev://start-claudePOST /api/commandsCommandType.PROVISION_LOCAL
fabric-dev://prPOST /api/commands → (PR is a portal workflow, not a command)
fabric-dev://provisionPOST /api/commandsCommandType.PROVISION_LOCAL

The scheme now handles two no-authority nudges only:

  • fabric-dev://open-feature?slug=<slug> — ensures the local worktree exists and opens the editor (no cloud delegation, no privileged child process).
  • fabric-dev://show-job?job_id=<id> — focuses the local tray UI job view.

No security or RBAC changes are needed for this retirement. The HKCU Software\Classes\fabric-dev registry key remains registered so existing bookmarks continue to wake the tray — they simply no longer execute jobs.


Verification

After completing all steps, confirm:

SUB="dfcc3a1c-fc89-4f3b-ab97-66464c60f9d8"
SB_NS_ID="/subscriptions/$SUB/resourceGroups/rg-fabric-dbt-platform/providers/Microsoft.ServiceBus/namespaces/sb-geris-devportal"

# Namespace + queues exist
az servicebus queue list \
  --namespace-name sb-geris-devportal \
  -g rg-fabric-dbt-platform \
  --query "[].{name:name,session:properties.requiresSession}" -o table

# Key exists
az keyvault key show --vault-name kv-fabric-dbt-keys --name devportal-command-signing \
  --query "{name:name,kty:key.kty}" -o json

# RBAC on each queue
az role assignment list --subscription "$SUB" --scope "${SB_NS_ID}/queues/agent-commands" \
  --query "[].{principal:principalId,role:roleDefinitionName}" -o table
az role assignment list --subscription "$SUB" --scope "${SB_NS_ID}/queues/agent-results" \
  --query "[].{principal:principalId,role:roleDefinitionName}" -o table