Create a Service Principal to create and manage other Service Principals

When you need to work with service principals in your Azure environment, you are probably creating them via some script using the az ad sp command.

This works quite well, but these are created with your account. The account you used to log in with the Azure CLI. The same goes when using PowerShell, it’s always running in the context you used to log in. Most of the time your personal or environment administrator account.

There are a several reasons why you probably don’t want or need, these privileges assigned to your account. But you probably still want to create new service principals & assign them roles to specific services or applications. This is where an ‘administrator’ (for lack of a better name) service principal might come in to help.
By having a service principal with enough permissions on a subscription, management group, or tenant, you can get rid of these permissions on your account.

First, you need to create the ‘administrator’ service principal. I’m using the built-in Owner role for this, but you would be wise to use a custom role with just enough permissions to create users/service principals.

$createdAdminServicePrincipal = az ad sp create-for-rbac --name $administratorServicePrincipalName --role Owner --scopes /subscriptions/$subscriptionId | ConvertFrom-Json
$createdAdminServicePrincipalAppId = $createdAdminServicePrincipal.appId
$createdAdminServicePrincipalPassword = $createdAdminServicePrincipal.password

The appId and password are used to log in later on.

The object created is an Owner on the subscription ($subscriptionId), but does not have enough permission to create & assign new service principals. Trying to do so will result in an error similar to this:

Insufficient privileges to complete the operation.

What you need is the built-in Application Developer-role. This role has the following description:

Description: Users in this role will continue to be able to register app registrations even if the Global Admin has turned off the tenant level switch for “Users can register apps”.

For a complete list of all built-in roles, you should check out the documentation on this matter. These pages will also help you create custom roles as the used Actions in a role are also described over here.

Now comes the hard part, assigning this role
Assigning these Administrative Roles isn’t supported via the Azure CLI (yet?), but can be invoked by using the REST API.

With this information at hand, the script can now be extended with the following code:

# Retrieving the created service principal, because we need the `Object Id`, which isn't available in the earlier stored output.
$aadObjectCreatedAdminServicePrincipal = az ad sp show --id $createdAdminServicePrincipalAppId | ConvertFrom-Json

$applicationDeveloperBuiltInRoleId = "cf1c38e5-3621-4004-a7cb-879624dced7c"

az rest `
    --method post `
    --uri https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments `
    --headers "{ 'content-type': 'application/json' }" `
    --body "{ '@odata.type': '#microsoft.graph.unifiedRoleAssignment', 'roleDefinitionId': '$($applicationDeveloperBuiltInRoleId)', 'principalId': '$($aadObjectCreatedAdminServicePrincipal.objectId)', 'directoryScopeId': '/' }"

# Letting AAD catch its breath (also to make sure all updates are propagated). Most of the time AAD is done within 10 seconds, but I once saw it took 90+ seconds!
Start-Sleep -Seconds 30

Running this will assign the Application Developer-role to the earlier created service principal, using the Object Id.
You might notice the sleep at the end of the script. Well, as the comment states, it takes a while for this change to be propagated. It might take 1 second, it might take 90+ seconds. I’ve chosen to wait for 30 seconds before continuing with any command. This solves ~99% of the issues I sometimes have when proceeding to run other commands.

With the changes on the original, administrator, service principal are in effect, it is now safe to log in with this account and create new service principals with very limited scopes, like in the example below.

az login --service-principal --username $createdAdminServicePrincipalAppId --password $createdAdminServicePrincipalPassword --tenant $tenantId

az ad sp create-for-rbac --name $storageAccountServicePrincipalName `
    --role 'Storage Blob Data Contributor' `
    --scopes "/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Storage/storageAccounts/$storageAccountName"

The above example will create a new SP with the Storage Blob Data Contributor role on a specific storage account.

There are also some (old) posts out there stating you need the Application.ReadWrite.All permission on the AAD Graph API. This isn’t the case (anymore). While it might have sufficed in the past, I’ve tested it without this permission and it still works.
Still, if you want to add this permission for fun & giggles, or don’t take my word for it, here’s an excerpt.

az ad app permission add --id $createdAdminServicePrincipalAppId --api 00000003-0000-0000-c000-000000000000 --api-permissions 1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9=Role
az ad app permission grant --id $createdAdminServicePrincipalAppId --api 00000003-0000-0000-c000-000000000000
az ad app permission admin-consent --id $createdAdminServicePrincipalAppId

This is all there is to it for making an administrator/management service principal.
Do note, that the above code samples aren’t ready to be copy-pasted in a production-like environment. As I mentioned already, using the built-in roles isn’t a best practice in (large) environments, using Owner surely isn’t, and passing around the password (Client Secret) isn’t a good idea either. But this might help you, or at least myself, start up.


Share