MSEndpointMgr
Azure managed identities

Managed Identities in Azure Automation (PowerShell)

If you love Azure Automation and Security, you probably know that since around April 2021, Managed Identities in Azure Automation is the best way to access resources securely.

This article will show why and how you should use Managed Identities to simplify your resource access management. And end it with showing how I have made my Graph API-related Runbooks much leaner.

The expected audience for this article is IT Pros with general experience managing access to Azure AD roles and Azure Resources.

UPDATE: Here is an example of how to connect to an Exchange Online PSSession.
ExchangeOnlineScripts/ConnectEXOwithMSIRunbookExample.ps1 at main ยท mardahl/ExchangeOnlineScripts (github.com)

What are managed identities?

Managed Identities are accounts in your Azure Active Directory that are only available for use by the resources that you have assigned them to. This could be an Azure automation account or some other service like Azure Functions or Azure VM. But it is not a given that they are supported in every type of Azure Resource.

The list of supported services/resource is found here: Azure Services that support managed identities – Azure AD | Microsoft Docs

Managed Identities come in two flavors

  • System assigned
  • User assigned

System assigned is tied to the lifecycle of a single resource (i.e., an Azure Automation account) much like a classic Active Directory managed service account (MSA), while User assigned can be thought of as a classic group managed service account (gMSA), that is available to multiple resources.

NB: In this article we only deal with System assigned as I feel that is easiest to manage when thingking about the lifecycle management of our solutions in Azure.

How are they more secure?

Managed Identities are more secure because their “credentials” are only available to the resource they are assigned to, and they are never exposed in code. Unlike other account types that rely on an administrator to handle the credentials at some point, Managed Identities credentials are at no point handled by the systems administrator or anyone else. It is all handled by Azure, like a boss!

They are never exposed in code

Not having to deal with any form of credential (i.e., certificates or secrets) greatly enhances the security posture of the solutions you develop. And having to monitor for expired client secrets or certificates for app registrations is a thing of the past.

How do I get started with using Managed Identities in Azure Automation?

To get started with Managed Identities you first need an Azure Automation account. And I suggest you start with a fresh Azure Automation account in a new Resource Group that will hold you automation solution.

How to get started with Azure Automation itself is another topic entirely, but here are some good resources:

Create a standalone Azure Automation account | Microsoft Docs
An introduction to Azure Automation | Microsoft Docs

NB: When you create your new Azure Automation Account, a default option is to create a RunAs account. I would like you to consider not using that option during creation. It is not wise to enable RunAs accounts unless you will be using them. You will not be using RunAs accounts in this article because Managed Identities.

Actually using the managed identity for something cool!

First off, it is extremely easy to use a managed identity within your Runbook, once it is supported by the PowerShell modules that you use. Currently Az.Accounts is the only one that I have been using.

Using managed identities with the connect-azaccount cmdlet is very easy. simply add this line to your runbook:

Connect-AzAccount -Identity

Yes. It is that easy! You just connected to Azure using a managed identity.

But what about connecting to other Azure Services? Well, Microsoft has some great explanations on how to use Azure Key Vault to save credentials for many things and then only allowing the managed identity to access that key vault.

Enable a managed identity for your Azure Automation account (preview) | Microsoft Docs

But like many IT Pro automation experts, I like to use Microsoft Graph a lot! But the Microsoft Graph PowerShell SDK does not yet support managed identities (Preview version 2.0 supports it though). Luckily Microsoft has also covered how to generate access tokens using the managed identity – but the article can be a little daunting for beginners, and understanding everything that goes on might not be for everyone…

Accessing Microsoft Graph API with a managed identity

There are many ways to work with Microsoft Graph API. I prefer the PowerShell SDK when working with the Microsoft Graph API in a Runbook since it will make it much leaner and less prone to errors than invoking web requests. Still, as mentioned earlier, support for managed identity is missing at the time of writing this.

The PowerShell module does, however, support the use of an access token. So we can simply call on the system assigned managed identity, to generate an access token that is valid for the Microsoft Graph API endpoint (Beta or v1.0). It is not as simple as the Connect-AzAccount cmdlet, but pretty close. Take a look at this code:

