Skip to content

Spoke Canvas: What a Deployment Creates

The spoke canvas is the baseline that the platform deploys into a spoke. One spoke JSON file goes in, and a subscription with network, identity, access and a few shared building blocks comes out. This page explains what is created, how the service principal of a spoke behaves, and what the deployment identity needs. For the settings of the JSON file, see the Spoke JSON reference. For the inputs and outputs of the Terraform behind it, see the Spoke canvas module reference.

The documentation uses spoke canvas and workload environment for the same thing. This page uses spoke canvas or spoke.

Purpose

The deployment is fully automated and driven by one JSON file per spoke. The result is a foundation, with connectivity, identity and secure defaults, on which the workload team builds its own solution. The team gets enough freedom to deploy and configure its own resources, but the base resources stay under control of the platform team.

Deployment flow

flowchart LR
    f["Spoke JSON file<br/>+ common settings"] --> p["Prepare<br/>schema + config tests"]
    p --> s["Subscription layer<br/>name, tags, management group"]
    s --> c["Spoke canvas layer<br/>plan"]
    c --> a["Approve<br/>(optional)"]
    a --> x["Apply"]
    x --> r["Spoke ready:<br/>network, groups, PIM,<br/>service principal, Key Vault, storage"]

The parts match the stages of a spoke run described in Pipelines and workflows. Two Terraform roots do the work, with separate state: the subscription layer and the spoke canvas layer (Provisioning). The subscription must exist before the canvas is deployed; see Subscription provisioning for why and how.

What is created in Azure

Resources are there to give the workload basic functionality and the freedom to use it. Example: the Key Vault holds the credentials of the spoke service principal, and the workload team can also use it for its own secrets. The workload's own resources can then be redeployed without losing those secrets.

Resource Intended use Notes
Resource group (per region) Holds the resources below Network Watchers use a separate, dedicated resource group
Log Analytics workspace The workload team's own logging, for example an Application Insights instance connected to it On by default
Virtual network Connectivity between the spoke's resources, and to resources outside the spoke Traffic from or to outside the spoke goes through the hub firewall and needs firewall rules
Subnets Flexible number per virtual network Each subnet can have an NSG, a route table, service endpoints and delegations
Network security groups One or more per virtual network. One per subnet is good practice, but not required A subnet without an NSG gets a default NSG
Route tables For the rare cases that need specific routes Current platform versions need no extra routes. Link a route table to a subnet in the spoke file
Hub connection Connects the virtual network to the virtual hub of the region Default peerHub: true. The virtual network DNS points to the hub DNS server of the region
Key Vault Credentials of the spoke service principal, and secrets of the workload On by default. RBAC mode. Private endpoint and public access are configurable
Storage account For the workload, for example Terraform state On by default. Encrypted with a customer-managed key that lives in the Key Vault. Private endpoints and public access are configurable
Network Watcher Network diagnostics Optional. One per region when enabled
Service principal The identity the workload team deploys with Always created. See Service principal and credentials
Azure DevOps repository and service connection A place for the workload code and a connection for its pipeline Optional, only when the azdo block exists. The repository is created only if it does not exist yet
GitHub repository and federated credentials Same, for GitHub Optional, through the GitHub extension
Shared services Use of shared API Management, Container Registry, Service Bus or Event Hub Optional, through extensions

Resource names, defaults and the optional blocks are in the Spoke JSON reference and in What gets created.

Virtual network and address space

A virtual network is configured with its address space in the spoke file. Plan the size with room to grow: changing the address space of a network in use is a disruptive change. Use the IP sizing guidance.

Key Vault and storage account switches

Setting Effect
resourceconfiguration.keyvault.deploy: false No Key Vault and no Key Vault private endpoint. No service principal secret is stored
resourceconfiguration.storageaccount.deploy: false No storage account and no storage private endpoints
Storage account enabled The Key Vault is always deployed, because the storage account needs it for customer-managed key encryption
Both set to false Neither is created

Service principal and credentials

A service principal is always created for the spoke. The workload team uses it to deploy and configure resources in the subscription. It is a permanent member of the spoke's Owners group, so it has Owner on the subscription. When the Key Vault and storage account exist, it is also Key Vault Administrator on the Key Vault and Storage Blob Data Owner on the storage account.

What credential the service principal gets depends on how the spoke is configured:

Spoke configuration Credential
Azure DevOps service connection with useFederatedCredentials: true, or the GitHub extension enabled No secret. The service connection or workflow uses workload identity federation (OIDC)
Azure DevOps service connection without useFederatedCredentials (default) A secret is generated. Client ID and secret are stored in the Key Vault
No Azure DevOps and no GitHub, Key Vault enabled A secret is generated and stored in the Key Vault
No Azure DevOps and no GitHub, Key Vault disabled The service principal is created without credentials. The workload team sets them up outside the spoke deployment

