PR.

AzureMicrosoft EntraIdentityAzure CLI

Use Azure CLI to keep Service Principal Azure RBAC assignments up to date

published: 02-22-2024

One of the rules of Identity Management is the principal of least privilege. This means that a user should only have the permissions necessary to perform their job. In Azure, this is achieved through Role-Based Access Control (RBAC). When I am first setting up a project and connect my repository to Azure I will typically assign the Contributor role to the service principal that is created. This is a good start, but as the project grows and more resources are added, the permissions of the service principal should be updated to reflect the new resources.

This can be a bit of a hassle and you don't want to be doing this manually. The script below is what I use to add or remove roles from a service principal. It uses the Azure CLI to modify the roles assigned to a service principal.

param(
    [Parameter(Mandatory=$true)]    
    [string]
    $APP_ID
)

# ------------------------------------------------------------------------
# 1. Get the Service Principal Object ID
# ------------------------------------------------------------------------

$SP_JSON = az ad sp show `
    --id $APP_ID

$SP_DATA = $SP_JSON | ConvertFrom-Json
$SP_ASSIGNEE_OBJECT_ID = $SP_DATA.id

# ------------------------------------------------------------------------
# 2. List of roles to assign, roles not in the list will be removed from the service principal
# ------------------------------------------------------------------------

$SUBSCRIPTION_ID = az account show --query id --output tsv # Returns the current subscription ID

# Create an array of roles with their respective scopes
$ROLES_TO_ASSIGN = @(
    [PSCustomObject]@{Role="Contributor"; Scope="/subscriptions/${SUBSCRIPTION_ID}"}
)

# ------------------------------------------------------------------------
# 3. Get a list of existing role assignments and compare with the list of roles to assign
# ------------------------------------------------------------------------

# Get a list of existing role assignments for the service principal
$ASSIGNED_ROLES_JSON = az role assignment list `
    --assignee $SP_ASSIGNEE_OBJECT_ID `
    --query "[].{Role:roleDefinitionName, RoleId:roleDefinitionId, Scope:scope}" `
    --output json

$ASSIGNED_ROLES = $ASSIGNED_ROLES_JSON | ConvertFrom-Json

# Find Roles that are not in the list of roles to assign, these will be removed.
$ROLES_TO_REMOVE = $ASSIGNED_ROLES | Where-Object { 
    $role = $_.Role
    -not ($ROLES_TO_ASSIGN | Where-Object { $_.Role -eq $role -and $_.Scope -eq $_.Scope })
}

# ------------------------------------------------------------------------
# 4. Remove roles that are not in the list of roles to assign
# ------------------------------------------------------------------------

# Loop over the array and remove the role from the service principal
$ROLES_TO_REMOVE | ForEach-Object {
    az role assignment delete `
        --role $_.RoleId `
        --assignee $SP_ASSIGNEE_OBJECT_ID `
        --scope $_.Scope
}

# ------------------------------------------------------------------------
# 5. Assign roles to the service principal
# ------------------------------------------------------------------------

# Loop over the array and assign the role to the service principal
$ROLES_TO_ASSIGN | ForEach-Object {
    az role assignment create `
        --role $_.Role `
        --subscription $SUBSCRIPTION_ID `
        --assignee-object-id  $SP_ASSIGNEE_OBJECT_ID `
        --assignee-principal-type ServicePrincipal `
        --scope $_.Scope
}

Here's a step-by-step breakdown:

  1. Get the Service Principal Object ID Parameter Setup: Accepts an application ID $APP_ID. Service Principal Lookup: Uses the Azure CLI command az ad sp show to fetch details about the service principal associated with the given application ID. The output is in JSON format and is converted to a PowerShell object for easier manipulation. The service principal's object ID is extracted and stored in $SP_ASSIGNEE_OBJECT_ID.

  2. Define Roles to Assign Current Subscription ID: Retrieves the current Azure subscription ID using az account show. This ID is used to define the scope for role assignments. Roles to Assign: Defines an array of roles $ROLES_TO_ASSIGN that should be assigned to the service principal. Each role is associated with a scope, typically at the subscription level in this script.

  3. Compare Existing Role Assignments Fetch Existing Assignments: Lists the current role assignments for the service principal using az role assignment list, filtering the output to include the role name, role ID, and scope. This information is converted from JSON to a PowerShell object. Identify Roles to Remove: Compares the existing role assignments against the desired roles defined in $ROLES_TO_ASSIGN. Roles that are currently assigned but not in the desired list are marked for removal.

  4. Remove Unwanted Roles Deletion of Roles: For each role identified as no longer needed, the script uses az role assignment delete to remove the assignment from the service principal. It iterates over $ROLES_TO_REMOVE and deletes each role based on its role ID and scope.

  5. Assign Specified Roles Role Assignment: Finally, the script assigns each role defined in $ROLES_TO_ASSIGN to the service principal. It loops over the array, creating a role assignment for each role using az role assignment create. The command specifies the role, the service principal's object ID, the principal type (ServicePrincipal), and the scope of the assignment (usually the subscription).

Summary

This script ensures that a specific service principal has precisely the roles defined in the $ROLES_TO_ASSIGN array, within the context of the current Azure subscription. It removes any existing role assignments not listed and adds any that are missing. This can be particularly useful for managing access control in automated deployment scripts or in scenarios where access needs to be strictly controlled and updated programmatically.