#Obtain AccessToken for Microsoft Graph via the managed identity
$resourceURL = "https://graph.microsoft.com/" 
$response = [System.Text.Encoding]::Default.GetString((Invoke-WebRequest -UseBasicParsing -Uri "$($env:IDENTITY_ENDPOINT)?resource=$resourceURL" -Method 'GET' -Headers @{'X-IDENTITY-HEADER' = "$env:IDENTITY_HEADER"; 'Metadata' = 'True'}).RawContentStream.ToArray()) | ConvertFrom-Json 
$accessToken = $response.access_token 

#Define the desired graph endpoint
$graphApiVersion = 'beta'
Select-MgProfile -Name $graphApiVersion

#Connect to the Microsoft Graph using the aquired AccessToken
Connect-Graph -AccessToken $accessToken

The comments probably gave it all away, but let’s just run through what is going on here…

Micro deep dive

First, we define the API URL that we know the Microsoft Graph API PowerShell SDK uses in the background, so if you want to use this for similar PowerShell modules, you need to figure out which Modern Authentication compatible URL they use on the backend and enter it here.

Please notice that I am not defining if it is beta or v1.0 when asking for a token, this is not needed.

Second, we build the Uri for our invoke-webrequest. Nothing special here, except the fact that we are using two very magic environment variables:

  • $env:IDENTITY_ENDPOINT
  • $env:IDENTITY_HEADER

These two variables are only accessible by the automation account and only valid when processing the runbook. It’s sort of like a little private webserver within your runbook that you can send a request to. In this case, we are sending a web request that is, in fact, asking the managed identity service to go to the requested resource URL and get us an access token. We will then receive the access token as part of the response from the tiny private web service.

Lastly, we use the Microsoft.Graph PowerShell modules cmdlets to define the Profile we want to connect to, and then initiate the connection with our newly acquired access token.

The end result is a successful connection to the Microsoft Graph!

Assigning permissions to the Managed Identity

Assigning permissions to a managed identity is much like with any other service principal (but..).

You can add the managed identity to a role in Azure AD and Azure that gives it the required access to the resources you need to access from Azure Automation.

You are however going to have to use PowerShell in order to assign app permissions to things like Exchange Online or Microsoft Graph. And I have created a script to be executed in the comfort of your own Azure Cloud Shell: PSBucket/Add-MGraphMSIPermissions.ps1 at master ยท mardahl/PSBucket (github.com)

Needless to say, you should assign the least required privilege.

Assign a managed identity access to a resource using the Azure portal – Azure AD | Microsoft Docs

Conclusion

Using managed identities to access Azure resources is easy and reliable. You should prioritize assessing if they can be used in your solutions from now and going forward. Since client secrets for app registrations are now limited to a maximum of 24 months expiration count, the risk of a service outage due to bad monitoring grows even bigger.

I hope this article helped you to understand that there is more to managed identities than meets the eye.

As always, I hope you will give your comments below and follow on twitter to show your support.

(19236)

Michael Mardahl

Michael Mardahl is a seasoned IT pro with over 25 years of experience under his belt. He's a Microsoft Certified Cloud Architect at APENTO in Denmark, where he helps customers move from traditional infrastructure to the cloud while keeping security top of mind. When he's not working, Michael's either spending time with his family and friends or passionately blogging about Microsoft cloud technology. His expertise in this area has even earned him the prestigious title of Microsoft Most Valuable Professional (MVP) in both the Enterprise Mobility and Security categories. In short, Michael is the IT equivalent of a rockstar, but don't expect him to act like one - he's way too down-to-earth for that.