Prefer federated credentials. They remove the secret, and with it the rotation work and the expiry risk (Secret watcher). The validity of generated secrets comes from the secret rotation period in the common spoke settings; changing that value creates new secrets for every service principal that the spoke deployment controls. Changes to the required_resource_access of the service principal are ignored, to prevent needless updates (release history).

Identity in Entra ID

Two scopes need identity configuration.

Scope What it needs
The deployment framework itself A deployment service principal that can create groups, service principals and PIM settings, and an administrative unit to limit its reach. No extra groups are created for the framework
Each spoke A service principal and four groups

The administrative unit

The deployment can only manage groups that live in an administrative unit. This keeps the privileges of the deployment identity small: it is Groups Administrator on that administrative unit, not on the whole tenant. The ID of the administrative unit is part of the common spoke settings (SpokeCommon.tenantinfo.AdministrativeUnitId in mcp.json).

Groups per spoke

Group (default pattern) Role on the subscription Purpose
SG_SPOKE_<wl>_<env>_Owners Owner Full control over the subscription and everything in it, including access management
SG_SPOKE_<wl>_<env>_Contributors Contributor Full control over the resources, without the ability to set privileges
SG_SPOKE_<wl>_<env>_Readers Reader View only
SG_SPOKE_<wl>_<env>_Support None No privileges of its own. Used so that support staff can reach the Owners group through PIM

The prefix and the exact pattern come from the platform's naming settings (Naming and state). The team group named in ProductTeamGroupNames becomes a member of the groups, active or PIM-eligible depending on the PIMGroupConfig mode (nopim, self-approve, approval). See Access, groups and PIM and the Identity and access model.

Data-plane access through PIM

Reader, Contributor and Owner on the subscription do not give access to the data in Key Vault and storage by themselves. The spoke deployment therefore assigns data-plane roles from a built-in role map, at subscription scope. One assignment covers every matching Key Vault or storage account in the subscription, whether the spoke deploys them or not.

Group Standing access PIM-eligible access
Readers Key Vault Reader, Storage Blob Data Reader None
Contributors Key Vault Reader, Storage Blob Data Reader Read and write roles (Key Vault Secrets/Certificates/Crypto User, Storage Blob Data Contributor), self-approved
Owners Key Vault Reader, Storage Blob Data Reader Full control roles (Key Vault Secrets/Certificates/Crypto Officer, Storage Blob Data Owner), with approval

The role map, its levels and its PIM profiles can be overridden. The override is keyed by the exact Azure role name:

Field Meaning
levels At which access levels the role is granted. levels = [] removes the role
pim_profile Which PIM profile applies (for example nopim, self-approve, approval)

Set one field to change only that field; an omitted field keeps its built-in value. A role that is not built in must set both fields. Any built-in Azure role can be used, for example for SQL or Cosmos DB, without changing modules. An override in the common spoke settings applies to all spokes, and adInfo.PIMAssignments in a spoke file overrides it for that spoke. A spoke override wins.

"adInfo": {
  "PIMAssignments": {
    "Storage Blob Data Owner": { "pim_profile": "approval" }
  }
}

What the deployment identity needs

A deployment service principal creates the spokes. Use separate identities for the canary and the production platform environment, with equal privileges. Setting it up is part of Prepare identity.

Scope Privilege Why
Azure, the landing zones management group of the platform environment (canary or production) Owner Subscriptions are placed below it. The identity configures resources and RBAC in the spokes
Entra ID, administrative unit Groups Administrator Create and manage the spoke groups
Entra ID, Microsoft Graph application permissions See below. Admin consent is required Create service principals and groups, configure PIM

The two sources of this list differ slightly. This table shows where they agree and where only one of them has an entry. Check it against the documentation of the version you run.

Graph application permission In both lists
Application.ReadWrite.All Yes
DelegatedPermissionGrant.ReadWrite.All Yes
Group.Read.All, GroupMember.Read.All, User.Read.All Yes
PrivilegedAccess.ReadWrite.AzureADGroup Yes
PrivilegedEligibilitySchedule.ReadWrite.AzureADGroup Yes
RoleManagement.ReadWrite.Directory Yes
RoleManagementPolicy.ReadWrite.AzureADGroup Yes
RoleManagementPolicy.ReadWrite.Directory One list only
AdministrativeUnit.ReadWrite.All One list only
RoleManagement.ReadWrite.AzureADGroup, RoleManagementPolicy.Read.AzureADGroup, PrivilegedAccess.Read.AzureADGroup, PrivilegedEligibilitySchedule.Read.AzureADGroup One list only (read-level permissions)

The deployment identity also needs a data-plane role on the Terraform state storage account (Deployment layers).

Treat these permissions as high privilege

These Graph permissions can create service principals and change PIM policy. Limit the deployment identity to the pipeline that needs it, protect the service connection, and monitor its use. In the managed service model the platform team holds this identity; in the self-managed model your own team does.