Enable Self-Service Creation of Azure Subscriptions!


I have often seen customers struggle to scale when it comes to creating Azure Subscriptions. This is one of the reasons I developed a small Azure Function in combination with Microsoft Power Automate and Microsoft Forms to help tackle this challenge. I wanted to demonstrate a way on how to enable the creation of Azure Subscriptions with self service in mind, while leveraging concepts/frameworks like Enterprise-Scale1 or others2 in the backend.

linkPrerequisites

The solution is based on three different services that work across the multiple Microsoft clouds. This allows a quite seamless integration between the user input and the technical implementation. The services used are:

  1. Microsoft Forms
  2. Microsoft Power Automate
  3. Azure Functions

In the next couple of paragraphs we will dive deeper into the different services and how to configure them.

linkMicrosoft Form

To enable the self-service of subscription creation we need to have some sort of Input mask that we can use to get some general data from the requestor. One of the easiest options is to go ahead and use Microsoft Forms for this use case. Using https://forms.office.com/ you can easily create a from by like the one you see below:

MS Forms

The form itself can easily be shared and customized to meet your questions and requirements. Additionally it has already an integration with Azure (AD) and therefore, the requestor can be identified quite easily. For my questionnaire/form I chose the following questions:

  1. Enter your Cost Center:
    (Free Text)
  2. Enter Project / Use Case / App Name:
    (Free Text)
  3. Enter Organization or Team Name:
    (Free Text)
  4. Choose the environment stage of your Project / Use Case / App:
    (Choose)
    Production
    Testing
    Development
    Sandbox
  5. Choose compliance requirement:
    (Choose)
    High (e.g. GDPR)
    Medium (e.g. ISO Compliance)
    Low (e.g. Corporate Compliance)
    None (e.g. Sandbox)
  6. Choose the business criticality of your Project / Use Case / App:
    (Choose)
    High
    Medium
    Low
    None (e.g. Sandbox)
  7. Choose the data confidentiality of your Project / Use Case / App:
    (Choose)
    High
    Medium
    Low
    None (e.g. Sandbox)
  8. My Use Case or Application is Dev/Test related and all developers do have a valid MSDN licence
    (Choose)
    Yes
    No
  9. Enter the name(s) of any helping external partner(s) (if exist):
    (Free Text)

The created Microsoft form can be found and duplicated here:
https://forms.office.com/Pages/ShareFormPage.aspx?id=yhBjFeK3GEShsVflrr9qJwRTHhnL69ROqdmQTMjmO9NUM1I0WUlJWEZIV0pRTEVaSFk4QkpZTlNNVC4u&sharetoken=rMxBljCWPjxop8s7hXyC

This minimal set of questions asked are needed to have a general understanding of the use case. Additionally those questions are needed to decide to which management group the created subscription should be moved.

Please be sure, that you share the Microsoft Form only within your company and not make it public!

linkPower Automate

After the questionnaire/form has been created, we need to integrate it with the Power Automate in order to create a workflow that triggers the function whenever a response is being created. This integration needs a minimum of three steps.

  1. Get/Read the created Forms response
  2. Get response details
  3. Execute a HTTP trigger

The final result should look something like this:

PowerAutomate

The first step is quite easy, we just need to connect to the Microsoft Forms service and choose the form that we have created beforehand. The action or initial trigger is called When a new response is submitted. After this step, we need to get the detailed responses from the requestor, this can be achieved using the action Get response details. In this step we can publish the detailed responses into the next action using Response Id.

