Skip to content

Commit 1905d57

Browse files
feat: update subscription creator role assignment function (#340)
# Pull Request ## Description Remove dependency on AZ PowerShell and refactor the code for the ALZ use case ## License By submitting this pull request, I confirm that my contribution is made under the terms of the projects associated license.
1 parent a3d2f4b commit 1905d57

File tree

4 files changed

+151
-194
lines changed

4 files changed

+151
-194
lines changed

Diff for: actions_bootstrap.ps1

-9
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,6 @@ $null = $modulesToInstall.Add(([PSCustomObject]@{
2929
ModuleName = 'platyPS'
3030
ModuleVersion = '0.12.0'
3131
}))
32-
# Required for Invoke-EABillingSPNPermissionsSetup to work
33-
$null = $modulesToInstall.Add(([PSCustomObject]@{
34-
ModuleName = 'Az.Accounts'
35-
ModuleVersion = '2.10.4'
36-
}))
37-
$null = $modulesToInstall.Add(([PSCustomObject]@{
38-
ModuleName = 'Az.Resources'
39-
ModuleVersion = '6.5.0'
40-
}))
4132

4233
'Installing PowerShell Modules'
4334
foreach ($module in $modulesToInstall) {

Diff for: src/ALZ/ALZ.psd1

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
FunctionsToExport = @(
7575
'Test-AcceleratorRequirement',
7676
'Deploy-Accelerator',
77-
'Invoke-EABillingSPNPermissionsSetup'
77+
'Grant-SubscriptionCreatorRole'
7878
)
7979

8080
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.

Diff for: src/ALZ/Public/Grant-SubscriptionCreatorRole.ps1

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
function Grant-SubscriptionCreatorRole {
2+
<#
3+
.SYNOPSIS
4+
Assigns the 'SubscriptionCreator' role to a specified service principal to allow it to create subscriptions in the specified billing account.
5+
6+
.DESCRIPTION
7+
Assigns the 'SubscriptionCreator' role to a specified service principal to allow it to create subscriptions in the specified billing account.
8+
9+
.EXAMPLE
10+
# Grant the 'SubscriptionCreator' role on the specified Enterprise Agreement billing account - using the 'billingAccountID' and 'enrollmentAccountID' parameters
11+
Grant-SubscriptionCreatorRole -servicePrincipalObjectId "bd42568a-7dd8-489b-bbbb-cb96cfe10fb5" -billingAccountID "1234567" -enrollmentAccountID "987654"
12+
13+
# Grant the 'SubscriptionCreator' role on the specified Enterprise Agreement billing account - using the 'billingResourceID' parameter
14+
Grant-SubscriptionCreatorRole -servicePrincipalObjectId "bd42568a-7dd8-489b-bbbb-cb96cfe10fb5" -billingResourceID "/providers/Microsoft.Billing/billingAccounts/1234567/enrollmentAccounts/987654"
15+
16+
# Grant the 'SubscriptionCreator' role on the specified Microsoft Customer Agreement billing account - using the 'billingAccountID', 'billingProfileID', and 'invoiceSectionID' parameters
17+
Grant-SubscriptionCreatorRole -servicePrincipalObjectId "bd42568a-7dd8-489b-bbbb-cb96cfe10fb5" -billingAccountID "aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_xxxx-xx-xx" -billingProfileID "AW4F-xxxx-xxx-xxx" -invoiceSectionID "SH3V-xxxx-xxx-xxx"
18+
19+
# Grant the 'SubscriptionCreator' role on the specified Microsoft Customer Agreement billing account - using the 'billingResourceID' parameter
20+
Grant-SubscriptionCreatorRole -servicePrincipalObjectId "bd42568a-7dd8-489b-bbbb-cb96cfe10fb5" -billingResourceID "/providers/Microsoft.Billing/billingAccounts/aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_xxxx-xx-xx/billingProfiles/AW4F-xxxx-xxx-xxx/invoiceSections/SH3V-xxxx-xxx-xxx"
21+
#>
22+
23+
[CmdletBinding(DefaultParameterSetName = "Default")]
24+
param (
25+
[Parameter(ParameterSetName = "Default", Mandatory = $true, Position = 1, HelpMessage = "(Required) Provide a Service Principal Object ID to grant the 'SubscriptionCreator' role to on the specified billing account. This can be an app registration or a managed identity. Example: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'.")]
26+
[string]
27+
$servicePrincipalObjectId,
28+
29+
[Parameter(ParameterSetName = "Default", Mandatory = $false, Position = 2, HelpMessage = "(Optional) If using an Enterprise Agreement or Microsoft Customer Agreement, provide the billing account ID that the service principal will be granted the 'SubscriptionCreator' role upon. Examples: '1234567' or 'aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_xxxx-xx-xx'.")]
30+
[string]
31+
$billingAccountID = "",
32+
33+
[Parameter(ParameterSetName = "Default", Mandatory = $false, Position = 3, HelpMessage = "(Optional) If using an Enterprise Agreement, provide the enrollment account ID that the service principal will be granted the 'SubscriptionCreator' role upon. Example: '987654'.")]
34+
[string]
35+
$enrollmentAccountID = "",
36+
37+
[Parameter(ParameterSetName = "Default", Mandatory = $false, Position = 4, HelpMessage = "(Optional) If using a Microsoft Customer Agreement, provide the billing profile ID that the service principal will be granted the 'SubscriptionCreator' role upon. Example: 'AW4F-xxxx-xxx-xxx'.")]
38+
[string]
39+
$billingProfileID = "",
40+
41+
[Parameter(ParameterSetName = "Default", Mandatory = $false, Position = 5, HelpMessage = "(Optional) If using a Microsoft Customer Agreement, provide the invoice section ID that the service principal will be granted the 'SubscriptionCreator' role upon. Example: 'SH3V-xxxx-xxx-xxx'.")]
42+
[string]
43+
$invoiceSectionID = "",
44+
45+
[Parameter(ParameterSetName = "Advanced", Mandatory = $false, Position = 6, HelpMessage = "(Optional) Provide the resource ID for the billing account that the service will be granted the 'SubscriptionCreator' role upon. This differs based on the type of agreement you have. Examples: '/providers/Microsoft.Billing/billingAccounts/1234567/enrollmentAccounts/987654' or '/providers/Microsoft.Billing/billingAccounts/aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_xxxx-xx-xx/billingProfiles/AW4F-xxxx-xxx-xxx/invoiceSections/SH3V-xxxx-xxx-xxx'.")]
46+
[string]
47+
$billingResourceID = "",
48+
49+
50+
[Parameter(ParameterSetName = "Default", Mandatory = $false, Position = 7, HelpMessage = "(Optional) Provide a the Azure management API url prefix. Default: 'https://management.azure.com'.")]
51+
[string]
52+
$managementApiPrefix = "https://management.azure.com"
53+
)
54+
55+
# Checks
56+
Write-Host "Checking inputs..." -ForegroundColor Cyan
57+
Write-Host ""
58+
59+
if($null -eq $servicePrincipalObjectId -or $servicePrincipalObjectId -eq "") {
60+
$errorMessage = "The 'Service Principal Object ID' parameter is required. Please provide a valid value and try again."
61+
Write-Error $errorMessage
62+
throw $errorMessage
63+
}
64+
65+
$enterpriseAgreementResourceIDFormat = "/providers/Microsoft.Billing/billingAccounts/$billingAccountID/enrollmentAccounts/$enrollmentAccountID"
66+
$microsoftCustomerAgreementResourceIDFormat = "/providers/Microsoft.Billing/billingAccounts/$billingAccountID/billingProfiles/$billingProfileID/invoiceSections/$invoiceSectionID"
67+
68+
if($null -ne $billingAccountID -and $billingAccountID -ne "" -and $null -ne $billingResourceID -and $billingResourceID -ne "" -and $null -ne $invoiceSectionID -and $invoiceSectionID -ne "") {
69+
$billingResourceID = $microsoftCustomerAgreementResourceIDFormat
70+
Write-Host "Microsoft Customer Agreement (MCA) parameters provided..."
71+
}
72+
73+
if($null -ne $billingAccountID -and $billingAccountID -ne "" -and $null -ne $enrollmentAccountID -and $enrollmentAccountID -ne "") {
74+
$billingResourceID = $enterpriseAgreementResourceIDFormat
75+
Write-Host "Enterpruse Agreement (EA) parameters provided..."
76+
}
77+
78+
if($null -ne $billingResourceID -and $billingResourceID -ne "") {
79+
Write-Host "Billing Resource ID or required parameters provided..." -ForegroundColor Green
80+
} else {
81+
$errorMessage = "No Billing Resource ID or required parameters provided."
82+
Write-Error $errorMessage
83+
throw $errorMessage
84+
}
85+
86+
Write-Host "Checking the specified billing account resource ID '$($billingResourceID)' exists..." -ForegroundColor Yellow
87+
88+
# Check $billingResourceID is valid and exists
89+
$getbillingResourceID = $(az rest --method GET --url "$managementApiPrefix$($billingResourceID)?api-version=2024-04-01") | ConvertFrom-Json
90+
91+
if ($null -eq $getbillingResourceID) {
92+
$errorMessage = "The specified billing account resource ID '$($billingResourceID)' does not exist or you do not have access to it. Please check the value and try again. Also ensure you are logged in as the Account Owner for the specified billing account."
93+
Write-Error $errorMessage
94+
throw $errorMessage
95+
} else {
96+
Write-Host "The specified billing account ID '$($billingResourceID)' exists. Continuing..." -ForegroundColor Green
97+
Write-Host ""
98+
}
99+
100+
# Check $existingSpnMiObjectId is valid and exists
101+
Write-Host "Checking the specified service principal 'Object ID' '$($servicePrincipalObjectId)' exists..." -ForegroundColor Yellow
102+
$getexistingSpnMiObjectId = $(az ad sp show --id $servicePrincipalObjectId) | ConvertFrom-Json
103+
104+
if ($null -eq $getexistingSpnMiObjectId) {
105+
$errorMessage = "The specified service principal 'Object ID' '$($existingSpnMiObjectId)' does not exist. Please check the value and try again."
106+
Write-Error $errorMessage
107+
throw $errorMessage
108+
} else {
109+
$finalSpnMiObjectId = $getexistingSpnMiObjectId.id
110+
$finalSpnMiDisplayName = $getexistingSpnMiObjectId.displayName
111+
$finalSpnMiType = $getexistingSpnMiObjectId.servicePrincipalType
112+
113+
Write-Host "The specified service principal 'Object ID' '$($servicePrincipalObjectId)' exists with a Display Name of: '$finalSpnMiDisplayName' with a Type of: '$finalSpnMiType'. Continuing..." -ForegroundColor Green
114+
Write-Host ""
115+
}
116+
117+
# Grant service principal access to the specified EA billing account
118+
$subscriptionCreatorRoleId = "a0bcee42-bf30-4d1b-926a-48d21664ef71"
119+
Write-Host "Pre-reqs passed and complete..." -ForegroundColor Cyan
120+
Write-Host "Granting the 'SubscriptionCreator' role (ID: '$subscriptionCreatorRoleId') on the Billing Account ID of: '$($billingResourceID)' to the AAD Object ID of: '$($finalSpnMiObjectId)' which has the Display Name of: '$($finalSpnMiDisplayName)'..." -ForegroundColor Yellow
121+
122+
# Get the current AAD Tenant ID
123+
$tenantId = $(az account show --query tenantId -o tsv)
124+
125+
# Create GUID for role assignment name
126+
$roleAssignmentName = New-Guid
127+
128+
$roleAssignmentHashTable = [ordered]@{
129+
"properties" = @{
130+
"principalId" = "$finalSpnMiObjectId"
131+
"roleDefinitionId" = "$billingResourceID/billingRoleDefinitions/$subscriptionCreatorRoleId"
132+
"principalTenantId" = $tenantId
133+
}
134+
}
135+
$roleAssignmentPayloadJson = $roleAssignmentHashTable | ConvertTo-Json -Depth 100 -Compress
136+
$roleAssignmentPayloadJson = $roleAssignmentPayloadJson -replace '"', '\"'
137+
138+
$grantRbac = $(az rest --method PUT --url "$managementApiPrefix$($billingResourceID)/billingRoleAssignments/$($roleAssignmentName)?api-version=2024-04-01" --body $roleAssignmentPayloadJson) | ConvertFrom-Json
139+
140+
if ($null -eq $grantRbac) {
141+
$errorMessage = "The 'SubscriptionCreator' role could not be granted to the service principal. Please check the error message above and try again."
142+
Write-Error $errorMessage
143+
throw $errorMessage
144+
} else {
145+
Write-Host "The 'SubscriptionCreator' role has been granted to the service principal." -ForegroundColor Green
146+
Write-Host ""
147+
}
148+
149+
return
150+
}

0 commit comments

Comments
 (0)