Create an Azure Service Principal at the Management Group scope

I’ve been seriously behind in writing blog posts for the this blog. So, here’s a quicky. The PowerShell script below will create an Azure Active Directory (AAD) service principal with RBAC permissions scoped to the management group level.

Why would you do this? Azure management groups are “supra” containers which can contain subscriptions. In large Azure deployments, there are likely to be many subscriptions and management groups are a convenient container for subscription management.

In my particular case, I needed to link a client’s Azure DevOps repo to Azure China. They deploy Azure Policy and other management artifacts from code and they expect to be able to do that in groups of subscriptions — IOW, management groups.

This script is simple and, of course, you could do this in the console but it does have at least one cool feature: it will generate an OK-ish password (it’s no random character generator), add that to the service principal and then write it to your desktop. You should store this password in a Key Vault, of course. Also the script will process an array of both management groups and RBAC roles so you can assign the service principal to multiple management groups with multiple RBAC roles for the service principal.

I hope this helps you by making Azure service principals easier to manage.

<#
	.SYNOPSIS
		Creates an Azure AD service principal for use as a service connection in Azure DevOps
	
	.DESCRIPTION
		A script to create a service principal at the management group level. Will add any number of RBAC roles to any number of management groups (specified as array parameters) when creating a single service principal. Uses the Az cmdlets and runs under PowerShell 7 or later.
	
	.PARAMETER NameAdSp
		The name of the user-assigned managed identity
	
	.PARAMETER NameManagementGroups
		An array of strings containing the management groups to which the service principal should be granted scope access to.       
	
    .PARAMETER RBACRoles
		String array of RBAC roles to be assigned to the SP. See https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles

    .PARAMETER DurationYears
		Duration, in years, for the service principal. Defaults to 1
	
	.EXAMPLE
		PS C:\> .\CreateServicePrincipalForAzCnAdo.ps1 -NameAdSp 'MsiAzCnAdo' -NameManagementGroups [array of RBAC roles] -RBACRoles [array of RBAC roles]
	
	.NOTES
	Alex Neihaus 2021-07-09
        (c) 2021 Air11 Technology LLC -- licensed under the Apache OpenSource 2.0 license, https://opensource.org/licenses/Apache-2.0
        Licensed under the Apache License, Version 2.0 (the "License");
        you may not use this file except in compliance with the License.
        You may obtain a copy of the License at
        http://www.apache.org/licenses/LICENSE-2.0
    
        Unless required by applicable law or agreed to in writing, software
        distributed under the License is distributed on an "AS IS" BASIS,
        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        See the License for the specific language governing permissions and
        limitations under the License.
    
        Author's blog: https://yobyot.com
#>
[CmdletBinding()]
param
(
    [Parameter(Mandatory = $true,
        HelpMessage = 'Enter the name of the new service principal')]
    [string]$NameAdSp,
    [Parameter(Mandatory = $true,
        HelpMessage = 'Enter the name(s) of the management groups to be scoped to the service principal')]
    [string[]]$NameManagementGroups,
    [Parameter(Mandatory = $true,
        HelpMessage = 'Enter the name(s) of the RBAC roles to be assigned to the service principal')]
    [string[]]$RBACRoles,
    [Parameter(Mandatory = $false,
        HelpMessage = 'Enter the number of years the generated service principal password is good for; defaults to one year')]
    [int]$durationYears = 1
)
Function New-AlmostRandomPassword
{
    $pwdchararray = "!?@#%^&*0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".tochararray()
    ($pwdchararray | Get-Random -Count 22) -Join ''
}
$pwdFromFunction = New-AlmostRandomPassword

# Create AzureAD password credential object
$passwordCredential = New-Object -TypeName Microsoft.Azure.Commands.ActiveDirectory.PSADPasswordCredential
$passwordCredential.StartDate = $(Get-Date)
$passwordCredential.EndDate = $(Get-Date).AddYears($durationYears)
$passwordCredential.Password = $pwdFromFunction

###

$newAzAdSp = New-AzADServicePrincipal -DisplayName $NameAdSp -PasswordCredential $passwordCredential
Update-AzADApplication -ApplicationId $newAzAdSp.ApplicationId -ReplyUrl "https://$($NameAdSp)" -DisplayName $NameAdSp

foreach ($role in $RBACRoles)
{
    foreach ($managementGroup in $NameManagementGroups)
    {
        # Get the management group ID 
        $managementGroupId = (Get-AzManagementGroup -GroupId $managementGroup).Id
        # Assign the RBAC role(s) to the service principal
        New-AzRoleAssignment -ObjectId $newAzAdSp.Id -RoleDefinitionName $role -Scope $managementGroupId
    }
}

$objOutput = New-Object -TypeName PSObject -Property (
    [ordered]@{
        "AzureTenantId"        = "$((Get-AzTenant).Id)"
        "ServicePrincipalName" = "$(($newAzAdSp).DisplayName)"
        "ApplicationId"        = "$(($newAzAdSp).ApplicationId)"
        "ApplicationPassword"  = $pwdFromFunction
        "ManagementGroupName"  = "$($NameManagementGroups -join ",")"
    }
)

$objOutput | ConvertTo-Json | Out-File "$HOME/Desktop/$NameAdSp.json"

Posted

in

,

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *