Grant a service principal access to Azure

Completed

By itself, a service principal can't do anything in your Azure environment. It's just like how a user can't work with your Azure resources unless they're authorized to do so. In this unit, you'll learn how to authorize service principals to deploy and configure Azure resources, while avoiding granting unnecessary permissions.

Note

The commands in this unit are shown to illustrate concepts. Don't run the commands yet. You'll practice what you learn here soon.

Service principal authorization

Until now, you've focused on what service principals are and how they can be used to prove the identity of a pipeline to Microsoft Entra ID. This is all about authentication.

After Microsoft Entra ID has authenticated a service principal, the next question becomes: what can this service principal do? This is the concept of authorization. It's the responsibility of the Azure role-based access control (RBAC) system, sometimes called identity and access management (IAM). By using Azure RBAC, you can grant a service principal access to a specific resource group, subscription, or management group.

Note

Everything you're doing here is using the Azure RBAC system to grant access to create and manage Azure resources, like your storage accounts, App Service plan, and virtual networks. Microsoft Entra ID also has its own role system, which is sometimes called directory roles. You use these roles to grant permissions for service principals to manage Microsoft Entra ID. This module doesn't discuss this subject in depth, but be aware that the term role can be used for both situations in some documentation.

Select the right role assignment for your pipeline

A role assignment has three key parts: who the role is assigned to (the assignee), what they can do (the role), and what resource or resources the role assignment applies to (the scope).

Assignee

When you work with a service principal, you assign roles for that service principal. You use the service principal's application ID to identify the correct service principal for that assignee.

Role

It can be a little more work to figure out which role to assign. In Azure, there are a few common roles:

  • Reader, which allows the assignee to read information about resources but not modify or delete them.
  • Contributor, which allows the assignee to create resources, and to read and modify existing resources. However, contributors can't grant other principals access to resources.
  • Owner, which allows full control over resources, including granting other principals access.

Caution

You should only grant service principals the minimum permissions that they need to do their jobs. Most of the time, the Owner role is too permissive for a deployment pipeline.

There are also lots of specific roles that provide access just to a subset of functionality. You can also create your own custom role definition to specify the exact list of permissions that you want to assign.

Note

Custom role definitions can be a powerful way to grant permissions for your Azure resources, but they can be difficult to work with. It's not always easy to determine exactly which permissions you need to add to a custom role definition, and you might accidentally make the role definitions too restrictive or too permissive. If you're not sure what to do, it's best to use one of the built-in role definitions instead. Custom role definitions are beyond the scope of this module.

Scope

You need to determine how broadly you assign the role. This decision affects the number of resources that service principal can modify. Common scopes include:

  • Single resource: You can grant access just to a specific resource. Typically, deployment pipelines don't use this scope because a pipeline creates resources that don't exist yet, or it reconfigures multiple resources.
  • Resource group: You can grant access to all resources within a resource group. Contributors and Owners can also create resources within the group. This is a good option for many deployment pipelines.
  • Subscription: You can grant access to all resources within a subscription. If you have multiple applications, workloads, or environments in a single subscription, you can grant permissions to the subscription's scope. This is usually too permissive for a deployment pipeline, though. You should instead consider scoping your role assignments to resource groups, unless your deployment workflow itself needs to create resource groups.

Remember that role assignments are inherited. If you assign a role at a subscription, the assignee will have access to every resource group and resource inside that subscription.

Selecting the right role assignment

Now that you understand the components of a role assignment, you can decide the appropriate values for your scenarios. Here are some general guidelines to consider:

  • Use the least permissive role that you can. If your pipeline is only going to deploy basic Bicep templates and won't manage role assignments, don't use the Owner role.
  • Use the narrowest scope that you can. Most pipelines only need to deploy resources to a resource group, so they shouldn't be given subscription-scoped role assignments.
  • For many pipelines, a good default option for a role assignment is the Contributor role on the resource group scope.
  • Consider everything your pipeline does, and everything it might do in the future. For example, you might consider creating a custom role definition for your website's deployment pipeline and only grant permissions for App Service and Application Insights. Next month, you might need to add an Azure Cosmos DB account to your Bicep file, but the custom role will block Azure Cosmos DB resources from being created. Instead, it's often better to use a built-in role, or a combination of built-in roles, to avoid having to repeatedly change your role definitions. Consider using Azure Policy to enforce your governance requirements for allowed services, SKUs, and locations.
  • Test the pipeline to verify that the role assignment works.

Mixing and matching role assignments

