Terraform Provider for Microsoft Fabric: #3 Creating a workload identity with Fabric permissions
After the first two blog posts you were able to get working quickly in the user context and then some real world advice in expanding your config and defining those Fabric Administrator resources. This is a common way to develop and test your initial config. In the third and fourth blog posts we will look at implementing an example production deployment pipeline, starting in this post with creating and configuring the workload identity.
This post is the third in the series.
- Accelerating first steps using the CLIs.
- Using MCP servers and Fabric CLI to help define your fabric resources.
- Creating a workload identity with Fabric permissions.
- Deploying a fabric config with Terraform in GitHub Actions.
This workload identity config will be common to all of the main CI/CD platforms used on Azure such as GitHub Actions, Azure DevOps, GitLab, BitBucket, Jenkins, CircleCI and more.
We will create a managed identity and look at how to add authorisations for Azure, for the Microsoft Graph, and of course for the Fabric APIs to enable the Fabric Terraform Provider. This then opens up a rich mix of Terraform providers, so you can include resources from the azurerm, azapi, azuread, and fabric providers in your configs. The commands here will also create a storage account for your remote backend, with blob versioning and enforced RBAC authentication mode.
The last blog page in the series will give an example of a GitHub Actions deployment pipeline with OpenID Connect configured with a federated credential on the managed identity.
Configuring a User Assigned Managed Identity for use in CI/CD pipelines
The fabric provider documentation includes a number of options for manually creating workload identities (service principals and managed identities) for use in CI/CD pipelines. Here is an example scripted configuration using a user assigned managed identity that follows many of the security recommendations.
Core Terraform bootstrap
First, create a remote state backend and a user assigned managed identity. The storage account will enforce RBAC authorization and other security recommendations, and the managed identity will have an RBAC role assignment for writing state to the backend. In an Azure Landing Zone environment this may use the management subscription in the platform landing zone.
Customise the variable values.
terraform_resource_group_name="rg-terraform" location="uksouth" managed_identity_name="mi-terraform" storage_account_prefix="saterraform" management_subscription_id="<subscription_guid>"
Run the commands to create and configure the core bootstrap resources.
az account set --subscription $management_subscription_id az group create --name $terraform_resource_group_name --location $location az identity create --name $managed_identity_name --resource-group $terraform_resource_group_name --location $location managed_identity_object_id=$(az identity show --name $managed_identity_name --resource-group $terraform_resource_group_name --query principalId -otsv) managed_identity_client_id=$(az identity show --name $managed_identity_name --resource-group $terraform_resource_group_name --query clientId -otsv) storage_account_name="$storage_account_prefix$(az group show --name $terraform_resource_group_name --query id -otsv | sha1sum | cut -c1-8)" az storage account create --name $storage_account_name --resource-group $terraform_resource_group_name --location $location --min-tls-version TLS1_2 --sku Standard_LRS --https-only true --default-action "Allow" --public-network-access "Enabled" --allow-shared-key-access false --allow-blob-public-access false storage_account_id=$(az storage account show --name $storage_account_name --resource-group $terraform_resource_group_name --query id -otsv) az storage account blob-service-properties update --account-name $storage_account_name --enable-versioning --enable-delete-retention --delete-retention-days 7 az storage container create --name prod --account-name $storage_account_name --auth-mode login az role assignment create --role "Storage Blob Data Contributor" --assignee $managed_identity_object_id --scope "$storage_account_id/blobServices/default/containers/prod" az storage container create --name dev --account-name $storage_account_name --auth-mode login az role assignment create --role "Storage Blob Data Contributor" --assignee $(az ad signed-in-user show --query id -otsv) --scope "$storage_account_id/blobServices/default/containers/dev" echo "Core bootstrap complete."
You have created the managed identity and storage account for the backend.
Configure Fabric tenant level Developer settings
The Developer settings in Fabric Admin portal’s tenant settings are the control point to enable workload identities to use the Fabric REST APIs used by the fabric Terraform provider. In this section we will create an Entra security group, add the managed identity, and then configure the Developer settings.
Run the example commands below to create the Entra ID security group and add the user assigned managed identity as a member.
fabric_group_name="Microsoft Fabric Workload Identities" fabric_group_description="Service Principals and Managed Identities used for Fabric automation." fabric_group_mail_nickname="FabricWorkloadIdentities" az ad group create --display-name "$fabric_group_name" --description "$fabric_group_description" --mail-nickname "$fabric_group_mail_nickname" az ad group member add --group "$fabric_group_name" --member-id "$managed_identity_object_id" fabric_group_id=$(az ad group show --group "Microsoft Fabric Workload Identities" --query id -otsv) echo "Created security group $fabric_group_name"
Specify the group in the Developer settings within Fabric Admin portal’s tenant settings. This configuration is the control point for the workload identity to be authorized to access the Fabric APIs used by the Terraform provider.
body=$(jq -nc --arg oid "$fabric_group_id" --arg name "$fabric_group_name" '{"enabled":true,"canSpecifySecurityGroups":true,"enabledSecurityGroups":[{"graphId":$oid,"name":$name}]}')
fab api --method post admin/tenantsettings/ServicePrincipalAccessGlobalAPIs/update -i "$body"
fab api --method post admin/tenantsettings/ServicePrincipalAccessPermissionAPIs/update -i "$body"
fab api --method post admin/tenantsettings/AllowServicePrincipalsCreateAndUseProfiles/update -i "$body"
The Fabric CLI commands are a useful example of using the fab api command to call the Fabric REST API directly, similar to using Azure CLI’s az rest command.