19 comments

  • Hello,

    what could be the reason for the connection to the SQL database error??
    I am connecting to Exchange correctly

    I have an error: Unable to connect to the remote server
    Cannot bind argument to parameter ‘InputObject’ because it is null.
    System.Management.Automation.MethodInvocationException: Exception calling “Open” with “0” argument (s): “A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible . Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (Provider: Named Pipes Provider, error: 40 – Could not open a connection to SQL Server) ”

    I added the option to connect an Azure Automation account in the database

    WHAT am I doing wrong?

    $ response = Invoke-WebRequest -Uri ‘http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fdatabase.windows.net%2F’ – Method GET -Headers @ {Metadata = “true”}

    $ content = $ response.Content | ConvertFrom-Json

    $ AccessToken = $ content.access_token

    $ SqlConnection = New-Object System.Data.SqlClient.SqlConnection
    $ SqlConnection.ConnectionString = “Data Source = SQLSerwerName; Initial Catalog = SQLDatabaseName”
    $ SqlConnection.AccessToken = $ AccessToken
    $ SqlConnection.Open ()

    $ SqlCmd = New-Object System.Data.SqlClient.SqlCommand
    $ SqlCmd.CommandText = “SELECT * from StatisticsLD;”
    $ SqlCmd.Connection = $ SqlConnection
    $ SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
    $ SqlAdapter.SelectCommand = $ SqlCmd
    $ DataSet = New-Object System.Data.DataSet
    $ SqlAdapter.Fill ($ DataSet)

    $ DataSet.Tables [0]

  • Hi Michael,

    I am trying to automate the service principal secret renewal using automation runbook. For for this I believe the automation account managed identity need to have the Application.ReadWrite.OwnedBy API permission. But from the portal I dont see any option to assign the API permissions. Any idea on doing this?

  • I’m trying to automate user onboarding/offboarding.

    Trying to avoid RunAs accounts in my Runbook as they are being phased out.

    But I am having trouble getting the managed identity to produce a token to MSGraph with the required permissions.

    Can you expand on what needs to be in place for MSGraph permissions, and how to assign them?

  • Hi Michael,

    I have created User Identity and attached to Azure Automation->Identity->User Identity.

    Now I am creating a runbook to list VM properties
    Get-AzVM -ResourceGroupName “myRgName” -Name “VmName”

    For authentication just adding below lines:
    $identity = Get-AzUserAssignedIdentity -ResourceGroupName ‘myRgName’ -Name ‘automation-account’
    Connect-AzAccount -Identity -AccountId $identity.ClientId

    But when I try to run I get first below error:
    Could not convert string to DateTimeOffset: 1648175844. Path ‘expires_on’, line 1, position 1567.
    Run Connect-AzAccount to login.

    • Hi Mathw,

      I have not seen that error before and unfortunately can’t reproduce it.
      Have you tried just starting simple with a system managed identity.
      Ideally using a different system managed identity for everything you build will reduce the risk of permissions creep on a user-assigned identity and will cleanup the account and permissions once the life of that specific automation or VM ends. Unlike the user-assigned which poses a greater risk than the system-assigned

  • Hi, in one of my azure runbook I need to connect to azure ad (like using connect-azureAD after running Connect-AzAccount -Identity cmdlet), let me know how to run this command as it is failing with plain connect-azuread, let me know do i need to pass it like msatoken etc if yes how to get it?

    • Yes, you would simply request an Access token for Azure AD with the “Get-AzAccessToken”
      Then connect to AzureAD with the -AadAccessToken parameter instead of credentials.

      • I’m playing around with this with no luck ๐Ÿ˜

        I would really like this to work as it would be very readable for others viewing the code.

        Connect-AzAccount -Identity
        $token = Get-AzAccessToken -ResourceTypeName MSGraph
        Connect-AzureAD -AadAccessToken $token.Token -AccountId $token.UserId

        But I end up with a message that my token has expired

        C:\>Get-AzureADUser
        Get-AzureADUser : Error occurred while executing GetUsers
        Code: Authentication_ExpiredToken
        Message: Your access token has expired. Please renew it before submitting the request.
        HttpStatusCode: Unauthorized
        HttpStatusDescription: Unauthorized
        HttpResponseStatus: Completed

      • You are thinking in the right way, but you are forgetting that you need to request a token for the the service (API) you intent to use it with.
        MSGraph is NOT AzureAD.

        Try this instead:
        Get-AzAccessToken -ResourceUrl “https://management.azure.com”
        It should get you going in the right direction ๐Ÿ˜‰

  • Hi Michael
    Great article! One question: Can we use Managed Identities in Azure Automation to have PS scripts that manage Exchange Online or SharePoint Online?

    • That is a bit different, but I have just made an example here you can use for reference.


      <# .DESCRIPTION An example runbook which connects to Exchange Online using the Managed Identity .NOTES AUTHOR: Michael Mardahl LASTEDIT: Nov 7, 2021 .CMDLETS On line 46 there is a filter on the command names (cmdlets) that get imported for use. You must adjust this to include the cmdlets you need. Please keep to a minimum as Automation has limited memory. .PERMISSIONS This script requires quite excessive permission to Exchange Online in order to work with a Managed Identity. Assignment of these permissions is done through Azure Cloud shell using the following script. Remember: Set the correct ObjectID of the $MSIObjectID variable before running the script. #Begin script Connect-AzureAD $MSIObjectID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" $EXOServicePrincipal = Get-AzureADServicePrincipal -Filter "displayName eq 'Office 365 Exchange Online'" $Approle=$EXOServicePrincipal.AppRoles.Where({$_.Value -eq 'Exchange.ManageAsApp'}) New-AzureADServiceAppRoleAssignment -ObjectId $MSIObjectID -Id $Approle[0].Id -PrincipalId $MSIObjectID -ResourceId $EXOServicePrincipal.ObjectId $AADRole = Get-AzureADDirectoryRole | where DisplayName -eq 'Global Reader' Add-AzureADDirectoryRoleMember -ObjectId $AADRole.ObjectId -RefObjectId $MSIObjectID #End script #>

      #region functions
      function makeMSIOAuthCred () {
      $accessToken = Get-AzAccessToken -ResourceUrl "https://outlook.office365.com/"
      $authorization = "Bearer {0}" -f $accessToken.Token
      $Password = ConvertTo-SecureString -AsPlainText $authorization -Force
      $tenantID = (Get-AzTenant).Id
      $MSIcred = New-Object System.Management.Automation.PSCredential -ArgumentList ("OAuthUser@$tenantID",$Password)
      return $MSICred
      }

      function connectEXOAsMSI ($OAuthCredential) {
      #Function to connect to Exchange Online using OAuth credentials from the MSI
      $psSessions = Get-PSSession | Select-Object -Property State, Name
      #$psSessions
      If (((@($psSessions) -like '@{State=Opened; Name=RunSpace*').Count -gt 0) -ne $true) {
      Write-Verbose "Creating new EXOPSSession..." -Verbose
      try {
      $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/PowerShell-LiveId?BasicAuthToOAuthConversion=true -Credential $OAuthCredential -Authentication Basic -AllowRedirection
      $null = Import-PSSession $Session -DisableNameChecking -CommandName "*mailbox*", "*unified*" -AllowClobber
      Write-Verbose "New EXOPSSession established!" -Verbose
      } catch {
      Write-Error $_
      }
      } else {
      Write-Verbose "Found existing EXOPSSession! Skipping connection." -Verbose
      }
      }
      #endregion functions

      #region execute
      $null = Connect-AzAccount -Identity
      connectEXOAsMSI -OAuthCredential (makeMSIOAuthCred)
      get-unifiedgroup
      #endregion execute

      • And what about Sharepoint online? I cannot find anything useful…

      • Thanks a lot for pasting a script! and sorry for late follow-up ๐Ÿ˜‰
        I have one question – as I can see – the example scripts uses “Authentication Basic” – is this basic authentication or I don’t get something?

        Will this work with Connect-ExchangeOnline ? (in tenant where basic authentication is blocked)

      • Hi Maciej,

        There is no legacy authentication involved. thought it might seem like that with Exchange online powershell. But there is a “hidden” proxy service that does some magic to allow modern authentication against a legacy authentication endpoint. Don’t worry about it, I have 100% block of legacy auth in my tests.

Sponsors