Azure Automation with Managed Identity for PowerShell Runbooks
Table of Contents
Setting Up Azure Automation with Managed Identity for PowerShell Runbooks
In this blog post, we’ll walk through the process of setting up a Managed Identity in an Azure Automation Account, assigning it the necessary Application Administrator role, and configuring a PowerShell Runbook to read Azure Application Client Secrets. Let’s dive in!
1. Setting up Managed Identity in Azure Automation Account
Managed identities provide a secure and manageable way for applications to access Azure resources. Here’s how you can set one up in your Azure Automation Account.
Step-by-Step Guide:
Create an Azure Automation Account:
- Log in to the Azure portal.
- Navigate to Automation Accounts and click on “Add”.
- Fill in the necessary details such as the name, subscription, resource group, and location.
- Click “Create”.
Enable Managed Identity:
- Once the Automation Account is created, navigate to it.
- Look for “Identity” under the “Settings” section.
- In the “System assigned” tab, toggle the status to “On”.
- Click “Save” to create and enable the managed identity for your Automation Account.
2. Assigning Application Administrator Role to the Managed Identity
To allow the Managed Identity to access and manage application resources, it needs the Application Administrator role.
- Navigate to Azure Active Directory:
- In the Azure portal, go to “Azure Active Directory”.
- Select Enterprise Applications:
- From the left menu, click on “Enterprise applications”.
- Find the Managed Identity:
- In the “Application Type” drop-down, select “All applications”.
- Use the search box to enter the name of the managed identity you created.
Assign Roles:
- Click on the found application.
- Navigate to “Roles and administrators”.
- Click on “Add role assignment”, select “Application Administrator” from the role dropdown, and choose the Managed Identity.
- Click “Assign” to save the changes.
3. Setting up a PowerShell Runbook to Read Azure Application Client Secrets
Now, we will create a PowerShell Runbook to read Azure Application Client Secrets using the managed identity. Create a New Runbook:
- Navigate to your Automation Account.
- Go to “Runbooks” under “Process Automation”.
- Click “Create a runbook”, provide necessary details such as the name, and select “PowerShell” as the runbook type.
- Click “Create”.
Powershell script
Connect-AzAccount -Identity
Function Set-CellColor {
[CmdletBinding()]
Param (
[Parameter(Mandatory,Position=0)]
[string]$Property,
[Parameter(Mandatory,Position=1)]
[string]$Color,
[Parameter(Mandatory,ValueFromPipeline)]
[Object[]]$InputObject,
[Parameter(Mandatory)]
[string]$Filter,
[switch]$Row
)
Begin {
Write-Verbose "$(Get-Date): Function Set-CellColor begins"
If ($Filter) {
If ($Filter.ToUpper().IndexOf($Property.ToUpper()) -ge 0) {
$Filter = $Filter.ToUpper().Replace($Property.ToUpper(),"`$Value")
Try {
[scriptblock]$Filter = [scriptblock]::Create($Filter)
}
Catch {
Write-Warning "$(Get-Date): ""$Filter"" caused an error, stopping script!"
Write-Warning $Error[0]
Exit
}
} else {
Write-Warning "Could not locate $Property in the Filter, which is required. Filter: $Filter"
Exit
}
}
}
Process {
ForEach ($Line in $InputObject) {
If ($Line.IndexOf("<tr><th") -ge 0) {
Write-Verbose "$(Get-Date): Processing headers..."
$Search = $Line | Select-String -Pattern '<th ?[a-z\-:;"=]*>(.*?)<\/th>' -AllMatches
$Index = 0
ForEach ($Match in $Search.Matches) {
If ($Match.Groups[1].Value -eq $Property) {
Break
}
$Index ++
}
If ($Index -eq $Search.Matches.Count) {
Write-Warning "$(Get-Date): Unable to locate property: $Property in table header"
Exit
}
Write-Verbose "$(Get-Date): $Property column found at index: $Index"
}
If ($Line -match "<tr( style=""background-color:.+?"")?><td") {
$Search = $Line | Select-String -Pattern '<td ?[a-z\-:;"=]*>(.*?)<\/td>' -AllMatches
$Value = $Search.Matches[$Index].Groups[1].Value -as [double]
If (-not $Value) {
$Value = $Search.Matches[$Index].Groups[1].Value
}
If (Invoke-Command $Filter) {
If ($Row) {
Write-Verbose "$(Get-Date): Criteria met! Changing row to $Color..."
If ($Line -match "<tr style=""background-color:(.+?)"">") {
$Line = $Line -replace "<tr style=""background-color:$($Matches[1])","<tr style=""background-color:$Color"
} else {
$Line = $Line.Replace("<tr>","<tr style=""background-color:$Color"">")
}
} else {
Write-Verbose "$(Get-Date): Criteria met! Changing cell to $Color..."
$Line = $Line.Replace($Search.Matches[$Index].Value,"<td style=""background-color:$Color"">$Value</td>")
}
}
}
Write-Output $Line
}
}
End {
Write-Verbose "$(Get-Date): Function Set-CellColor completed"
}
}
Function Get-AZSPInfo {
$AZSPInfoBody = @"
<h1>Azure Service Principal Report</h1>
<p>The following report was run on $(Get-Date)</p>
"@
$ADSPs = Get-AzADApplication
$CustomReport = @()
foreach ($ADSP in $ADSPs) {
$AZADAppCreds = Get-AzADAppCredential -ApplicationId $ADSP.AppID
foreach ($AZADAppCred in $AZADAppCreds) {
$EndDate = $AZADAppCred.EndDateTime
$Currentdate = Get-Date
$diffDays = (New-TimeSpan -Start $Currentdate -End $EndDate).Days
$SPReport = New-Object PSObject
$SPReport | Add-Member -type NoteProperty -name DisplayName -Value $ADSP.DisplayName
$SPReport | Add-Member -type NoteProperty -name AppID -Value $ADSP.AppID
$SPReport | Add-Member -type NoteProperty -name StartDate -Value $AZADAppCred.StartDateTime
$SPReport | Add-Member -type NoteProperty -name EndDate -Value $AZADAppCred.EndDateTime
$SPReport | Add-Member -type NoteProperty -name DaysToExpire -Value $diffDays
$SPReport | Add-Member -type NoteProperty -name Type -Value $(if ($AZADAppCred.Type -eq "AsymmetricX509Cert") { "AsymmetricX509Cert" } else { "Secret" })
$CustomReport += $SPReport
}
}
$AZSPInfo = $CustomReport | Sort-Object { $_.enddate -as [datetime] }
$AZSPInfoHTML = $AZSPInfo | ConvertTo-HTML | Set-CellColor DaysToExpire yellow -Filter "DaysToExpire -lt 90"
$AZSPInfoHTML = $AZSPInfoHTML | Set-CellColor DaysToExpire red -Filter "DaysToExpire -lt 30"
$AZSPInfoHTML = $AZSPInfoHTML | Set-CellColor DaysToExpire green -Filter "DaysToExpire -ge 90"
$AZSPInfoBody + $AZSPInfoHTML
}
Write-Host "- Capturing Service Principal Expiration Information. " -ForegroundColor Yellow
Write-Host " "
$AZSPHTML = Get-AZSPInfo # Pull report for Service Principal expiration
# HTML Title
$Title = @"
<title>Azure Service Principal Report</title>
"@
# HTML Header
$Header = @"
<style>
BODY {font-family:verdana;}
TABLE {border-width: 1px; border-style: solid; border-color: black; border-collapse: collapse;}
TH {border-width: 1px; padding: 3px; border-style: solid; border-color: black; padding: 5px; background-color: #d1c3cd;}
TD {border-width: 1px; padding: 3px; border-style: solid; border-color: black; padding: 5px}
</style>
"@
# Combine HTML Reports
$FinalHTML = $Title + $Header + $AZSPHTML
# App settings
$ClientID = "xxxx"
$ClientSecret = "xxxx"
$TenantID = "xxxx"
# Token endpoint
$TokenEndpoint = "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/token"
# Get an access token
$Body = @{
'grant_type' = 'client_credentials'
'client_id' = $ClientID
'client_secret' = $ClientSecret
'scope' = 'https://graph.microsoft.com/.default'
}
$TokenResponse = Invoke-RestMethod -Method Post -Uri $TokenEndpoint -ContentType "application/x-www-form-urlencoded" -Body $Body
$AccessToken = $TokenResponse.access_token
# Email settings
$EmailFrom = "xxx@domain.com"
$EmailTo = "xxx@domain.com"
$EmailSubject = "Azure Service Principal Report"
$AttachmentName = "ServicePrincipalReport-" + "{0:yyyyMMdd-HHmm}" -f (Get-Date) + ".html"
# Create email
$Email = @{
Message = @{
Subject = $EmailSubject
ToRecipients = @(@{ EmailAddress = @{ Address = $EmailTo }})
Attachments = @(
@{
"@odata.type" = "#microsoft.graph.fileAttachment"
Name = $AttachmentName
ContentType = "text/html"
ContentBytes = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($FinalHTML))
}
)
Body = @{
ContentType = "Text"
Content = "Please find the attached Azure Service Principal Report."
}
}
SaveToSentItems = "true"
} | ConvertTo-Json -Depth 10
# Send the email
$GraphEndpoint = "https://graph.microsoft.com/v1.0/users/$EmailFrom/sendMail"
Invoke-RestMethod -Method Post -Uri $GraphEndpoint -Headers @{Authorization = "Bearer $AccessToken"} -ContentType "application/json" -Body $Email
Save and Publish the Runbook
By following these steps, you can effectively set up a Managed Identity via Azure Automation Account and create a PowerShell Runbook for reading Azure Application Client Secrets. This setup ensures a secure and streamlined way to manage Azure resources and automate tasks efficiently.