Authorise access to a Fabric Capacity
At this point the managed identity can access the Fabric REST APIs, but it does not currently have access to a Fabric Capacity F-SKU.
There are two ways to grant capacity-level access:
- Azure: Assign the identity as an administrator on the Azure fabric capacity resource. Requires an Azure RBAC role assignment such as Contributor role on the fabric capacity.
- Fabric: Assign a Fabric RBAC role (e.g. Admin, Contributor) to the capacity in the Fabric Admin Portal. Requires either the Fabric Administrator role assigned in Entra, or Admin role on the Capacity in Fabric Admin.
In this section we will add the user assigned managed identity as an Administrator on the fabric capacity in Azure. The example commands below will extend the array with the workload identity’s object ID. (It is also very simple to do this within the Azure Portal.)
Get the Fabric Capacity resource id, e.g.:
fabric_resource_id=$(az fabric capacity show --resource-group my_fabric_rg --name my_capacity_name --query id -otsv)
Then extend the array of administrators:
admins=$(az fabric capacity show --ids $fabric_resource_id --query administration.members --output json | jq --arg oid "$managed_identity_object_id" '. += [$oid]')
az fabric capacity update --ids $fabric_resource_id --administration "{\"members\": $admins}"
Additional RBAC role assignments (optional)
The managed identity can be configured with additional Azure RBAC role assignments if required. For example:
role="Contributor" workload_subscription_id="<subscription_guid>"
Create the role assignment.
scope="/subscriptions/$workload_subscription_id" az role assignment create --role $role --scope $scope --assignee-object-id $managed_identity_object_id --assignee-principal-type ServicePrincipal
An obvious use case here is to enable the managed identity to be able to create a Fabric Capacity using azurerm_fabric_capacity. If you are looking to adhere to least privilege recommendations, then you could first create a custom role that included "Microsoft.Fabric/*" (or a subset) in the actions array.
Additional Entra ID app role permissions (optional)
A common Fabric Administration requirement is to create a Fabric RBAC role assignment for an Entra security group to have Admin, Contributor, Member, or Viewer access to a workspace. The fabric_workspace_role_assignment requires the object ID in the principal block, so it is useful to be able to reference the azuread_group data source.
Adding app role permissions to the managed identity is a little publicized feature that opens up access to the Microsoft Graph and the azuread provider. The commands below use az rest as there is currently no support for managed identity app roles in the Azure CLI.
graph_app_id="00000003-0000-0000-c000-000000000000"
graph_object_id=$(az ad sp show --id $graph_app_id --query id -otsv)
for role in User.Read.All Group.Read.All
do
app_role_id=$(az ad sp show --id $graph_app_id --query "appRoles[?value == '"$role"'].id" -otsv)
body=$(jq -nc --arg graph "$graph_object_id" --arg mi "$managed_identity_object_id" --arg role "$app_role_id" '{principalId:$mi,resourceId:$graph,appRoleId:$role}')
echo "Adding app role $role:"
jq . <<< $body
az rest --method post --uri "https://graph.microsoft.com/v1.0/servicePrincipals/${managed_identity_object_id}/appRoleAssignments" --body "$body"
done
You can view app roles for any managed identity in the Entra Admin portal. Select Enterprise Applications in the navigation panel and then change the Application type filter to Managed Identities and then use the search to find the managed identity. Select Permissions to view the app roles, which are the permissions in the Admin consent tab.

You may add additional permissions as required, the following command lists all of the possible app roles for the Microsoft Graph:
az ad sp show --id 00000003-0000-0000-c000-000000000000 --query "appRoles[].value" -oyamlc
App role permissions for managed identities are equivalent to the API role permissions for a service principal and have implicit admin consent, so use with care.
Summary
You now have a managed identity configured with the correct permissions to write the Terraform state to the remote backend in the storage account, plus authorisations for Fabric, Azure and the Microsoft Graph.
In the final blog post in the series we will walk through adding a federated credential to enable OpenID Connect authentication from GitHub Actions, and see an example YAML workflow to deploy your synced Terraform config.