Use Azure Automation for creating Resource Groups despite having limited permissions only

In Azure-related engagements I often observe that Azure users are only assigned Contributor-role at one single Azure Resource Group. In general, motivation for this is…

  • Users can provision resources within this Resource Group
  • Users are protected from accidentally deleting resources in other Resource Groups (e.g. settings of a Virtual Network, security settings, …)

On the other hand, this approach leads to a few issues:

  • With only having Contributor role on one Resource Group, you cannot create additional Resource Groups (which would be nice to structure your Azure resources).
  • Some Azure Marketplace solutions require to be installed in empty Resource Groups. So, if a user already provisioned resources in “his” Resource Group, he just can’t provision these solutions.

Solution

Azure Automation lets you – among other features – configure PowerShell skripts (called Runbooks) that can run under elevated privileges allowing Contributors to call these Runbooks and perform actions they normally wouldn’t have sufficient permissions for. Such a Runbook can perform following actions (under Subscription Owner role):

  1. Create a new Resource Group (name specified by a parameter)
  2. Assign an AD Group (name specified by a parameter) as Contributor to this Resource Group

Necessary steps for implementing the solution

For implementing this, following steps must be taken:

  • Step 1: Create an Azure Automation Account
  • Step 2: Create a Run As Account with sufficient access permissions
  • Step 3: Create and test a new Automation Runbook that creates Resource Groups
  • Step 4: Publish the Runbook

Description of the Solution

Step 1: Create an Azure Automation Account

  • In Azure Portal click on New / Monitoring + Management / Automation

    image

  • Fill in the required parameters and make sure you create a Run As account

    image

  • Confirm with Create.

For more information about creating Azure Automation Accounts see:
https://docs.microsoft.com/en-us/azure/automation/automation-quickstart-create-account

Step 2: Create a Run As Account with sufficient access permissions

  • If you haven’t created the Run As Account during the creation of the Azure Automation Account, create one following this description: https://docs.microsoft.com/en-us/azure/automation/automation-create-runas-account

  • Go to your Azure Automation Account

  • Navigate to Connections / AzureRunAsConnection

    image

  • Now you see all the information of the service principal that the Runbook is later running under. Copy the Application ID to the clipboard.

    image

  • Navigate to the Subscription. Type “Subscriptions” into the search field of the portal.

    image

    Click on Subscriptions.

  • Select your Subscription. On the Subscription overview page click Access Control (IAM) / Add.

    image

  • Choose the “Owner”-Role. Enter the Application ID from the clipboard to the Select field and click on the User that gets displayed. That is the service principal from your Azure AD.

    image

  • Confirm with Save.

You have now successfully assigned Subscription Owner rights to the Service Principal created for the Azure Automation RunAs-Account the Runbook will run under.

Step 3: Create and test a new Automation Runbook that creates Resource Groups

Go back to your Automation Account.

  • Select Runbooks and click on Add a Runbook.

    image

  • Select Create a new runbook.

    image

    Fill in the requested fields:
    Name: <a name for your runbook>
    Runbook type: PowerShell
    Description: <some description for the runbook>

  • Confirm with Create

In the editor add the following lines of code

 Param(
 [string]$ResourceGroupName,
 [string]$RGGroupID
)

$connectionName = "AzureRunAsConnection"

try
{
    # Get the connection "AzureRunAsConnection "
    $servicePrincipalConnection=Get-AutomationConnection -Name $connectionName  

    $tenantID = $servicePrincipalConnection.TenantId
    $applicationId = $servicePrincipalConnection.ApplicationId
    $certificateThumbprint = $servicePrincipalConnection.CertificateThumbprint

    "Logging in to Azure..."
    Login-AzureRmAccount `
        -ServicePrincipal `
        -TenantId $tenantID `
        -ApplicationId $applicationId `
        -CertificateThumbprint $certificateThumbprint

    New-AzureRmResourceGroup -Name $ResourceGroupName -Location 'West Europe'

    # Set the scope to the Resource Group created above
    $scope = (Get-AzureRmResourceGroup -Name $ResourceGroupName).ResourceId

    # Assign Contributor role to the group
    New-AzureRmRoleAssignment -ObjectId $RGGroupID -Scope $scope -RoleDefinitionName "Contributor"
}
catch {
   if (!$servicePrincipalConnection)
   {
      $ErrorMessage = "Connection $connectionName not found."
      throw $ErrorMessage
  } else{
      Write-Error -Message $_.Exception
      throw $_.Exception
  }
}

  • Save the Runbook and click on Test Pane.

    image

  • Fill in the two parameter fields (Resource Group Name and Group ID) and click on Start.

    image

This creates the Resource Group and assigns Contributor role to the Azure AD group specified. You might get an error message “Authentication_Unauthorized” because of a bug that occurs when working with a service principal. Ignore this message as the script (according to my tests) does the job.

Step 4: Publish the Runbook

  • Close the test pane and click on Publish.

    image

    Confirm with Yes.

  • That’s it. Users can now go to the Automation Account, select the Runbook and click on Start.

    image

  • This opens the form for entering the parameters. After clicking OK, the Resource Group will be created and the group assignment will be done.

    image

Final Words

One extension to this (runbook) could be to allow the user to enter a group name instead of the group id. This would require one additional step: an AD lookup. See the code here:

 $groupID = (Get-AzureRmADGroup -SearchString $RGGroup).Id
New-AzureRmRoleAssignment -ObjectId $groupID -Scope $scope -RoleDefinitionName "Contributor"

This, however, would require giving the service principal (Run As account) access permissions to the Azure AD. That’s something I wanted to avoid here.