Eventually, we need to send the detailed responses in a JSON format to the Azure function using the HTTP action. (Be aware that you need Power Automate Premium3 for this action. To call the function we can use different methods of authentication, I chose a rather simple one by using a created Function Key in the URI field. The body of the HTTP request should be formatted like a JSON document in order to function properly:

{ "costcenter": "@{outputs('Get_response_details')?['body/rd43742f58a6945a5833a8140a8c6a07f']}", "compliance": "@{outputs('Get_response_details')?['body/r3f5e95d4fa0d476a9dc71f90c1ba10f7']}", "environment": "@{outputs('Get_response_details')?['body/r477d171f09854617a46bafe6e1fe31ce']}", "managedby": "@{outputs('Get_response_details')?['body/r36caba56e4954176af8f5a47ffae9f89']}", "projectname": "@{outputs('Get_response_details')?['body/r77e98d66187c42e0a85bbcd4a830fbb1']}", "criticality": "@{outputs('Get_response_details')?['body/r81e76dab7d494e00b350d3282c300b8d']}", "confidentiality": "@{outputs('Get_response_details')?['body/r4a44db97a10d479c86bae58cb09824ec']}", "msdn": "@{outputs('Get_response_details')?['body/r9073f32b4b384d949159fe521f5045d1']}", "partner": "@{outputs('Get_response_details')?['body/r081532d229bc44a3b13b2fc1c68ca1ac']}", "owner": "@{outputs('Get_response_details')?['body/responder']}" }

The last call/request runs as long as the function runs and will be successful, if the function provides a positive status code like: HTTP 200 at the end of the execution (Which is does by default).

linkPossible Improvements

To additionally approve the Integration/Workflow and make it more Enterprise ready there are some points to quite easily improve this MVP. Out of my head there are two major points that can be implemented quite quickly.

  1. Approval Workflow In order to allow a minimum set of Governance, an Approval workflow might be needed to serve as a gatekeeper. This can be configured similar to this: Approval

Furthermore, you could branch actions when the requestor wants a Sandbox Subscription, to create the subscription straight away without the need for an approval. Find more information about how to create an approval action/workflow here: https://docs.microsoft.com/en-us/power-automate/create-approval-response-options

  1. Welcome or getting started Mail To allow a smooth onboarding of the requestor a welcome or getting started mail is quite useful. This allows the requestor to quickly know how to work with Azure in the context of the company. This mail can be created quite easily, either by using the Outlook/Exchange Action or via Sendgrid (Used in this example).

PowerAutomate

linkAzure Function

In order for the Azure Function to work, the following (additional) prerequisites/requirements need to be met:

  1. You must have an Owner role on an Enrollment Account to create a subscription

This is a "One Time Task" and you must grant access to create Azure Enterprise subscriptions to the Managed Service Identity/Service Principal (using the object ID) of the Azure Function. The easiest way (IMHO) is to use a Managed Service Identity with the function and assign the permission to it. For more detailed steps on how to do it, see: https://docs.microsoft.com/en-us/azure/cost-management-billing/manage/grant-access-to-create-subscription?tabs=azure-powershell%2Cazure-powershell-2

  1. Management Groups (that will be leveraged in the Function) need to exist! Since we want to make additional decisions based on the response details and not only create a subscription, The Management Group and Subscription Hierarchy need to be in place. Your Management Group hierarchy might vary, but you can easily customize the function to work with your hierarchy.

The main function itself is depicted below:

1...

2#

3##

4### Create Azure Subscription for Bootstrapping

5##

6#

7

8[String]$ProjectName = $Request.Body.projectname.Replace(' ', '').ToLower()

9

10If($Request.Body.environment -like "Production"){

11 $Environment = "prod"

12}

13If($Request.Body.environment -like "Testing"){

14 $Environment = "test"

15}

16If($Request.Body.environment -like "Development"){

17 $Environment = "dev"

18}

19If($Request.Body.environment -like "Sandbox"){

20 $Environment = "sbox"

21}

22

23# Build SubscriptionName

24$SubscriptionName ="sub-$OrganizationName-$ProjectName-$Environment"

25

26# Define Subscription Offer Type

27

28 # MS-AZR-0017P for EA-Subscription

29 # MS-AZR-0148P for Dev/Test Subscriptions (MSDN Licences needed)

30

31If($Request.Body.msdn -like "No"){

32 $OfferType = "MS-AZR-0017P"

33}

34else{

35 $OfferType = "MS-AZR-0148P"

36}

37

38# Get Object ID of the Managed Service Identity/Service Principal

39$EnrollmentId = (Get-AzEnrollmentAccount).ObjectId

40

41# Create Subscription

42$NewSubscription = New-AzSubscription -OfferType $OfferType -Name $SubscriptionName -EnrollmentAccountObjectId $EnrollmentId -OwnerSignInName $Request.Body.owner

43

44# Wait for the subscription to be created

45Start-Sleep -Seconds 10

46

47#Log Subscription Details

48Write-Output "New subscription:" $NewSubscription | ConvertTo-Json | Write-Output

49

50#

51##

52### Move Azure Subscription to Management Group

53##

54#

55

56# Get created subscription

57

58$consistent = $false

59 $loops = 0

60

61 while (-not $consistent) {

62 $subscription = $null

63 try {

64 $Subscription = Get-AzSubscription -SubscriptionName $SubscriptionName

65 }

66 catch {

67 $subscription = $null

68 if ($loops -eq 30) {

69 throw "Took too long for subscription to become consistent."

70 }

71 Write-Output "Loop: $loops"

72 Start-Sleep -Seconds 1

73 }

74 if ($null -ne $subscription) {

75 $consistent = $true

76 }

77 $loops++

78 }

79

80# Move subscription to correlating Management Group for further bootstrapping (either using Enterprise Scale or Azure Blueprint)

81New-AzManagementGroupSubscription -GroupName $Environment -SubscriptionId $Subscription.Id

82

83# Wait for the subscription to be moved

84Start-Sleep -Seconds 10

85

86#

87##

88### Define subscription tags

89##

90#

91

92# Define Naming for Tags

93$company_costcenter = "$OrganizationName"+"_costcenter"

94$company_managedby = "$OrganizationName"+"_managedby"

95$company_complianceLevel = "$OrganizationName"+"_complianceLevel"

96$company_project_app_name = "$OrganizationName"+"_project_app_name"

97$company_subscription_requestor = "$OrganizationName"+"subscription_requestor"

98$company_environment = "$OrganizationName"+"_environment"

99$company_criticality = "$OrganizationName"+"_criticality"

100$company_confidentiality = "$OrganizationName"+"_confidentiality"

101$company_reviewdate = "$OrganizationName"+"_reviewdate"

102$company_maintenancewindow = "$OrganizationName"+"_maintenancewindow"

103$company_external_partner = "$OrganizationName"+"_external_partner"

104

105# Define Tag Table

106If($Request.Body.partner -like ""){

107$tags = @{

108 "$company_costcenter"=$Request.Body.costcenter;

109 "$company_managedby"=$Request.Body.managedby;

110 "$company_complianceLevel"=$Request.Body.compliance;

111 "$company_project_app_name"=$Request.Body.projectname;

112 "$company_subscription_requestor"=$Request.Body.owner

113 "$company_environment"=$Request.Body.environment;

114 "$company_criticality"=$Request.Body.criticality;

115 "$company_confidentiality"=$Request.Body.confidentiality;

116 "$company_reviewdate"="TBD";

117 "$company_maintenancewindow"="TBD";

118 }

119}

120else{

121$tags = @{

122 "$company_costcenter"=$Request.Body.costcenter;

123 "$company_managedby"=$Request.Body.managedby;

124 "$company_complianceLevel"=$Request.Body.compliance;

125 "$company_project_app_name"=$Request.Body.projectname;

126 "$company_subscription_requestor"=$Request.Body.owner

127 "$company_environment"=$Request.Body.environment;

128 "$company_criticality"=$Request.Body.criticality;

129 "$company_confidentiality"=$Request.Body.confidentiality;

130 "$company_reviewdate"="TBD";

131 "$company_maintenancewindow"="TBD";

132 "$company_external_partner"=$Request.Body.partner;

133 }

134}

135

136#

137##

138### Assign initial subscription tags

139##

140#

141

142$subid = $subscription.Id

143# Assign Tags

144New-AzTag -ResourceId "/subscriptions/$subid" -Tag $tags

145...

The complete function can be found and forked/cloned here: https://github.com/lpassig/SubscriptionCreation

linkFinal Thoughts

Using the Microsoft cloud services you can easily create a self service portal for your company to create Azure subscription. Additionally, you can download and analyze the input data on the Forms quite easy:

Overview

However, this approach has also its limits and if you have a service management tool in place you might want to use this one instead to implement the workflow.

Let me know what you think!

Cheers!

linkSources

1 Enterprise Scale (https://github.com/Azure/Enterprise-Scale)

2 Azure Spoke Blueprint (https://github.com/lpassig/AzureSpokeBlueprint/)

3 MS Power Automate Pricing (https://flow.microsoft.com/en-us/pricing/)


PrerequisitesMicrosoft FormPower AutomatePossible ImprovementsAzure FunctionFinal ThoughtsSources

Home Successful cloud adoption primer COVID-19's impact on cloud strategy! Pillars of Well-Architected Azure Workloads! Azure Custom Cost Management Report! Enable Self-Service Creation of Azure Subscriptions!