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.
Related pages¶
- Spoke canvas module reference: inputs, outputs and modules of the Terraform
- Spoke config and New spoke
- Deploy the spoke canvas: the first spoke of a new platform
- Subscription provisioning and Configuration drift detection: the design decisions behind two parts of the canvas
- What you get: a spoke: the consumer's view
- Operating pages for the spoke pipelines: Spoke drift detection, Validate spoke files, Dedicated group manager, Billing export, Spoke extensions