You can create multiple role assignments that provide different permissions at different scopes. For example, you might assign a service principal the role of Reader with a scope of the entire subscription, and then separately assign the same service principal the role of Contributor for a specific resource group. When the service principal tries to work with the resource group, the more permissive assignment is applied.

Working with multiple environments

You likely work with multiple environments, like development, test, and production environments for your applications. The resources for each environment should be deployed to different resource groups or subscriptions.

You should create separate service principals for each environment, and grant each service principal the minimum set of permissions that it needs for its deployments. Be especially careful to avoid mixing permissions for production deployments with those for deployments to non-production environments.

Create a role assignment for a service principal

To create a role assignment for a service principal, use the az role assignment create command. You need to specify the assignee, role, and scope:

az role assignment create \
  --assignee 00001111-aaaa-2222-bbbb-3333cccc4444 \
  --role Contributor \
  --scope "/subscriptions/aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e/resourceGroups/ToyWebsite" \
  --description "The deployment pipeline for the company's website needs to be able to create resources within the resource group."

Let's look at each argument:

  • --assignee specifies the service principal. To avoid ambiguity, it's a good practice to use the application ID.
  • --role specifies the role. If you use a built-in role, you can specify it by name. If you use a custom role definition, specify the full role definition ID.
  • --scope specifies the scope. This is usually a resource ID for a single resource, a resource group, or a subscription.
  • --description is a human-readable description of the role assignment.

To create a role assignment for a service principal, use the New-AzRoleAssignment cmdlet. You need to specify the assignee, role, and scope:

New-AzRoleAssignment `
  -ApplicationId 00001111-aaaa-2222-bbbb-3333cccc4444 `
  -RoleDefinitionName Contributor `
  -Scope '/subscriptions/aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e/resourceGroups/ToyWebsite' `
  -Description "The deployment pipeline for the company's website needs to be able to create resources within the resource group."

Let's look at each argument:

  • -ApplicationId specifies the service principal's application registration ID.
  • -RoleDefinitionName specifies the name of a built-in role. If you use a custom role definition, specify the full role definition ID by using the -RoleDefinitionId argument instead.
  • -Scope specifies the scope. This is usually a resource ID for a single resource, a resource group, or a subscription.
  • -Description is a human-readable description of the role assignment.

Tip

It's a good practice to provide a justification for your role assignments by specifying a description. A description helps anyone who reviews the role assignments later to understand their purpose, and to understand how you decided on the assignee, role, and scope.

Note

Role assignments can take a few minutes to become active.

Create a service principal and role assignment in one operation

You can also create a role assignment at the same time that you create a service principal. The code is similar to the command that you used to create a service principal in the previous units, but with some additional arguments:

az ad sp create-for-rbac \
  --name MyPipeline \
  --role Contributor \
  --scopes "/subscriptions/f0750bbe-ea75-4ae5-b24d-a92ca601da2c/resourceGroups/ToyWebsite"
$servicePrincipal = New-AzADServicePrincipal `
  -DisplayName MyPipeline `
  -Role Contributor `
  -Scope '/subscriptions/f0750bbe-ea75-4ae5-b24d-a92ca601da2c/resourceGroups/ToyWebsite'

Grant access using Bicep

Role assignments are Azure resources. This means that you can create a role assignment by using Bicep. You might do this if you initialize your resource groups using Bicep, and then deploy the resources into the resource group using a service principal. Here's an example Bicep definition for the role assignment above:

resource roleAssignment 'Microsoft.Authorization/roleAssignments@2023-04-01-preview' = {
  name: guid(principalId, roleDefinitionId, resourceGroup().id)
  properties: {
    principalType: 'ServicePrincipal'
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId)
    principalId: principalId
    description: 'The deployment pipeline for the company\'s website needs to be able to create resources within the resource group.'
  }
}

Let's look at each argument:

  • name is a unique identifier for the role assignment. This must be in the form of a globally unique identifier (GUID). It's a good practice to use the guid() function in Bicep to create a GUID, and to use the principal ID, role definition ID, and scope as the seed arguments for the function to ensure you create a name that's unique for each role assignment.
  • principalType should be set to ServicePrincipal.
  • roleDefinitionId is the fully qualified resource ID for the role definition you're assigning. Mostly you'll work with built-in roles, and you'll find the role definition ID in the Azure built-in roles documentation. For example, the Contributor role has the role definition ID b24988ac-6180-42a0-ab88-20f7382dd24c. When you specify it in your Bicep file, you express this using a fully qualified resource ID, such as /subscriptions/aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c.
  • principalId is the service principal's object ID. Make sure you don't use the application ID or the application registration's object ID.
  • description is a human-readable description of the role assignment.