Navigation
GuidesUpdated July 3, 2026

Cross-Subscription Service Principal RBAC

guidehow-toazurerbacservice-principalnetworkingprivate-dnshub-spoketerraformazure-cli

Cross-Subscription service principal RBAC

When a Terraform workspace deploys resources in a spoke subscription that reference infrastructure in the hub connectivity subscription — such as Private DNS Zone VNet links — the service principal (SP) running the workspace must hold an RBAC role on the hub resource. Because the hub lives in a separate subscription (OHEMR-SUB-CONNECTIVITY-PRO-001), this role assignment must be created there, not in the spoke.

When you need this

The most common trigger is a LinkedAuthorizationFailed 403 during a TFE apply:

Error: updating Virtual Network Link ... performing CreateOrUpdate: unexpected status
403 (403 Forbidden) with error: LinkedAuthorizationFailed: The client '<app-id>'
with object id '<object-id>' has permission to perform action
'Microsoft.Network/privateDnsZones/virtualNetworkLinks/write' on scope
'/subscriptions/<spoke-sub>/...' however, it does not have permission to perform
action(s) 'Microsoft.Network/virtualNetworks/join/action' on the linked scope(s)
'/subscriptions/<hub-sub>/resourceGroups/<hub-rg>/providers/Microsoft.Network/
virtualNetworks/<hub-vnet>'

This error means:

  • The SP has write permission on the Private DNS Zone (spoke subscription) ✅
  • The SP lacks virtualNetworks/join/action on the hub VNet (hub subscription) ❌

Key values to extract from the error

FieldWhere in the error
SP object idwith object id '<object-id>'
Hub subscription IDlinked scope /subscriptions/<hub-sub>/...
Hub resource grouplinked scope .../resourceGroups/<hub-rg>/...
Hub VNet namelinked scope .../virtualNetworks/<hub-vnet>

Prerequisites

  • Azure CLI authenticated with a session valid for the hub subscription (OHEMR-SUB-CONNECTIVITY-PRO-001, 6ad81c44-89ef-46b7-b8c8-a9ae34e3fe13)
  • Owner or User Access Administrator role on that subscription (or scoped to the target resource group / VNet)

Verify your token is live before proceeding:

az account get-access-token \
  --subscription 6ad81c44-89ef-46b7-b8c8-a9ae34e3fe13 \
  --query expiresOn \
  -o tsv

Steps

Option A: Network Contributor on the hub VNet (recommended)

Scope the assignment to the specific VNet resource — minimum scope that satisfies virtualNetworks/join/action without granting broader network management rights.

az role assignment create \
  --assignee-object-id <sp-object-id> \
  --assignee-principal-type ServicePrincipal \
  --role "Network Contributor" \
  --scope "/subscriptions/<hub-sub-id>/resourceGroups/<hub-rg>/providers/Microsoft.Network/virtualNetworks/<hub-vnet-name>"

Portal alternative

  1. Search for the hub VNet in the Azure portal top bar
  2. Confirm the subscription filter shows OHEMR-SUB-CONNECTIVITY-PRO-001
  3. Open the VNet → Access control (IAM)AddAdd role assignment
  4. Role: Network Contributor → Next
  5. Members: Service principal → Select members → paste the SP object id → Next
  6. Review + assign

Option B: Minimal custom role (optional)

If Network Contributor is too broad for your security posture, create a custom role with only the required action. This is a one-time setup reusable across multiple SPs.

# Create the role definition (run once per subscription)
az role definition create --role-definition '{
  "Name": "VNet Join Only",
  "Description": "Grants join/action on virtual networks for Private DNS VNet links",
  "Actions": ["Microsoft.Network/virtualNetworks/join/action"],
  "AssignableScopes": ["/subscriptions/6ad81c44-89ef-46b7-b8c8-a9ae34e3fe13"]
}'

# Assign it
az role assignment create \
  --assignee-object-id <sp-object-id> \
  --assignee-principal-type ServicePrincipal \
  --role "VNet Join Only" \
  --scope "/subscriptions/6ad81c44-89ef-46b7-b8c8-a9ae34e3fe13/resourceGroups/<hub-rg>/providers/Microsoft.Network/virtualNetworks/<hub-vnet-name>"

Verification

After creating the assignment, re-trigger the TFE run. RBAC propagation is near-instant but occasionally takes up to 2 minutes.

# Confirm the assignment exists
az role assignment list \
  --assignee <sp-object-id> \
  --scope "/subscriptions/6ad81c44-89ef-46b7-b8c8-a9ae34e3fe13/resourceGroups/<hub-rg>/providers/Microsoft.Network/virtualNetworks/<hub-vnet-name>" \
  --output table

A successful TFE apply with no LinkedAuthorizationFailed error confirms the fix.

Rollback / troubleshooting

SymptomResolution
403 persists after assignmentWait 2 minutes for RBAC propagation, then re-trigger the TFE run
RoleDefinitionDoesNotExist when assigning custom roleRun the az role definition create step first
AuthorizationFailed creating the assignmentYour account lacks Owner or User Access Administrator on the hub subscription
TFE run succeeds but a different resource 403sA second Private DNS Zone links to a different VNet — repeat the diagnosis for that VNet's linked scope

To remove the assignment if it was created in error:

az role assignment delete \
  --assignee <sp-object-id> \
  --role "Network Contributor" \
  --scope "/subscriptions/<hub-sub-id>/resourceGroups/<hub-rg>/providers/Microsoft.Network/virtualNetworks/<hub-vnet-name>"

Worked example: api-containers-001 (NPD, 2026-06-30)

TFE workspace aide-0085665-tfews-epic-npd-tfe-api-containers-001 failed to update a Private DNS Zone VNet link (ohemr-pnetlk-hub_fw-shared-cus-001) because the NPD service principal lacked join/action on the hub VNet in the connectivity subscription.

Values from the error:

FieldValue
SP object idc758aa20-7fc3-4f15-8073-4e90cb2b28b1
Hub subscriptionOHEMR-SUB-CONNECTIVITY-PRO-001 (6ad81c44-89ef-46b7-b8c8-a9ae34e3fe13)
Hub resource groupohemr-rg-hubconnectivity-pro-cus-001
Hub VNetohemr-vnet-hub_fw-shared-cus-001

Command run:

az role assignment create \
  --assignee-object-id c758aa20-7fc3-4f15-8073-4e90cb2b28b1 \
  --assignee-principal-type ServicePrincipal \
  --role "Network Contributor" \
  --scope "/subscriptions/6ad81c44-89ef-46b7-b8c8-a9ae34e3fe13/resourceGroups/ohemr-rg-hubconnectivity-pro-cus-001/providers/Microsoft.Network/virtualNetworks/ohemr-vnet-hub_fw-shared-cus-001"

Role assignment id: 24dc2fa5-7eda-499a-8d8b-7d530f356333. Subsequent TFE run (run-uutPdVq9xMRQvyLX) applied successfully.

Related