Working with Service Principals in Azure, Bicep and Azure DevOps

Published on
Reading time
Authors

Trying to assign a Service Principal to an RBAC role in Azure and receiveing the error "Principals of type Application cannot validly be used in role assignments"? Read on to find out how to fix it!

Background

I have recently been deploying a lot of Azure infrastucture using a combination of Bicep and Azure Pipelines. The Pipelines have been authorised to access target Azure subscriptions using Service Connections configured to use an Azure Active Directory (AAD) Service Principal.

If you've done any work with Service Principals you've perhaps (like me) found it hard to know sometimes how to configure them because there are few different identifiers that entities in Azure AD can have. Another challenge is the terms used to refer to Service Principals and one or more of these Azure AD identifiers are used inconsistently or interchangeably.

Let's start by looking at how you create a Service Principal in Azure AD using the Azure CLI. Often organisations will block access to the Azure AD blade in the Azure Portal, but even if this is the case you can still use the Azure CLI to at least inspect Service Principal details - excluding secrets, of course!

az ad sp create-for-rbac \
--name sp-demo-principal \
--role "Contributor" \
--scopes /subscriptions/00000000-0000-0000-0000-000000000000

If you have the appropritate permissions you will receive a response in this format.

{
  "appId": "f8e37177-9df8-4d64-8fbd-3b07bead7762",
  "displayName": "sp-demo-principal",
  "password": "NOT_A_REAL_PASSWORD",
  "tenant": "4c9ddf36-94fd-4f93-8eed-96ea3b873381"
}

OK, so now we have the appId for the new Service Principal. This is also called the Client ID. A Service Princpal is a type of Azure AD Application, as oppsed to a User object.

Using Client ID in Azure DevOps

When you setup a new Service Connection in Azure DevOps you will be presented by a form similar to the one below. Note the red box at the bottom. It has a title of "Service Princpal Id", then underneath it says "Client Id".. which tells us what we actually need to use! Yes, you need to use the Client ID (aka appId) for this setup.

Setting up a new Service Connection in Azure DevOps.

Object ID

Now the fun starts! Some times you will come across references to needing the Object ID of a Service Principal. Guess what? That's not the same as the Client ID!

So how do we get the Object ID?

az ad app show \
--id f8e37177-9df8-4d64-8fbd-3b07bead7762 \
--query id

It will return a single GUID.

"{8a6c2b1e-510a-4e64-b0bf-1bcefdcb8844}"

If you try and use this value in either a Service Connection for Azure DevOps or in an Azure RBAC assignment you will receive an error. For or Azure RBAC assignments the error message will be the one at the very top of this post.

For now let's move on as we're not going to use Object ID in this scenario, but I did want to include it here so you can understand it is different to Client ID and the next one...

Service Principal ID

This is the most confusing one, especially given the reference to this value in the Service Connection dialog for Azure DevOps. Guess what? It's not the Object ID and it's not the Client ID! Yay! 🙃

So how do you get the Service Principal ID?

az ad sp show \
--id f8e37177-9df8-4d64-8fbd-3b07bead7762 \
--query id

This will return a single GUID (that's not the same as the Object ID...)

"{6566ad53-ad3a-4be0-8891-e5439d65448b}"

Where do you use the Service Principal GUID? If you are doing Azure RBAC assignments in your Bicep that include your Service Principal you will need to use this GUID.

Here's a sample from the Bicep documentation.

main.bicep
@description('Specifies the role definition ID used in the role assignment.')
param roleDefinitionID string

@description('Specifies the principal ID assigned to the role.')
param principalId string

var roleAssignmentName= guid(principalId, roleDefinitionID, resourceGroup().id)
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2021-04-01-preview' = {
  name: roleAssignmentName
  properties: {
    roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionID)
    principalId: principalId
  }
}

Using our created Service Princpal and we can now assign it an RBAC role as follows.

az group create \
--name exampleRG \
--location eastus

az deployment group create \
--resource-group exampleRG \
--template-file main.bicep \
--parameters roleDefinitionID=9980e02c-c2be-4d73-94e8-173b1dc7cf3c \
             principalId=6566ad53-ad3a-4be0-8891-e5439d65448b

If you tried the Client or Object ID values the assignment would fail and you may receive this error:

"Principals of type Application cannot validly be used in role assignments."

The solution is to use the ID retrieved using the Service Principal ID method.

I tore my hair out for a while on this, so I'm documenting it so I remember for next time (and hopefully save you some time too!)

Happy Days!

😎