Cross-Subscription Service Principal RBAC
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/actionon the hub VNet (hub subscription) ❌
Key values to extract from the error
| Field | Where in the error |
|---|---|
| SP object id | with object id '<object-id>' |
| Hub subscription ID | linked scope /subscriptions/<hub-sub>/... |
| Hub resource group | linked scope .../resourceGroups/<hub-rg>/... |
| Hub VNet name | linked 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) OwnerorUser Access Administratorrole 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
- Search for the hub VNet in the Azure portal top bar
- Confirm the subscription filter shows OHEMR-SUB-CONNECTIVITY-PRO-001
- Open the VNet → Access control (IAM) → Add → Add role assignment
- Role:
Network Contributor→ Next - Members: Service principal → Select members → paste the SP object id → Next
- 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
| Symptom | Resolution |
|---|---|
| 403 persists after assignment | Wait 2 minutes for RBAC propagation, then re-trigger the TFE run |
RoleDefinitionDoesNotExist when assigning custom role | Run the az role definition create step first |
AuthorizationFailed creating the assignment | Your account lacks Owner or User Access Administrator on the hub subscription |
| TFE run succeeds but a different resource 403s | A 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:
| Field | Value |
|---|---|
| SP object id | c758aa20-7fc3-4f15-8073-4e90cb2b28b1 |
| Hub subscription | OHEMR-SUB-CONNECTIVITY-PRO-001 (6ad81c44-89ef-46b7-b8c8-a9ae34e3fe13) |
| Hub resource group | ohemr-rg-hubconnectivity-pro-cus-001 |
| Hub VNet | ohemr-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
- Creating a new VNET — hub-spoke peering and routing
- OIDC conversion guide — replacing SP secrets with federated identity
- Rotating Azure SPN — SP secret lifecycle management